4992 lines
263 KiB
JavaScript
4992 lines
263 KiB
JavaScript
// == Pi Lab Page AI Runtime ==
|
||
// MNote-native adapter for Pi-first Page AI Lab.
|
||
// Renders the MNote-owned Pi Rust Page AI surface. Retired hosts stay in
|
||
// recycle; this runtime must stay independent.
|
||
// NO setInterval polling.
|
||
//
|
||
// UI note: @earendil-works/pi-web-ui@0.75.3 was checked as the mature upstream UI.
|
||
// It expects browser-side pi-agent-core, IndexedDB storage, API-key dialogs, model
|
||
// selection and builtin tools/artifacts. MNote's lab backend owns Pi RPC, access
|
||
// scope and tools, so this file keeps a host-owned adapter while matching the
|
||
// upstream ChatPanel/AgentInterface layout: message list, streaming composer,
|
||
// tool timeline, model/runtime controls and artifacts-style right rail.
|
||
//
|
||
// Default model: Omniroute/gpt-5.4-mini (consumed from backend status/default fields).
|
||
|
||
(function () {
|
||
'use strict';
|
||
|
||
var STATE_IDLE = 'idle';
|
||
var STATE_STARTING = 'starting';
|
||
var STATE_STARTED = 'started';
|
||
var STATE_STREAMING = 'streaming';
|
||
var STATE_ABORTED = 'aborted';
|
||
var STATE_ERROR = 'error';
|
||
|
||
var API = {
|
||
STATUS: '/api/page-ai/pi/status',
|
||
START: '/api/page-ai/pi/start',
|
||
SEND: '/api/page-ai/pi/send',
|
||
ABORT: '/api/page-ai/pi/abort',
|
||
RPC_COMMAND: '/api/page-ai/pi/rpc-command',
|
||
UI_RESPONSE: '/api/page-ai/pi/ui-response',
|
||
EVENTS: '/api/page-ai/pi/events',
|
||
BOOTSTRAP: '/api/page-ai/pi/bootstrap',
|
||
SESSIONS: '/api/page-ai/pi/sessions',
|
||
DIFF: '/api/page-ai/pi/artifacts',
|
||
FORK: '/api/page-ai/pi/fork',
|
||
STATE: '/api/page-ai/pi/state',
|
||
COMPACT: '/api/page-ai/pi/compact',
|
||
CONFIGURE: '/api/page-ai/pi/configure',
|
||
QUEUE_CONFIG: '/api/page-ai/pi/queue-config',
|
||
DIAGNOSTICS: '/api/page-ai/pi/diagnostics',
|
||
EFFECTIVE: '/api/ai-settings/effective',
|
||
};
|
||
|
||
var DEFAULT_MODEL_PROVIDER = 'omniroute';
|
||
var DEFAULT_MODEL_ID = 'gpt-5.4-mini';
|
||
var DEFAULT_THINKING_LEVEL = 'medium';
|
||
var DEFAULT_PERMISSION_MODE = 'confirm';
|
||
|
||
var piLabInstalled = false;
|
||
var piLabPanelEl = null;
|
||
var piLabDrawerEl = null;
|
||
var piLabLauncherEl = null;
|
||
var piLabEventSource = null;
|
||
var piLabStartPromise = null;
|
||
var piLabState = {
|
||
status: STATE_IDLE,
|
||
enabled: false,
|
||
active: false,
|
||
standalone: false,
|
||
sessionId: null,
|
||
providerSessionId: null,
|
||
defaultModelProvider: DEFAULT_MODEL_PROVIDER,
|
||
defaultModelId: DEFAULT_MODEL_ID,
|
||
defaultThinkingLevel: DEFAULT_THINKING_LEVEL,
|
||
modelProvider: null,
|
||
modelId: null,
|
||
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
||
modelMenuOpen: false,
|
||
thinkingMenuOpen: false,
|
||
permissionMode: DEFAULT_PERMISSION_MODE,
|
||
permissionMenuOpen: false,
|
||
pendingPermissionModeApply: false,
|
||
pendingPermissionMode: null,
|
||
applyingPermissionMode: false,
|
||
actionMenuOpen: false,
|
||
runtimeMode: null,
|
||
runtimeImplementation: null,
|
||
runtimeBinary: null,
|
||
runtimeAvailable: true,
|
||
runtimeInstallHint: '',
|
||
runtimeError: '',
|
||
runtimePid: null,
|
||
advancedRuntime: {},
|
||
warmupRunning: false,
|
||
warmupSessionId: null,
|
||
managedBuiltinTools: [],
|
||
piExtensionSources: [],
|
||
piExtensionToolNames: [],
|
||
session: null,
|
||
currentToolEventId: null,
|
||
allowedRootsSummary: '',
|
||
selectionSummary: '',
|
||
selectionText: '',
|
||
changedFiles: [],
|
||
messages: [],
|
||
diagnostics: '',
|
||
receipts: [],
|
||
modelOptions: [],
|
||
history: [],
|
||
pendingQueue: { steering: [], followUp: [] },
|
||
pendingImages: [],
|
||
viewingHistorySessionId: null,
|
||
contextSelection: {
|
||
currentPage: false,
|
||
currentFolder: false,
|
||
selection: false,
|
||
lightrag: false,
|
||
},
|
||
queuedMessages: [],
|
||
pendingMessageCount: 0,
|
||
isCompacting: false,
|
||
contextUsage: {},
|
||
};
|
||
var streamingAssistantMsg = null;
|
||
var piRunViewModel = createPiRunViewModel();
|
||
var activePiUiDialog = null;
|
||
var piCustomUiState = {
|
||
panel: null,
|
||
widgetKey: '',
|
||
title: '',
|
||
lines: [],
|
||
selectedOptionIndex: 0,
|
||
keyQueue: [],
|
||
submitted: false,
|
||
pendingCustomRequest: null,
|
||
};
|
||
var piToastSeq = 0;
|
||
|
||
function createPiRunViewModel(seed) {
|
||
seed = seed || {};
|
||
return {
|
||
turns: Array.isArray(seed.turns) ? seed.turns : [],
|
||
messages: Array.isArray(seed.messages) ? seed.messages : [],
|
||
toolCallsById: seed.toolCallsById || {},
|
||
pendingQueue: seed.pendingQueue || { steering: [], followUp: [] },
|
||
currentAssistant: seed.currentAssistant || null,
|
||
runtimeStatus: seed.runtimeStatus || STATE_IDLE,
|
||
abortResponse: seed.abortResponse || null,
|
||
lastEventType: seed.lastEventType || '',
|
||
lastToolCall: null,
|
||
};
|
||
}
|
||
|
||
function normalizeQueueItems(value) {
|
||
if (!Array.isArray(value)) return [];
|
||
return value.map(function (item) {
|
||
if (item == null) return '';
|
||
if (typeof item === 'string') return item;
|
||
return {
|
||
id: item.id || item.messageId || item.queueId || '',
|
||
text: item.text || item.message || item.prompt || '',
|
||
createdAt: item.createdAt || item.timestamp || '',
|
||
};
|
||
}).filter(function (item) {
|
||
return typeof item === 'string' ? item.trim() : (item.text || item.id);
|
||
});
|
||
}
|
||
|
||
function rebuildPiRunToolIndex(vm) {
|
||
vm.toolCallsById = {};
|
||
(vm.messages || []).forEach(function (msg) {
|
||
(msg && Array.isArray(msg.toolCalls) ? msg.toolCalls : []).forEach(function (tc) {
|
||
if (!tc) return;
|
||
var key = tc.id || tc.name || ('tool-' + Object.keys(vm.toolCallsById).length);
|
||
vm.toolCallsById[key] = tc;
|
||
});
|
||
});
|
||
return vm.toolCallsById;
|
||
}
|
||
|
||
function syncPiRunViewModelFromState() {
|
||
piRunViewModel.messages = piLabState.messages;
|
||
piRunViewModel.pendingQueue = piLabState.pendingQueue || { steering: [], followUp: [] };
|
||
piRunViewModel.currentAssistant = streamingAssistantMsg;
|
||
piRunViewModel.runtimeStatus = piLabState.status;
|
||
rebuildPiRunToolIndex(piRunViewModel);
|
||
return piRunViewModel;
|
||
}
|
||
|
||
function syncPiRunViewModelToState() {
|
||
piLabState.messages = piRunViewModel.messages;
|
||
piLabState.pendingQueue = piRunViewModel.pendingQueue || { steering: [], followUp: [] };
|
||
streamingAssistantMsg = piRunViewModel.currentAssistant || null;
|
||
return piRunViewModel;
|
||
}
|
||
|
||
function resetPiRunViewModel(options) {
|
||
piRunViewModel = createPiRunViewModel({ runtimeStatus: piLabState.status });
|
||
if (!options || options.sync !== false) syncPiRunViewModelToState();
|
||
return piRunViewModel;
|
||
}
|
||
|
||
function enterHistoryReplayMode(sessionId) {
|
||
piLabState.viewingHistorySessionId = sessionId || piLabState.sessionId || null;
|
||
if (piLabEventSource) {
|
||
piLabEventSource.close();
|
||
piLabEventSource = null;
|
||
}
|
||
}
|
||
|
||
function exitHistoryReplayMode() {
|
||
piLabState.viewingHistorySessionId = null;
|
||
}
|
||
|
||
function ensurePiRunAssistant(vm) {
|
||
if (!vm.currentAssistant) {
|
||
vm.currentAssistant = { role: 'assistant', text: '', toolCalls: [], citations: [], diffSummary: null, status: 'streaming' };
|
||
vm.messages.push(vm.currentAssistant);
|
||
}
|
||
vm.currentAssistant.status = vm.currentAssistant.status || 'streaming';
|
||
vm.runtimeStatus = STATE_STREAMING;
|
||
return vm.currentAssistant;
|
||
}
|
||
|
||
function findLastAssistant(vm) {
|
||
return (vm.messages || []).slice().reverse().find(function (message) {
|
||
return message && message.role === 'assistant';
|
||
}) || null;
|
||
}
|
||
|
||
function piRunUpsertToolCall(vm, raw, status, patch) {
|
||
var msg = vm.currentAssistant || findLastAssistant(vm) || ensurePiRunAssistant(vm);
|
||
var payload = raw || {};
|
||
var calls = ensureToolCalls(msg);
|
||
var id = normalizeToolCallId(payload);
|
||
var name = toolDisplayName(payload);
|
||
var found = null;
|
||
if (id) found = calls.find(function (item) { return item && item.id === id; });
|
||
if (!found && name) {
|
||
found = calls.slice().reverse().find(function (item) {
|
||
return item && item.name === name && (item.status === 'running' || item.status === 'pending');
|
||
});
|
||
}
|
||
if (!found) {
|
||
found = { id: id || ('tool-' + (calls.length + 1)), name: name, args: {}, status: status || 'running' };
|
||
calls.push(found);
|
||
}
|
||
found.id = id || found.id;
|
||
found.name = name || found.name;
|
||
found.status = status || found.status || 'running';
|
||
if (payload.args || payload.input || payload.params) found.args = payload.args || payload.input || payload.params;
|
||
if (payload.details) found.details = payload.details;
|
||
Object.keys(patch || {}).forEach(function (key) { found[key] = patch[key]; });
|
||
vm.lastToolCall = found;
|
||
rebuildPiRunToolIndex(vm);
|
||
return found;
|
||
}
|
||
|
||
function reducePiRunViewModel(vm, event) {
|
||
vm = vm || createPiRunViewModel();
|
||
event = event || {};
|
||
var payload = event.payload || {};
|
||
vm.lastEventType = event.type || '';
|
||
if (event.type === 'clear') {
|
||
vm.messages = [];
|
||
vm.toolCallsById = {};
|
||
vm.pendingQueue = { steering: [], followUp: [] };
|
||
vm.currentAssistant = null;
|
||
vm.abortResponse = null;
|
||
return vm;
|
||
}
|
||
if (event.type === 'queue_update') {
|
||
vm.pendingQueue = {
|
||
steering: normalizeQueueItems(payload.steering),
|
||
followUp: normalizeQueueItems(payload.followUp),
|
||
};
|
||
return vm;
|
||
}
|
||
if (event.type === 'user_prompt') {
|
||
vm.messages.push({ role: 'user', text: event.text || payload.text || payload.message || '', meta: event.meta || payload.meta || '' });
|
||
return vm;
|
||
}
|
||
if (event.type === 'assistant_begin') {
|
||
ensurePiRunAssistant(vm);
|
||
return vm;
|
||
}
|
||
if (event.type === 'assistant_delta') {
|
||
var deltaMsg = ensurePiRunAssistant(vm);
|
||
if (event.delta || payload.delta) deltaMsg.text += event.delta || payload.delta;
|
||
if ((event.text || payload.text) && !(event.delta || payload.delta)) deltaMsg.text = event.text || payload.text;
|
||
return vm;
|
||
}
|
||
if (event.type === 'assistant_text_end') {
|
||
var textEndMsg = ensurePiRunAssistant(vm);
|
||
textEndMsg.text = event.text || payload.text || payload.content || textEndMsg.text;
|
||
return vm;
|
||
}
|
||
if (event.type === 'assistant_done') {
|
||
var doneMsg = ensurePiRunAssistant(vm);
|
||
if (event.text || payload.text) doneMsg.text = event.text || payload.text;
|
||
if (event.toolCalls || payload.toolCalls) doneMsg.toolCalls = event.toolCalls || payload.toolCalls;
|
||
if (event.citations || payload.citations) doneMsg.citations = event.citations || payload.citations;
|
||
if (event.diffSummary || payload.diffSummary) doneMsg.diffSummary = event.diffSummary || payload.diffSummary;
|
||
doneMsg.status = 'done';
|
||
vm.currentAssistant = null;
|
||
vm.runtimeStatus = STATE_STARTED;
|
||
rebuildPiRunToolIndex(vm);
|
||
return vm;
|
||
}
|
||
if (event.type === 'assistant_abort') {
|
||
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;
|
||
return vm;
|
||
}
|
||
if (event.type === 'tool_upsert') {
|
||
piRunUpsertToolCall(vm, payload.raw || payload, payload.status || event.status, payload.patch || event.patch || {});
|
||
return vm;
|
||
}
|
||
if (event.type === 'citation') {
|
||
var citMsg = ensurePiRunAssistant(vm);
|
||
citMsg.citations.push({ source: payload.source || '', title: payload.title || '', url: payload.url || '#' });
|
||
return vm;
|
||
}
|
||
if (event.type === 'diff') {
|
||
var diffMsg = ensurePiRunAssistant(vm);
|
||
diffMsg.diffSummary = diffMsg.diffSummary || { files: [], toolEventId: '' };
|
||
diffMsg.diffSummary.files = payload.files || [];
|
||
if (payload.toolEventId) diffMsg.diffSummary.toolEventId = payload.toolEventId;
|
||
return vm;
|
||
}
|
||
return vm;
|
||
}
|
||
|
||
function applyPiRunEvent(event) {
|
||
syncPiRunViewModelFromState();
|
||
reducePiRunViewModel(piRunViewModel, event);
|
||
syncPiRunViewModelToState();
|
||
return piRunViewModel;
|
||
}
|
||
|
||
function injectStyles() {
|
||
var styleId = 'pi-lab-runtime-style';
|
||
if (document.getElementById(styleId)) return;
|
||
var style = document.createElement('style');
|
||
style.id = styleId;
|
||
style.textContent =
|
||
'.wolai-page-ai-pi-lab-launcher{position:fixed;right:26px;bottom:92px;z-index:2600;width:36px;height:36px;border-radius:18px;border:1px solid rgba(37,37,33,.14);background:#fff;color:#252521;box-shadow:0 8px 24px rgba(15,23,42,.16);display:grid;place-items:center;font:600 15px/1 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-launcher:hover{background:#f7f7f5;box-shadow:0 10px 28px rgba(15,23,42,.20)}' +
|
||
'.wolai-page-ai-pi-lab-launcher[data-state="streaming"]::after{content:"";position:absolute;right:4px;top:4px;width:8px;height:8px;border-radius:50%;background:#10a37f;box-shadow:0 0 0 3px rgba(16,163,127,.14)}' +
|
||
'.wolai-page-ai-pi-lab-drawer{position:fixed;right:18px;bottom:18px;top:64px;z-index:2590;width:min(760px,calc(100vw - 36px));border:1px solid rgba(35,35,30,.10);border-radius:20px;background:#f5f6fa;box-shadow:0 24px 70px rgba(15,23,42,.22);overflow:hidden;display:flex;flex-direction:column}' +
|
||
'.wolai-page-ai-pi-lab-drawer[hidden]{display:none!important}' +
|
||
'.wolai-page-ai-pi-lab-drawer[data-minimized="true"]{top:auto;height:54px;width:min(420px,calc(100vw - 36px))}' +
|
||
'.wolai-page-ai-pi-lab-drawer[data-minimized="true"] .wolai-page-ai-pi-lab-config,.wolai-page-ai-pi-lab-drawer[data-minimized="true"] .wolai-page-ai-pi-lab-context-strip,.wolai-page-ai-pi-lab-drawer[data-minimized="true"] .wolai-page-ai-pi-lab-body,.wolai-page-ai-pi-lab-drawer[data-minimized="true"] .wolai-page-ai-pi-lab-diagnostics{display:none!important}' +
|
||
'.wolai-page-ai-pi-lab-shell{position:relative;display:flex;flex-direction:column;height:100%;min-height:0;background:#f5f6fa;color:#242424;font:13px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;overflow:hidden}' +
|
||
'.wolai-page-ai-pi-lab-shell[hidden]{display:none!important}' +
|
||
'.wolai-page-ai-pi-lab-topbar{height:58px;display:flex;align-items:center;gap:10px;padding:0 18px;background:#f5f6fa;flex:0 0 auto}' +
|
||
'.wolai-page-ai-pi-lab-brand{display:flex;align-items:center;gap:8px;min-width:0;flex:1}' +
|
||
'.wolai-page-ai-pi-lab-logo{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;background:#1f6feb;color:#fff;font-weight:700}' +
|
||
'.wolai-page-ai-pi-lab-title{display:flex;flex-direction:column;gap:1px;min-width:0}' +
|
||
'.wolai-page-ai-pi-lab-title strong{font-size:22px;font-weight:760;letter-spacing:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' +
|
||
'.wolai-page-ai-pi-lab-title span{font-size:11px;color:#6f6a60;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' +
|
||
'.wolai-page-ai-pi-lab-icon-btn{width:30px;height:30px;border:1px solid #e2ded6;border-radius:8px;background:#fff;color:#514f49;display:grid;place-items:center;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-icon-btn:hover{background:#f7f6f2}' +
|
||
'.wolai-page-ai-pi-lab-commandbar{height:50px;margin:0 14px 12px;padding:0 12px;border:1px solid #e4e7ef;border-radius:14px;background:#fff;display:flex;align-items:center;gap:8px;box-shadow:0 2px 8px rgba(15,23,42,.04);flex:0 0 auto}' +
|
||
'.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon{width:32px;height:32px;border:0;background:transparent;color:#3c424c;display:grid;place-items:center;cursor:pointer;border-radius:9px}' +
|
||
'.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon svg,.wolai-page-ai-pi-lab-square svg,.wolai-page-ai-pi-lab-icon-btn svg{width:17px;height:17px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' +
|
||
'.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon:hover,.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon[data-active="true"]{background:#f3f5f8;color:#111827}' +
|
||
'.wolai-page-ai-pi-lab-commandbar .wolai-page-ai-pi-lab-tool-icon[disabled]{opacity:.35;cursor:default}' +
|
||
'.wolai-page-ai-pi-lab-commandbar-spacer{flex:1}' +
|
||
'.wolai-page-ai-pi-lab-config{display:none;grid-template-columns:minmax(0,1fr) auto;gap:10px;margin:0 14px 12px;padding:10px 12px;border:1px solid #e5e8ef;border-radius:12px;background:#fff;box-shadow:0 4px 14px rgba(15,23,42,.05);flex:0 0 auto}' +
|
||
'.wolai-page-ai-pi-lab-drawer[data-config-open="true"] .wolai-page-ai-pi-lab-config{display:grid}' +
|
||
'.wolai-page-ai-pi-lab-model-row{display:flex;flex-wrap:wrap;gap:6px;min-width:0}' +
|
||
'.wolai-page-ai-pi-lab-settings{border:0;min-width:0}' +
|
||
'.wolai-page-ai-pi-lab-settings>summary{display:inline-flex;align-items:center;gap:6px;min-height:28px;cursor:pointer;list-style:none;color:#514f49;font-size:12px}' +
|
||
'.wolai-page-ai-pi-lab-settings>summary::-webkit-details-marker{display:none}' +
|
||
'.wolai-page-ai-pi-lab-settings>summary::before{content:"▸";font-size:10px;color:#8a8378}' +
|
||
'.wolai-page-ai-pi-lab-settings[open]>summary::before{content:"▾"}' +
|
||
'.wolai-page-ai-pi-lab-settings-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:8px;max-width:560px}' +
|
||
'.wolai-page-ai-pi-lab-history-layer{position:absolute;inset:0;z-index:28;display:block;pointer-events:auto}' +
|
||
'.wolai-page-ai-pi-lab-history-layer[hidden]{display:none!important}' +
|
||
'.wolai-page-ai-pi-lab-history-scrim{position:absolute;inset:0;background:rgba(15,23,42,.22);backdrop-filter:blur(1px);border-radius:18px}' +
|
||
'.wolai-page-ai-pi-lab-history-panel{position:absolute;left:0;top:0;bottom:0;width:min(340px,calc(100vw - 34px));border-right:1px solid #dfe4ec;background:#fff;box-shadow:18px 0 42px rgba(15,23,42,.18);display:flex;flex-direction:column;overflow:hidden}' +
|
||
'.wolai-page-ai-pi-lab-history-head{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:48px;padding:0 14px;border-bottom:1px solid #edf0f5;background:#fff;color:#1f2937;font-size:14px;font-weight:750}' +
|
||
'.wolai-page-ai-pi-lab-history-close{position:relative;width:26px;height:26px;border:1px solid #e1e4eb;border-radius:8px;background:#fff;color:#4a4f58;display:grid;place-items:center;cursor:pointer;font-size:0;line-height:1}' +
|
||
'.wolai-page-ai-pi-lab-history-close::before,.wolai-page-ai-pi-lab-history-close::after{content:"";position:absolute;left:7px;right:7px;top:12px;height:1.5px;background:currentColor;border-radius:999px}' +
|
||
'.wolai-page-ai-pi-lab-history-close::before{transform:rotate(45deg)}.wolai-page-ai-pi-lab-history-close::after{transform:rotate(-45deg)}' +
|
||
'.wolai-page-ai-pi-lab-history-close svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' +
|
||
'.wolai-page-ai-pi-lab-history-toolbar{display:flex;align-items:center;gap:8px;padding:12px 14px 10px;border-bottom:1px solid #f0f2f6;background:#fafbfc}' +
|
||
'.wolai-page-ai-pi-lab-history-new{height:32px;flex:1;border:1px solid #1f6feb;border-radius:8px;background:#1f6feb;color:#fff;font:12px/1 inherit;font-weight:700;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-history-refresh,.wolai-page-ai-pi-lab-history-clear{height:32px;border:1px solid #d9dee8;border-radius:8px;background:#fff;color:#4b5563;font:12px/1 inherit;padding:0 9px;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-history-clear{color:#b42318;border-color:#f2c4bd}' +
|
||
'.wolai-page-ai-pi-lab-history-meta{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 14px;color:#6b7280;font-size:11px;border-bottom:1px solid #f0f2f6}' +
|
||
'.wolai-page-ai-pi-lab-history-list{min-height:0;overflow:auto;flex:1}' +
|
||
'.wolai-page-ai-pi-lab-history-row{border-bottom:1px solid #f0eee9;background:#fff;padding:11px 12px;display:grid;grid-template-columns:10px minmax(0,1fr) auto;gap:10px;align-items:center}' +
|
||
'.wolai-page-ai-pi-lab-history-row:hover{background:#f8faff}' +
|
||
'.wolai-page-ai-pi-lab-history-dot{width:7px;height:7px;border-radius:999px;background:#1f6feb;opacity:.35}' +
|
||
'.wolai-page-ai-pi-lab-history-row[data-active="true"] .wolai-page-ai-pi-lab-history-dot{opacity:1}' +
|
||
'.wolai-page-ai-pi-lab-history-row strong{display:block;font-size:12px;color:#1f2937}' +
|
||
'.wolai-page-ai-pi-lab-history-row span{font-size:11px;color:#6b7280}' +
|
||
'.wolai-page-ai-pi-lab-history-main{min-width:0;border:0;background:transparent;padding:0;text-align:left;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-history-main strong,.wolai-page-ai-pi-lab-history-main span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
|
||
'.wolai-page-ai-pi-lab-history-actions{display:flex;gap:4px;align-items:center}' +
|
||
'.wolai-page-ai-pi-lab-history-action{border:1px solid #d8dee8;border-radius:6px;background:#fff;padding:4px 6px;font-size:11px;color:#4b5563;cursor:pointer;white-space:nowrap}' +
|
||
'.wolai-page-ai-pi-lab-history-action:hover{background:#f3f6fb}' +
|
||
'.wolai-page-ai-pi-lab-history-danger{color:#b42318;border-color:#f2c4bd}' +
|
||
'.wolai-page-ai-pi-lab-history-row[data-active="true"]{background:#f4f7ff}.wolai-page-ai-pi-lab-history-row[data-active="true"] .wolai-page-ai-pi-lab-history-main strong{color:#1f3f8f}' +
|
||
'.wolai-page-ai-pi-lab-replay-banner{margin:0 18px 10px;padding:8px 10px;border:1px solid #dbe5ff;border-radius:8px;background:#f6f8ff;color:#36507d;font-size:12px;display:flex;align-items:center;justify-content:space-between;gap:10px}' +
|
||
'.wolai-page-ai-pi-lab-replay-banner[hidden]{display:none!important}.wolai-page-ai-pi-lab-replay-banner button{border:1px solid #cfd9f5;border-radius:7px;background:#fff;color:#24406d;height:26px;padding:0 9px;font:12px/1 inherit;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-field{display:flex;flex-direction:column;gap:4px;font-size:11px;color:#6f6a60}' +
|
||
'.wolai-page-ai-pi-lab-field select,.wolai-page-ai-pi-lab-field input{height:30px;border:1px solid #ddd8cf;border-radius:8px;background:#fff;color:#242424;padding:0 8px;font:12px/1.3 inherit;min-width:0}' +
|
||
'.wolai-page-ai-pi-lab-context-strip{display:none;padding:0;margin:-6px 14px 12px;border:1px solid #ebe7de;border-radius:12px;background:#faf9f6;flex:0 0 auto}' +
|
||
'.wolai-page-ai-pi-lab-drawer[data-config-open="true"] .wolai-page-ai-pi-lab-context-strip{display:block}' +
|
||
'.wolai-page-ai-pi-lab-context-strip>summary{display:flex;align-items:center;gap:8px;min-height:34px;padding:0 12px;cursor:pointer;list-style:none;color:#5d594f;font-size:12px}' +
|
||
'.wolai-page-ai-pi-lab-context-strip>summary::-webkit-details-marker{display:none}' +
|
||
'.wolai-page-ai-pi-lab-context-strip>summary::before{content:"▸";font-size:10px;color:#8a8378}' +
|
||
'.wolai-page-ai-pi-lab-context-strip[open]>summary::before{content:"▾"}' +
|
||
'.wolai-page-ai-pi-lab-context-title{font-weight:650;color:#42403a}' +
|
||
'.wolai-page-ai-pi-lab-context-current{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#242424;font-weight:650}' +
|
||
'.wolai-page-ai-pi-lab-context-count{margin-left:auto;color:#8a8378;font-size:11px}' +
|
||
'.wolai-page-ai-pi-lab-context-content{display:flex;align-items:center;gap:6px;padding:0 12px 8px;overflow-x:auto}' +
|
||
'.wolai-page-ai-pi-lab-chip{height:24px;display:inline-flex;align-items:center;gap:5px;border:1px solid #e4e0d8;border-radius:999px;background:#f8f7f3;color:#514f49;padding:0 8px;font-size:11px;max-width:100%}' +
|
||
'.wolai-page-ai-pi-lab-chip strong{font-weight:650;color:#242424;min-width:0;overflow:hidden;text-overflow:ellipsis}' +
|
||
'.wolai-page-ai-pi-lab-context-strip .wolai-page-ai-pi-lab-chip{flex:0 0 auto}' +
|
||
'.wolai-page-ai-pi-lab-chip[data-tone="ok"]{background:#eef8f3;border-color:#ccebdc;color:#176b4b}' +
|
||
'.wolai-page-ai-pi-lab-chip[data-tone="warn"]{background:#fff6e6;border-color:#f2dfb5;color:#865b13}' +
|
||
'.wolai-page-ai-pi-lab-chip[data-tone="danger"]{background:#fff0ee;border-color:#f2c4bd;color:#a73b2f}' +
|
||
'.wolai-page-ai-pi-lab-actions{display:flex;align-items:center;gap:6px;justify-content:flex-end}' +
|
||
'.wolai-page-ai-pi-lab-btn{height:28px;border:1px solid #d9d4ca;border-radius:7px;background:#fff;color:#383631;font-size:12px;padding:0 10px;cursor:pointer;white-space:nowrap}' +
|
||
'.wolai-page-ai-pi-lab-btn:hover{background:#f7f6f2}' +
|
||
'.wolai-page-ai-pi-lab-btn:disabled{opacity:.45;cursor:default}' +
|
||
'.wolai-page-ai-pi-lab-btn-primary{background:#1f6feb;border-color:#1f6feb;color:#fff}' +
|
||
'.wolai-page-ai-pi-lab-btn-primary:hover{background:#1b61cf}' +
|
||
'.wolai-page-ai-pi-lab-btn-danger{background:#d93025;border-color:#d93025;color:#fff}' +
|
||
'.wolai-page-ai-pi-lab-body{position:relative;display:block;min-height:0;flex:1;background:#f5f6fa;padding:0 14px 12px}' +
|
||
'.wolai-page-ai-pi-lab-main{position:relative;display:flex;flex-direction:column;min-width:0;min-height:0;height:100%;overflow:hidden}' +
|
||
'.wolai-page-ai-pi-lab-messages{flex:1;min-height:0;overflow-y:auto;padding:22px 18px 20px;display:flex;flex-direction:column;gap:12px;overscroll-behavior:contain;background:#fff;border:1px solid #e4e7ef;border-radius:16px 16px 0 0;box-shadow:0 10px 28px rgba(15,23,42,.07)}' +
|
||
'.wolai-page-ai-pi-lab-empty{margin:auto;max-width:460px;text-align:center;color:#8a9099;background:transparent;border:0;border-radius:0;padding:10px 0 4px;box-shadow:none}' +
|
||
'.wolai-page-ai-pi-lab-empty-icon{width:76px;height:76px;margin:0 auto 18px;border-radius:50%;background:#f0f2f6;color:#8a93a2;display:grid;place-items:center;font-size:42px;font-weight:400}' +
|
||
'.wolai-page-ai-pi-lab-empty strong{display:block;color:#242833;margin-bottom:8px;font-size:25px;font-weight:760;letter-spacing:0}' +
|
||
'.wolai-page-ai-pi-lab-empty p{margin:0 0 20px;color:#8a9099;font-size:16px}' +
|
||
'.wolai-page-ai-pi-lab-examples{display:flex;flex-direction:column;align-items:center;gap:10px}' +
|
||
'.wolai-page-ai-pi-lab-example{height:42px;min-width:min(390px,100%);border:1px solid #e1e4eb;border-radius:12px;background:#fff;color:#4a4f58;font-size:16px;display:flex;align-items:center;justify-content:flex-start;gap:12px;padding:0 18px;box-shadow:0 3px 9px rgba(15,23,42,.05);cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-example:hover{background:#f8fafc}' +
|
||
'.wolai-page-ai-pi-lab-example span{font-size:18px;color:#5c6470}' +
|
||
'.wolai-page-ai-pi-lab-message{display:flex;flex-direction:column;gap:6px;max-width:92%}' +
|
||
'.wolai-page-ai-pi-lab-message[data-role="user"]{align-self:flex-end;align-items:flex-end}' +
|
||
'.wolai-page-ai-pi-lab-message[data-role="assistant"],.wolai-page-ai-pi-lab-message[data-role="system"]{align-self:stretch}' +
|
||
'.wolai-page-ai-pi-lab-role{display:flex;align-items:center;gap:6px;color:#777166;font-size:11px;font-weight:650}' +
|
||
'.wolai-page-ai-pi-lab-message[data-role="user"] .wolai-page-ai-pi-lab-role{justify-content:flex-end}' +
|
||
'.wolai-page-ai-pi-lab-bubble{position:relative;border:1px solid #e6e2da;border-radius:12px;background:#fff;padding:10px 12px;color:#272521;word-break:break-word;box-shadow:0 4px 14px rgba(15,23,42,.035)}' +
|
||
'.wolai-page-ai-pi-lab-message[data-role="user"] .wolai-page-ai-pi-lab-bubble{background:#f3f7ff;border-color:#d9e6ff}' +
|
||
'.wolai-page-ai-pi-lab-text{white-space:pre-wrap}' +
|
||
'.wolai-page-ai-pi-lab-markdown{font-size:14px;line-height:1.62;color:#272521}' +
|
||
'.wolai-page-ai-pi-lab-markdown>*:first-child{margin-top:0}' +
|
||
'.wolai-page-ai-pi-lab-markdown>*:last-child{margin-bottom:0}' +
|
||
'.wolai-page-ai-pi-lab-markdown p{margin:0 0 10px}' +
|
||
'.wolai-page-ai-pi-lab-markdown h1,.wolai-page-ai-pi-lab-markdown h2,.wolai-page-ai-pi-lab-markdown h3{margin:14px 0 8px;font-weight:720;letter-spacing:0;line-height:1.28;color:#202124}' +
|
||
'.wolai-page-ai-pi-lab-markdown h1{font-size:20px}.wolai-page-ai-pi-lab-markdown h2{font-size:17px}.wolai-page-ai-pi-lab-markdown h3{font-size:15px}' +
|
||
'.wolai-page-ai-pi-lab-markdown ul,.wolai-page-ai-pi-lab-markdown ol{margin:0 0 10px 20px;padding:0}' +
|
||
'.wolai-page-ai-pi-lab-markdown li{margin:3px 0;padding-left:2px}' +
|
||
'.wolai-page-ai-pi-lab-markdown blockquote{margin:0 0 10px;padding:2px 0 2px 12px;border-left:3px solid #d8dce6;color:#5f6368}' +
|
||
'.wolai-page-ai-pi-lab-markdown pre{margin:8px 0 12px;padding:10px 12px;border:1px solid #e4e7ef;border-radius:8px;background:#f7f8fb;overflow:auto;white-space:pre;font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#242833}' +
|
||
'.wolai-page-ai-pi-lab-markdown code{border:1px solid #e5e7eb;border-radius:5px;background:#f7f8fb;padding:1px 4px;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#1f2937}' +
|
||
'.wolai-page-ai-pi-lab-markdown pre code{border:0;background:transparent;padding:0;font:inherit;color:inherit}' +
|
||
'.wolai-page-ai-pi-lab-markdown table{width:100%;border-collapse:collapse;margin:8px 0 12px;font-size:13px}' +
|
||
'.wolai-page-ai-pi-lab-markdown th,.wolai-page-ai-pi-lab-markdown td{border:1px solid #e4e7ef;padding:6px 8px;text-align:left;vertical-align:top}' +
|
||
'.wolai-page-ai-pi-lab-markdown th{background:#f7f8fb;font-weight:650}' +
|
||
'.wolai-page-ai-pi-lab-markdown a{color:#1f6feb;text-decoration:none}.wolai-page-ai-pi-lab-markdown a:hover{text-decoration:underline}' +
|
||
'.wolai-page-ai-pi-lab-text.streaming-cursor::after,.wolai-page-ai-pi-lab-markdown.streaming-cursor::after{content:"";display:inline-block;width:7px;height:14px;margin-left:3px;vertical-align:-2px;background:#1f6feb;animation:pi-blink .85s steps(2,start) infinite}' +
|
||
'@keyframes pi-blink{50%{opacity:.18}}' +
|
||
'.wolai-page-ai-pi-lab-tool-calls,.wolai-page-ai-pi-lab-citations{display:flex;flex-direction:column;gap:6px;margin-top:8px}' +
|
||
'.wolai-page-ai-pi-lab-tool-timeline{margin-top:10px;border:1px solid #e4e7ef;border-radius:8px;background:#fafbfc;overflow:hidden}' +
|
||
'.wolai-page-ai-pi-lab-tool-timeline>summary{min-height:32px;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 10px;cursor:pointer;list-style:none;color:#41464f;font-size:12px;font-weight:650}' +
|
||
'.wolai-page-ai-pi-lab-tool-timeline>summary::-webkit-details-marker{display:none}' +
|
||
'.wolai-page-ai-pi-lab-tool-timeline>summary::after{content:"▸";font-size:10px;color:#7c8593}' +
|
||
'.wolai-page-ai-pi-lab-tool-timeline[open]>summary::after{content:"▾"}' +
|
||
'.wolai-page-ai-pi-lab-tool-timeline-body{display:flex;flex-direction:column;gap:6px;padding:8px;border-top:1px solid #e4e7ef}' +
|
||
'.wolai-page-ai-pi-lab-tool-call{border:1px solid #e5e0d8;border-radius:9px;background:#fff;overflow:hidden}' +
|
||
'.wolai-page-ai-pi-lab-tool-call[data-status="running"]{border-color:#bcd7ff;background:#f7fbff}' +
|
||
'.wolai-page-ai-pi-lab-tool-call[data-status="error"]{border-color:#f2c4bd;background:#fff8f7}' +
|
||
'.wolai-page-ai-pi-lab-tool-call-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:7px 9px;font-size:12px;font-weight:650;color:#42403a}' +
|
||
'.wolai-page-ai-pi-lab-tool-call-header span{font-size:11px;color:#777166;font-weight:500}' +
|
||
'.wolai-page-ai-pi-lab-tool-call-summary-line{padding:0 9px 7px;color:#6f6a60;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' +
|
||
'.wolai-page-ai-pi-lab-tool-call-details{border-top:1px solid #ebe7de;background:#fff}' +
|
||
'.wolai-page-ai-pi-lab-tool-call-details>summary{min-height:28px;display:flex;align-items:center;gap:6px;padding:0 9px;cursor:pointer;list-style:none;color:#5d594f;font-size:11px}' +
|
||
'.wolai-page-ai-pi-lab-tool-call-details>summary::-webkit-details-marker{display:none}' +
|
||
'.wolai-page-ai-pi-lab-tool-call-details>summary::before{content:"▸";font-size:10px;color:#8a8378}.wolai-page-ai-pi-lab-tool-call-details[open]>summary::before{content:"▾"}' +
|
||
'.wolai-page-ai-pi-lab-tool-call pre{margin:0;border-top:1px solid #ebe7de;background:#fff;padding:8px;max-height:140px;overflow:auto;font:11px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;color:#42403a;white-space:pre-wrap}' +
|
||
'.wolai-page-ai-pi-lab-tool-policy{display:inline-flex;align-items:center;border-radius:999px;border:1px solid #ded7ca;background:#fff;padding:1px 6px;font-size:10px;color:#6f6557;margin-left:6px}' +
|
||
'.wolai-page-ai-pi-lab-tool-policy[data-policy="ask"]{border-color:#e1b870;background:#fff8e8;color:#8a5c13}' +
|
||
'.wolai-page-ai-pi-lab-tool-policy[data-approved="true"]{border-color:#83b99a;background:#eef9f1;color:#27613b}' +
|
||
'.wolai-page-ai-pi-lab-tool-policy[data-approved="false"]{border-color:#e0a39a;background:#fff0ee;color:#9f352c}' +
|
||
'.wolai-page-ai-pi-lab-abort-note{margin-top:8px;border:1px solid #f2c4bd;border-radius:8px;background:#fff8f7;color:#9f352c;padding:7px 9px;font-size:12px}' +
|
||
'.wolai-page-ai-pi-lab-message-actions{position:absolute;right:8px;top:8px;display:flex;gap:6px;opacity:0;pointer-events:none;transition:opacity .12s ease}' +
|
||
'.wolai-page-ai-pi-lab-bubble:hover .wolai-page-ai-pi-lab-message-actions,.wolai-page-ai-pi-lab-bubble:focus-within .wolai-page-ai-pi-lab-message-actions{opacity:1;pointer-events:auto}' +
|
||
'.wolai-page-ai-pi-lab-message-actions button{width:28px;height:28px;border:1px solid #e1e4eb;border-radius:8px;background:#fff;color:#4a4f58;padding:0;display:grid;place-items:center;cursor:pointer;box-shadow:0 4px 14px rgba(15,23,42,.08)}' +
|
||
'.wolai-page-ai-pi-lab-message-actions button:hover{background:#f8fafc}' +
|
||
'.wolai-page-ai-pi-lab-message-actions svg{width:15px;height:15px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' +
|
||
'.wolai-page-ai-pi-lab-citation{display:inline-flex;align-items:center;gap:6px;width:max-content;max-width:100%;border:1px solid #d9e6ff;border-radius:999px;background:#f3f7ff;color:#2456a6;text-decoration:none;padding:4px 8px;font-size:11px}' +
|
||
'.wolai-page-ai-pi-lab-diff{display:inline-flex;align-items:center;width:max-content;max-width:100%;margin-top:8px;border:1px solid #f0dcb7;border-radius:999px;background:#fff7e8;color:#865b13;padding:4px 8px;font-size:11px}' +
|
||
'.wolai-page-ai-pi-lab-composer{position:relative;flex:0 0 auto;border:1px solid #e4e7ef;border-top:0;border-radius:0 0 16px 16px;background:#fff;box-shadow:0 14px 34px rgba(15,23,42,.09);overflow:visible}' +
|
||
'.wolai-page-ai-pi-lab-input-wrap{position:relative;background:#f7f8fb}' +
|
||
'.wolai-page-ai-pi-lab-input{width:100%;min-height:98px;max-height:190px;box-sizing:border-box;border:0;resize:none;padding:18px 62px 12px 22px;font:18px/1.45 inherit;color:#242424;background:transparent;outline:none}' +
|
||
'.wolai-page-ai-pi-lab-input::placeholder{color:#b6bbc5}' +
|
||
'.wolai-page-ai-pi-lab-quick{height:24px;border:1px solid #e5e0d8;border-radius:999px;background:#f8f7f3;color:#5d594f;font-size:11px;padding:0 8px;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-modebar{min-height:46px;display:flex;align-items:center;gap:8px;padding:6px 12px;border-top:1px solid #eef0f5;background:#fff;box-sizing:border-box}' +
|
||
'.wolai-page-ai-pi-lab-square{width:34px;height:34px;flex:0 0 34px;border:1px solid #e1e4eb;border-radius:9px;background:#fff;color:#242833;display:grid;place-items:center;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-square:hover{background:#f8fafc}' +
|
||
'.wolai-page-ai-pi-lab-square[data-active="true"],.wolai-page-ai-pi-lab-chip-button[data-active="true"]{border-color:#9ec5fe;background:#eef6ff;color:#1558a6}' +
|
||
'.wolai-page-ai-pi-lab-chip-button{height:34px;display:inline-flex;align-items:center;gap:6px;border:1px solid #e1e4eb;border-radius:10px;background:#fff;color:#4a4f58;padding:0 10px;font:12px/1 inherit;white-space:nowrap;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-chip-button svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' +
|
||
'.wolai-page-ai-pi-lab-chip-button[data-tone="warn"]{border-color:#f0d8aa;background:#fff9ed;color:#865b13}.wolai-page-ai-pi-lab-chip-button[data-tone="danger"]{border-color:#f2c4bd;background:#fff8f7;color:#9f352c}.wolai-page-ai-pi-lab-chip-button[data-tone="ok"]{border-color:#bfe6ac;background:#f4fff0;color:#27613b}' +
|
||
'.wolai-page-ai-pi-lab-control-wrap{position:relative;display:inline-flex;min-width:0;flex:0 0 auto}' +
|
||
'.wolai-page-ai-pi-lab-model-control-wrap{flex:1 1 160px;max-width:210px}' +
|
||
'.wolai-page-ai-pi-lab-control-button{height:34px;min-width:0;width:100%;border:1px solid #e1e4eb;border-radius:10px;background:#fff;color:#4a4f58;padding:0 9px;display:inline-flex;align-items:center;justify-content:space-between;gap:7px;font:12px/1 inherit;white-space:nowrap;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-model-control{font-size:13px}' +
|
||
'.wolai-page-ai-pi-lab-thinking-control{max-width:118px}' +
|
||
'.wolai-page-ai-pi-lab-control-button:hover{background:#f8fafc}.wolai-page-ai-pi-lab-control-button[data-active="true"]{border-color:#9ec5fe;background:#eef6ff;color:#1558a6}.wolai-page-ai-pi-lab-control-button:disabled{opacity:.45;cursor:default}' +
|
||
'.wolai-page-ai-pi-lab-control-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}' +
|
||
'.wolai-page-ai-pi-lab-control-caret{flex:0 0 auto;color:#8a9099;font-size:11px;line-height:1;transform:translateY(-1px)}' +
|
||
'.wolai-page-ai-pi-lab-control-menu{position:absolute;left:0;bottom:40px;z-index:20;width:min(280px,calc(100vw - 28px));max-height:min(280px,calc(100vh - 160px));overflow:auto;border:1px solid #dfe3ea;border-radius:12px;background:#fff;box-shadow:0 18px 46px rgba(15,23,42,.22);padding:6px;display:none;color:#242424}' +
|
||
'.wolai-page-ai-pi-lab-thinking-control-wrap .wolai-page-ai-pi-lab-control-menu{width:180px}' +
|
||
'.wolai-page-ai-pi-lab-control-wrap[data-open="true"] .wolai-page-ai-pi-lab-control-menu{display:block}' +
|
||
'.wolai-page-ai-pi-lab-control-option{width:100%;min-height:34px;border:0;border-radius:8px;background:#fff;color:#242424;display:grid;grid-template-columns:minmax(0,1fr) 18px;gap:8px;align-items:center;text-align:left;padding:7px 9px;font:13px/1.25 inherit;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-control-option:hover{background:#f7f8fb}.wolai-page-ai-pi-lab-control-option[data-active="true"]{background:#f3f6fb;color:#1558a6}' +
|
||
'.wolai-page-ai-pi-lab-control-option-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wolai-page-ai-pi-lab-control-option-check{text-align:right;color:#6b7280}' +
|
||
'.wolai-page-ai-pi-lab-permission-wrap{position:relative;display:inline-flex;flex:0 0 auto}' +
|
||
'.wolai-page-ai-pi-lab-permission-menu{position:absolute;left:0;bottom:40px;z-index:18;width:min(306px,calc(100vw - 28px));border:1px solid #dfe3ea;border-radius:12px;background:#fff;box-shadow:0 18px 46px rgba(15,23,42,.22);padding:6px;display:none;color:#242424}' +
|
||
'.wolai-page-ai-pi-lab-permission-wrap[data-open="true"] .wolai-page-ai-pi-lab-permission-menu{display:block}' +
|
||
'.wolai-page-ai-pi-lab-permission-option{width:100%;min-height:58px;border:0;border-radius:9px;background:#fff;color:#242424;display:grid;grid-template-columns:22px 1fr 18px;gap:10px;align-items:center;text-align:left;padding:8px 9px;font:13px/1.25 inherit;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-permission-option:hover{background:#f7f8fb}.wolai-page-ai-pi-lab-permission-option[data-active="true"]{background:#f3f6fb}' +
|
||
'.wolai-page-ai-pi-lab-permission-option svg{width:17px;height:17px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round;color:#5d6470}' +
|
||
'.wolai-page-ai-pi-lab-permission-option strong{display:block;font-size:13px;font-weight:650}.wolai-page-ai-pi-lab-permission-option small{display:block;margin-top:4px;color:#6b7280;font-size:12px;line-height:1.25}.wolai-page-ai-pi-lab-permission-check{color:#6b7280;text-align:right}' +
|
||
'.wolai-page-ai-pi-lab-action-menu{position:absolute;left:12px;bottom:52px;z-index:16;width:min(300px,calc(100% - 24px));border:1px solid #dfe3ea;border-radius:12px;background:#fff;box-shadow:0 18px 46px rgba(15,23,42,.22);padding:6px;display:none;color:#242424}' +
|
||
'.wolai-page-ai-pi-lab-composer[data-menu-open="true"] .wolai-page-ai-pi-lab-action-menu{display:block}' +
|
||
'.wolai-page-ai-pi-lab-menu-section{padding:5px 7px 4px;color:#8a9099;font-size:11px;font-weight:650}' +
|
||
'.wolai-page-ai-pi-lab-menu-item{width:100%;min-height:34px;border:0;border-radius:8px;background:#fff;color:#242424;display:flex;align-items:center;gap:9px;text-align:left;padding:0 9px;font:13px/1.2 inherit;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-menu-item:hover{background:#f7f8fb}.wolai-page-ai-pi-lab-menu-item[data-active="true"]{background:#eef6ff;color:#1558a6}.wolai-page-ai-pi-lab-menu-item[disabled]{opacity:.45;cursor:default}.wolai-page-ai-pi-lab-menu-item[disabled]:hover{background:#fff}' +
|
||
'.wolai-page-ai-pi-lab-menu-item svg{width:15px;height:15px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}' +
|
||
'.wolai-page-ai-pi-lab-menu-item strong{margin-left:auto;color:#1558a6;font-size:13px}' +
|
||
'.wolai-page-ai-pi-lab-menu-sep{height:1px;background:#edf0f5;margin:6px 3px}' +
|
||
'.wolai-page-ai-pi-lab-segment{display:inline-flex;border:1px solid #e1e4eb;border-radius:10px;overflow:hidden;background:#fff}' +
|
||
'.wolai-page-ai-pi-lab-segment button{height:36px;border:0;background:#fff;color:#4a4f58;padding:0 16px;font:14px/1 inherit;cursor:pointer;white-space:nowrap}' +
|
||
'.wolai-page-ai-pi-lab-segment button[data-active=\"true\"]{background:#111827;color:#fff}' +
|
||
'.wolai-page-ai-pi-lab-stream-controls{display:inline-flex;align-items:center;gap:8px}' +
|
||
'.wolai-page-ai-pi-lab-queue-count{height:22px;min-width:22px;padding:0 6px;border-radius:999px;background:#eef2ff;color:#334155;display:inline-flex;align-items:center;justify-content:center;font-size:11px}' +
|
||
'.wolai-page-ai-pi-lab-spacer{flex:1}' +
|
||
'.wolai-page-ai-pi-lab-send{position:absolute;right:12px;bottom:11px;width:38px;height:38px;border:0;border-radius:12px;background:#1d9bf0;color:#fff;display:grid;place-items:center;cursor:pointer;box-shadow:0 10px 24px rgba(29,155,240,.25)}' +
|
||
'.wolai-page-ai-pi-lab-send svg{width:18px;height:18px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}' +
|
||
'.wolai-page-ai-pi-lab-send:disabled{opacity:.45;cursor:default}' +
|
||
'.wolai-page-ai-pi-lab-rail{position:absolute;right:14px;top:0;bottom:104px;width:260px;z-index:4;display:none;flex-direction:column;gap:8px;min-height:0;overflow:auto;padding:8px;border:1px solid #e4e7ef;border-radius:14px;background:#f7f8fb;box-shadow:0 18px 46px rgba(15,23,42,.18)}' +
|
||
'.wolai-page-ai-pi-lab-body[data-rail-open="true"] .wolai-page-ai-pi-lab-rail{display:flex}' +
|
||
'.wolai-page-ai-pi-lab-rail-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 2px 2px;color:#42403a;font-size:12px;font-weight:650}' +
|
||
'.wolai-page-ai-pi-lab-rail-close{width:24px;height:24px;border:1px solid #e1e4eb;border-radius:7px;background:#fff;color:#4a4f58;display:grid;place-items:center;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-rail-section{border:1px solid #e5e0d8;border-radius:10px;background:#fff;overflow:hidden}' +
|
||
'.wolai-page-ai-pi-lab-rail-section>summary{display:flex;align-items:center;justify-content:space-between;gap:8px;cursor:pointer;list-style:none;padding:8px 9px;font-size:11px;font-weight:650;color:#5d594f;background:#faf9f6}' +
|
||
'.wolai-page-ai-pi-lab-rail-section>summary::-webkit-details-marker{display:none}' +
|
||
'.wolai-page-ai-pi-lab-rail-section>summary::after{content:"▸";font-size:10px;color:#8a8378}' +
|
||
'.wolai-page-ai-pi-lab-rail-section[open]>summary::after{content:"▾"}' +
|
||
'.wolai-page-ai-pi-lab-rail-count{font-weight:600;color:#8a8378}' +
|
||
'.wolai-page-ai-pi-lab-rail-title{padding:8px 9px;border-bottom:1px solid #eee9e0;font-size:11px;font-weight:650;color:#5d594f;background:#faf9f6}' +
|
||
'.wolai-page-ai-pi-lab-tool-list{display:flex;flex-direction:column;gap:5px;padding:8px}' +
|
||
'.wolai-page-ai-pi-lab-tool-pill{border:1px solid #e6e2da;border-radius:7px;background:#fff;color:#42403a;padding:5px 7px;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}' +
|
||
'.wolai-page-ai-pi-lab-artifacts{display:flex;flex-direction:column;gap:6px;padding:8px;max-height:250px;overflow:auto}' +
|
||
'.wolai-page-ai-pi-lab-artifact{border:1px solid #e6e2da;border-radius:8px;background:#fff;color:#42403a;padding:7px;font-size:11px;line-height:1.42;overflow:hidden}' +
|
||
'.wolai-page-ai-pi-lab-artifact[data-kind="citation"]{border-color:#d9e6ff;background:#f7faff}.wolai-page-ai-pi-lab-artifact[data-kind="diff"]{border-color:#f0dcb7;background:#fff9ed}.wolai-page-ai-pi-lab-artifact[data-kind="mcp"]{border-color:#dfe6f2;background:#fafcff}' +
|
||
'.wolai-page-ai-pi-lab-artifact strong{display:block;font-size:11px;font-weight:700;color:#2f343b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wolai-page-ai-pi-lab-artifact span{display:block;margin-top:3px;color:#6f6a60;word-break:break-word}.wolai-page-ai-pi-lab-artifact-actions{display:flex;flex-wrap:wrap;gap:5px}.wolai-page-ai-pi-lab-artifact button,.wolai-page-ai-pi-lab-artifact a,.wolai-page-ai-pi-lab-rail-action{display:inline-flex;align-items:center;height:24px;margin-top:6px;border:1px solid #e1e4eb;border-radius:7px;background:#fff;color:#315d9f;text-decoration:none;padding:0 7px;font-size:11px;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-rail-action{margin:8px 8px 0}.wolai-page-ai-pi-lab-rail-action:hover,.wolai-page-ai-pi-lab-artifact button:hover,.wolai-page-ai-pi-lab-artifact a:hover{background:#f8fafc}' +
|
||
'.wolai-page-ai-pi-lab-receipts{display:flex;flex-direction:column;gap:5px;padding:8px;max-height:170px;overflow:auto}' +
|
||
'.wolai-page-ai-pi-lab-receipt{border-radius:7px;background:#f8f7f3;border:1px solid #e6e2da;padding:6px;font-size:11px;color:#4d4941}' +
|
||
'.wolai-page-ai-pi-lab-receipt[data-allowed="false"]{background:#fff0ee;border-color:#f2c4bd;color:#9f352c}' +
|
||
'.wolai-page-ai-pi-lab-ui-backdrop{position:relative;z-index:12;display:block;padding:10px 12px 0;background:#fff;pointer-events:none}' +
|
||
'.wolai-page-ai-pi-lab-ui-dialog{width:100%;border:1px solid #dfe4ec;border-radius:12px;background:#fff;box-shadow:0 10px 28px rgba(15,23,42,.12);overflow:hidden;color:#242424;pointer-events:auto}' +
|
||
'.wolai-page-ai-pi-lab-ui-dialog[data-collapsed="true"] .wolai-page-ai-pi-lab-ui-dialog-body,.wolai-page-ai-pi-lab-ui-dialog[data-collapsed="true"] .wolai-page-ai-pi-lab-ui-dialog-actions{display:none}' +
|
||
'.wolai-page-ai-pi-lab-ui-dialog-header{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:start;padding:11px 12px 9px;border-bottom:1px solid #edf0f5;background:#fafbfc}.wolai-page-ai-pi-lab-ui-dialog-header strong{display:block;font-size:13px;font-weight:720;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wolai-page-ai-pi-lab-ui-dialog-header span{display:block;margin-top:3px;font-size:12px;color:#6f6a60;white-space:pre-wrap}.wolai-page-ai-pi-lab-ui-collapse{width:28px;height:28px;border:1px solid #e1e4eb;border-radius:8px;background:#fff;color:#4a4f58;cursor:pointer}' +
|
||
'.wolai-page-ai-pi-lab-ui-dialog-body{padding:10px 12px;display:flex;flex-direction:column;gap:8px;max-height:min(320px,38vh);overflow:auto}.wolai-page-ai-pi-lab-ui-dialog-body input,.wolai-page-ai-pi-lab-ui-dialog-body textarea{width:100%;box-sizing:border-box;border:1px solid #ddd8cf;border-radius:8px;background:#fff;color:#242424;padding:8px 9px;font:13px/1.45 inherit;outline:none}.wolai-page-ai-pi-lab-ui-dialog-body textarea{min-height:76px;resize:vertical}' +
|
||
'.wolai-page-ai-pi-lab-ui-question{border:1px solid #edf0f5;border-radius:10px;background:#fff;padding:9px}.wolai-page-ai-pi-lab-ui-question-title{font-size:12px;font-weight:700;color:#2f343b;margin-bottom:3px}.wolai-page-ai-pi-lab-ui-question-prompt{font-size:12px;color:#6f6a60;white-space:pre-wrap;margin-bottom:7px}.wolai-page-ai-pi-lab-ui-option{width:100%;min-height:34px;border:1px solid #e1e4eb;border-radius:8px;background:#fff;color:#242424;text-align:left;padding:7px 9px;cursor:pointer;display:flex;align-items:flex-start;gap:8px}.wolai-page-ai-pi-lab-ui-option:hover,.wolai-page-ai-pi-lab-ui-option[data-selected="true"]{background:#f3f7ff;border-color:#bcd7ff}.wolai-page-ai-pi-lab-ui-option small{display:block;margin-top:2px;color:#737a86;font-size:11px;line-height:1.35}.wolai-page-ai-pi-lab-ui-option-mark{flex:0 0 auto;color:#5d6675}.wolai-page-ai-pi-lab-ui-note{border-top:1px solid #edf0f5;padding-top:8px}.wolai-page-ai-pi-lab-ui-note label{display:block;margin-bottom:4px;font-size:11px;color:#737a86}' +
|
||
'.wolai-page-ai-pi-lab-ui-dialog-actions{display:flex;justify-content:flex-end;gap:8px;padding:9px 12px 11px;border-top:1px solid #edf0f5}.wolai-page-ai-pi-lab-ui-dialog-actions button{height:30px;border:1px solid #ddd8cf;border-radius:8px;background:#fff;color:#4a4f58;padding:0 11px;cursor:pointer}.wolai-page-ai-pi-lab-ui-dialog-actions button[data-primary="true"]{border-color:#111827;background:#111827;color:#fff}' +
|
||
'.wolai-page-ai-pi-lab-ui-tui{margin:0;padding:8px 9px;border:1px solid #edf0f5;border-radius:8px;background:#fafbfc;color:#242833;white-space:pre-wrap;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:auto}.wolai-page-ai-pi-lab-ui-tui-options{display:flex;flex-direction:column;gap:6px}.wolai-page-ai-pi-lab-ui-option[data-custom-selected="true"]{background:#eef6ff;border-color:#9ec5fe}' +
|
||
'.wolai-page-ai-pi-lab-toast-stack{position:absolute;left:18px;right:18px;bottom:166px;z-index:13;display:flex;flex-direction:column;align-items:flex-end;gap:8px;pointer-events:none}.wolai-page-ai-pi-lab-toast{width:min(340px,100%);border:1px solid #dbe3ef;border-radius:10px;background:#fff;color:#242424;box-shadow:0 12px 34px rgba(15,23,42,.18);padding:10px 12px;font-size:13px;line-height:1.45;pointer-events:auto}.wolai-page-ai-pi-lab-toast[data-type="warning"]{border-color:#f0dcb7;background:#fff9ed}.wolai-page-ai-pi-lab-toast[data-type="error"]{border-color:#f2c4bd;background:#fff8f7}.wolai-page-ai-pi-lab-toast[data-type="success"]{border-color:#bfe6ac;background:#f4fff0}' +
|
||
'.wolai-page-ai-pi-lab-diagnostics{display:none;border-top:1px solid #e6e2da;background:#fff;flex:0 0 auto}' +
|
||
'.wolai-page-ai-pi-lab-drawer[data-config-open="true"] .wolai-page-ai-pi-lab-diagnostics{display:block}' +
|
||
'.wolai-page-ai-pi-lab-diagnostics>summary{cursor:pointer;padding:8px 12px;font-size:11px;color:#6f6a60}' +
|
||
'.wolai-page-ai-pi-lab-diagnostic-actions{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:0 12px 8px}.wolai-page-ai-pi-lab-diagnostic-actions button{height:26px;border:1px solid #ddd8cf;border-radius:7px;background:#fff;color:#42403a;padding:0 8px;font-size:11px;cursor:pointer}.wolai-page-ai-pi-lab-diagnostic-actions button:hover{background:#f8f7f3}.wolai-page-ai-pi-lab-diagnostic-actions input{height:26px;min-width:120px;border:1px solid #ddd8cf;border-radius:7px;background:#fff;color:#42403a;padding:0 8px;font-size:11px;outline:none}.wolai-page-ai-pi-lab-diagnostic-output[hidden]{display:none!important}.wolai-page-ai-pi-lab-diagnostic-output{border-top:1px solid #eee9e0;background:#faf9f6}' +
|
||
'.wolai-page-ai-pi-lab-diagnostics pre{margin:0;padding:0 12px 10px;white-space:pre-wrap;word-break:break-word;font:11px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;color:#5d594f}' +
|
||
'@media (max-width: 760px){.wolai-page-ai-pi-lab-drawer{left:10px;right:10px;top:58px;bottom:10px;width:auto}.wolai-page-ai-pi-lab-history-panel{width:min(320px,calc(100vw - 34px))}.wolai-page-ai-pi-lab-rail{left:14px;right:14px;top:auto;bottom:132px;width:auto;max-height:min(360px,calc(100% - 164px))}.wolai-page-ai-pi-lab-config{grid-template-columns:1fr}}' +
|
||
'@media (max-width: 520px){.wolai-page-ai-pi-lab-topbar{height:54px;padding:0 14px}.wolai-page-ai-pi-lab-title strong{font-size:21px}.wolai-page-ai-pi-lab-commandbar{height:50px;margin:0 14px 12px;gap:6px;overflow:hidden}.wolai-page-ai-pi-lab-history-panel{top:116px;left:12px;right:12px;max-height:min(300px,calc(100% - 300px))}.wolai-page-ai-pi-lab-history-row{grid-template-columns:1fr}.wolai-page-ai-pi-lab-history-actions{justify-content:flex-start}.wolai-page-ai-pi-lab-input{min-height:88px;padding:16px 58px 10px 18px;font-size:16px}.wolai-page-ai-pi-lab-modebar{min-height:48px;flex-wrap:nowrap;gap:6px;padding:6px 10px}.wolai-page-ai-pi-lab-model-control-wrap{flex:1 1 130px;min-width:92px;max-width:none}.wolai-page-ai-pi-lab-toast-stack{left:14px;right:14px;bottom:154px}.wolai-page-ai-pi-lab-toast{width:100%}}';
|
||
/* Split diff overlay styles */
|
||
var diffStyles = '.wolai-page-ai-pi-lab-diff-overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999;background:rgba(0,0,0,0.4);display:flex;flex-direction:column;padding:40px 60px}' +
|
||
'.wolai-page-ai-pi-lab-diff-header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#f0f0ed;border-radius:8px 8px 0 0;font:600 14px/1.4 system-ui}' +
|
||
'.wolai-page-ai-pi-lab-diff-close{background:none;border:none;font:18px/1 sans-serif;cursor:pointer;padding:4px 8px;border-radius:4px}' +
|
||
'.wolai-page-ai-pi-lab-diff-close:hover{background:rgba(0,0,0,0.08)}' +
|
||
'.wolai-page-ai-pi-lab-diff-body{flex:1;overflow:auto;background:#fff;border-radius:0 0 8px 8px;padding:0;font:12px/1.5 Monaco,Consolas,monospace}' +
|
||
'.wolai-page-ai-pi-lab-diff-file-header{background:#f8f8f5;font-weight:600;padding:6px 12px;border-bottom:1px solid #e5e5e0}' +
|
||
'.wolai-page-ai-pi-lab-diff-hunk-header{background:#f0f0ed;color:#666;padding:4px 12px;border-bottom:1px solid #e5e5e0;font-size:11px}' +
|
||
'.wolai-page-ai-pi-lab-diff-line{padding:1px 12px;border-bottom:1px solid #f0f0ed;white-space:pre}' +
|
||
'.wolai-page-ai-pi-lab-diff-line-add{background:#e6ffec}' +
|
||
'.wolai-page-ai-pi-lab-diff-line-del{background:#ffebe9}' +
|
||
'.wolai-page-ai-pi-lab-diff-line-ctx{background:#f8f8f5;color:#666}' +
|
||
'.wolai-page-ai-pi-lab-diff-line-num{display:inline-block;width:40px;text-align:right;color:#999;padding-right:12px;user-select:none}' +
|
||
'.wolai-page-ai-pi-lab-diff-collapse{cursor:pointer;text-align:center;color:#888;background:#f5f5f2;padding:2px;font-size:11px;border-bottom:1px solid #e5e5e0}' +
|
||
'.wolai-page-ai-pi-lab-diff-collapse:hover{background:#eee}';
|
||
style.textContent += diffStyles;
|
||
document.head.appendChild(style);
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
if (str == null) return '';
|
||
return String(str)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
function escapeAttr(str) {
|
||
return escapeHtml(str).replace(/`/g, '`');
|
||
}
|
||
|
||
function renderMarkdownInline(text) {
|
||
var placeholders = [];
|
||
function stash(html) {
|
||
var key = '\u0000MD' + placeholders.length + '\u0000';
|
||
placeholders.push(html);
|
||
return key;
|
||
}
|
||
var value = escapeHtml(text || '');
|
||
value = value.replace(/`([^`]+)`/g, function (_, code) {
|
||
return stash('<code>' + 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('');
|
||
}
|
||
|
||
function el(tag, attrs, children) {
|
||
var elem = document.createElement(tag);
|
||
if (attrs) {
|
||
Object.keys(attrs).forEach(function (key) {
|
||
if (key === 'className') elem.className = attrs[key];
|
||
else if (key === 'textContent') elem.textContent = attrs[key];
|
||
else if (key === 'innerHTML') elem.innerHTML = attrs[key];
|
||
else if (key === 'style' && attrs[key] && typeof attrs[key] === 'object') Object.keys(attrs[key]).forEach(function (styleKey) { elem.style[styleKey] = attrs[key][styleKey]; });
|
||
else elem.setAttribute(key, attrs[key]);
|
||
});
|
||
}
|
||
if (children) {
|
||
(Array.isArray(children) ? children : [children]).forEach(function (child) {
|
||
if (typeof child === 'string') elem.appendChild(document.createTextNode(child));
|
||
else if (child instanceof Node) elem.appendChild(child);
|
||
});
|
||
}
|
||
return elem;
|
||
}
|
||
|
||
function compactJson(value) {
|
||
if (value == null || value === '') return '';
|
||
if (typeof value === 'string') return value;
|
||
try {
|
||
return JSON.stringify(value, null, 2);
|
||
} catch (_) {
|
||
return String(value);
|
||
}
|
||
}
|
||
|
||
function extractResultText(value) {
|
||
if (value == null) return '';
|
||
if (typeof value === 'string') return value;
|
||
var content = value.content;
|
||
if (Array.isArray(content)) {
|
||
var text = content.map(function (part) {
|
||
if (!part) return '';
|
||
if (typeof part === 'string') return part;
|
||
if (typeof part.text === 'string') return part.text;
|
||
return '';
|
||
}).filter(Boolean).join('');
|
||
if (text) return text;
|
||
}
|
||
return compactJson(value);
|
||
}
|
||
|
||
function truncateSingleLine(value, limit) {
|
||
var text = String(value || '').replace(/\s+/g, ' ').trim();
|
||
var max = limit || 120;
|
||
return text.length > max ? text.slice(0, max - 1) + '…' : text;
|
||
}
|
||
|
||
function copyTextToClipboard(text) {
|
||
text = String(text || '');
|
||
if (!text) return Promise.resolve(false);
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
return navigator.clipboard.writeText(text).then(function () { return true; }).catch(function () { return false; });
|
||
}
|
||
return Promise.resolve(false);
|
||
}
|
||
|
||
function messageToMarkdown(msg) {
|
||
if (!msg) return '';
|
||
var lines = [];
|
||
if (msg.text) lines.push(String(msg.text));
|
||
if (Array.isArray(msg.citations) && msg.citations.length) {
|
||
lines.push('', 'References:');
|
||
msg.citations.forEach(function (cit, index) {
|
||
lines.push((index + 1) + '. ' + (cit.title || cit.source || 'Citation') + (cit.url ? ' - ' + cit.url : ''));
|
||
});
|
||
}
|
||
if (msg.diffSummary && Array.isArray(msg.diffSummary.files) && msg.diffSummary.files.length) {
|
||
lines.push('', 'Changed files:');
|
||
msg.diffSummary.files.forEach(function (file) { lines.push('- ' + file); });
|
||
}
|
||
if (Array.isArray(msg.toolCalls) && msg.toolCalls.length) {
|
||
lines.push('', 'Tool calls:');
|
||
msg.toolCalls.forEach(function (tc) {
|
||
lines.push('- ' + toolDisplayName({ toolName: tc.name, args: tc.args, details: tc.details }) + ' · ' + (tc.status || ''));
|
||
});
|
||
}
|
||
return lines.join('\n').trim();
|
||
}
|
||
|
||
function currentSessionEventsUrl() {
|
||
if (!piLabState.sessionId) return '';
|
||
return API.SESSIONS + '/' + encodeURIComponent(piLabState.sessionId) + '/events?limit=1000';
|
||
}
|
||
|
||
function openCurrentSessionJsonl() {
|
||
var url = currentSessionEventsUrl();
|
||
if (!url) {
|
||
showPiToast('No Pi session to open', 'warning');
|
||
return Promise.resolve(false);
|
||
}
|
||
return fetch(url, { credentials: 'same-origin', cache: 'no-store' })
|
||
.then(function (response) {
|
||
if (!response.ok) throw new Error('Session events failed: ' + response.status);
|
||
return response.json();
|
||
})
|
||
.then(function (payload) {
|
||
var lines = (Array.isArray(payload.events) ? payload.events : []).map(function (event) {
|
||
return JSON.stringify({
|
||
eventType: event.eventType,
|
||
createdAt: event.createdAt,
|
||
payload: event.payload || {},
|
||
});
|
||
}).join('\n');
|
||
var blob = new Blob([lines + (lines ? '\n' : '')], { type: 'application/x-ndjson;charset=utf-8' });
|
||
var objectUrl = URL.createObjectURL(blob);
|
||
window.open(objectUrl, '_blank', 'noopener');
|
||
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 30000);
|
||
showPiToast('Session JSONL opened', 'success');
|
||
return true;
|
||
})
|
||
.catch(function (error) {
|
||
updateDiagnostics(error.message || '打开 session JSONL 失败');
|
||
showPiToast('Open session JSONL failed', 'error');
|
||
return false;
|
||
});
|
||
}
|
||
|
||
function normalizeToolCallId(payload) {
|
||
payload = payload || {};
|
||
return String(payload.toolCallId || payload.tool_call_id || payload.id || payload.callId || '').trim();
|
||
}
|
||
|
||
function normalizeToolName(payload) {
|
||
payload = payload || {};
|
||
return String(payload.toolName || payload.tool_name || payload.name || payload.tool || 'tool').trim();
|
||
}
|
||
|
||
function parseMcpArgs(args) {
|
||
if (!args) return {};
|
||
if (typeof args === 'string') {
|
||
try {
|
||
return JSON.parse(args);
|
||
} catch (_) {
|
||
return { raw: args };
|
||
}
|
||
}
|
||
return args;
|
||
}
|
||
|
||
function toolDisplayName(payload) {
|
||
var name = normalizeToolName(payload);
|
||
var args = parseMcpArgs(payload && payload.args);
|
||
var details = payload && payload.details ? payload.details : {};
|
||
var server = String(args.server || details.server || '').trim();
|
||
var tool = String(args.tool || details.tool || '').trim();
|
||
if (name === 'mcp' && server) return 'mcp:' + server + (tool ? '/' + tool : '/list');
|
||
return name;
|
||
}
|
||
|
||
function assistantToolCallPayload(msgData) {
|
||
if (!msgData) return null;
|
||
var toolCall = msgData.toolCall || null;
|
||
if (!toolCall && msgData.partial && msgData.partial.type === 'toolCall') toolCall = msgData.partial;
|
||
if (!toolCall && msgData.toolName) toolCall = msgData;
|
||
if (!toolCall && msgData.name) toolCall = msgData;
|
||
if (!toolCall || !(toolCall.name || toolCall.toolName || toolCall.tool_name || toolCall.id || toolCall.toolCallId)) return null;
|
||
return {
|
||
toolCallId: toolCall.toolCallId || toolCall.id || msgData.toolCallId || msgData.id || '',
|
||
toolName: toolCall.toolName || toolCall.tool_name || toolCall.name || msgData.toolName || msgData.name || '',
|
||
args: toolCall.arguments || toolCall.args || msgData.arguments || msgData.args || {},
|
||
details: msgData.details || null,
|
||
result: msgData.result || null,
|
||
};
|
||
}
|
||
|
||
function ensureToolCalls(msg) {
|
||
if (!msg.toolCalls) msg.toolCalls = [];
|
||
return msg.toolCalls;
|
||
}
|
||
|
||
function upsertToolCall(raw, status, patch) {
|
||
var viewModel = applyPiRunEvent({
|
||
type: 'tool_upsert',
|
||
payload: {
|
||
raw: raw || {},
|
||
status: status || 'running',
|
||
patch: patch || {},
|
||
},
|
||
});
|
||
updateMessages();
|
||
return viewModel.lastToolCall;
|
||
}
|
||
|
||
function citationFromMarkdown(markdown, fallbackTitle) {
|
||
var value = String(markdown || '').trim();
|
||
if (!value || value[0] !== '[' || value[value.length - 1] !== ')') return null;
|
||
var split = value.lastIndexOf('](');
|
||
if (split <= 0) return null;
|
||
var title = value.slice(1, split).replace(/\\([\[\]])/g, '$1').trim();
|
||
var url = value.slice(split + 2, -1).trim();
|
||
if (!url) return null;
|
||
return {
|
||
source: String(fallbackTitle || title || url),
|
||
title: title || String(fallbackTitle || '资料库引用'),
|
||
url: url,
|
||
};
|
||
}
|
||
|
||
function collectKnowledgeRagCitations(value) {
|
||
var citations = [];
|
||
var seen = {};
|
||
function add(markdown, fallbackTitle) {
|
||
var citation = citationFromMarkdown(markdown, fallbackTitle);
|
||
if (!citation || seen[citation.url] || citations.length >= 8) return;
|
||
seen[citation.url] = true;
|
||
citations.push(citation);
|
||
}
|
||
function visit(node) {
|
||
if (node == null || citations.length >= 8) return;
|
||
if (Array.isArray(node)) {
|
||
node.forEach(visit);
|
||
return;
|
||
}
|
||
if (typeof node !== 'object') return;
|
||
var title = String(node.sourceRootRelativePath || node.citationLabel || node.title || '').trim();
|
||
if (typeof node.citationMarkdown === 'string') add(node.citationMarkdown, title);
|
||
Object.keys(node).forEach(function (key) {
|
||
if (key !== 'citationMarkdown') visit(node[key]);
|
||
});
|
||
}
|
||
visit(value);
|
||
return citations;
|
||
}
|
||
|
||
function appendKnowledgeRagCitations(value) {
|
||
var citations = collectKnowledgeRagCitations(value);
|
||
if (!citations.length) return;
|
||
syncPiRunViewModelFromState();
|
||
var current = piRunViewModel.currentAssistant || findLastAssistant(piRunViewModel);
|
||
if (!current) current = ensurePiRunAssistant(piRunViewModel);
|
||
if (!Array.isArray(current.citations)) current.citations = [];
|
||
var existing = {};
|
||
current.citations.forEach(function (citation) {
|
||
existing[String(citation && citation.url || '')] = true;
|
||
});
|
||
citations.forEach(function (citation) {
|
||
if (existing[citation.url]) return;
|
||
existing[citation.url] = true;
|
||
current.citations.push(citation);
|
||
});
|
||
syncPiRunViewModelToState();
|
||
}
|
||
|
||
function renderToolCallCard(tc) {
|
||
var displayName = toolDisplayName({ toolName: tc.name, args: tc.args, details: tc.details });
|
||
var resultText = extractResultText(tc.result || tc.partialResult || '');
|
||
var summaryParts = [];
|
||
if (tc.args && Object.keys(tc.args).length) {
|
||
var args = parseMcpArgs(tc.args);
|
||
if (args.server) summaryParts.push('server=' + args.server);
|
||
if (args.tool) summaryParts.push('tool=' + args.tool);
|
||
}
|
||
if (resultText) summaryParts.push(truncateSingleLine(resultText, 140));
|
||
var item = el('div', {
|
||
className: 'wolai-page-ai-pi-lab-tool-call',
|
||
'data-page-ai-pi-lab-tool-card': tc.id || displayName || 'tool',
|
||
'data-page-ai-pi-lab-tool-call': displayName || 'tool',
|
||
'data-status': tc.status || '',
|
||
});
|
||
var policy = tc.toolPolicy || (tc.approvalRequired ? 'ask' : '');
|
||
var policyBadge = policy ? '<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;
|
||
}
|
||
|
||
function normalizeSlashes(value) {
|
||
return String(value || '').trim().replace(/\\/g, '/');
|
||
}
|
||
|
||
function currentUrl() {
|
||
try {
|
||
return new URL(window.location.href);
|
||
} catch (_) {
|
||
return new URL('/', window.location.origin);
|
||
}
|
||
}
|
||
|
||
function currentDocumentId() {
|
||
var fromBody = document.body && document.body.dataset ? String(document.body.dataset.documentId || '').trim() : '';
|
||
if (fromBody) return fromBody;
|
||
var pageTab = document.querySelector('[data-mnote-main-tab="page"]');
|
||
if (pageTab instanceof HTMLElement) {
|
||
var tabDocumentId = String(pageTab.getAttribute('data-document-id') || '').trim();
|
||
if (tabDocumentId) return tabDocumentId;
|
||
}
|
||
var match = currentUrl().pathname.match(/^\/documents\/([^/]+)/);
|
||
if (match) return decodeURIComponent(match[1]);
|
||
var missingPage = String(currentUrl().searchParams.get('missingPage') || currentUrl().searchParams.get('pageId') || '').trim();
|
||
return missingPage ? missingPage : '';
|
||
}
|
||
|
||
function currentWorkspaceId() {
|
||
return currentUrl().searchParams.get('workspaceId') || (document.body && document.body.dataset ? String(document.body.dataset.workspaceId || '').trim() : '');
|
||
}
|
||
|
||
function currentRootUri() {
|
||
var fromUrl = String(currentUrl().searchParams.get('rootUri') || '').trim();
|
||
if (fromUrl) return fromUrl;
|
||
var fromBody = document.body instanceof HTMLElement ? String(document.body.getAttribute('data-mnote-root-uri') || '').trim() : '';
|
||
if (fromBody) return fromBody;
|
||
return String((piLabState.session && piLabState.session.rootUri) || '').trim();
|
||
}
|
||
|
||
function localMarkdownRelativePathFromDocumentId(documentId) {
|
||
var value = String(documentId || '').trim();
|
||
if (!value.startsWith('local-md:')) return '';
|
||
var encoded = value.slice('local-md:'.length);
|
||
try {
|
||
return decodeURIComponent(encoded.replace(/~([0-9A-Fa-f]{2})/g, '%$1'));
|
||
} catch (_) {
|
||
return encoded.replace(/~2F/gi, '/');
|
||
}
|
||
}
|
||
|
||
function cssEscape(value) {
|
||
var text = String(value || '');
|
||
if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(text);
|
||
return text.replace(/["\\]/g, '\\$&');
|
||
}
|
||
|
||
function currentSelectionText() {
|
||
try {
|
||
var selection = window.getSelection ? window.getSelection() : null;
|
||
return selection ? String(selection.toString() || '').trim() : '';
|
||
} catch (_) {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
function activeEditorSnapshot() {
|
||
try {
|
||
var snapshot = window.__mnoteDocumentPaneRuntime
|
||
&& typeof window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot === 'function'
|
||
? window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot()
|
||
: null;
|
||
if (!snapshot || typeof snapshot !== 'object') return null;
|
||
if (snapshot.activeEditor) return snapshot.activeEditor;
|
||
var entries = []
|
||
.concat(Array.isArray(snapshot.editors) ? snapshot.editors : [])
|
||
.concat(Array.isArray(snapshot.resourceEditors) ? snapshot.resourceEditors : []);
|
||
return entries.find(function (entry) { return entry && entry.active === true; }) || entries[0] || null;
|
||
} catch (_) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function activeFileTreeWorkspacePath(documentId) {
|
||
try {
|
||
var relativePath = localMarkdownRelativePathFromDocumentId(documentId);
|
||
var selector = '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]';
|
||
if (relativePath) selector += '[data-local-relative-path="' + cssEscape(relativePath) + '"]';
|
||
var row = document.querySelector(selector);
|
||
var reader = window.__mnoteFileTreeRuntime && typeof window.__mnoteFileTreeRuntime.readWorkspacePathFromRow === 'function'
|
||
? window.__mnoteFileTreeRuntime.readWorkspacePathFromRow
|
||
: null;
|
||
if (row instanceof HTMLElement && reader) {
|
||
var workspacePath = reader(row);
|
||
if (workspacePath && typeof workspacePath === 'object') {
|
||
return Object.assign({}, workspacePath, {
|
||
relativePath: workspacePath.relativePath || row.getAttribute('data-local-relative-path') || '',
|
||
rootUri: workspacePath.rootUri || row.getAttribute('data-root-uri') || '',
|
||
sourceKind: workspacePath.sourceKind || row.getAttribute('data-source-kind') || '',
|
||
documentId: workspacePath.documentId || row.getAttribute('data-document-id') || documentId || '',
|
||
});
|
||
}
|
||
}
|
||
if (row instanceof HTMLElement) {
|
||
return {
|
||
relativePath: row.getAttribute('data-local-relative-path') || '',
|
||
rootUri: row.getAttribute('data-root-uri') || '',
|
||
sourceKind: row.getAttribute('data-source-kind') || '',
|
||
documentId: row.getAttribute('data-document-id') || documentId || '',
|
||
};
|
||
}
|
||
} catch (_) {}
|
||
return null;
|
||
}
|
||
|
||
function currentPageContext() {
|
||
var active = activeEditorSnapshot() || {};
|
||
var activeWorkspacePath = active.workspacePath && typeof active.workspacePath === 'object' ? active.workspacePath : {};
|
||
var urlDocumentId = currentDocumentId();
|
||
var urlPagePath = localMarkdownRelativePathFromDocumentId(urlDocumentId);
|
||
var documentId = String(urlDocumentId || activeWorkspacePath.documentId || active.documentId || '').trim();
|
||
var fileTreeWorkspacePath = activeFileTreeWorkspacePath(documentId) || {};
|
||
var workspacePath = Object.assign({}, activeWorkspacePath, fileTreeWorkspacePath);
|
||
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();
|
||
var titleNode = document.querySelector('[data-page-title-current="true"]') || document.querySelector('.wolai-breadcrumb-current');
|
||
var title = String(active.title || workspacePath.title || (titleNode && titleNode.textContent) || document.title || '').trim();
|
||
var selection = currentSelectionText();
|
||
return {
|
||
pageId: documentId,
|
||
pagePath: pagePath,
|
||
pageTitle: title,
|
||
rootUri: rootUri,
|
||
workspaceId: workspaceId,
|
||
selection: selection,
|
||
};
|
||
}
|
||
|
||
function currentFolderPathFromPagePath(pagePath) {
|
||
var normalized = normalizeSlashes(pagePath || '').replace(/^\/+/, '');
|
||
if (!normalized) return null;
|
||
var parts = normalized.split('/').filter(Boolean);
|
||
if (parts.length <= 1) return '';
|
||
parts.pop();
|
||
return parts.join('/');
|
||
}
|
||
|
||
function contextSelectionState() {
|
||
if (!piLabState.contextSelection || typeof piLabState.contextSelection !== 'object') {
|
||
piLabState.contextSelection = {};
|
||
}
|
||
if (typeof piLabState.contextSelection.currentPage !== 'boolean') piLabState.contextSelection.currentPage = false;
|
||
if (typeof piLabState.contextSelection.currentFolder !== 'boolean') piLabState.contextSelection.currentFolder = false;
|
||
if (typeof piLabState.contextSelection.selection !== 'boolean') piLabState.contextSelection.selection = false;
|
||
if (typeof piLabState.contextSelection.lightrag !== 'boolean') piLabState.contextSelection.lightrag = false;
|
||
return piLabState.contextSelection;
|
||
}
|
||
|
||
function selectedContextRefs() {
|
||
var selection = contextSelectionState();
|
||
var refs = [];
|
||
if (selection.currentPage) refs.push('current_page');
|
||
if (selection.currentFolder) refs.push('folder');
|
||
if (selection.selection) refs.push('selection');
|
||
if (selection.lightrag) refs.push('lightrag');
|
||
return refs;
|
||
}
|
||
|
||
function selectedContextPayload(context) {
|
||
context = context || currentPageContext();
|
||
var selection = contextSelectionState();
|
||
var folderPath = currentFolderPathFromPagePath(context.pagePath);
|
||
return {
|
||
refs: selectedContextRefs(),
|
||
currentPage: selection.currentPage ? {
|
||
rootUri: context.rootUri || null,
|
||
workspaceId: context.workspaceId || null,
|
||
pagePath: context.pagePath || null,
|
||
pageTitle: context.pageTitle || null,
|
||
} : null,
|
||
currentFolder: selection.currentFolder ? {
|
||
rootUri: context.rootUri || null,
|
||
workspaceId: context.workspaceId || null,
|
||
folderPath: folderPath === null ? null : folderPath,
|
||
} : null,
|
||
selection: selection.selection ? {
|
||
text: context.selection || piLabState.selectionText || '',
|
||
source: 'mnote_sidebar_host',
|
||
} : null,
|
||
lightrag: selection.lightrag ? {
|
||
enabled: true,
|
||
provider: 'lightrag',
|
||
} : null,
|
||
};
|
||
}
|
||
|
||
function refreshSelectionSummary() {
|
||
var selection = currentSelectionText();
|
||
if (selection) piLabState.selectionText = selection;
|
||
var visibleSelection = selection || piLabState.selectionText;
|
||
piLabState.selectionSummary = visibleSelection ? ('已选中 ' + visibleSelection.length + ' 字') : '';
|
||
return visibleSelection;
|
||
}
|
||
|
||
function applyCurrentContextToState() {
|
||
var context = currentPageContext();
|
||
refreshSelectionSummary();
|
||
if (!piLabState.session) piLabState.session = {};
|
||
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;
|
||
updateContextStrip();
|
||
return context;
|
||
}
|
||
|
||
function setContextQuickActive(kind, active) {
|
||
var selection = contextSelectionState();
|
||
if (kind === 'read-page') selection.currentPage = active !== false;
|
||
if (kind === 'current-folder') selection.currentFolder = active !== false;
|
||
if (kind === 'selection') selection.selection = active !== false;
|
||
if (kind === 'rag') selection.lightrag = active !== false;
|
||
updateContextStrip();
|
||
updateButtons();
|
||
}
|
||
|
||
function toggleContextQuick(kind) {
|
||
var selection = contextSelectionState();
|
||
var current = false;
|
||
if (kind === 'read-page') current = selection.currentPage;
|
||
else if (kind === 'current-folder') current = selection.currentFolder;
|
||
else if (kind === 'selection') current = selection.selection;
|
||
else if (kind === 'rag') current = selection.lightrag;
|
||
setContextQuickActive(kind, !current);
|
||
}
|
||
|
||
function currentModelLabel() {
|
||
var provider = piLabState.modelProvider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER;
|
||
var modelId = piLabState.modelId || piLabState.defaultModelId || DEFAULT_MODEL_ID;
|
||
if (String(modelId).indexOf(provider + '/') === 0) modelId = String(modelId).slice(provider.length + 1);
|
||
return [provider, modelId].filter(Boolean).join('/');
|
||
}
|
||
|
||
function modelControlOptions() {
|
||
var source = piLabState.modelOptions.length ? piLabState.modelOptions : [{
|
||
provider: piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER,
|
||
id: piLabState.defaultModelId || DEFAULT_MODEL_ID,
|
||
name: piLabState.defaultModelId || DEFAULT_MODEL_ID,
|
||
}];
|
||
source = source.concat([{
|
||
provider: DEFAULT_MODEL_PROVIDER,
|
||
id: 'freefirst',
|
||
name: DEFAULT_MODEL_PROVIDER + '/freefirst',
|
||
}, {
|
||
provider: DEFAULT_MODEL_PROVIDER,
|
||
id: DEFAULT_MODEL_ID,
|
||
name: DEFAULT_MODEL_ID,
|
||
}]);
|
||
var seen = {};
|
||
return source.map(function (item) {
|
||
var provider = String(item.provider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER);
|
||
var modelId = String(item.id || item.modelId || item.name || DEFAULT_MODEL_ID);
|
||
if (modelId.indexOf(provider + '/') === 0) modelId = modelId.slice(provider.length + 1);
|
||
var key = provider + '/' + modelId;
|
||
if (seen[key]) return null;
|
||
seen[key] = true;
|
||
return {
|
||
provider: provider,
|
||
modelId: modelId,
|
||
label: String(item.name || key),
|
||
key: key,
|
||
};
|
||
}).filter(Boolean);
|
||
}
|
||
|
||
function normalizeThinkingLevel(value) {
|
||
var level = String(value || '').trim().toLowerCase();
|
||
return ['off', 'minimal', 'low', 'medium', 'high', 'xhigh'].indexOf(level) >= 0 ? level : DEFAULT_THINKING_LEVEL;
|
||
}
|
||
|
||
function thinkingLevelOptions() {
|
||
return [
|
||
{ level: 'off', label: '思考 关' },
|
||
{ level: 'minimal', label: '思考 极低' },
|
||
{ level: 'low', label: '思考 低' },
|
||
{ level: 'medium', label: '思考 中' },
|
||
{ level: 'high', label: '思考 高' },
|
||
{ level: 'xhigh', label: '思考 最高' },
|
||
];
|
||
}
|
||
|
||
function currentThinkingLabel() {
|
||
var labels = {
|
||
off: '关',
|
||
minimal: '极低',
|
||
low: '低',
|
||
medium: '中',
|
||
high: '高',
|
||
xhigh: '最高',
|
||
};
|
||
return labels[normalizeThinkingLevel(piLabState.thinkingLevel || piLabState.defaultThinkingLevel)] || '中';
|
||
}
|
||
|
||
function normalizePermissionMode(value) {
|
||
var mode = String(value || '').trim().toLowerCase();
|
||
if (mode === 'ask') return 'confirm';
|
||
return ['confirm', 'auto_edit', 'plan', 'full_access'].indexOf(mode) >= 0 ? mode : DEFAULT_PERMISSION_MODE;
|
||
}
|
||
|
||
function permissionModeOptions() {
|
||
return [
|
||
{ mode: 'confirm', icon: 'status', label: '变更前确认', description: '改文件、执行命令前先问我。' },
|
||
{ mode: 'auto_edit', icon: 'zap', label: '自动编辑', description: '授权目录内读写自动放行。' },
|
||
{ mode: 'plan', icon: 'settings', label: '计划模式', description: '只读分析,禁止写入和命令。' },
|
||
{ mode: 'full_access', icon: 'shield', label: '完全访问', description: '本会话内尽量不再确认。' },
|
||
];
|
||
}
|
||
|
||
function permissionModeLabel(mode) {
|
||
mode = normalizePermissionMode(mode);
|
||
var found = permissionModeOptions().filter(function (item) { return item.mode === mode; })[0];
|
||
return found ? found.label : '变更前确认';
|
||
}
|
||
|
||
function currentRuntimePolicy() {
|
||
return (piLabState.session && (piLabState.session.runtimePolicySnapshot || piLabState.session.runtimePolicy)) || {};
|
||
}
|
||
|
||
function toolPolicyValues() {
|
||
var policy = currentRuntimePolicy();
|
||
var policies = policy.mnoteToolPolicies || policy.mnote_tool_policies || {};
|
||
return Object.keys(policies).map(function (key) { return String(policies[key] || '').toLowerCase(); }).filter(Boolean);
|
||
}
|
||
|
||
function permissionSummary() {
|
||
var selectedMode = normalizePermissionMode(piLabState.permissionMode);
|
||
if (selectedMode === 'plan') return { label: '计划模式', tone: 'warn' };
|
||
if (selectedMode === 'auto_edit') return { label: '自动编辑', tone: 'ok' };
|
||
if (selectedMode === 'full_access') return { label: '完全访问', tone: 'ok' };
|
||
var values = toolPolicyValues();
|
||
var hasAsk = values.indexOf('ask') >= 0;
|
||
var hasDeny = values.indexOf('deny') >= 0;
|
||
var rootsText = String(piLabState.allowedRootsSummary || '').toLowerCase();
|
||
var readonly = rootsText.indexOf('read') >= 0 || rootsText.indexOf('只读') >= 0;
|
||
if (hasDeny || readonly) return { label: hasAsk ? '受限/审批' : '受限', tone: 'danger' };
|
||
if (hasAsk || !values.length) return { label: '变更前确认', tone: 'warn' };
|
||
return { label: '自动工具', tone: 'ok' };
|
||
}
|
||
|
||
function updateThinkingControl() {
|
||
if (!piLabPanelEl) return;
|
||
var wrap = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-menu-wrap]');
|
||
var control = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-menu-toggle]');
|
||
var label = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-label]');
|
||
var menu = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-menu]');
|
||
var level = normalizeThinkingLevel(piLabState.thinkingLevel || piLabState.defaultThinkingLevel);
|
||
piLabState.thinkingLevel = level;
|
||
if (wrap) wrap.setAttribute('data-open', piLabState.thinkingMenuOpen ? 'true' : 'false');
|
||
if (control) {
|
||
control.setAttribute('data-active', piLabState.thinkingMenuOpen ? 'true' : 'false');
|
||
control.setAttribute('aria-expanded', piLabState.thinkingMenuOpen ? 'true' : 'false');
|
||
control.title = '思考深度:' + currentThinkingLabel() + ';启动本次 Pi 会话时应用';
|
||
}
|
||
if (label) label.textContent = '思考 ' + currentThinkingLabel();
|
||
if (menu) menu.innerHTML = thinkingLevelOptions().map(function (item) {
|
||
var active = item.level === level;
|
||
return '<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('');
|
||
}
|
||
|
||
function updatePermissionControl() {
|
||
if (!piLabPanelEl) return;
|
||
var control = piLabPanelEl.querySelector('[data-page-ai-pi-lab-permission]');
|
||
var label = piLabPanelEl.querySelector('[data-page-ai-pi-lab-permission-label]');
|
||
var wrap = piLabPanelEl.querySelector('[data-page-ai-pi-lab-permission-wrap]');
|
||
var summary = permissionSummary();
|
||
if (label) label.textContent = summary.label;
|
||
if (control) {
|
||
control.setAttribute('data-tone', summary.tone || '');
|
||
control.setAttribute('data-active', piLabState.permissionMenuOpen ? 'true' : 'false');
|
||
control.title = 'Pi 模式:' + permissionModeLabel(piLabState.permissionMode) + ';目录范围:' + (piLabState.allowedRootsSummary || 'pending');
|
||
}
|
||
if (wrap) wrap.setAttribute('data-open', piLabState.permissionMenuOpen ? 'true' : 'false');
|
||
Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-permission-mode]')).forEach(function (node) {
|
||
node.setAttribute('data-active', node.getAttribute('data-page-ai-pi-lab-permission-mode') === normalizePermissionMode(piLabState.permissionMode) ? 'true' : 'false');
|
||
var check = node.querySelector('[data-page-ai-pi-lab-permission-check]');
|
||
if (check) check.textContent = node.getAttribute('data-active') === 'true' ? '✓' : '';
|
||
});
|
||
}
|
||
|
||
function setPermissionMenuOpen(open) {
|
||
if (open) {
|
||
setActionMenuOpen(false);
|
||
setModelMenuOpen(false);
|
||
setThinkingMenuOpen(false);
|
||
}
|
||
piLabState.permissionMenuOpen = !!open;
|
||
updatePermissionControl();
|
||
}
|
||
|
||
function setModelMenuOpen(open) {
|
||
if (open) {
|
||
setActionMenuOpen(false);
|
||
setPermissionMenuOpen(false);
|
||
setThinkingMenuOpen(false);
|
||
}
|
||
piLabState.modelMenuOpen = !!open;
|
||
updateModelLabel();
|
||
}
|
||
|
||
function setThinkingMenuOpen(open) {
|
||
if (open) {
|
||
setActionMenuOpen(false);
|
||
setPermissionMenuOpen(false);
|
||
setModelMenuOpen(false);
|
||
}
|
||
piLabState.thinkingMenuOpen = !!open;
|
||
updateThinkingControl();
|
||
}
|
||
|
||
function closeComposerControlMenus() {
|
||
if (!piLabState.actionMenuOpen && !piLabState.permissionMenuOpen && !piLabState.modelMenuOpen && !piLabState.thinkingMenuOpen) return;
|
||
setActionMenuOpen(false);
|
||
setPermissionMenuOpen(false);
|
||
setModelMenuOpen(false);
|
||
setThinkingMenuOpen(false);
|
||
}
|
||
|
||
function selectModelControl(provider, modelId) {
|
||
piLabState.modelProvider = String(provider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER);
|
||
piLabState.modelId = String(modelId || piLabState.defaultModelId || DEFAULT_MODEL_ID);
|
||
setModelMenuOpen(false);
|
||
updateModelLabel();
|
||
applyModelConfigToRuntime();
|
||
}
|
||
|
||
function selectThinkingControl(level) {
|
||
piLabState.thinkingLevel = normalizeThinkingLevel(level);
|
||
setThinkingMenuOpen(false);
|
||
updateThinkingControl();
|
||
applyModelConfigToRuntime();
|
||
}
|
||
|
||
function selectPermissionMode(mode) {
|
||
piLabState.permissionMode = normalizePermissionMode(mode);
|
||
setPermissionMenuOpen(false);
|
||
updatePermissionControl();
|
||
if (piLabState.sessionId
|
||
&& piLabState.status !== STATE_STREAMING
|
||
&& piLabState.status !== STATE_STARTING
|
||
&& !piLabState.applyingPermissionMode) {
|
||
return applyPermissionModeToRuntime();
|
||
}
|
||
if (piLabState.sessionId
|
||
&& (piLabState.status === STATE_STREAMING
|
||
|| piLabState.status === STATE_STARTING
|
||
|| piLabState.applyingPermissionMode)) {
|
||
piLabState.pendingPermissionModeApply = true;
|
||
piLabState.pendingPermissionMode = piLabState.permissionMode;
|
||
showPiToast('当前回复结束后自动应用:' + permissionModeLabel(piLabState.permissionMode), 'warning');
|
||
return Promise.resolve({ queued: true });
|
||
}
|
||
showPiToast('Pi 模式已切换为:' + permissionModeLabel(piLabState.permissionMode), 'info');
|
||
return Promise.resolve({ queued: false });
|
||
}
|
||
|
||
function maybeApplyPendingPermissionMode() {
|
||
if (!piLabState.pendingPermissionModeApply || piLabState.applyingPermissionMode) return;
|
||
if (!piLabState.sessionId || !piLabState.enabled) return;
|
||
if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING) return;
|
||
window.setTimeout(function () {
|
||
if (!piLabState.pendingPermissionModeApply || piLabState.applyingPermissionMode) return;
|
||
if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING) return;
|
||
if (piLabState.pendingPermissionMode) {
|
||
piLabState.permissionMode = normalizePermissionMode(piLabState.pendingPermissionMode);
|
||
piLabState.pendingPermissionMode = null;
|
||
updatePermissionControl();
|
||
}
|
||
applyPermissionModeToRuntime().catch(function () {});
|
||
}, 0);
|
||
}
|
||
|
||
function applyPermissionModeToRuntime() {
|
||
if (!piLabState.sessionId || !piLabState.enabled) return Promise.resolve({ skipped: true });
|
||
if (piLabState.applyingPermissionMode) {
|
||
piLabState.pendingPermissionModeApply = true;
|
||
piLabState.pendingPermissionMode = normalizePermissionMode(piLabState.permissionMode);
|
||
return Promise.resolve({ queued: true });
|
||
}
|
||
var context = applyCurrentContextToState();
|
||
var nextMode = normalizePermissionMode(piLabState.permissionMode);
|
||
ensurePiToolCapableModel();
|
||
piLabState.pendingPermissionModeApply = false;
|
||
piLabState.pendingPermissionMode = null;
|
||
piLabState.applyingPermissionMode = true;
|
||
setState(STATE_STARTING);
|
||
updateDiagnostics('Applying Pi mode');
|
||
return fetch(API.START, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sessionId: piLabState.sessionId,
|
||
rootUri: context.rootUri || (piLabState.session && piLabState.session.rootUri) || undefined,
|
||
workspaceId: context.workspaceId || (piLabState.session && piLabState.session.workspaceId) || undefined,
|
||
pagePath: context.pagePath || (piLabState.session && piLabState.session.pagePath) || undefined,
|
||
pageTitle: context.pageTitle || (piLabState.session && piLabState.session.pageTitle) || undefined,
|
||
modelProvider: piLabState.modelProvider || piLabState.defaultModelProvider,
|
||
modelId: piLabState.modelId || piLabState.defaultModelId,
|
||
thinkingLevel: normalizeThinkingLevel(piLabState.thinkingLevel || piLabState.defaultThinkingLevel),
|
||
permissionMode: nextMode,
|
||
}),
|
||
})
|
||
.then(function (r) {
|
||
if (!r.ok) throw new Error('Mode switch failed: ' + r.status);
|
||
return r.json();
|
||
})
|
||
.then(function (data) {
|
||
var session = data.session || {};
|
||
piLabState.sessionId = session.sessionId || data.sessionId || piLabState.sessionId;
|
||
piLabState.providerSessionId = session.providerSessionId || data.providerSessionId || piLabState.providerSessionId;
|
||
piLabState.permissionMode = normalizePermissionMode(session.permissionMode || data.permissionMode || (session.runtimePolicySnapshot && session.runtimePolicySnapshot.permissionMode) || nextMode);
|
||
piLabState.session = session || piLabState.session || null;
|
||
piLabState.runtimeMode = session.runtimeMode || data.runtimeMode || piLabState.runtimeMode || null;
|
||
piLabState.runtimePid = session.runtimePid || data.pid || piLabState.runtimePid || null;
|
||
piLabState.piExtensionSources = data.piExtensionSources || piLabState.piExtensionSources || [];
|
||
piLabState.piExtensionToolNames = data.piExtensionToolNames || piLabState.piExtensionToolNames || [];
|
||
connectEventSource();
|
||
piLabState.applyingPermissionMode = false;
|
||
setState(STATE_STARTED);
|
||
updateDiagnostics('Pi mode applied');
|
||
updatePermissionControl();
|
||
showPiToast('Pi 模式已应用:' + permissionModeLabel(piLabState.permissionMode), 'success');
|
||
maybeApplyPendingPermissionMode();
|
||
return data;
|
||
})
|
||
.catch(function (error) {
|
||
piLabState.applyingPermissionMode = false;
|
||
setState(STATE_ERROR);
|
||
updateDiagnostics(error && error.message ? error.message : 'Pi mode switch failed');
|
||
showPiToast('Pi 模式切换失败', 'error');
|
||
throw error;
|
||
});
|
||
}
|
||
|
||
function setActionMenuOpen(open) {
|
||
if (open) {
|
||
setPermissionMenuOpen(false);
|
||
setModelMenuOpen(false);
|
||
setThinkingMenuOpen(false);
|
||
}
|
||
piLabState.actionMenuOpen = !!open;
|
||
if (!piLabPanelEl) return;
|
||
var composer = piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-composer');
|
||
var toggle = piLabPanelEl.querySelector('[data-page-ai-pi-lab-action-menu-toggle]');
|
||
if (composer) composer.setAttribute('data-menu-open', piLabState.actionMenuOpen ? 'true' : 'false');
|
||
if (toggle) toggle.setAttribute('data-active', piLabState.actionMenuOpen ? 'true' : 'false');
|
||
}
|
||
|
||
function closeActionMenu() {
|
||
setActionMenuOpen(false);
|
||
}
|
||
|
||
function permissionModeMenuHtml() {
|
||
return permissionModeOptions().map(function (item) {
|
||
var active = item.mode === normalizePermissionMode(piLabState.permissionMode);
|
||
return '<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 === 'attach-file' || action === 'camera') {
|
||
pickPiPromptImages(action === 'camera');
|
||
return;
|
||
}
|
||
if (action === 'send-steer') {
|
||
sendCurrentInput({ streamingBehavior: 'steer' });
|
||
return;
|
||
}
|
||
if (action === 'send-followup') {
|
||
sendCurrentInput({ streamingBehavior: 'followUp' });
|
||
return;
|
||
}
|
||
if (action === 'abort-bash') {
|
||
callPiRpcCommand('abort_bash', {}, 5000).then(function () {
|
||
showPiToast('已请求中止 bash', 'info');
|
||
}).catch(function (error) {
|
||
showPiToast(error && error.message ? error.message : 'bash 中止失败', 'warning');
|
||
});
|
||
return;
|
||
}
|
||
if (action === 'plan-mode') {
|
||
var input = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
|
||
var current = input ? input.value.trim() : '';
|
||
piLabState.permissionMode = 'plan';
|
||
updatePermissionControl();
|
||
if (piLabState.sessionId && piLabState.status !== STATE_STREAMING && piLabState.status !== STATE_STARTING) {
|
||
applyPermissionModeToRuntime().catch(function () {});
|
||
} else {
|
||
piLabState.pendingPermissionModeApply = true;
|
||
}
|
||
var planPrompt = current || '请先只做计划评审,不要修改文件。';
|
||
if (input) {
|
||
input.value = planPrompt;
|
||
input.focus();
|
||
updateButtons();
|
||
}
|
||
showPiToast('已切换到 MNote 计划评审模式;发送后由后端只读 wrapper 处理', 'info');
|
||
}
|
||
}
|
||
|
||
function applyModelControls() {
|
||
if (!piLabPanelEl) return;
|
||
var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]');
|
||
var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]');
|
||
var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]');
|
||
if (providerSelect && providerSelect.value) piLabState.modelProvider = providerSelect.value;
|
||
if (customInput && customInput.value.trim()) piLabState.modelId = customInput.value.trim();
|
||
else if (modelSelect && modelSelect.value) piLabState.modelId = modelSelect.value;
|
||
ensurePiToolCapableModel();
|
||
piLabState.permissionMode = normalizePermissionMode(piLabState.permissionMode);
|
||
updateModelLabel();
|
||
updateThinkingControl();
|
||
updatePermissionControl();
|
||
}
|
||
|
||
function applyModelConfigToRuntime() {
|
||
if (!piLabState.sessionId) return Promise.resolve();
|
||
if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING) {
|
||
piLabState.pendingModelConfigApply = true;
|
||
return Promise.resolve();
|
||
}
|
||
piLabState.pendingModelConfigApply = false;
|
||
return fetch(API.CONFIGURE, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sessionId: piLabState.sessionId,
|
||
modelProvider: piLabState.modelProvider,
|
||
modelId: piLabState.modelId,
|
||
thinkingLevel: piLabState.thinkingLevel,
|
||
}),
|
||
}).then(function (r) { return r.json(); }).then(function (data) {
|
||
if (data && (data.message || data.error)) {
|
||
showPiToast(data.message || data.error, 'warning');
|
||
}
|
||
return data;
|
||
}).catch(function (err) {
|
||
showPiToast('配置应用失败:' + (err.message || ''), 'error');
|
||
});
|
||
}
|
||
|
||
function maybeApplyPendingModelConfig() {
|
||
if (!piLabState.pendingModelConfigApply || !piLabState.sessionId) return;
|
||
if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING) return;
|
||
piLabState.pendingModelConfigApply = false;
|
||
setTimeout(function () {
|
||
applyModelConfigToRuntime().catch(function () {});
|
||
}, 0);
|
||
}
|
||
|
||
|
||
function splitModelRef(value) {
|
||
var raw = String(value || '').trim();
|
||
var slash = raw.indexOf('/');
|
||
if (slash < 0) return { provider: DEFAULT_MODEL_PROVIDER, modelId: raw || DEFAULT_MODEL_ID };
|
||
return { provider: raw.slice(0, slash), modelId: raw.slice(slash + 1) };
|
||
}
|
||
|
||
function stripModelProviderPrefix(provider, modelId) {
|
||
var normalizedProvider = String(provider || DEFAULT_MODEL_PROVIDER).trim();
|
||
var normalizedModelId = String(modelId || '').trim();
|
||
if (normalizedProvider && normalizedModelId.indexOf(normalizedProvider + '/') === 0) {
|
||
return normalizedModelId.slice(normalizedProvider.length + 1);
|
||
}
|
||
return normalizedModelId;
|
||
}
|
||
|
||
function ensurePiToolCapableModel() {
|
||
var provider = piLabState.modelProvider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER;
|
||
var modelId = piLabState.modelId || piLabState.defaultModelId || DEFAULT_MODEL_ID;
|
||
modelId = stripModelProviderPrefix(provider, modelId);
|
||
piLabState.modelProvider = provider || DEFAULT_MODEL_PROVIDER;
|
||
piLabState.modelId = modelId || DEFAULT_MODEL_ID;
|
||
return true;
|
||
}
|
||
|
||
function applyEffectiveConfig(data) {
|
||
data = data || {};
|
||
var defaultRef = splitModelRef(data.defaultModel || data.default_model || '');
|
||
piLabState.defaultModelProvider = defaultRef.provider || DEFAULT_MODEL_PROVIDER;
|
||
piLabState.defaultModelId = defaultRef.modelId || DEFAULT_MODEL_ID;
|
||
if (!piLabState.modelProvider && !piLabState.modelId) {
|
||
piLabState.modelProvider = piLabState.defaultModelProvider;
|
||
piLabState.modelId = piLabState.defaultModelId;
|
||
}
|
||
ensurePiToolCapableModel();
|
||
piLabState.defaultThinkingLevel = normalizeThinkingLevel(data.defaultThinkingLevel || data.default_thinking_level || piLabState.defaultThinkingLevel);
|
||
if (!piLabState.thinkingLevel) piLabState.thinkingLevel = piLabState.defaultThinkingLevel;
|
||
piLabState.modelOptions = Array.isArray(data.models) ? data.models : [];
|
||
piLabState.modelOptions = piLabState.modelOptions.concat([{
|
||
provider: DEFAULT_MODEL_PROVIDER,
|
||
id: 'freefirst',
|
||
name: DEFAULT_MODEL_PROVIDER + '/freefirst',
|
||
}, {
|
||
provider: DEFAULT_MODEL_PROVIDER,
|
||
id: DEFAULT_MODEL_ID,
|
||
name: DEFAULT_MODEL_ID,
|
||
}]);
|
||
if (Array.isArray(data.allowedRoots)) {
|
||
piLabState.allowedRootsSummary = data.allowedRoots.length + ' roots';
|
||
}
|
||
if (!piLabPanelEl) return;
|
||
var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]');
|
||
var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]');
|
||
var providers = [];
|
||
piLabState.modelOptions.forEach(function (item) {
|
||
var provider = String(item.provider || DEFAULT_MODEL_PROVIDER);
|
||
if (providers.indexOf(provider) < 0) providers.push(provider);
|
||
});
|
||
if (!providers.length) providers.push(piLabState.defaultModelProvider);
|
||
if (providerSelect) {
|
||
providerSelect.innerHTML = providers.map(function (provider) {
|
||
return '<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();
|
||
updateThinkingControl();
|
||
updateContextStrip();
|
||
}
|
||
|
||
function loadEffectiveConfig() {
|
||
return fetch(API.EFFECTIVE, { credentials: 'same-origin', cache: 'no-store' })
|
||
.then(function (response) {
|
||
if (!response.ok) throw new Error('Effective config failed: ' + response.status);
|
||
return response.json();
|
||
})
|
||
.then(function (data) {
|
||
applyEffectiveConfig(data);
|
||
return data;
|
||
});
|
||
}
|
||
|
||
function currentRuntimeLabel() {
|
||
var parts = [];
|
||
if (piLabState.runtimeMode) parts.push(piLabState.runtimeMode);
|
||
if (piLabState.runtimePid) parts.push('pid ' + piLabState.runtimePid);
|
||
return parts.join(' · ') || 'runtime pending';
|
||
}
|
||
|
||
function statusTone() {
|
||
if (!piLabState.enabled || piLabState.status === STATE_ERROR) return 'danger';
|
||
if (piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTED) return 'ok';
|
||
if (piLabState.status === STATE_STARTING || piLabState.status === STATE_ABORTED) return 'warn';
|
||
return '';
|
||
}
|
||
|
||
function statusText() {
|
||
if (!piLabState.enabled) return 'disabled';
|
||
if (piLabState.status === STATE_IDLE) return 'idle';
|
||
if (piLabState.status === STATE_STARTING) return 'starting';
|
||
if (piLabState.status === STATE_STARTED) return 'ready';
|
||
if (piLabState.status === STATE_STREAMING) return 'streaming';
|
||
if (piLabState.status === STATE_ABORTED) return 'aborted';
|
||
return 'error';
|
||
}
|
||
|
||
function piIcon(name) {
|
||
var icons = {
|
||
plus: '<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"/>',
|
||
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>';
|
||
}
|
||
|
||
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>' +
|
||
'</div>' +
|
||
'<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>' +
|
||
'</div>' +
|
||
'<div class="wolai-page-ai-pi-lab-commandbar" data-page-ai-pi-lab-commandbar>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-tool-icon" data-page-ai-pi-lab-new title="新对话" aria-label="新对话">' + 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>' +
|
||
'</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>' +
|
||
'<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>' +
|
||
'<label class="wolai-page-ai-pi-lab-field">自定义模型<input data-page-ai-pi-lab-model-custom placeholder="可选,启动时应用"></label>' +
|
||
'<span class="wolai-page-ai-pi-lab-chip"><span>runtime</span><strong data-page-ai-pi-lab-runtime-chip>runtime pending</strong></span>' +
|
||
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-builtin-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>' +
|
||
'</div>' +
|
||
'</details>' +
|
||
'<div class="wolai-page-ai-pi-lab-actions">' +
|
||
'<span class="wolai-page-ai-pi-lab-chip"><span>操作</span><strong>见顶部工具栏</strong></span>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'<div class="wolai-page-ai-pi-lab-history-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>' +
|
||
'</div>' +
|
||
'<details class="wolai-page-ai-pi-lab-context-strip" data-page-ai-pi-lab-context-strip>' +
|
||
'<summary><span class="wolai-page-ai-pi-lab-context-title">上下文</span><span class="wolai-page-ai-pi-lab-context-current" data-page-ai-pi-lab-current-page>未绑定</span><span class="wolai-page-ai-pi-lab-context-count"><strong data-page-ai-pi-lab-changed-files>0</strong> changed</span></summary>' +
|
||
'<div class="wolai-page-ai-pi-lab-context-content">' +
|
||
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-context="selection"><span>选区</span><strong data-page-ai-pi-lab-selection>无选区</strong></span>' +
|
||
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-context="allowed-roots"><span>allowed roots</span><strong data-page-ai-pi-lab-allowed-roots>pending</strong></span>' +
|
||
'<span class="wolai-page-ai-pi-lab-chip" data-page-ai-pi-lab-context="lightrag"><span>LightRAG</span><strong data-page-ai-pi-lab-lightrag>default</strong></span>' +
|
||
'</div>' +
|
||
'</details>' +
|
||
'<div class="wolai-page-ai-pi-lab-body" data-page-ai-pi-lab-body data-rail-open="false">' +
|
||
'<div class="wolai-page-ai-pi-lab-main">' +
|
||
'<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>' +
|
||
'<div class="wolai-page-ai-pi-lab-messages" data-page-ai-pi-lab-messages></div>' +
|
||
'<div class="wolai-page-ai-pi-lab-composer">' +
|
||
'<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">' + piIcon('image') + '<span>添加图片和文件</span></button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-menu-item" data-page-ai-pi-lab-menu-action="camera">' + 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="abort-bash">' + piIcon('stop') + '<span>中止 bash</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>' +
|
||
'</div>' +
|
||
'<div class="wolai-page-ai-pi-lab-modebar" data-page-ai-pi-lab-modebar>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-square" data-page-ai-pi-lab-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>' +
|
||
'<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>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'<aside class="wolai-page-ai-pi-lab-rail" data-page-ai-pi-lab-rail>' +
|
||
'<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>' +
|
||
'<details class="wolai-page-ai-pi-lab-rail-section">' +
|
||
'<summary>MNote tools <span class="wolai-page-ai-pi-lab-rail-count">5</span></summary>' +
|
||
'<div class="wolai-page-ai-pi-lab-tool-list" data-page-ai-pi-lab-tool-list>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.current_page.read</span>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.selection.read</span>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.local_file.patch</span>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.knowledge_rag.query</span>' +
|
||
'<span class="wolai-page-ai-pi-lab-tool-pill">mnote.reference.open</span>' +
|
||
'</div>' +
|
||
'</details>' +
|
||
'<details class="wolai-page-ai-pi-lab-rail-section" data-page-ai-pi-lab-receipts-section>' +
|
||
'<summary>Receipts <span class="wolai-page-ai-pi-lab-rail-count" data-page-ai-pi-lab-receipt-count>0</span></summary>' +
|
||
'<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>' +
|
||
'</aside>' +
|
||
'</div>' +
|
||
'<details class="wolai-page-ai-pi-lab-diagnostics" data-page-ai-pi-lab-diagnostics>' +
|
||
'<summary>诊断</summary>' +
|
||
'<div class="wolai-page-ai-pi-lab-diagnostic-actions">' +
|
||
'<button type="button" data-page-ai-pi-lab-diagnostic-command="doctor">doctor</button>' +
|
||
'<button type="button" data-page-ai-pi-lab-diagnostic-command="context-preview">context-preview</button>' +
|
||
'<button type="button" data-page-ai-pi-lab-diagnostic-command="list">list</button>' +
|
||
'<button type="button" data-page-ai-pi-lab-diagnostic-command="update-index">update-index</button>' +
|
||
'<input type="search" data-page-ai-pi-lab-diagnostic-query placeholder="search/info keyword">' +
|
||
'<button type="button" data-page-ai-pi-lab-diagnostic-command="search">search</button>' +
|
||
'<button type="button" data-page-ai-pi-lab-diagnostic-command="info">info</button>' +
|
||
'</div>' +
|
||
'<pre data-page-ai-pi-lab-diag-text></pre>' +
|
||
'<pre class="wolai-page-ai-pi-lab-diagnostic-output" data-page-ai-pi-lab-diagnostic-output hidden></pre>' +
|
||
'</details>' +
|
||
'</div>';
|
||
}
|
||
|
||
function renderEmptyState(container) {
|
||
container.innerHTML = '' +
|
||
'<div class="wolai-page-ai-pi-lab-empty">' +
|
||
'<div class="wolai-page-ai-pi-lab-empty-icon">?</div>' +
|
||
'<strong>开始对话</strong>' +
|
||
'<p>试试以下示例,或直接输入您的指令</p>' +
|
||
'<div class="wolai-page-ai-pi-lab-examples">' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="帮我解释当前页面的逻辑"><span>▤</span>帮我解释当前页面的逻辑</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="如何优化这段内容的结构?"><span>?</span>如何优化这段内容的结构?</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="用 LightRAG 查询当前页面相关资料"><span>▥</span>用 LightRAG 查询当前页面相关资料</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="帮我把选区改写成更清晰的版本"><span>↗</span>帮我把选区改写成更清晰的版本</button>' +
|
||
'<button type="button" class="wolai-page-ai-pi-lab-example" data-page-ai-pi-lab-example="检查当前 Markdown 有什么问题"><span>⌫</span>检查当前 Markdown 有什么问题</button>' +
|
||
'</div>' +
|
||
'</div>';
|
||
}
|
||
|
||
function 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>';
|
||
}
|
||
|
||
function renderHistory() {
|
||
if (!piLabPanelEl) return;
|
||
var panel = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history-panel]');
|
||
if (!panel) return;
|
||
if (!piLabState.history.length) {
|
||
panel.innerHTML = historyShell('<div class="wolai-page-ai-pi-lab-empty">暂无历史对话</div>');
|
||
return;
|
||
}
|
||
panel.innerHTML = historyShell(piLabState.history.map(function (session) {
|
||
var title = escapeHtml(historyTitle(session));
|
||
var meta = escapeHtml([formatHistoryTime(session.updatedAt || session.createdAt), session.modelId || session.status].filter(Boolean).join(' · '));
|
||
var sessionId = escapeHtml(session.sessionId || '');
|
||
var active = piLabState.sessionId === (session.sessionId || '');
|
||
return '<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();
|
||
}
|
||
|
||
function loadHistory() {
|
||
return fetch(API.SESSIONS + '?limit=50', { credentials: 'same-origin', cache: 'no-store' })
|
||
.then(function (response) {
|
||
if (!response.ok) throw new Error('History failed: ' + response.status);
|
||
return response.json();
|
||
})
|
||
.then(function (data) {
|
||
piLabState.history = Array.isArray(data.sessions) ? data.sessions : [];
|
||
renderHistory();
|
||
return data;
|
||
});
|
||
}
|
||
|
||
|
||
function loadSessionTree(sessionId) {
|
||
if (!sessionId) return Promise.resolve(null);
|
||
return fetch(API.SESSIONS + '/' + encodeURIComponent(sessionId) + '/tree', {
|
||
credentials: 'same-origin',
|
||
cache: 'no-store'
|
||
}).then(function (r) {
|
||
if (!r.ok) return null;
|
||
return r.json();
|
||
}).then(function (data) {
|
||
if (data && data.schema === 'mnote.page_ai_pi.session_tree.v1' && Array.isArray(data.entries)) {
|
||
return data;
|
||
}
|
||
if (data && Array.isArray(data.entries)) return data;
|
||
return null;
|
||
}).catch(function () { return null; });
|
||
}
|
||
|
||
function treeEntriesToMessages(entries) {
|
||
if (!Array.isArray(entries)) return [];
|
||
return entries.map(function (entry) {
|
||
var msg = {
|
||
entryId: entry.id || '',
|
||
role: entry.role === 'user' ? 'user' : 'assistant',
|
||
text: entry.text || entry.content || entry.preview || '',
|
||
meta: entry.meta || '',
|
||
toolCalls: Array.isArray(entry.toolCalls) ? entry.toolCalls : [],
|
||
citations: Array.isArray(entry.citations) ? entry.citations : [],
|
||
diffSummary: entry.diffSummary || null,
|
||
};
|
||
return msg;
|
||
}).filter(function (m) { return m.text || m.toolCalls.length || m.citations.length || m.diffSummary; });
|
||
}
|
||
|
||
function applyReplayMessages(messages) {
|
||
(Array.isArray(messages) ? messages : []).forEach(function (msg) {
|
||
if (msg.role === 'user') {
|
||
applyPiRunEvent({ type: 'user_prompt', text: msg.text, meta: msg.meta });
|
||
} else if (msg.abortReason) {
|
||
applyPiRunEvent({ type: 'assistant_abort', payload: { reason: msg.abortReason } });
|
||
} else {
|
||
applyPiRunEvent({ type: 'assistant_done', text: msg.text, meta: msg.meta, toolCalls: msg.toolCalls, citations: msg.citations, diffSummary: msg.diffSummary });
|
||
}
|
||
});
|
||
}
|
||
|
||
function openHistorySession(sessionId) {
|
||
if (!sessionId) return Promise.resolve();
|
||
if (piLabEventSource) {
|
||
piLabEventSource.close();
|
||
piLabEventSource = null;
|
||
}
|
||
return fetch(API.SESSIONS + '/' + encodeURIComponent(sessionId), { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (detail) {
|
||
var detailSession = detail.session || detail;
|
||
var runtime = detailSession.runtime || {};
|
||
var runtimePolicy = runtime.runtimePolicy || runtime.runtime_policy || {};
|
||
piLabState.sessionId = detailSession.sessionId || sessionId;
|
||
piLabState.providerSessionId = runtime.providerSessionId || detailSession.providerSessionId || null;
|
||
piLabState.session = Object.assign({}, runtime, detailSession, {
|
||
sessionId: piLabState.sessionId,
|
||
providerSessionId: piLabState.providerSessionId,
|
||
pagePath: runtime.pagePath || detailSession.pagePath,
|
||
pageTitle: runtime.pageTitle || detailSession.pageTitle || detailSession.title,
|
||
rootUri: runtime.rootUri || detailSession.rootUri,
|
||
workspaceId: runtime.workspaceId || detailSession.workspaceId,
|
||
modelProvider: runtime.modelProvider || detailSession.modelProvider,
|
||
modelId: runtime.modelId || detailSession.modelId,
|
||
thinkingLevel: runtime.thinkingLevel || detailSession.thinkingLevel,
|
||
runtimePolicySnapshot: runtimePolicy,
|
||
});
|
||
piLabState.modelProvider = piLabState.session.modelProvider || piLabState.modelProvider || null;
|
||
piLabState.modelId = piLabState.session.modelId || piLabState.modelId || null;
|
||
piLabState.thinkingLevel = normalizeThinkingLevel(piLabState.session.thinkingLevel || piLabState.thinkingLevel || piLabState.defaultThinkingLevel);
|
||
piLabState.permissionMode = normalizePermissionMode(runtimePolicy.permissionMode || runtimePolicy.permission_mode || detailSession.permissionMode || piLabState.permissionMode);
|
||
piLabState.allowedRootsSummary = summarizeAllowedRoots(runtime.allowedRootsSnapshot || piLabState.session.allowedRootsSnapshot);
|
||
exitHistoryReplayMode();
|
||
resetPiRunViewModel();
|
||
return loadSessionTree(sessionId).then(function (treeData) {
|
||
var treeMessages = treeData && Array.isArray(treeData.messages) && treeData.messages.length
|
||
? treeData.messages
|
||
: (treeData && treeData.entries && treeData.entries.length ? treeEntriesToMessages(treeData.entries) : []);
|
||
applyReplayMessages(treeMessages);
|
||
}).then(function () {
|
||
if (streamingAssistantMsg && String(streamingAssistantMsg.text || '').trim()) {
|
||
applyPiRunEvent({ type: 'assistant_done', text: streamingAssistantMsg.text });
|
||
}
|
||
setState(STATE_IDLE);
|
||
updateDiagnostics('已打开历史 session,可直接继续发送。');
|
||
updateMessages();
|
||
updateButtons();
|
||
updateArtifactRail();
|
||
updateReceiptDisplay();
|
||
updateQueueDisplay();
|
||
setHistoryPanelVisible(false);
|
||
});
|
||
});
|
||
}
|
||
|
||
function currentHistorySession(sessionId) {
|
||
return (piLabState.history || []).find(function (item) {
|
||
return item && item.sessionId === sessionId;
|
||
}) || null;
|
||
}
|
||
|
||
function renameHistorySession(sessionId) {
|
||
if (!sessionId) return Promise.resolve();
|
||
var existing = currentHistorySession(sessionId);
|
||
var nextTitle = window.prompt('重命名 Pi session', (existing && (existing.title || existing.preview || existing.pageTitle)) || '');
|
||
if (nextTitle == null) return Promise.resolve();
|
||
nextTitle = String(nextTitle || '').trim();
|
||
if (!nextTitle) {
|
||
showPiToast('标题不能为空', 'warning');
|
||
return Promise.resolve();
|
||
}
|
||
return fetch(API.SESSIONS + '/' + encodeURIComponent(sessionId), {
|
||
method: 'PATCH',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json', accept: 'application/json' },
|
||
body: JSON.stringify({
|
||
title: nextTitle,
|
||
workspaceId: (piLabState.session && piLabState.session.workspaceId) || currentWorkspaceId() || undefined,
|
||
}),
|
||
}).then(function (response) {
|
||
return response.json().catch(function () { return {}; }).then(function (payload) {
|
||
if (!response.ok) throw new Error(payload.message || ('Rename failed: ' + response.status));
|
||
showPiToast('Session renamed', 'success');
|
||
return loadHistory();
|
||
});
|
||
});
|
||
}
|
||
|
||
function resetCurrentSessionAfterHistoryDelete(sessionId) {
|
||
if (piLabState.sessionId !== sessionId && piLabState.viewingHistorySessionId !== sessionId) return;
|
||
if (piLabEventSource) {
|
||
piLabEventSource.close();
|
||
piLabEventSource = null;
|
||
}
|
||
exitHistoryReplayMode();
|
||
piLabState.sessionId = null;
|
||
piLabState.providerSessionId = null;
|
||
piLabState.status = STATE_IDLE;
|
||
piLabState.session = null;
|
||
resetPiRunViewModel();
|
||
piLabState.receipts = [];
|
||
updateMessages();
|
||
updateReceiptDisplay();
|
||
updateQueueDisplay();
|
||
updateConfig();
|
||
}
|
||
|
||
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');
|
||
});
|
||
}
|
||
|
||
|
||
|
||
function renderMessage(msg) {
|
||
var card = el('article', {
|
||
className: 'wolai-page-ai-pi-lab-message',
|
||
'data-role': msg.role || 'assistant',
|
||
'data-page-ai-pi-lab-message-role': msg.role || 'assistant',
|
||
});
|
||
if (msg.status === 'streaming') card.setAttribute('data-page-ai-pi-lab-streaming', 'true');
|
||
var role = msg.role === 'assistant' ? 'Pi' : (msg.role === 'user' ? 'You' : 'System');
|
||
var meta = msg.status === 'streaming' ? 'streaming' : (msg.status === 'aborted' ? 'aborted' : (msg.meta || ''));
|
||
card.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-role' }, role + (meta ? ' · ' + meta : '')));
|
||
var bubble = el('div', { className: 'wolai-page-ai-pi-lab-bubble' });
|
||
if (msg.text) {
|
||
var rendersMarkdown = msg.role === 'assistant' || msg.role === 'system';
|
||
bubble.appendChild(el('div', {
|
||
className: (rendersMarkdown ? 'wolai-page-ai-pi-lab-markdown' : 'wolai-page-ai-pi-lab-text') + (msg.status === 'streaming' ? ' streaming-cursor' : ''),
|
||
innerHTML: rendersMarkdown
|
||
? renderMarkdownBlock(msg.text)
|
||
: escapeHtml(msg.text).replace(/\n/g, '<br>'),
|
||
}));
|
||
}
|
||
if (msg.toolCalls && msg.toolCalls.length) {
|
||
var tools = el('details', {
|
||
className: 'wolai-page-ai-pi-lab-tool-timeline',
|
||
'data-page-ai-pi-lab-tool-timeline': 'true',
|
||
'data-page-ai-pi-tool-timeline': 'true',
|
||
});
|
||
tools.appendChild(el('summary', {}, [
|
||
el('span', {}, '工具调用'),
|
||
el('span', { className: 'wolai-page-ai-pi-lab-rail-count' }, String(msg.toolCalls.length)),
|
||
]));
|
||
var toolsBody = el('div', { className: 'wolai-page-ai-pi-lab-tool-timeline-body' });
|
||
msg.toolCalls.forEach(function (tc) { toolsBody.appendChild(renderToolCallCard(tc)); });
|
||
tools.appendChild(toolsBody);
|
||
bubble.appendChild(tools);
|
||
}
|
||
if (msg.citations && msg.citations.length) {
|
||
var citations = el('div', { className: 'wolai-page-ai-pi-lab-citations' });
|
||
msg.citations.forEach(function (cit, i) {
|
||
citations.appendChild(el('a', {
|
||
className: 'wolai-page-ai-pi-lab-citation',
|
||
href: cit.url || '#',
|
||
target: '_blank',
|
||
rel: 'noopener',
|
||
'data-page-ai-pi-lab-citation': cit.source || '',
|
||
}, '[' + (i + 1) + '] ' + (cit.title || cit.source || 'source')));
|
||
});
|
||
bubble.appendChild(citations);
|
||
}
|
||
if (msg.diffSummary) {
|
||
var files = msg.diffSummary.files || [];
|
||
bubble.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-diff', 'data-page-ai-pi-lab-diff': 'true' }, 'patch · ' + files.join(', ')));
|
||
}
|
||
if (msg.status === 'aborted' || msg.abortReason) {
|
||
var abortPayload = msg.abortResponse || {};
|
||
var abortLines = [msg.abortReason || '已中止'];
|
||
if (abortPayload.schema) abortLines.push('schema=' + abortPayload.schema);
|
||
if (abortPayload.stopReason) abortLines.push('stopReason=' + abortPayload.stopReason);
|
||
if (abortPayload.command) abortLines.push('command=' + abortPayload.command);
|
||
var queue = piLabState.pendingQueue || {};
|
||
var steering = Array.isArray(queue.steering) ? queue.steering.length : 0;
|
||
var followUp = Array.isArray(queue.followUp) ? queue.followUp.length : 0;
|
||
abortLines.push('queued messages=' + (steering + followUp));
|
||
bubble.appendChild(el('div', {
|
||
className: 'wolai-page-ai-pi-lab-abort-note',
|
||
'data-page-ai-pi-lab-abort-note': 'true',
|
||
'data-page-ai-pi-lab-stop-reason': abortPayload.stopReason || 'aborted',
|
||
}, abortLines.join(' · ')));
|
||
}
|
||
if (msg.role === 'assistant' && (msg.text || msg.toolCalls || msg.citations || msg.diffSummary)) {
|
||
bubble.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-message-actions' }, [
|
||
el('button', {
|
||
type: 'button',
|
||
title: '复制 Markdown',
|
||
'aria-label': '复制 Markdown',
|
||
'data-page-ai-pi-lab-copy-markdown': messageToMarkdown(msg),
|
||
innerHTML: piIcon('copy'),
|
||
}),
|
||
]));
|
||
}
|
||
|
||
if (piLabState.viewingHistorySessionId && msg.role !== 'system') {
|
||
var forkEntryBtn = el('div', { className: 'wolai-page-ai-pi-lab-message-actions wolai-page-ai-pi-lab-fork-entry' }, [
|
||
el('button', {
|
||
type: 'button',
|
||
title: '从该条消息继续',
|
||
'aria-label': '从该条消息继续',
|
||
'data-page-ai-pi-lab-fork-entry': msg.entryId || msg._entryIndex || '',
|
||
innerHTML: '从这里继续',
|
||
}),
|
||
]);
|
||
bubble.appendChild(forkEntryBtn);
|
||
}
|
||
|
||
card.appendChild(bubble);
|
||
return card;
|
||
}
|
||
|
||
function isVisibleMessage(msg) {
|
||
if (!msg) return false;
|
||
if (msg.role === 'system' && /^Pi session started:/.test(String(msg.text || ''))) return false;
|
||
if (msg.status === 'streaming') return true;
|
||
if (msg.status === 'aborted' || msg.abortReason) return true;
|
||
if (String(msg.text || '').trim()) return true;
|
||
if (msg.toolCalls && msg.toolCalls.length) return true;
|
||
if (msg.citations && msg.citations.length) return true;
|
||
if (msg.diffSummary) return true;
|
||
return msg.role !== 'assistant';
|
||
}
|
||
|
||
function updateStatusBadge() {
|
||
updateConfig();
|
||
}
|
||
|
||
function updateModelLabel() {
|
||
if (!piLabPanelEl) return;
|
||
var titleLabel = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model]');
|
||
var modelChip = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-chip]');
|
||
var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]');
|
||
var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]');
|
||
var modelWrap = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-menu-wrap]');
|
||
var modelControl = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-menu-toggle]');
|
||
var modelLabel = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-label]');
|
||
var modelMenu = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-menu]');
|
||
var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]');
|
||
var provider = piLabState.modelProvider || piLabState.defaultModelProvider || DEFAULT_MODEL_PROVIDER;
|
||
var modelId = piLabState.modelId || piLabState.defaultModelId || DEFAULT_MODEL_ID;
|
||
modelId = stripModelProviderPrefix(provider, modelId);
|
||
piLabState.modelProvider = provider;
|
||
piLabState.modelId = modelId;
|
||
if (titleLabel) titleLabel.textContent = '默认模型:' + currentModelLabel();
|
||
if (modelChip) modelChip.textContent = currentModelLabel();
|
||
if (modelWrap) modelWrap.setAttribute('data-open', piLabState.modelMenuOpen ? 'true' : 'false');
|
||
if (modelControl) {
|
||
modelControl.setAttribute('data-active', piLabState.modelMenuOpen ? 'true' : 'false');
|
||
modelControl.setAttribute('aria-expanded', piLabState.modelMenuOpen ? 'true' : 'false');
|
||
modelControl.title = '模型:' + currentModelLabel();
|
||
}
|
||
if (modelLabel) modelLabel.textContent = currentModelLabel();
|
||
if (modelMenu) {
|
||
modelMenu.innerHTML = modelControlOptions().map(function (item) {
|
||
var active = item.provider === provider && item.modelId === modelId;
|
||
return '<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('');
|
||
}
|
||
if (providerSelect && providerSelect.value !== provider) providerSelect.value = provider;
|
||
if (modelSelect && modelSelect.value !== modelId) {
|
||
if (Array.prototype.some.call(modelSelect.options, function (option) { return option.value === modelId; })) {
|
||
modelSelect.value = modelId;
|
||
} else if (Array.prototype.some.call(modelSelect.options, function (option) { return option.value === provider + '/' + modelId; })) {
|
||
modelSelect.value = provider + '/' + modelId;
|
||
}
|
||
}
|
||
if (customInput) customInput.value = modelId !== DEFAULT_MODEL_ID && modelId !== 'freefirst' ? modelId : '';
|
||
}
|
||
|
||
function updateConfig() {
|
||
if (!piLabPanelEl) return;
|
||
updateModelLabel();
|
||
updateThinkingControl();
|
||
var statusChip = piLabPanelEl.querySelector('[data-page-ai-pi-lab-status-chip]');
|
||
var statusNode = piLabPanelEl.querySelector('[data-page-ai-pi-lab-status-text]');
|
||
var runtimeNode = piLabPanelEl.querySelector('[data-page-ai-pi-lab-runtime-chip]');
|
||
var builtinNode = piLabPanelEl.querySelector('[data-page-ai-pi-lab-builtin-managed]');
|
||
var extensionCountNode = piLabPanelEl.querySelector('[data-page-ai-pi-lab-extension-count]');
|
||
var replayBanner = piLabPanelEl.querySelector('[data-page-ai-pi-lab-replay-banner]');
|
||
if (statusChip) {
|
||
var tone = statusTone();
|
||
if (tone) statusChip.setAttribute('data-tone', tone);
|
||
else statusChip.removeAttribute('data-tone');
|
||
}
|
||
if (statusNode) statusNode.textContent = statusText();
|
||
if (runtimeNode) runtimeNode.textContent = currentRuntimeLabel();
|
||
if (builtinNode) {
|
||
var managed = Array.isArray(piLabState.managedBuiltinTools) ? piLabState.managedBuiltinTools : [];
|
||
builtinNode.style.display = managed.length ? '' : 'none';
|
||
var strong = builtinNode.querySelector('strong');
|
||
if (strong) strong.textContent = managed.length ? managed.join('/') : 'pending';
|
||
}
|
||
if (extensionCountNode) {
|
||
var extensions = Array.isArray(piLabState.piExtensionSources) ? piLabState.piExtensionSources : [];
|
||
extensionCountNode.textContent = extensions.length ? String(extensions.length) + ' packages' : 'pending';
|
||
extensionCountNode.title = extensions.join('\n');
|
||
}
|
||
if (replayBanner) {
|
||
replayBanner.hidden = !piLabState.viewingHistorySessionId;
|
||
var replayText = replayBanner.querySelector('span');
|
||
if (replayText) replayText.textContent = piLabState.viewingHistorySessionId ? '正在查看历史,只读' : '';
|
||
}
|
||
updateContextStrip();
|
||
updatePermissionControl();
|
||
updateButtons();
|
||
updateQueueDisplay();
|
||
updateLauncherState();
|
||
}
|
||
|
||
function updateContextStrip() {
|
||
if (!piLabPanelEl) return;
|
||
var currentPage = piLabPanelEl.querySelector('[data-page-ai-pi-lab-current-page]');
|
||
var selection = piLabPanelEl.querySelector('[data-page-ai-pi-lab-selection]');
|
||
var allowedRoots = piLabPanelEl.querySelector('[data-page-ai-pi-lab-allowed-roots]');
|
||
var lightrag = piLabPanelEl.querySelector('[data-page-ai-pi-lab-lightrag]');
|
||
var changedFiles = piLabPanelEl.querySelector('[data-page-ai-pi-lab-changed-files]');
|
||
var session = piLabState.session || {};
|
||
if (currentPage) currentPage.textContent = session.pageTitle || session.pagePath || document.title || '当前页面';
|
||
if (selection) selection.textContent = piLabState.selectionSummary || '无选区';
|
||
if (allowedRoots) allowedRoots.textContent = piLabState.allowedRootsSummary || 'pending';
|
||
if (lightrag) lightrag.textContent = '唯一默认';
|
||
if (changedFiles) {
|
||
var files = {};
|
||
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);
|
||
}
|
||
var selected = contextSelectionState();
|
||
Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-quick]')).forEach(function (btn) {
|
||
var kind = btn.getAttribute('data-page-ai-pi-lab-quick') || '';
|
||
var active = (kind === 'read-page' && selected.currentPage)
|
||
|| (kind === 'current-folder' && selected.currentFolder)
|
||
|| (kind === 'selection' && selected.selection)
|
||
|| (kind === 'rag' && selected.lightrag);
|
||
btn.setAttribute('data-active', active ? 'true' : 'false');
|
||
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||
});
|
||
Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-menu-action]')).forEach(function (btn) {
|
||
var action = btn.getAttribute('data-page-ai-pi-lab-menu-action') || '';
|
||
var active = (action === 'read-page' && selected.currentPage)
|
||
|| (action === 'current-folder' && selected.currentFolder)
|
||
|| (action === 'selection' && selected.selection)
|
||
|| (action === 'rag' && selected.lightrag);
|
||
if (['read-page', 'current-folder', 'selection', 'rag'].indexOf(action) >= 0) {
|
||
btn.setAttribute('data-active', active ? 'true' : 'false');
|
||
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||
}
|
||
});
|
||
Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-context-mark]')).forEach(function (mark) {
|
||
var action = mark.getAttribute('data-page-ai-pi-lab-context-mark') || '';
|
||
var active = (action === 'read-page' && selected.currentPage)
|
||
|| (action === 'current-folder' && selected.currentFolder)
|
||
|| (action === 'selection' && selected.selection)
|
||
|| (action === 'rag' && selected.lightrag);
|
||
mark.textContent = active ? '✓' : '';
|
||
});
|
||
updatePermissionControl();
|
||
}
|
||
|
||
function updateButtons() {
|
||
if (!piLabPanelEl) return;
|
||
var sendBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-send]');
|
||
var abortBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-abort]');
|
||
var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
|
||
var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]');
|
||
var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]');
|
||
var modelControl = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-menu-toggle]');
|
||
var thinkingControl = piLabPanelEl.querySelector('[data-page-ai-pi-lab-thinking-menu-toggle]');
|
||
var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]');
|
||
var isReplay = !!piLabState.viewingHistorySessionId;
|
||
var isIdle = piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR;
|
||
var isStarted = piLabState.status === STATE_STARTED;
|
||
var isStreaming = piLabState.status === STATE_STREAMING;
|
||
var compactBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-compact]');
|
||
if (sendBtn) {
|
||
sendBtn.disabled = isReplay || !piLabState.enabled || !input || !input.value.trim();
|
||
}
|
||
if (abortBtn) abortBtn.hidden = !(isStreaming || piLabState.status === STATE_STARTING);
|
||
if (compactBtn) compactBtn.disabled = isReplay || piLabState.isCompacting;
|
||
if (input) {
|
||
input.disabled = isReplay;
|
||
input.placeholder = isReplay ? '历史对话为只读,Fork 后继续' : '问 Pi...';
|
||
}
|
||
var controlsDisabled = isReplay || piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING;
|
||
[providerSelect, modelSelect, modelControl, thinkingControl, customInput].forEach(function (node) {
|
||
if (node) node.disabled = isReplay || piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING;
|
||
});
|
||
if (controlsDisabled && (piLabState.modelMenuOpen || piLabState.thinkingMenuOpen)) {
|
||
piLabState.modelMenuOpen = false;
|
||
piLabState.thinkingMenuOpen = false;
|
||
updateModelLabel();
|
||
updateThinkingControl();
|
||
}
|
||
}
|
||
|
||
function updateQueueDisplay() {
|
||
if (!piLabPanelEl) return;
|
||
var queue = piLabState.pendingQueue || {};
|
||
var steering = Array.isArray(queue.steering) ? queue.steering.length : 0;
|
||
var followUp = Array.isArray(queue.followUp) ? queue.followUp.length : 0;
|
||
var count = steering + followUp;
|
||
var node = piLabPanelEl.querySelector('[data-page-ai-pi-lab-queue-count]');
|
||
if (node) {
|
||
node.textContent = String(count);
|
||
node.title = '排队消息 ' + count;
|
||
}
|
||
}
|
||
|
||
function updateMessages() {
|
||
var container = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-messages]');
|
||
if (!container) return;
|
||
container.innerHTML = '';
|
||
var visibleMessages = piLabState.messages.filter(isVisibleMessage);
|
||
if (!visibleMessages.length) {
|
||
renderEmptyState(container);
|
||
updateArtifactRail();
|
||
return;
|
||
}
|
||
visibleMessages.forEach(function (msg) { container.appendChild(renderMessage(msg)); });
|
||
container.scrollTop = container.scrollHeight;
|
||
updateContextStrip();
|
||
updateQueueDisplay();
|
||
updateArtifactRail();
|
||
}
|
||
|
||
function updateDiagnostics(text) {
|
||
piLabState.diagnostics = text || piLabState.diagnostics || '';
|
||
if (!piLabPanelEl) return;
|
||
var pre = piLabPanelEl.querySelector('[data-page-ai-pi-lab-diag-text]');
|
||
if (!pre) return;
|
||
var lines = [];
|
||
lines.push('session=' + (piLabState.sessionId || 'none'));
|
||
lines.push('providerSession=' + (piLabState.providerSessionId || 'none'));
|
||
lines.push('model=' + currentModelLabel());
|
||
lines.push('runtime=' + currentRuntimeLabel());
|
||
lines.push('advancedRuntime=' + advancedRuntimeSummary(piLabState.advancedRuntime));
|
||
lines.push('managedBuiltinTools=' + ((Array.isArray(piLabState.managedBuiltinTools) && piLabState.managedBuiltinTools.length) ? piLabState.managedBuiltinTools.join('/') : 'pending'));
|
||
if (Array.isArray(piLabState.piExtensionSources) && piLabState.piExtensionSources.length) {
|
||
lines.push('piExtensions=' + piLabState.piExtensionSources.join(','));
|
||
}
|
||
if (piLabState.allowedRootsSummary) lines.push('allowedRoots=' + piLabState.allowedRootsSummary);
|
||
if (piLabState.diagnostics) lines.push('message=' + piLabState.diagnostics);
|
||
pre.textContent = lines.join('\n');
|
||
}
|
||
|
||
function advancedRuntimeSummary(config) {
|
||
config = config || {};
|
||
var pairs = [
|
||
['extensionPolicy', config.extensionPolicy],
|
||
['repairPolicy', config.repairPolicy],
|
||
['sessionDurability', config.sessionDurability],
|
||
['requestTimeoutSecs', config.requestTimeoutSecs],
|
||
['maxToolIterations', config.maxToolIterations],
|
||
['hideCwdInPrompt', config.hideCwdInPrompt === true ? 'true' : 'false'],
|
||
].filter(function (item) { return item[1] !== undefined && item[1] !== null && item[1] !== ''; });
|
||
return pairs.length ? pairs.map(function (item) { return item[0] + '=' + item[1]; }).join(',') : 'default';
|
||
}
|
||
|
||
function setDiagnosticOutput(payload) {
|
||
if (!piLabPanelEl) return;
|
||
var output = piLabPanelEl.querySelector('[data-page-ai-pi-lab-diagnostic-output]');
|
||
var details = piLabPanelEl.querySelector('[data-page-ai-pi-lab-diagnostics]');
|
||
if (!output) return;
|
||
if (details) details.open = true;
|
||
output.hidden = false;
|
||
output.textContent = typeof payload === 'string' ? payload : JSON.stringify(payload || {}, null, 2);
|
||
}
|
||
|
||
function updatePiFloatingLayerPosition(layer) {
|
||
if (!layer || !piLabPanelEl) return;
|
||
var composer = piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-composer');
|
||
if (!composer) return;
|
||
layer.style.bottom = Math.max(0, Math.ceil(composer.offsetHeight) + 6) + 'px';
|
||
}
|
||
|
||
function ensureToastStack() {
|
||
var stack = document.querySelector('[data-page-ai-pi-lab-toast-stack]');
|
||
if (stack) {
|
||
updatePiFloatingLayerPosition(stack);
|
||
return stack;
|
||
}
|
||
var host = piLabPanelEl && piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-main');
|
||
stack = el('div', {
|
||
className: 'wolai-page-ai-pi-lab-toast-stack',
|
||
'data-page-ai-pi-lab-toast-stack': 'true',
|
||
});
|
||
(host || document.body).appendChild(stack);
|
||
updatePiFloatingLayerPosition(stack);
|
||
return stack;
|
||
}
|
||
|
||
function showPiToast(message, type) {
|
||
var stack = ensureToastStack();
|
||
var toast = el('div', {
|
||
className: 'wolai-page-ai-pi-lab-toast',
|
||
'data-page-ai-pi-lab-toast': String(++piToastSeq),
|
||
'data-type': type || 'info',
|
||
textContent: message || '',
|
||
});
|
||
stack.appendChild(toast);
|
||
setTimeout(function () {
|
||
if (toast.parentNode) toast.parentNode.removeChild(toast);
|
||
}, 4200);
|
||
return toast;
|
||
}
|
||
|
||
function closePiUiDialog() {
|
||
if (activePiUiDialog && activePiUiDialog.parentNode) activePiUiDialog.parentNode.removeChild(activePiUiDialog);
|
||
activePiUiDialog = null;
|
||
}
|
||
|
||
function closePiCustomUiDialog() {
|
||
if (piCustomUiState.panel && piCustomUiState.panel.parentNode) piCustomUiState.panel.parentNode.removeChild(piCustomUiState.panel);
|
||
piCustomUiState.panel = null;
|
||
piCustomUiState.lines = [];
|
||
piCustomUiState.title = '';
|
||
piCustomUiState.selectedOptionIndex = 0;
|
||
piCustomUiState.submitted = false;
|
||
piCustomUiState.pendingCustomRequest = null;
|
||
}
|
||
|
||
function sendPiUiResponse(request, response) {
|
||
if (!request || !request.id || !piLabState.sessionId) return Promise.resolve(null);
|
||
var body = {
|
||
sessionId: piLabState.sessionId,
|
||
requestId: request.id,
|
||
method: request.method || '',
|
||
};
|
||
if (request.mnoteApproval) body.mnoteApproval = request.mnoteApproval;
|
||
Object.keys(response || {}).forEach(function (key) { body[key] = response[key]; });
|
||
return fetch(API.UI_RESPONSE, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body),
|
||
}).catch(function (error) {
|
||
updateDiagnostics('Extension UI response failed: ' + (error && error.message ? error.message : String(error)));
|
||
});
|
||
}
|
||
|
||
function stripPiAnsi(text) {
|
||
return String(text == null ? '' : text)
|
||
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, '')
|
||
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
|
||
.replace(/\x1b[@-Z\\-_]/g, '');
|
||
}
|
||
|
||
function piCustomUiWidth() {
|
||
var panel = piCustomUiState.panel;
|
||
var body = panel && panel.querySelector('.wolai-page-ai-pi-lab-ui-dialog-body');
|
||
var px = (body && body.clientWidth) || (panel && panel.clientWidth) || 640;
|
||
return Math.max(40, Math.min(120, Math.floor(px / 8)));
|
||
}
|
||
|
||
function parsePiCustomUiOptions(lines) {
|
||
var options = [];
|
||
(Array.isArray(lines) ? lines : []).forEach(function (line) {
|
||
var clean = stripPiAnsi(line).replace(/[│┃║]/g, '').trim();
|
||
var match = clean.match(/^(?:>\s*)?(\d+)\.\s+(.+?)\s*(?:[✎*])?$/);
|
||
if (!match) return;
|
||
var label = match[2].trim();
|
||
if (!label) return;
|
||
options.push({ index: Number(match[1]) - 1, label: label });
|
||
});
|
||
return options;
|
||
}
|
||
|
||
function renderPiCustomUiDialog(request) {
|
||
request = request || {};
|
||
var widgetKey = String(request.widgetKey || request.widget_key || piCustomUiState.widgetKey || '__pi_custom_overlay');
|
||
if (request.clear || request.close) {
|
||
closePiCustomUiDialog();
|
||
return null;
|
||
}
|
||
if (piCustomUiState.submitted) return null;
|
||
var lines = Array.isArray(request.lines)
|
||
? request.lines
|
||
: (Array.isArray(request.widgetLines) ? request.widgetLines : String(request.content || '').split('\n'));
|
||
piCustomUiState.widgetKey = widgetKey;
|
||
piCustomUiState.lines = lines.map(stripPiAnsi);
|
||
piCustomUiState.title = String(request.title || piCustomUiState.title || 'Pi 需要输入').trim();
|
||
var options = parsePiCustomUiOptions(piCustomUiState.lines);
|
||
if (piCustomUiState.selectedOptionIndex >= options.length) piCustomUiState.selectedOptionIndex = 0;
|
||
|
||
var panel = piCustomUiState.panel;
|
||
if (!panel) {
|
||
panel = el('div', {
|
||
className: 'wolai-page-ai-pi-lab-ui-backdrop',
|
||
'data-page-ai-pi-lab-ui-dialog': 'custom',
|
||
'data-page-ai-pi-lab-ui-widget-key': widgetKey,
|
||
});
|
||
var composer = piLabPanelEl && piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-composer');
|
||
if (composer) composer.insertBefore(panel, composer.firstChild);
|
||
else document.body.appendChild(panel);
|
||
piCustomUiState.panel = panel;
|
||
}
|
||
panel.setAttribute('data-page-ai-pi-lab-ui-widget-key', widgetKey);
|
||
panel.innerHTML = '';
|
||
var dialog = el('section', { className: 'wolai-page-ai-pi-lab-ui-dialog', role: 'dialog', 'aria-modal': 'false' });
|
||
dialog.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-header' }, [
|
||
el('div', {}, [
|
||
el('strong', {}, piCustomUiState.title || 'Pi 需要输入'),
|
||
el('span', {}, '官方 Pi Rust custom UI'),
|
||
]),
|
||
el('button', { type: 'button', className: 'wolai-page-ai-pi-lab-ui-collapse', 'data-page-ai-pi-lab-ui-collapse': 'true', title: '折叠/展开' }, '▾'),
|
||
]));
|
||
var body = el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-body' });
|
||
body.appendChild(el('pre', { className: 'wolai-page-ai-pi-lab-ui-tui' }, piCustomUiState.lines.join('\n')));
|
||
if (options.length) {
|
||
var optionList = el('div', { className: 'wolai-page-ai-pi-lab-ui-tui-options' });
|
||
options.forEach(function (option, visibleIndex) {
|
||
var selected = visibleIndex === piCustomUiState.selectedOptionIndex;
|
||
optionList.appendChild(el('button', {
|
||
type: 'button',
|
||
className: 'wolai-page-ai-pi-lab-ui-option',
|
||
'data-page-ai-pi-lab-ui-option': option.label,
|
||
'data-custom-option-index': String(visibleIndex),
|
||
'data-custom-selected': selected ? 'true' : 'false',
|
||
}, [
|
||
el('span', { className: 'wolai-page-ai-pi-lab-ui-option-mark' }, selected ? '◉' : '○'),
|
||
el('span', {}, option.label),
|
||
]));
|
||
});
|
||
body.appendChild(optionList);
|
||
}
|
||
dialog.appendChild(body);
|
||
var actions = el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-actions' });
|
||
actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-ui-cancel': 'true' }, '取消'));
|
||
actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-ui-submit': 'true', 'data-primary': 'true' }, '提交'));
|
||
dialog.appendChild(actions);
|
||
panel.appendChild(dialog);
|
||
panel.onclick = function (event) {
|
||
if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-collapse]')) {
|
||
var collapsed = dialog.getAttribute('data-collapsed') === 'true';
|
||
dialog.setAttribute('data-collapsed', collapsed ? 'false' : 'true');
|
||
event.target.textContent = collapsed ? '▾' : '▸';
|
||
return;
|
||
}
|
||
var option = event.target && event.target.closest ? event.target.closest('[data-custom-option-index]') : null;
|
||
if (option) {
|
||
piCustomUiState.selectedOptionIndex = Number(option.getAttribute('data-custom-option-index')) || 0;
|
||
renderPiCustomUiDialog(request);
|
||
return;
|
||
}
|
||
if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-cancel]')) {
|
||
var cancelRequest = piCustomUiState.pendingCustomRequest;
|
||
piCustomUiState.pendingCustomRequest = null;
|
||
piCustomUiState.submitted = true;
|
||
if (panel.parentNode) panel.parentNode.removeChild(panel);
|
||
piCustomUiState.panel = null;
|
||
if (cancelRequest) sendPiUiResponse(cancelRequest, { value: { width: piCustomUiWidth(), key: '\x1b' } });
|
||
else piCustomUiState.keyQueue.push('\x1b');
|
||
return;
|
||
}
|
||
if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-submit]')) {
|
||
var selectedIndex = Math.max(0, piCustomUiState.selectedOptionIndex || 0);
|
||
var submitKeys = [];
|
||
for (var i = 0; i < selectedIndex; i += 1) submitKeys.push('\x1b[B');
|
||
submitKeys.push('\r');
|
||
var submitRequest = piCustomUiState.pendingCustomRequest;
|
||
piCustomUiState.pendingCustomRequest = null;
|
||
piCustomUiState.submitted = true;
|
||
if (panel.parentNode) panel.parentNode.removeChild(panel);
|
||
piCustomUiState.panel = null;
|
||
if (submitRequest) {
|
||
var firstKey = submitKeys.shift();
|
||
sendPiUiResponse(submitRequest, { value: { width: piCustomUiWidth(), key: firstKey } });
|
||
}
|
||
submitKeys.forEach(function (key) { piCustomUiState.keyQueue.push(key); });
|
||
}
|
||
};
|
||
return panel;
|
||
}
|
||
|
||
function respondToPiCustomUiPoll(request) {
|
||
var value = { width: piCustomUiWidth() };
|
||
var key = piCustomUiState.keyQueue.shift();
|
||
if (key) value.key = key;
|
||
return sendPiUiResponse(request, { value: value });
|
||
}
|
||
|
||
function normalizePiAskQuestions(request) {
|
||
var source = Array.isArray(request.questions) && request.questions.length ? request.questions : [{
|
||
header: request.header || request.question || request.title || 'Pi 需要选择',
|
||
tab: request.tab || 'answer',
|
||
prompt: request.prompt || request.message || request.context || '',
|
||
options: request.options || [],
|
||
multiSelect: request.allowMultiple === true || request.multiSelect === true,
|
||
allowSkip: request.allowSkip !== false,
|
||
allowFreeform: request.allowFreeform !== false,
|
||
}];
|
||
return source.map(function (question, index) {
|
||
return {
|
||
header: String(question.header || question.title || question.question || ('问题 ' + (index + 1))).trim(),
|
||
tab: String(question.tab || question.id || ('q' + (index + 1))).trim(),
|
||
prompt: String(question.prompt || question.description || '').trim(),
|
||
options: Array.isArray(question.options) ? question.options : [],
|
||
multiSelect: question.multiSelect === true || question.allowMultiple === true,
|
||
allowSkip: question.allowSkip !== false,
|
||
allowFreeform: question.allowFreeform !== false,
|
||
};
|
||
});
|
||
}
|
||
|
||
function renderPiAskQuestion(body, question, index) {
|
||
var section = el('div', { className: 'wolai-page-ai-pi-lab-ui-question', 'data-page-ai-pi-lab-ui-question': String(index), 'data-tab': question.tab });
|
||
section.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-question-title' }, question.header));
|
||
if (question.prompt) section.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-question-prompt' }, question.prompt));
|
||
question.options.forEach(function (option, optionIndex) {
|
||
var label = typeof option === 'string' ? option : String((option && (option.label || option.title || option.value)) || '');
|
||
var description = typeof option === 'string' ? '' : String((option && option.description) || '');
|
||
var value = typeof option === 'string' ? option : String((option && (option.value || option.label || option.title)) || label);
|
||
var row = el('button', {
|
||
type: 'button',
|
||
className: 'wolai-page-ai-pi-lab-ui-option',
|
||
'data-page-ai-pi-lab-ui-option': value,
|
||
'data-question-index': String(index),
|
||
'data-option-index': String(optionIndex),
|
||
'data-selected': 'false',
|
||
}, [
|
||
el('span', { className: 'wolai-page-ai-pi-lab-ui-option-mark' }, question.multiSelect ? '□' : '○'),
|
||
el('span', {}, [
|
||
el('span', {}, label || value),
|
||
description ? el('small', {}, description) : null,
|
||
]),
|
||
]);
|
||
section.appendChild(row);
|
||
});
|
||
if (question.allowFreeform) {
|
||
section.appendChild(el('textarea', {
|
||
'data-page-ai-pi-lab-ui-custom': String(index),
|
||
placeholder: 'Type something...',
|
||
}));
|
||
}
|
||
body.appendChild(section);
|
||
}
|
||
|
||
function collectPiAskResponse(panel, questions) {
|
||
var answers = questions.map(function (question, index) {
|
||
var section = panel.querySelector('[data-page-ai-pi-lab-ui-question="' + index + '"]');
|
||
var selected = Array.from(section ? section.querySelectorAll('[data-page-ai-pi-lab-ui-option][data-selected="true"]') : [])
|
||
.map(function (node) { return node.getAttribute('data-page-ai-pi-lab-ui-option') || ''; })
|
||
.filter(Boolean);
|
||
var customNode = section ? section.querySelector('[data-page-ai-pi-lab-ui-custom="' + index + '"]') : null;
|
||
var custom = customNode ? customNode.value.trim() : '';
|
||
var answer = { tab: question.tab };
|
||
if (question.multiSelect) {
|
||
if (selected.length) answer.answers = selected;
|
||
if (custom) answer.custom = custom;
|
||
if (!selected.length && !custom) answer.answers = [];
|
||
} else if (selected[0]) {
|
||
answer.answer = selected[0];
|
||
} else if (custom) {
|
||
answer.custom = custom;
|
||
} else {
|
||
answer.skipped = true;
|
||
}
|
||
return answer;
|
||
});
|
||
var noteNode = panel.querySelector('[data-page-ai-pi-lab-ui-note]');
|
||
var value = { cancelled: false, answers: answers };
|
||
if (noteNode && noteNode.value.trim()) value.message = noteNode.value.trim();
|
||
return { value: value };
|
||
}
|
||
|
||
function showPiUiDialog(request) {
|
||
closePiUiDialog();
|
||
request = request || {};
|
||
var method = String(request.method || '').trim();
|
||
var isAskUser = method === 'ask_user' || method === 'ask-user' || method === 'questionnaire' || Array.isArray(request.questions);
|
||
var title = String(request.title || request.question || (method === 'confirm' ? '确认操作' : 'Pi 需要输入')).trim();
|
||
var message = String(request.message || request.context || '').trim();
|
||
var panel = el('div', {
|
||
className: 'wolai-page-ai-pi-lab-ui-backdrop',
|
||
'data-page-ai-pi-lab-ui-dialog': method || 'dialog',
|
||
});
|
||
var dialog = el('section', { className: 'wolai-page-ai-pi-lab-ui-dialog', role: 'dialog', 'aria-modal': 'false' });
|
||
dialog.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-header' }, [
|
||
el('div', {}, [
|
||
el('strong', {}, title),
|
||
el('span', {}, message || (method === 'select' ? '请选择一个选项' : '')),
|
||
]),
|
||
el('button', { type: 'button', className: 'wolai-page-ai-pi-lab-ui-collapse', 'data-page-ai-pi-lab-ui-collapse': 'true', title: '折叠/展开' }, '▾'),
|
||
]));
|
||
var body = el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-body' });
|
||
var inputNode = null;
|
||
var askQuestions = [];
|
||
var finish = function (response) {
|
||
closePiUiDialog();
|
||
sendPiUiResponse(request, response || {});
|
||
};
|
||
if (isAskUser) {
|
||
askQuestions = normalizePiAskQuestions(request);
|
||
askQuestions.forEach(function (question, index) { renderPiAskQuestion(body, question, index); });
|
||
body.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-ui-note' }, [
|
||
el('label', {}, 'Note to assistant'),
|
||
el('textarea', { 'data-page-ai-pi-lab-ui-note': 'true', placeholder: '可选补充说明' }),
|
||
]));
|
||
} else if (method === 'select') {
|
||
(Array.isArray(request.options) ? request.options : []).forEach(function (option) {
|
||
var value = typeof option === 'string' ? option : String((option && (option.value || option.label)) || '');
|
||
var label = typeof option === 'string' ? option : String((option && (option.label || option.value)) || '');
|
||
body.appendChild(el('button', {
|
||
type: 'button',
|
||
className: 'wolai-page-ai-pi-lab-ui-option',
|
||
'data-page-ai-pi-lab-ui-option': value,
|
||
textContent: label || value,
|
||
}));
|
||
});
|
||
} else if (method === 'input') {
|
||
inputNode = el('input', {
|
||
'data-page-ai-pi-lab-ui-input': 'true',
|
||
placeholder: request.placeholder || '',
|
||
value: request.prefill || '',
|
||
});
|
||
body.appendChild(inputNode);
|
||
} else if (method === 'editor') {
|
||
inputNode = el('textarea', {
|
||
'data-page-ai-pi-lab-ui-input': 'true',
|
||
placeholder: request.placeholder || '',
|
||
textContent: request.prefill || '',
|
||
});
|
||
body.appendChild(inputNode);
|
||
}
|
||
if (!body.childNodes.length && method !== 'confirm') {
|
||
body.appendChild(el('div', { textContent: message || 'Pi extension UI request' }));
|
||
}
|
||
dialog.appendChild(body);
|
||
var actions = el('div', { className: 'wolai-page-ai-pi-lab-ui-dialog-actions' });
|
||
actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-ui-cancel': 'true' }, '取消'));
|
||
if (method !== 'select') actions.appendChild(el('button', { type: 'button', 'data-page-ai-pi-lab-ui-submit': 'true', 'data-primary': 'true' }, method === 'confirm' ? '确认' : '提交'));
|
||
dialog.appendChild(actions);
|
||
panel.appendChild(dialog);
|
||
var composer = piLabPanelEl && piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-composer');
|
||
if (composer) composer.insertBefore(panel, composer.firstChild);
|
||
else document.body.appendChild(panel);
|
||
activePiUiDialog = panel;
|
||
var timeout = Number(request.timeout || request.timeoutMs || 0);
|
||
var timer = timeout > 0 ? setTimeout(function () { finish({ cancelled: true }); }, timeout) : null;
|
||
var finishOnce = function (response) {
|
||
if (timer) clearTimeout(timer);
|
||
finish(response);
|
||
};
|
||
panel.addEventListener('click', function (event) {
|
||
if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-collapse]')) {
|
||
var collapsed = dialog.getAttribute('data-collapsed') === 'true';
|
||
dialog.setAttribute('data-collapsed', collapsed ? 'false' : 'true');
|
||
event.target.textContent = collapsed ? '▾' : '▸';
|
||
return;
|
||
}
|
||
var option = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-ui-option]') : null;
|
||
if (option && isAskUser) {
|
||
var questionIndex = option.getAttribute('data-question-index');
|
||
var question = askQuestions[Number(questionIndex)];
|
||
if (question && question.multiSelect) {
|
||
var nextSelected = option.getAttribute('data-selected') !== 'true';
|
||
option.setAttribute('data-selected', nextSelected ? 'true' : 'false');
|
||
var mark = option.querySelector('.wolai-page-ai-pi-lab-ui-option-mark');
|
||
if (mark) mark.textContent = nextSelected ? '▣' : '□';
|
||
} else {
|
||
panel.querySelectorAll('[data-question-index="' + questionIndex + '"][data-selected="true"]').forEach(function (node) {
|
||
node.setAttribute('data-selected', 'false');
|
||
var oldMark = node.querySelector('.wolai-page-ai-pi-lab-ui-option-mark');
|
||
if (oldMark) oldMark.textContent = '○';
|
||
});
|
||
option.setAttribute('data-selected', 'true');
|
||
var selectedMark = option.querySelector('.wolai-page-ai-pi-lab-ui-option-mark');
|
||
if (selectedMark) selectedMark.textContent = '◉';
|
||
}
|
||
return;
|
||
}
|
||
if (option) {
|
||
finishOnce({ value: option.getAttribute('data-page-ai-pi-lab-ui-option') || option.textContent || '' });
|
||
return;
|
||
}
|
||
if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-cancel]')) {
|
||
finishOnce(isAskUser ? { value: { cancelled: true, answers: [] }, cancelled: true } : { cancelled: true });
|
||
return;
|
||
}
|
||
if (event.target && event.target.closest && event.target.closest('[data-page-ai-pi-lab-ui-submit]')) {
|
||
if (method === 'confirm') finishOnce({ confirmed: true });
|
||
else if (isAskUser) finishOnce(collectPiAskResponse(panel, askQuestions));
|
||
else finishOnce({ value: inputNode ? inputNode.value : '' });
|
||
}
|
||
});
|
||
panel.addEventListener('keydown', function (event) {
|
||
if (event.key === 'Escape') {
|
||
event.preventDefault();
|
||
finishOnce(isAskUser ? { value: { cancelled: true, answers: [] }, cancelled: true } : { cancelled: true });
|
||
}
|
||
if (event.key === 'Enter' && method === 'input') {
|
||
event.preventDefault();
|
||
finishOnce({ value: inputNode ? inputNode.value : '' });
|
||
}
|
||
});
|
||
setTimeout(function () {
|
||
var focusTarget = inputNode || panel.querySelector('[data-page-ai-pi-lab-ui-option], [data-page-ai-pi-lab-ui-submit]');
|
||
if (focusTarget && typeof focusTarget.focus === 'function') focusTarget.focus();
|
||
}, 20);
|
||
return panel;
|
||
}
|
||
|
||
function handlePiExtensionUiRequest(payload) {
|
||
if (!payload || payload.type !== 'extension_ui_request') return false;
|
||
if (payload.method === 'notify') {
|
||
showPiToast(payload.message || payload.title || 'Pi notification', payload.notifyType || 'info');
|
||
return true;
|
||
}
|
||
if (payload.method === 'setWidget' || payload.method === 'set_widget') {
|
||
renderPiCustomUiDialog(payload);
|
||
return true;
|
||
}
|
||
if (payload.method === 'custom') {
|
||
if (payload.close || payload.mode === 'close') {
|
||
closePiCustomUiDialog();
|
||
sendPiUiResponse(payload, { value: { closed: true } });
|
||
} else {
|
||
if (Array.isArray(payload.lines) || Array.isArray(payload.widgetLines) || payload.content) renderPiCustomUiDialog(payload);
|
||
if (piCustomUiState.keyQueue.length > 0 || piCustomUiState.submitted || !piCustomUiState.panel) {
|
||
respondToPiCustomUiPoll(payload);
|
||
} else {
|
||
piCustomUiState.pendingCustomRequest = payload;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
if (payload.method === 'confirm' || payload.method === 'select' || payload.method === 'input' || payload.method === 'editor' || payload.method === 'ask_user' || payload.method === 'ask-user' || payload.method === 'questionnaire' || Array.isArray(payload.questions)) {
|
||
showPiUiDialog(payload);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function updateBuiltinManaged() {
|
||
updateConfig();
|
||
}
|
||
|
||
function updateReceiptDisplay() {
|
||
if (!piLabPanelEl) return;
|
||
var receipts = piLabPanelEl.querySelector('[data-page-ai-pi-lab-receipts]');
|
||
var count = piLabPanelEl.querySelector('[data-page-ai-pi-lab-receipt-count]');
|
||
if (!receipts) return;
|
||
if (count) count.textContent = String(piLabState.receipts.length);
|
||
receipts.innerHTML = '';
|
||
if (!piLabState.receipts.length) {
|
||
receipts.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-receipt' }, 'No tool receipt yet'));
|
||
return;
|
||
}
|
||
piLabState.receipts.slice(-8).reverse().forEach(function (receipt) {
|
||
var label = (receipt.toolName || 'tool') + ' · ' + (receipt.allowed === false ? 'denied' : 'allowed');
|
||
if (receipt.approvalRequired) label += receipt.approvalConfirmed ? ' · approved' : ' · approval required';
|
||
if (receipt.denyReason) label += ' · ' + receipt.denyReason;
|
||
if (receipt.diffSummary) label += ' · diff ' + receipt.diffSummary;
|
||
if (receipt.citationCount) label += ' · citations ' + receipt.citationCount;
|
||
if (receipt.afterFileVersion) label += ' · v ' + String(receipt.afterFileVersion).slice(0, 8);
|
||
receipts.appendChild(el('div', {
|
||
className: 'wolai-page-ai-pi-lab-receipt',
|
||
'data-allowed': receipt.allowed === false ? 'false' : 'true',
|
||
}, label));
|
||
});
|
||
}
|
||
|
||
function collectPiArtifacts() {
|
||
var artifacts = [];
|
||
var seen = {};
|
||
piLabState.messages.forEach(function (msg) {
|
||
if (!msg) return;
|
||
(Array.isArray(msg.citations) ? msg.citations : []).forEach(function (cit, index) {
|
||
var key = 'citation:' + (cit.url || cit.source || cit.title || index);
|
||
if (seen[key]) return;
|
||
seen[key] = true;
|
||
artifacts.push({
|
||
kind: 'citation',
|
||
title: cit.title || cit.source || 'Citation',
|
||
summary: cit.source || cit.url || '',
|
||
url: cit.url || '#',
|
||
});
|
||
});
|
||
if (msg.diffSummary && Array.isArray(msg.diffSummary.files)) {
|
||
msg.diffSummary.files.forEach(function (file) {
|
||
if (!file) return;
|
||
var key = 'diff:' + file;
|
||
if (seen[key]) return;
|
||
seen[key] = true;
|
||
artifacts.push({
|
||
kind: 'diff',
|
||
title: file,
|
||
summary: '文件变更',
|
||
toolEventId: msg.diffSummary.toolEventId || piLabState.currentToolEventId || '',
|
||
sessionId: piLabState.sessionId || '',
|
||
});
|
||
});
|
||
}
|
||
(Array.isArray(msg.toolCalls) ? msg.toolCalls : []).forEach(function (tc) {
|
||
if (!tc) return;
|
||
var name = toolDisplayName({ toolName: tc.name, args: tc.args, details: tc.details });
|
||
if (String(name || '').indexOf('mcp:') !== 0) return;
|
||
var key = 'mcp:' + (tc.id || name);
|
||
if (seen[key]) return;
|
||
seen[key] = true;
|
||
artifacts.push({
|
||
kind: 'mcp',
|
||
title: name,
|
||
summary: extractResultText(tc.result || tc.partialResult || tc.details || tc.args).slice(0, 280),
|
||
raw: tc.result || tc.details || tc.args || {},
|
||
});
|
||
});
|
||
});
|
||
piLabState.changedFiles.forEach(function (file) {
|
||
if (!file || seen['diff:' + file]) return;
|
||
seen['diff:' + file] = true;
|
||
artifacts.push({ kind: 'diff', title: file, summary: '文件变更' });
|
||
});
|
||
return artifacts;
|
||
}
|
||
|
||
function renderArtifactItem(artifact) {
|
||
var item = el('div', {
|
||
className: 'wolai-page-ai-pi-lab-artifact',
|
||
'data-page-ai-pi-lab-artifact': artifact.kind || 'artifact',
|
||
'data-kind': artifact.kind || 'artifact',
|
||
});
|
||
item.appendChild(el('strong', {}, artifact.title || 'Artifact'));
|
||
if (artifact.summary) item.appendChild(el('span', {}, artifact.summary));
|
||
var actions = el('div', { className: 'wolai-page-ai-pi-lab-artifact-actions' });
|
||
var markdown = '- ' + (artifact.title || 'Artifact') + (artifact.summary ? ': ' + artifact.summary : '') + (artifact.url ? ' (' + artifact.url + ')' : '');
|
||
if (artifact.kind === 'citation') {
|
||
actions.appendChild(el('a', {
|
||
href: artifact.url || '#',
|
||
target: '_blank',
|
||
rel: 'noopener',
|
||
'data-page-ai-pi-lab-artifact-open': artifact.url || '#',
|
||
'data-page-ai-pi-lab-artifact-title': artifact.title || 'Citation',
|
||
}, '打开引用'));
|
||
}
|
||
if (artifact.kind === 'diff' && artifact.toolEventId) {
|
||
actions.appendChild(el('button', {
|
||
type: 'button',
|
||
'data-page-ai-pi-lab-artifact-view-diff': artifact.toolEventId,
|
||
'data-page-ai-pi-lab-artifact-diff-sid': artifact.sessionId || '',
|
||
}, '查看 diff'));
|
||
}
|
||
actions.appendChild(el('button', {
|
||
type: 'button',
|
||
'data-page-ai-pi-lab-artifact-copy-markdown': markdown,
|
||
}, '复制 MD'));
|
||
if (artifact.raw) {
|
||
actions.appendChild(el('button', {
|
||
type: 'button',
|
||
'data-page-ai-pi-lab-artifact-copy': compactJson(artifact.raw),
|
||
}, '复制 raw'));
|
||
}
|
||
item.appendChild(actions);
|
||
return item;
|
||
}
|
||
|
||
function updateArtifactRail() {
|
||
if (!piLabPanelEl) return;
|
||
var container = piLabPanelEl.querySelector('[data-page-ai-pi-lab-artifacts]');
|
||
var count = piLabPanelEl.querySelector('[data-page-ai-pi-lab-artifact-count]');
|
||
var body = piLabPanelEl.querySelector('[data-page-ai-pi-lab-body]');
|
||
if (!container) return;
|
||
var artifacts = collectPiArtifacts();
|
||
if (count) count.textContent = String(artifacts.length);
|
||
if (!artifacts.length && body) body.setAttribute('data-rail-open', 'false');
|
||
container.innerHTML = '';
|
||
if (!artifacts.length) {
|
||
container.appendChild(el('div', { className: 'wolai-page-ai-pi-lab-receipt' }, 'No artifact yet'));
|
||
return;
|
||
}
|
||
artifacts.slice(-12).reverse().forEach(function (artifact) {
|
||
container.appendChild(renderArtifactItem(artifact));
|
||
});
|
||
}
|
||
|
||
function setState(newState) {
|
||
piLabState.status = newState;
|
||
if (piRunViewModel) piRunViewModel.runtimeStatus = newState;
|
||
updateConfig();
|
||
maybeApplyPendingPermissionMode();
|
||
maybeApplyPendingModelConfig();
|
||
}
|
||
|
||
|
||
function piLabDraftKey() {
|
||
var wid = piLabState.workspaceId || currentWorkspaceId() || 'default';
|
||
var root = piLabState.rootUri || currentRootUri() || '';
|
||
var page = piLabState.pagePath || '';
|
||
var sid = piLabState.sessionId || piLabState.viewingHistorySessionId || 'new';
|
||
return 'pi-lab:' + wid + ':' + root + ':' + page + ':' + sid;
|
||
}
|
||
|
||
function savePiLabDraft() {
|
||
if (!piLabPanelEl) return;
|
||
var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
|
||
if (!input) return;
|
||
var text = input.value || '';
|
||
if (text.trim()) {
|
||
try { localStorage.setItem(piLabDraftKey(), text); } catch (e) {}
|
||
} else {
|
||
clearPiLabDraft();
|
||
}
|
||
}
|
||
|
||
function restorePiLabDraft() {
|
||
if (!piLabPanelEl) return;
|
||
var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
|
||
if (!input) return;
|
||
try {
|
||
var saved = localStorage.getItem(piLabDraftKey());
|
||
if (saved) {
|
||
input.value = saved;
|
||
updateButtons();
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
|
||
function clearPiLabDraft() {
|
||
try {
|
||
localStorage.removeItem(piLabDraftKey());
|
||
} catch (e) {}
|
||
}
|
||
|
||
|
||
function syncRuntimeState() {
|
||
if (!piLabState.sessionId && !piLabState.viewingHistorySessionId) return;
|
||
var sid = piLabState.sessionId || piLabState.viewingHistorySessionId;
|
||
fetch(API.STATE, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ sessionId: sid }),
|
||
}).then(function (r) { return r.json(); }).then(function (data) {
|
||
if (!data) return;
|
||
if (data.queuedMessages !== undefined) piLabState.queuedMessages = data.queuedMessages;
|
||
if (data.pendingMessageCount !== undefined) piLabState.pendingMessageCount = data.pendingMessageCount;
|
||
if (data.isCompacting !== undefined) piLabState.isCompacting = data.isCompacting;
|
||
if (data.contextUsage !== undefined) piLabState.contextUsage = data.contextUsage;
|
||
if (data.modelProvider) piLabState.modelProvider = data.modelProvider;
|
||
if (data.modelId) piLabState.modelId = data.modelId;
|
||
if (data.thinkingLevel) piLabState.thinkingLevel = normalizeThinkingLevel(data.thinkingLevel);
|
||
updateQueueDisplay();
|
||
updateConfig();
|
||
}).catch(function () {});
|
||
}
|
||
|
||
function callPiRpcCommand(type, params, timeoutMs) {
|
||
if (!piLabState.sessionId) return Promise.reject(new Error('Pi 会话尚未就绪'));
|
||
return fetch(API.RPC_COMMAND, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sessionId: piLabState.sessionId,
|
||
type: type,
|
||
params: params || {},
|
||
timeoutMs: timeoutMs || 10000,
|
||
}),
|
||
}).then(function (r) {
|
||
return responseJsonOrError(r, 'Pi RPC command failed');
|
||
});
|
||
}
|
||
|
||
function readFileAsPiImage(file) {
|
||
return new Promise(function (resolve, reject) {
|
||
if (!file || !/^image\//i.test(file.type || '')) {
|
||
reject(new Error('只支持图片附件;普通文件请在输入中使用 @文件路径'));
|
||
return;
|
||
}
|
||
var reader = new FileReader();
|
||
reader.onload = function () {
|
||
var dataUrl = String(reader.result || '');
|
||
var comma = dataUrl.indexOf(',');
|
||
if (comma < 0) {
|
||
reject(new Error('图片读取失败'));
|
||
return;
|
||
}
|
||
resolve({
|
||
type: 'image',
|
||
source: {
|
||
type: 'base64',
|
||
mediaType: file.type || 'image/png',
|
||
data: dataUrl.slice(comma + 1),
|
||
},
|
||
});
|
||
};
|
||
reader.onerror = function () { reject(new Error('图片读取失败')); };
|
||
reader.readAsDataURL(file);
|
||
});
|
||
}
|
||
|
||
function pickPiPromptImages(useCamera) {
|
||
var input = document.createElement('input');
|
||
input.type = 'file';
|
||
input.accept = 'image/*';
|
||
input.multiple = !useCamera;
|
||
if (useCamera) input.setAttribute('capture', 'environment');
|
||
input.addEventListener('change', function () {
|
||
var files = Array.prototype.slice.call(input.files || []);
|
||
if (!files.length) return;
|
||
Promise.all(files.map(readFileAsPiImage)).then(function (images) {
|
||
piLabState.pendingImages = (piLabState.pendingImages || []).concat(images);
|
||
showPiToast('已添加 ' + images.length + ' 张图片', 'success');
|
||
updateButtons();
|
||
}).catch(function (error) {
|
||
showPiToast(error && error.message ? error.message : '附件读取失败', 'warning');
|
||
});
|
||
});
|
||
input.click();
|
||
}
|
||
|
||
|
||
function responseJsonOrError(response, fallbackMessage) {
|
||
return response.json().catch(function () { return {}; }).then(function (payload) {
|
||
if (!response.ok) {
|
||
var message = (payload && (payload.message || payload.error)) || (fallbackMessage + ': ' + response.status);
|
||
var error = new Error(message);
|
||
error.payload = payload;
|
||
throw error;
|
||
}
|
||
return payload;
|
||
});
|
||
}
|
||
|
||
function runPiDiagnostic(command) {
|
||
var input = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-diagnostic-query]');
|
||
var keyword = input ? String(input.value || '').trim() : '';
|
||
var params = {};
|
||
if (command === 'doctor') params.format = 'json';
|
||
if (command === 'context-preview') {
|
||
params.format = 'json';
|
||
params.query = [keyword || 'MNote Pi Rust diagnostics'];
|
||
}
|
||
if (command === 'search') {
|
||
if (!keyword) {
|
||
showPiToast('请输入 search keyword', 'warning');
|
||
return Promise.resolve();
|
||
}
|
||
params.query = keyword;
|
||
params.limit = 10;
|
||
}
|
||
if (command === 'info') {
|
||
if (!keyword) {
|
||
showPiToast('请输入 info name/id', 'warning');
|
||
return Promise.resolve();
|
||
}
|
||
params.name = keyword;
|
||
}
|
||
updateDiagnostics('Pi diagnostics: ' + command);
|
||
setDiagnosticOutput('Running ' + command + '...');
|
||
return fetch(API.DIAGNOSTICS, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
command: command,
|
||
params: params,
|
||
rootUri: piLabState.session && piLabState.session.rootUri || undefined,
|
||
workspaceId: piLabState.session && piLabState.session.workspaceId || undefined,
|
||
}),
|
||
}).then(function (response) {
|
||
return responseJsonOrError(response, 'Diagnostics failed');
|
||
}).then(function (payload) {
|
||
setDiagnosticOutput(payload);
|
||
updateDiagnostics('Pi diagnostics done: ' + command);
|
||
return payload;
|
||
}).catch(function (error) {
|
||
setDiagnosticOutput({ ok: false, command: command, error: error.message || String(error), payload: error.payload || null });
|
||
updateDiagnostics('Pi diagnostics failed: ' + (error.message || String(error)));
|
||
showPiToast('诊断失败', 'error');
|
||
});
|
||
}
|
||
|
||
function checkStatus() {
|
||
return Promise.all([
|
||
fetch(API.STATUS, { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }),
|
||
loadEffectiveConfig().catch(function () { return null; }),
|
||
])
|
||
.then(function (results) {
|
||
var data = results[0] || {};
|
||
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;
|
||
if (data.defaultThinkingLevel) piLabState.defaultThinkingLevel = normalizeThinkingLevel(data.defaultThinkingLevel);
|
||
piLabState.modelProvider = (data.session && data.session.modelProvider) || piLabState.modelProvider || null;
|
||
piLabState.modelId = (data.session && data.session.modelId) || piLabState.modelId || null;
|
||
var sessionThinkingLevel = data.session && data.session.thinkingLevel;
|
||
if (sessionThinkingLevel) {
|
||
piLabState.thinkingLevel = normalizeThinkingLevel(sessionThinkingLevel);
|
||
} else if (!piLabState.thinkingLevel) {
|
||
piLabState.thinkingLevel = piLabState.defaultThinkingLevel;
|
||
}
|
||
piLabState.permissionMode = normalizePermissionMode((data.session && (data.session.permissionMode || (data.session.runtimePolicySnapshot && data.session.runtimePolicySnapshot.permissionMode))) || data.permissionMode || piLabState.permissionMode);
|
||
piLabState.session = data.session || piLabState.session || null;
|
||
piLabState.allowedRootsSummary = summarizeAllowedRoots(data.session && data.session.allowedRootsSnapshot);
|
||
piLabState.runtimeMode = data.runtimeMode || (data.session && data.session.runtimeMode) || piLabState.runtimeMode || null;
|
||
piLabState.runtimeImplementation = data.runtimeImplementation || data.runtime_implementation || piLabState.runtimeImplementation || null;
|
||
piLabState.runtimeBinary = data.runtimeBinary || data.runtime_binary || piLabState.runtimeBinary || null;
|
||
piLabState.runtimeAvailable = data.runtimeAvailable !== false;
|
||
piLabState.runtimeInstallHint = data.runtimeInstallHint || data.runtime_install_hint || '';
|
||
piLabState.runtimeError = data.runtimeError || (data.session && data.session.runtimeError) || '';
|
||
piLabState.runtimePid = data.pid || (data.session && data.session.runtimePid) || piLabState.runtimePid || null;
|
||
piLabState.advancedRuntime = data.advancedRuntime || (data.session && data.session.advancedRuntime) || (data.session && data.session.runtimePolicySnapshot && data.session.runtimePolicySnapshot.piRuntime) || piLabState.advancedRuntime || {};
|
||
piLabState.warmupRunning = data.warmupRunning === true;
|
||
piLabState.warmupSessionId = data.warmupSessionId || null;
|
||
piLabState.managedBuiltinTools = data.managedPiBuiltinTools || data.managedBuiltinTools || piLabState.managedBuiltinTools || [];
|
||
piLabState.piExtensionSources = data.piExtensionSources || (data.session && data.session.piExtensionSources) || piLabState.piExtensionSources || [];
|
||
piLabState.piExtensionToolNames = data.piExtensionToolNames || piLabState.piExtensionToolNames || [];
|
||
if (data.session && data.session.status === 'runtime_running') {
|
||
piLabState.sessionId = data.session.sessionId;
|
||
setState(STATE_STARTED);
|
||
if (!piLabEventSource || piLabEventSource.readyState === EventSource.CLOSED) connectEventSource(data.session.sessionId);
|
||
}
|
||
updateBuiltinManaged();
|
||
updateModelLabel();
|
||
updateThinkingControl();
|
||
updatePermissionControl();
|
||
updateReceiptDisplay();
|
||
updateDiagnostics('');
|
||
updateMessages();
|
||
return data;
|
||
})
|
||
.catch(function (error) {
|
||
piLabState.enabled = false;
|
||
setState(STATE_ERROR);
|
||
updateDiagnostics(error && error.message ? error.message : 'status failed');
|
||
});
|
||
}
|
||
|
||
function startRuntime() {
|
||
exitHistoryReplayMode();
|
||
if (piLabStartPromise) return piLabStartPromise;
|
||
if (piLabState.status === STATE_STREAMING) return Promise.resolve({ skipped: true });
|
||
if (piLabState.status === STATE_STARTED && piLabState.sessionId) return Promise.resolve({ ok: true, session: piLabState.session });
|
||
piLabStartPromise = Promise.resolve()
|
||
.then(function () {
|
||
return piLabState.enabled ? null : checkStatus();
|
||
})
|
||
.then(function () {
|
||
if (!piLabState.enabled) throw new Error('Pi Rust 服务未启用');
|
||
if (piLabState.runtimeAvailable === false) throw new Error(piLabState.runtimeInstallHint || 'Pi Rust runtime 不可用');
|
||
var context = applyCurrentContextToState();
|
||
applyModelControls();
|
||
ensurePiToolCapableModel();
|
||
setState(STATE_STARTING);
|
||
updateDiagnostics(piLabState.warmupRunning ? 'Binding current page Pi session' : 'Starting Pi Rust runtime');
|
||
return fetch(API.START, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
sessionId: piLabState.sessionId || undefined,
|
||
rootUri: context.rootUri || (piLabState.session && piLabState.session.rootUri) || undefined,
|
||
workspaceId: context.workspaceId || (piLabState.session && piLabState.session.workspaceId) || undefined,
|
||
pagePath: context.pagePath || (piLabState.session && piLabState.session.pagePath) || undefined,
|
||
pageTitle: context.pageTitle || (piLabState.session && piLabState.session.pageTitle) || undefined,
|
||
modelProvider: piLabState.modelProvider || piLabState.defaultModelProvider,
|
||
modelId: piLabState.modelId || piLabState.defaultModelId,
|
||
thinkingLevel: normalizeThinkingLevel(piLabState.thinkingLevel || piLabState.defaultThinkingLevel),
|
||
permissionMode: normalizePermissionMode(piLabState.permissionMode),
|
||
}),
|
||
}).then(function (response) {
|
||
return responseJsonOrError(response, 'Start failed');
|
||
});
|
||
})
|
||
.then(function (data) {
|
||
var session = data.session || {};
|
||
piLabState.sessionId = session.sessionId || data.sessionId || piLabState.sessionId;
|
||
piLabState.providerSessionId = session.providerSessionId || data.providerSessionId || piLabState.providerSessionId;
|
||
piLabState.modelProvider = session.modelProvider || piLabState.modelProvider || null;
|
||
piLabState.modelId = session.modelId || piLabState.modelId || null;
|
||
piLabState.thinkingLevel = normalizeThinkingLevel(session.thinkingLevel || data.thinkingLevel || piLabState.thinkingLevel);
|
||
piLabState.permissionMode = normalizePermissionMode(session.permissionMode || data.permissionMode || (session.runtimePolicySnapshot && session.runtimePolicySnapshot.permissionMode) || piLabState.permissionMode);
|
||
piLabState.session = session || piLabState.session || null;
|
||
piLabState.runtimeMode = session.runtimeMode || data.runtimeMode || piLabState.runtimeMode || null;
|
||
piLabState.runtimeImplementation = data.runtimeImplementation || data.runtime_implementation || piLabState.runtimeImplementation || null;
|
||
piLabState.runtimeBinary = data.runtimeBinary || data.runtime_binary || piLabState.runtimeBinary || null;
|
||
piLabState.runtimeAvailable = data.runtimeAvailable !== false;
|
||
piLabState.runtimeInstallHint = data.runtimeInstallHint || data.runtime_install_hint || '';
|
||
piLabState.runtimeError = session.runtimeError || data.runtimeError || '';
|
||
piLabState.runtimePid = session.runtimePid || data.pid || piLabState.runtimePid || null;
|
||
piLabState.advancedRuntime = data.advancedRuntime || session.advancedRuntime || (session.runtimePolicySnapshot && session.runtimePolicySnapshot.piRuntime) || piLabState.advancedRuntime || {};
|
||
piLabState.managedBuiltinTools = data.managedPiBuiltinTools || data.managedBuiltinTools || piLabState.managedBuiltinTools || [];
|
||
piLabState.piExtensionSources = data.piExtensionSources || piLabState.piExtensionSources || [];
|
||
piLabState.piExtensionToolNames = data.piExtensionToolNames || piLabState.piExtensionToolNames || [];
|
||
connectEventSource();
|
||
setState(STATE_STARTED);
|
||
updateDiagnostics('Pi runtime ready');
|
||
updateMessages();
|
||
return data;
|
||
})
|
||
.catch(function (err) {
|
||
setState(STATE_ERROR);
|
||
updateDiagnostics('Start error: ' + err.message);
|
||
throw err;
|
||
})
|
||
.finally(function () {
|
||
piLabStartPromise = null;
|
||
});
|
||
return piLabStartPromise;
|
||
}
|
||
|
||
function handleToolCallEventPayload(payload) {
|
||
payload = payload || {};
|
||
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; });
|
||
}
|
||
|
||
function handleAtMention(inputEl) {
|
||
if (!inputEl) return;
|
||
var text = inputEl.value || '';
|
||
var atIndex = text.lastIndexOf('@');
|
||
if (atIndex < 0 || (atIndex > 0 && text[atIndex - 1] !== ' ' && text[atIndex - 1] !== '\n' && text[atIndex - 1] !== '\t')) {
|
||
|
||
var existingMenu = document.querySelector('.wolai-page-ai-pi-lab-mention-menu');
|
||
if (existingMenu) existingMenu.remove();
|
||
return;
|
||
}
|
||
var query = text.slice(atIndex + 1).split(/\s/)[0] || '';
|
||
var candidates = [];
|
||
if (piLabState.allowedRootsSummary && piLabState.allowedRootsSummary !== 'pending') {
|
||
candidates.push({ label: '@' + piLabState.allowedRootsSummary, value: piLabState.allowedRootsSummary, type: 'root' });
|
||
}
|
||
var pagePath = piLabState.pagePath || currentFolderPathFromPagePath(currentPageContext().pagePath) || '';
|
||
if (pagePath) {
|
||
var parts = pagePath.split('/');
|
||
var folderName = parts[parts.length - 1] || pagePath;
|
||
candidates.push({ label: '@' + folderName, value: folderName, type: 'folder' });
|
||
}
|
||
if (query) {
|
||
candidates = candidates.filter(function (c) { return c.label.toLowerCase().indexOf(query.toLowerCase()) >= 0; });
|
||
}
|
||
var existingMenu = document.querySelector('.wolai-page-ai-pi-lab-mention-menu');
|
||
if (existingMenu) existingMenu.remove();
|
||
if (!candidates.length) return;
|
||
var menu = document.createElement('div');
|
||
menu.className = 'wolai-page-ai-pi-lab-mention-menu';
|
||
menu.style.cssText = 'position:fixed;bottom:100px;left:50%;transform:translateX(-50%);background:#fff;border:1px solid #e0e0dc;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.1);padding:4px 0;z-index:10000;max-height:160px;overflow-y:auto;min-width:200px;font:13px/1.5 system-ui';
|
||
candidates.forEach(function (cand) {
|
||
var item = document.createElement('button');
|
||
item.type = 'button';
|
||
item.style.cssText = 'display:block;width:100%;text-align:left;padding:6px 14px;border:none;background:none;cursor:pointer;color:#333';
|
||
item.textContent = cand.label;
|
||
item.addEventListener('click', function () {
|
||
var before = text.slice(0, atIndex);
|
||
var rest = text.slice(atIndex + 1 + query.length);
|
||
inputEl.value = before + '@' + cand.value + ' ' + rest;
|
||
inputEl.focus();
|
||
menu.remove();
|
||
updateButtons();
|
||
savePiLabDraft();
|
||
});
|
||
item.addEventListener('mouseenter', function () { item.style.background = '#f0f0ed'; });
|
||
item.addEventListener('mouseleave', function () { item.style.background = 'none'; });
|
||
menu.appendChild(item);
|
||
});
|
||
document.body.appendChild(menu);
|
||
}
|
||
|
||
|
||
function connectEventSource(sessionId) {
|
||
if (piLabEventSource) {
|
||
piLabEventSource.close();
|
||
piLabEventSource = null;
|
||
}
|
||
var sid = sessionId || piLabState.sessionId;
|
||
if (!sid) return;
|
||
var es = new EventSource(API.EVENTS + '?sessionId=' + encodeURIComponent(sid), { withCredentials: true });
|
||
es.addEventListener('connected', function () { updateDiagnostics('SSE connected'); });
|
||
es.addEventListener('runtime_started', function (e) {
|
||
try {
|
||
var data = JSON.parse(e.data);
|
||
var payload = data.payload || data;
|
||
piLabState.runtimePid = payload.pid || piLabState.runtimePid;
|
||
piLabState.runtimeMode = payload.runtimeMode || piLabState.runtimeMode;
|
||
piLabState.managedBuiltinTools = payload.managedBuiltinTools || payload.managedPiBuiltinTools || piLabState.managedBuiltinTools;
|
||
piLabState.advancedRuntime = payload.advancedRuntime || piLabState.advancedRuntime || {};
|
||
piLabState.piExtensionSources = payload.piExtensionSources || piLabState.piExtensionSources || [];
|
||
piLabState.piExtensionToolNames = payload.piExtensionToolNames || piLabState.piExtensionToolNames || [];
|
||
setState(STATE_STARTED);
|
||
updateDiagnostics('runtime_started');
|
||
} catch (_) {}
|
||
});
|
||
es.addEventListener('pi_rpc_event', function (e) {
|
||
try {
|
||
var data = JSON.parse(e.data);
|
||
handlePiRpcEvent(data.payload || data);
|
||
} catch (_) {}
|
||
});
|
||
es.addEventListener('tool_call', function (e) {
|
||
try {
|
||
var data = JSON.parse(e.data);
|
||
var payload = data.payload || data;
|
||
handleToolCallEventPayload(payload);
|
||
} catch (_) {}
|
||
});
|
||
es.addEventListener('runtime_aborted', function (e) {
|
||
var payload = { reason: '已中止', stopReason: 'aborted' };
|
||
try {
|
||
var data = JSON.parse(e.data);
|
||
payload = Object.assign(payload, data.payload || data || {});
|
||
} catch (_) {}
|
||
if (piLabEventSource === es) {
|
||
es.close();
|
||
piLabEventSource = null;
|
||
}
|
||
applyPiRunEvent({ type: 'assistant_abort', payload: payload });
|
||
setState(STATE_ABORTED);
|
||
updateMessages();
|
||
});
|
||
es.addEventListener('runtime_stdout_closed', function (e) {
|
||
var payload = { message: 'Pi Rust runtime stdout closed' };
|
||
try {
|
||
var data = JSON.parse(e.data);
|
||
payload = Object.assign(payload, data.payload || data || {});
|
||
} catch (_) {}
|
||
piLabState.runtimePid = null;
|
||
if (payload.duringTurn || piLabState.status === STATE_STREAMING) {
|
||
applyPiRunEvent({
|
||
type: 'assistant_error',
|
||
payload: { error: payload.message || 'Pi Rust runtime 已退出,未返回回复' },
|
||
});
|
||
setState(STATE_ERROR);
|
||
updateMessages();
|
||
} else {
|
||
setState(STATE_IDLE);
|
||
}
|
||
updateDiagnostics(payload.message || 'runtime_stdout_closed');
|
||
});
|
||
es.addEventListener('error', function () {
|
||
if (piLabEventSource && piLabEventSource.readyState === EventSource.CLOSED) updateDiagnostics('SSE connection closed');
|
||
});
|
||
piLabEventSource = es;
|
||
}
|
||
|
||
function ensureStreamingAssistant() {
|
||
if (!streamingAssistantMsg) applyPiRunEvent({ type: 'assistant_begin' });
|
||
setState(STATE_STREAMING);
|
||
return streamingAssistantMsg;
|
||
}
|
||
|
||
function extractVisibleAssistantText(message) {
|
||
if (!message || !Array.isArray(message.content)) return '';
|
||
return message.content
|
||
.filter(function (part) { return part && part.type === 'text' && typeof part.text === 'string'; })
|
||
.map(function (part) { return part.text; })
|
||
.join('');
|
||
}
|
||
|
||
function currentTurnToolCalls() {
|
||
var current = streamingAssistantMsg || (piRunViewModel && piRunViewModel.currentAssistant);
|
||
return current && Array.isArray(current.toolCalls) ? current.toolCalls : [];
|
||
}
|
||
|
||
function finishEmptyAssistantTurn(message) {
|
||
if (!message || message.role !== 'assistant') return false;
|
||
var content = Array.isArray(message.content) ? message.content : [];
|
||
if (content.some(function (part) { return part && (part.type === 'text' || part.type === 'toolCall'); })) return false;
|
||
var toolCalls = currentTurnToolCalls();
|
||
if (toolCalls.length > 0) {
|
||
var failed = toolCalls.filter(function (tool) { return tool && (tool.status === 'error' || tool.isError === true); });
|
||
var labels = toolCalls.map(function (tool) {
|
||
var name = String((tool && (tool.toolName || tool.name)) || 'tool');
|
||
return name + (tool && (tool.status === 'error' || tool.isError === true) ? '(失败)' : '(完成)');
|
||
});
|
||
var summary = (failed.length > 0 ? '工具检查已结束,其中有失败项:' : '工具检查已完成:') + labels.join('、') + '。';
|
||
applyPiRunEvent({ type: 'assistant_done', text: summary });
|
||
setState(STATE_STARTED);
|
||
updateMessages();
|
||
updateDiagnostics('Pi assistant completed with tool-only result');
|
||
return true;
|
||
}
|
||
applyPiRunEvent({
|
||
type: 'assistant_error',
|
||
payload: {
|
||
error: 'Pi runtime 返回了空回复;请新建会话后重试。',
|
||
},
|
||
});
|
||
setState(STATE_ERROR);
|
||
updateMessages();
|
||
updateDiagnostics('Pi assistant turn ended without text or tool calls');
|
||
return true;
|
||
}
|
||
|
||
function handlePiMessageEvent(payload) {
|
||
var message = payload && payload.message;
|
||
if (!message || !message.role) return false;
|
||
if (message.role === 'assistant') {
|
||
var content = Array.isArray(message.content) ? message.content : [];
|
||
var toolParts = content.filter(function (part) { return part && part.type === 'toolCall'; });
|
||
toolParts.forEach(function (part) {
|
||
upsertToolCall({
|
||
toolCallId: part.id || part.toolCallId || '',
|
||
toolName: part.name || part.toolName || 'tool',
|
||
args: part.arguments || part.args || {},
|
||
}, 'running');
|
||
});
|
||
var visibleText = extractVisibleAssistantText(message);
|
||
if (visibleText) {
|
||
applyPiRunEvent({ type: 'assistant_done', text: visibleText });
|
||
setState(STATE_STARTED);
|
||
updateMessages();
|
||
}
|
||
if (toolParts.length > 0 || !!visibleText) return true;
|
||
updateDiagnostics('Pi assistant message is empty; waiting for agent_end');
|
||
return true;
|
||
}
|
||
if (message.role === 'toolResult') {
|
||
var toolResultPayload = {
|
||
toolCallId: message.toolCallId || message.tool_call_id || '',
|
||
toolName: message.toolName || message.tool_name || 'tool',
|
||
details: message.details || {},
|
||
};
|
||
upsertToolCall(toolResultPayload, message.isError ? 'error' : 'done', {
|
||
result: {
|
||
content: message.content || [],
|
||
details: message.details || {},
|
||
},
|
||
isError: message.isError === true,
|
||
});
|
||
if (/knowledge_rag|knowledge\.rag/i.test(String(toolResultPayload.toolName || ''))) {
|
||
appendKnowledgeRagCitations(message.details || {});
|
||
updateMessages();
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function handlePiRpcEvent(payload) {
|
||
if (!payload) return;
|
||
var eventType = payload.type;
|
||
var msgData = payload.assistantMessageEvent || payload;
|
||
var msgDataType = msgData && msgData.type;
|
||
if (handlePiExtensionUiRequest(payload)) return;
|
||
if (msgDataType === 'thinking_start' || msgDataType === 'thinking_delta' || msgDataType === 'thinking_end') {
|
||
updateDiagnostics('Pi thinking event hidden from visible reply');
|
||
return;
|
||
}
|
||
if (eventType === 'queue_update') {
|
||
applyPiRunEvent({ type: 'queue_update', payload: payload });
|
||
updateQueueDisplay();
|
||
return;
|
||
}
|
||
if (eventType === 'message' && handlePiMessageEvent(payload)) return;
|
||
if (msgDataType === 'text_start') {
|
||
setState(STATE_STREAMING);
|
||
applyPiRunEvent({ type: 'assistant_begin' });
|
||
updateMessages();
|
||
return;
|
||
}
|
||
if (msgDataType === 'text_delta') {
|
||
applyPiRunEvent({ type: 'assistant_delta', payload: msgData });
|
||
updateMessages();
|
||
} else if (msgDataType === 'text_end') {
|
||
applyPiRunEvent({ type: 'assistant_text_end', payload: msgData });
|
||
updateMessages();
|
||
} else if (eventType === 'message_end' && payload.message && payload.message.role === 'toolResult') {
|
||
handlePiMessageEvent(payload);
|
||
} else if (eventType === 'message_end' && payload.message && payload.message.role === 'assistant') {
|
||
var visibleText = extractVisibleAssistantText(payload.message);
|
||
if (!visibleText) {
|
||
updateDiagnostics('Pi assistant message_end has no text; waiting for agent_end');
|
||
return;
|
||
}
|
||
applyPiRunEvent({ type: 'assistant_done', text: visibleText });
|
||
setState(STATE_STARTED);
|
||
updateMessages();
|
||
} else if (eventType === 'agent_end') {
|
||
var finalMessages = Array.isArray(payload.messages) ? payload.messages : [];
|
||
finalMessages.forEach(function (message) {
|
||
if (
|
||
message
|
||
&& message.role === 'toolResult'
|
||
&& /knowledge_rag|knowledge\.rag/i.test(String(message.toolName || message.tool_name || ''))
|
||
) {
|
||
appendKnowledgeRagCitations(message.details || {});
|
||
}
|
||
});
|
||
var finalAssistant = finalMessages.slice().reverse().find(function (message) { return message && message.role === 'assistant'; });
|
||
var finalText = extractVisibleAssistantText(finalAssistant);
|
||
var lastVisibleAssistant = piLabState.messages.slice().reverse().find(function (message) { return message && message.role === 'assistant'; });
|
||
if (!streamingAssistantMsg && lastVisibleAssistant && lastVisibleAssistant.status === 'done') {
|
||
if (finalText && !lastVisibleAssistant.text) {
|
||
lastVisibleAssistant.text = finalText;
|
||
updateMessages();
|
||
}
|
||
setState(STATE_STARTED);
|
||
return;
|
||
}
|
||
if (!finalText && finalAssistant && finishEmptyAssistantTurn(finalAssistant)) return;
|
||
applyPiRunEvent({ type: 'assistant_done', text: finalText });
|
||
setState(STATE_STARTED);
|
||
updateMessages();
|
||
} else if (eventType === 'tool_execution_start' || eventType === 'tool_call_start' || msgDataType === 'tool_call_start' || msgDataType === 'toolcall_start') {
|
||
var startToolPayload = eventType === 'tool_execution_start' || eventType === 'tool_call_start' ? payload : assistantToolCallPayload(msgData);
|
||
if (startToolPayload) upsertToolCall(startToolPayload, 'running');
|
||
} else if (eventType === 'tool_execution_update') {
|
||
upsertToolCall(payload, 'running', { partialResult: payload.partialResult || payload.result || null });
|
||
} else if (eventType === 'tool_execution_end' || eventType === 'tool_call_end' || msgDataType === 'tool_call_end' || msgDataType === 'toolcall_end') {
|
||
var endToolPayload = eventType === 'tool_execution_end' || eventType === 'tool_call_end' ? payload : assistantToolCallPayload(msgData);
|
||
if (endToolPayload) {
|
||
upsertToolCall(endToolPayload, payload.isError ? 'error' : 'done', {
|
||
result: payload.result || msgData.result || endToolPayload.result || {},
|
||
isError: payload.isError === true,
|
||
});
|
||
}
|
||
} else if (msgDataType === 'toolcall_delta') {
|
||
var deltaToolPayload = assistantToolCallPayload(msgData);
|
||
if (deltaToolPayload) upsertToolCall(deltaToolPayload, 'running', { args: deltaToolPayload.args || {} });
|
||
} else if (eventType === 'response' && payload.command === 'abort') {
|
||
applyPiRunEvent({ type: 'assistant_abort', payload: payload });
|
||
setState(STATE_ABORTED);
|
||
updateMessages();
|
||
} else if (eventType === 'citation' || msgDataType === 'citation') {
|
||
applyPiRunEvent({ type: 'citation', payload: { source: payload.source || msgData.source || '', title: payload.title || msgData.title || '', url: payload.url || msgData.url || '#' } });
|
||
updateMessages();
|
||
} else if (eventType === 'diff' || msgDataType === 'diff') {
|
||
var diffFiles = payload.files || msgData.files || [];
|
||
var diffToolEventId = payload.toolEventId || msgData.toolEventId || '';
|
||
applyPiRunEvent({ type: 'diff', payload: { files: diffFiles, toolEventId: diffToolEventId } });
|
||
diffFiles.forEach(function (file) { if (file) piLabState.changedFiles.push(file); });
|
||
if (diffToolEventId) { piLabState.currentToolEventId = diffToolEventId; }
|
||
updateMessages();
|
||
} else if (eventType === 'done' || msgDataType === 'done') {
|
||
applyPiRunEvent({
|
||
type: 'assistant_done',
|
||
payload: {
|
||
text: msgData.text,
|
||
toolCalls: msgData.toolCalls,
|
||
citations: msgData.citations,
|
||
diffSummary: msgData.diffSummary,
|
||
},
|
||
});
|
||
setState(STATE_STARTED);
|
||
updateMessages();
|
||
} else if (eventType === 'error' || msgDataType === 'error') {
|
||
applyPiRunEvent({ type: 'assistant_error', payload: { error: payload.error || msgData.message || 'Pi runtime error' } });
|
||
setState(STATE_STARTED);
|
||
updateMessages();
|
||
}
|
||
}
|
||
|
||
function sendPrompt(text, options) {
|
||
var prompt = String(text || '').trim();
|
||
if (!prompt) return;
|
||
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' });
|
||
setState(STATE_STREAMING);
|
||
updateMessages();
|
||
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;
|
||
if (piLabState.pendingImages && piLabState.pendingImages.length) {
|
||
body.images = piLabState.pendingImages;
|
||
piLabState.pendingImages = [];
|
||
}
|
||
return fetch(API.SEND, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body),
|
||
})
|
||
.then(function (r) {
|
||
return responseJsonOrError(r, 'Send failed');
|
||
})
|
||
.then(function (data) {
|
||
if (!data.accepted) throw new Error('Pi Lab rejected prompt');
|
||
if (!piLabEventSource || piLabEventSource.readyState === EventSource.CLOSED) connectEventSource(data.sessionId || piLabState.sessionId);
|
||
})
|
||
.catch(function (err) {
|
||
if (streamingAssistantMsg) applyPiRunEvent({ type: 'assistant_error', message: err.message });
|
||
setState(STATE_STARTED);
|
||
updateMessages();
|
||
});
|
||
}
|
||
|
||
function ensureRuntimeReady() {
|
||
if (piLabState.status === STATE_STARTED && piLabState.sessionId) {
|
||
return Promise.resolve({ ok: true, session: piLabState.session });
|
||
}
|
||
if (piLabState.status === STATE_STREAMING && piLabState.sessionId) {
|
||
return Promise.resolve({ ok: true, session: piLabState.session });
|
||
}
|
||
if (piLabState.status === STATE_STARTING || piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR || !piLabState.sessionId) {
|
||
return startRuntime();
|
||
}
|
||
return Promise.resolve({ ok: true });
|
||
}
|
||
|
||
function callMNoteTool(toolName, params) {
|
||
return ensureRuntimeReady().then(function () {
|
||
return fetch('/api/page-ai/pi/tool-call', {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json', accept: 'application/json' },
|
||
body: JSON.stringify({
|
||
sessionId: piLabState.sessionId,
|
||
toolName: toolName,
|
||
params: params || {},
|
||
}),
|
||
});
|
||
}).then(function (response) {
|
||
return response.json().catch(function () { return {}; }).then(function (payload) {
|
||
if (!response.ok) throw new Error(payload.message || ('Tool call failed: ' + response.status));
|
||
if (payload && payload.ok === false) {
|
||
var result = payload.result || {};
|
||
throw new Error(result.message || payload.message || result.code || 'Tool call failed');
|
||
}
|
||
return payload;
|
||
});
|
||
});
|
||
}
|
||
|
||
function emitLocalFolderRefresh(rootUri, relativePath, source) {
|
||
var normalizedPath = normalizeSlashes(relativePath).replace(/^\/+/, '');
|
||
if (!rootUri || !normalizedPath) return false;
|
||
var documentId = /\.md(?:own)?$/i.test(normalizedPath) ? 'local-md:' + normalizedPath.replace(/\//g, '~2F') : '';
|
||
if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitChangedFiles === 'function') {
|
||
window.__mnoteLocalFolderEventBus.emitChangedFiles({
|
||
source: source || 'pi_lab_tool_call',
|
||
reason: source || 'pi_lab_tool_call',
|
||
rootUri: rootUri,
|
||
workspaceId: (piLabState.session && piLabState.session.workspaceId) || currentWorkspaceId(),
|
||
changedFiles: [{
|
||
relativePath: normalizedPath,
|
||
documentId: documentId,
|
||
changeType: 'modified',
|
||
}],
|
||
affectedParents: [{
|
||
relativePath: normalizedPath.indexOf('/') >= 0 ? normalizedPath.split('/').slice(0, -1).join('/') : '',
|
||
reason: source || 'pi_lab_tool_call',
|
||
}],
|
||
});
|
||
return true;
|
||
}
|
||
window.dispatchEvent(new CustomEvent('tree:local-folder-watch-batch', {
|
||
detail: {
|
||
payload: {
|
||
schema: 'mnote.local_folder.watch_batch.v1',
|
||
source: source || 'pi_lab_tool_call',
|
||
rootUri: rootUri,
|
||
changedPaths: [{ relativePath: normalizedPath, documentId: documentId, changeType: 'modified' }],
|
||
},
|
||
},
|
||
}));
|
||
return true;
|
||
}
|
||
|
||
function refreshPatchedCurrentDocument(rootUri, relativePath, source) {
|
||
emitLocalFolderRefresh(rootUri, relativePath, source || 'pi_lab_patch');
|
||
var documentId = /\.md(?:own)?$/i.test(String(relativePath || '')) ? 'local-md:' + normalizeSlashes(relativePath).replace(/^\/+/, '').replace(/\//g, '~2F') : currentDocumentId();
|
||
var api = window.__mnoteDocumentPaneRuntime;
|
||
if (api && typeof api.refreshDocument === 'function') {
|
||
api.refreshDocument({
|
||
documentId: documentId,
|
||
workspaceId: (piLabState.session && piLabState.session.workspaceId) || currentWorkspaceId(),
|
||
source: source || 'pi_lab_patch',
|
||
}).catch(function () {});
|
||
}
|
||
}
|
||
|
||
function applyToolCallSideEffects(payload) {
|
||
if (!payload || payload.ok !== true) return;
|
||
var result = payload.result || {};
|
||
if (payload.toolName === 'mnote.local_file.patch' || result.diffSummary) {
|
||
var rootUri = String(result.rootUri || (piLabState.session && piLabState.session.rootUri) || currentRootUri() || '').trim();
|
||
var relativePath = String(result.relativePath || '').trim();
|
||
if (!relativePath && result.path) relativePath = result.path;
|
||
if (relativePath) refreshPatchedCurrentDocument(rootUri, relativePath, 'pi_lab_local_file_patch');
|
||
}
|
||
}
|
||
|
||
function setComposerText(text) {
|
||
if (!piLabPanelEl) return;
|
||
var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
|
||
if (!input) return;
|
||
input.value = text || '';
|
||
updateButtons();
|
||
input.focus();
|
||
}
|
||
|
||
function handleQuickAction(kind) {
|
||
var context = applyCurrentContextToState();
|
||
if (kind === 'read-page') {
|
||
if (!context.rootUri || !context.pagePath) {
|
||
showPiToast('当前页未绑定文件上下文', 'warning');
|
||
updateDiagnostics('当前页缺少 rootUri/pagePath,无法作为 Pi 上下文');
|
||
return;
|
||
}
|
||
toggleContextQuick(kind);
|
||
showPiToast((contextSelectionState().currentPage ? '已选择' : '已取消') + '当前页上下文', 'info');
|
||
return;
|
||
}
|
||
if (kind === 'current-folder') {
|
||
if (!context.rootUri || currentFolderPathFromPagePath(context.pagePath) === null) {
|
||
showPiToast('当前页没有可用文件夹上下文', 'warning');
|
||
updateDiagnostics('当前页缺少 rootUri/folderPath,无法作为 Pi 文件夹上下文');
|
||
return;
|
||
}
|
||
toggleContextQuick(kind);
|
||
showPiToast((contextSelectionState().currentFolder ? '已选择' : '已取消') + '当前文件夹上下文', 'info');
|
||
return;
|
||
}
|
||
if (kind === 'selection') {
|
||
var selection = refreshSelectionSummary();
|
||
if (!selection && !contextSelectionState().selection) showPiToast('当前没有选区', 'warning');
|
||
toggleContextQuick(kind);
|
||
return;
|
||
}
|
||
toggleContextQuick('rag');
|
||
showPiToast((contextSelectionState().lightrag ? '已启用' : '已关闭') + ' LightRAG 上下文', 'info');
|
||
}
|
||
|
||
function sendCurrentInput(options) {
|
||
if (!piLabPanelEl) return;
|
||
var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
|
||
var text = input ? input.value.trim() : '';
|
||
if (!text) return;
|
||
var startToast = piLabState.warmupRunning
|
||
? '正在绑定当前页 Pi 会话,完成后自动发送'
|
||
: 'Pi Rust 正在启动,启动后自动发送';
|
||
if (piLabState.status === STATE_IDLE || piLabState.status === STATE_ABORTED || piLabState.status === STATE_ERROR || !piLabState.sessionId) {
|
||
showPiToast(startToast, 'info');
|
||
ensureRuntimeReady().then(function () {
|
||
if (!piLabState.sessionId) throw new Error('Pi Rust 会话尚未就绪');
|
||
if (input) input.value = '';
|
||
updateButtons();
|
||
return sendPrompt(text, { streamingBehavior: undefined });
|
||
}).catch(function (err) {
|
||
showPiToast(err && err.message ? err.message : 'Pi Rust 启动失败', 'warning');
|
||
updateDiagnostics(err && err.message ? err.message : 'Pi Rust 启动失败');
|
||
updateButtons();
|
||
});
|
||
return;
|
||
}
|
||
if (piLabState.status === STATE_STARTING) {
|
||
showPiToast(startToast, 'info');
|
||
ensureRuntimeReady().then(function () {
|
||
if (!piLabState.sessionId) throw new Error('Pi Rust 会话尚未就绪');
|
||
if (input) input.value = '';
|
||
updateButtons();
|
||
return sendPrompt(text, { streamingBehavior: undefined });
|
||
}).catch(function (err) {
|
||
showPiToast(err && err.message ? err.message : 'Pi Rust 启动失败', 'warning');
|
||
updateDiagnostics(err && err.message ? err.message : 'Pi Rust 启动失败');
|
||
updateButtons();
|
||
});
|
||
return;
|
||
}
|
||
if (input) {
|
||
input.value = '';
|
||
clearPiLabDraft();
|
||
}
|
||
updateButtons();
|
||
var isMidStream = piLabState.status === STATE_STREAMING;
|
||
var streamingBehavior = isMidStream ? ((options && options.streamingBehavior) || 'steer') : undefined;
|
||
sendPrompt(text, { streamingBehavior: streamingBehavior }).catch(function () {});
|
||
}
|
||
|
||
function abortPrompt() {
|
||
var piInput = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
|
||
if (piInput && piInput.value.trim()) savePiLabDraft();
|
||
if (piLabEventSource) {
|
||
piLabEventSource.close();
|
||
piLabEventSource = null;
|
||
}
|
||
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();
|
||
if (piLabEventSource) {
|
||
piLabEventSource.close();
|
||
piLabEventSource = null;
|
||
}
|
||
if (previousSessionId && !piLabState.viewingHistorySessionId && (piLabState.status === STATE_STARTED || piLabState.status === STATE_STREAMING || piLabState.status === STATE_STARTING)) {
|
||
fetch(API.ABORT, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ sessionId: previousSessionId }),
|
||
}).catch(function () {});
|
||
}
|
||
closeActionMenu();
|
||
setHistoryPanelVisible(false);
|
||
exitHistoryReplayMode();
|
||
piLabState.sessionId = null;
|
||
piLabState.providerSessionId = null;
|
||
piLabState.session = {
|
||
rootUri: preservedContext.rootUri || undefined,
|
||
workspaceId: preservedContext.workspaceId || undefined,
|
||
pagePath: preservedContext.pagePath || undefined,
|
||
pageTitle: preservedContext.pageTitle || undefined,
|
||
};
|
||
piLabState.contextSelection = {
|
||
currentPage: false,
|
||
currentFolder: false,
|
||
selection: false,
|
||
lightrag: false,
|
||
};
|
||
piLabState.thinkingLevel = normalizeThinkingLevel(piLabState.defaultThinkingLevel || DEFAULT_THINKING_LEVEL);
|
||
piLabState.status = STATE_IDLE;
|
||
resetPiRunViewModel();
|
||
piLabState.receipts = [];
|
||
updateMessages();
|
||
updateReceiptDisplay();
|
||
updateQueueDisplay();
|
||
updateThinkingControl();
|
||
updateConfig();
|
||
}
|
||
|
||
function triggerCompact() {
|
||
if (piLabState.status === STATE_STREAMING) return;
|
||
if (piLabState.viewingHistorySessionId) return;
|
||
if (!piLabState.sessionId) return;
|
||
piLabState.isCompacting = true;
|
||
updateQueueDisplay();
|
||
fetch(API.COMPACT, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ sessionId: piLabState.sessionId }),
|
||
}).then(function (r) { return r.json(); }).then(function (data) {
|
||
piLabState.isCompacting = false;
|
||
if (data && data.summary) {
|
||
updateDiagnostics('Compaction: ' + data.summary);
|
||
showPiToast('会话压缩完成', 'success');
|
||
} else if (data && data.message) {
|
||
showPiToast(data.message, 'info');
|
||
} else {
|
||
showPiToast('会话压缩完成', 'success');
|
||
}
|
||
if (data && data.messages && data.messages.length) {
|
||
var compactCard = { role: 'system', text: '会话压缩已完成,共 ' + data.messages.length + ' 条消息', type: 'compaction', 'data-page-ai-pi-lab-compaction-result': 'true' };
|
||
piLabState.messages.push(compactCard);
|
||
updateMessages();
|
||
}
|
||
syncRuntimeState();
|
||
}).catch(function (err) {
|
||
piLabState.isCompacting = false;
|
||
showPiToast('压缩失败:' + (err.message || ''), 'error');
|
||
syncRuntimeState();
|
||
});
|
||
}
|
||
|
||
|
||
|
||
function parseUnifiedPatch(patchText) {
|
||
if (!patchText) return [];
|
||
var files = [];
|
||
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');
|
||
});
|
||
}
|
||
|
||
|
||
function ensurePanel() {
|
||
if (piLabPanelEl) return piLabPanelEl;
|
||
var container = document.createElement('div');
|
||
container.innerHTML = piLabPanelHTML();
|
||
piLabPanelEl = container.firstElementChild;
|
||
wireEvents();
|
||
updateReceiptDisplay();
|
||
updateArtifactRail();
|
||
updateMessages();
|
||
return piLabPanelEl;
|
||
}
|
||
|
||
function summarizeAllowedRoots(snapshot) {
|
||
if (!snapshot) return piLabState.allowedRootsSummary || 'pending';
|
||
var roots = Array.isArray(snapshot.roots) ? snapshot.roots : (Array.isArray(snapshot) ? snapshot : []);
|
||
if (!roots.length) return '0 roots';
|
||
return roots.length + ' root' + (roots.length === 1 ? '' : 's');
|
||
}
|
||
|
||
function ensureDrawer() {
|
||
if (piLabDrawerEl) return piLabDrawerEl;
|
||
piLabDrawerEl = el('section', {
|
||
className: 'wolai-page-ai-pi-lab-drawer',
|
||
'data-page-ai-pi-lab': 'drawer',
|
||
'data-page-ai-pi-lab-drawer': 'true',
|
||
'aria-label': 'Pi Lab',
|
||
hidden: 'true',
|
||
});
|
||
piLabDrawerEl.appendChild(ensurePanel());
|
||
document.body.appendChild(piLabDrawerEl);
|
||
return piLabDrawerEl;
|
||
}
|
||
|
||
function setDrawerVisible(visible) {
|
||
var drawer = ensureDrawer();
|
||
drawer.hidden = !visible;
|
||
drawer.setAttribute('data-open', visible ? 'true' : 'false');
|
||
if (visible) drawer.setAttribute('data-minimized', 'false');
|
||
}
|
||
|
||
function showPiLab() {
|
||
piLabState.active = true;
|
||
setDrawerVisible(true);
|
||
applyCurrentContextToState();
|
||
updateConfig();
|
||
checkStatus().then(function () {
|
||
if (piLabState.enabled && piLabState.runtimeAvailable !== false && !piLabState.sessionId && piLabState.status !== STATE_STARTING) {
|
||
return startRuntime().then(function () {
|
||
syncRuntimeState();
|
||
});
|
||
}
|
||
syncRuntimeState();
|
||
updateButtons();
|
||
return null;
|
||
}).then(function () {
|
||
updateButtons();
|
||
}).catch(function (error) {
|
||
updateDiagnostics(error && error.message ? error.message : 'Pi current page session prepare failed');
|
||
updateButtons();
|
||
});
|
||
var input = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
|
||
if (input) {
|
||
restorePiLabDraft();
|
||
setTimeout(function () { input.focus(); }, 60);
|
||
}
|
||
}
|
||
|
||
function hidePiLab() {
|
||
piLabState.active = false;
|
||
closeComposerControlMenus();
|
||
if (piLabDrawerEl) setDrawerVisible(false);
|
||
updateLauncherState();
|
||
}
|
||
|
||
function updateLauncherState() {
|
||
if (!piLabLauncherEl) return;
|
||
piLabLauncherEl.setAttribute('data-state', piLabState.status);
|
||
piLabLauncherEl.hidden = !!piLabState.active;
|
||
}
|
||
|
||
function installLauncher() {
|
||
if (piLabLauncherEl) return;
|
||
var hostButton = document.querySelector('[data-mnote-action="open-page-ai-pi-lab"], [data-testid="wolai-floating-ai"]');
|
||
if (hostButton instanceof HTMLElement) {
|
||
piLabLauncherEl = hostButton;
|
||
piLabLauncherEl.setAttribute('data-page-ai-pi-lab-launcher', 'true');
|
||
piLabLauncherEl.setAttribute('data-mnote-action', 'open-page-ai-pi-lab');
|
||
piLabLauncherEl.setAttribute('aria-label', '打开 Pi Lab');
|
||
piLabLauncherEl.setAttribute('title', '打开 Pi Lab');
|
||
piLabLauncherEl.textContent = 'π';
|
||
} else {
|
||
piLabLauncherEl = el('button', {
|
||
type: 'button',
|
||
className: 'wolai-page-ai-pi-lab-launcher',
|
||
'data-page-ai-pi-lab-launcher': 'true',
|
||
title: 'Pi Lab',
|
||
'aria-label': '打开 Pi Lab',
|
||
}, 'π');
|
||
document.body.appendChild(piLabLauncherEl);
|
||
}
|
||
piLabLauncherEl.addEventListener('click', function (event) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
showPiLab();
|
||
});
|
||
}
|
||
|
||
function wireEvents() {
|
||
if (!piLabPanelEl || piLabPanelEl.getAttribute('data-wired') === 'true') return;
|
||
piLabPanelEl.setAttribute('data-wired', 'true');
|
||
var input = piLabPanelEl.querySelector('[data-page-ai-pi-lab-input]');
|
||
var sendBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-send]');
|
||
var abortBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-abort]');
|
||
var clearBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-clear]');
|
||
var closeBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-close]');
|
||
var minimizeBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-minimize]');
|
||
var providerSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-provider]');
|
||
var modelSelect = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-id]');
|
||
var customInput = piLabPanelEl.querySelector('[data-page-ai-pi-lab-model-custom]');
|
||
var settingsBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-open-settings]');
|
||
var historyBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history]');
|
||
var compactBtn = piLabPanelEl.querySelector('[data-page-ai-pi-lab-btn-compact]');
|
||
var newBtns = piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-new]');
|
||
if (sendBtn) sendBtn.addEventListener('click', sendCurrentInput);
|
||
if (abortBtn) abortBtn.addEventListener('click', abortPrompt);
|
||
if (compactBtn) compactBtn.addEventListener('click', triggerCompact);
|
||
if (clearBtn) clearBtn.addEventListener('click', function () {
|
||
resetPiRunViewModel();
|
||
piLabState.receipts = [];
|
||
updateMessages();
|
||
updateReceiptDisplay();
|
||
updateQueueDisplay();
|
||
});
|
||
if (closeBtn) closeBtn.addEventListener('click', hidePiLab);
|
||
if (minimizeBtn) {
|
||
minimizeBtn.addEventListener('click', function () {
|
||
var drawer = ensureDrawer();
|
||
var minimized = drawer.getAttribute('data-minimized') === 'true';
|
||
drawer.setAttribute('data-minimized', minimized ? 'false' : 'true');
|
||
});
|
||
}
|
||
if (input) {
|
||
input.addEventListener('input', function () {
|
||
updateButtons();
|
||
handleAtMention(input);
|
||
savePiLabDraft();
|
||
});
|
||
input.addEventListener('keydown', function (e) {
|
||
if (e.isComposing || e.key === 'Process') return;
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
sendCurrentInput(e.altKey ? { streamingBehavior: 'followUp' } : undefined);
|
||
}
|
||
if (e.key === 'Escape' && piLabState.status === STATE_STREAMING) {
|
||
e.preventDefault();
|
||
abortPrompt();
|
||
}
|
||
});
|
||
}
|
||
[providerSelect, modelSelect].forEach(function (node) {
|
||
if (node) node.addEventListener('change', function () {
|
||
applyModelControls();
|
||
applyModelConfigToRuntime();
|
||
});
|
||
});
|
||
if (customInput) customInput.addEventListener('input', applyModelControls);
|
||
if (settingsBtn) {
|
||
settingsBtn.addEventListener('click', function () {
|
||
window.location.assign('/user/ai#ai-admin-access');
|
||
});
|
||
}
|
||
if (historyBtn) {
|
||
historyBtn.addEventListener('click', function () {
|
||
var layer = piLabPanelEl.querySelector('[data-page-ai-pi-lab-history-layer]');
|
||
if (!layer) return;
|
||
setHistoryPanelVisible(layer.hidden);
|
||
});
|
||
}
|
||
Array.prototype.slice.call(newBtns || []).forEach(function (btn) {
|
||
btn.addEventListener('click', startNewConversation);
|
||
});
|
||
piLabPanelEl.addEventListener('click', function (event) {
|
||
var historyNew = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-new-small]') : null;
|
||
if (historyNew) {
|
||
startNewConversation();
|
||
return;
|
||
}
|
||
var actionMenuToggle = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-action-menu-toggle]') : null;
|
||
if (actionMenuToggle) {
|
||
setActionMenuOpen(!piLabState.actionMenuOpen);
|
||
return;
|
||
}
|
||
var modelMenuToggle = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-model-menu-toggle]') : null;
|
||
if (modelMenuToggle && !modelMenuToggle.disabled) {
|
||
setModelMenuOpen(!piLabState.modelMenuOpen);
|
||
return;
|
||
}
|
||
var modelOption = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-model-option]') : null;
|
||
if (modelOption && !modelOption.disabled) {
|
||
selectModelControl(
|
||
modelOption.getAttribute('data-provider'),
|
||
modelOption.getAttribute('data-model-id')
|
||
);
|
||
return;
|
||
}
|
||
var thinkingMenuToggle = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-thinking-menu-toggle]') : null;
|
||
if (thinkingMenuToggle && !thinkingMenuToggle.disabled) {
|
||
setThinkingMenuOpen(!piLabState.thinkingMenuOpen);
|
||
return;
|
||
}
|
||
var thinkingOption = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-thinking-option]') : null;
|
||
if (thinkingOption && !thinkingOption.disabled) {
|
||
selectThinkingControl(thinkingOption.getAttribute('data-page-ai-pi-lab-thinking-option'));
|
||
return;
|
||
}
|
||
var menuAction = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-menu-action]') : null;
|
||
if (menuAction && !menuAction.disabled) {
|
||
handleActionMenuAction(menuAction.getAttribute('data-page-ai-pi-lab-menu-action'));
|
||
return;
|
||
}
|
||
var permissionBtn = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-permission]') : null;
|
||
if (permissionBtn) {
|
||
closeActionMenu();
|
||
setPermissionMenuOpen(!piLabState.permissionMenuOpen);
|
||
return;
|
||
}
|
||
var permissionMode = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-permission-mode]') : null;
|
||
if (permissionMode) {
|
||
selectPermissionMode(permissionMode.getAttribute('data-page-ai-pi-lab-permission-mode'));
|
||
return;
|
||
}
|
||
var closeHistory = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-close], [data-page-ai-pi-lab-history-scrim]') : null;
|
||
if (closeHistory) {
|
||
setHistoryPanelVisible(false);
|
||
return;
|
||
}
|
||
var refreshHistory = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-refresh]') : null;
|
||
if (refreshHistory) {
|
||
loadHistory().catch(function (error) {
|
||
updateDiagnostics(error.message || '历史刷新失败');
|
||
showPiToast('历史刷新失败', 'error');
|
||
});
|
||
return;
|
||
}
|
||
var clearHistory = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-clear]') : null;
|
||
if (clearHistory && !clearHistory.disabled) {
|
||
clearHistorySessions().catch(function (error) {
|
||
updateDiagnostics(error.message || '历史清空失败');
|
||
showPiToast('历史清空失败', 'error');
|
||
});
|
||
return;
|
||
}
|
||
var forkCurrent = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-fork-current]') : null;
|
||
if (forkCurrent) {
|
||
forkHistorySession(piLabState.viewingHistorySessionId || piLabState.sessionId).catch(function (error) {
|
||
updateDiagnostics(error.message || '历史会话 fork 失败');
|
||
showPiToast('Fork failed', 'error');
|
||
});
|
||
return;
|
||
|
||
var forkEntry = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-fork-entry]') : null;
|
||
if (forkEntry) {
|
||
var forkEntryId = forkEntry.getAttribute('data-page-ai-pi-lab-fork-entry');
|
||
forkHistoryEntry(piLabState.viewingHistorySessionId, forkEntryId).catch(function (error) {
|
||
updateDiagnostics(error.message || 'fork 失败');
|
||
showPiToast('Fork failed', 'error');
|
||
});
|
||
return;
|
||
}
|
||
|
||
}
|
||
var closeArtifacts = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-close-artifacts]') : null;
|
||
if (closeArtifacts) {
|
||
var closeBody = piLabPanelEl.querySelector('[data-page-ai-pi-lab-body]');
|
||
if (closeBody) closeBody.setAttribute('data-rail-open', 'false');
|
||
return;
|
||
}
|
||
var copyMarkdown = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-copy-markdown]') : null;
|
||
if (copyMarkdown) {
|
||
copyTextToClipboard(copyMarkdown.getAttribute('data-page-ai-pi-lab-copy-markdown') || '').then(function () {
|
||
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 diagnosticCommand = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-diagnostic-command]') : null;
|
||
if (diagnosticCommand) {
|
||
runPiDiagnostic(diagnosticCommand.getAttribute('data-page-ai-pi-lab-diagnostic-command') || 'doctor');
|
||
return;
|
||
}
|
||
var openArtifact = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-artifact-open]') : null;
|
||
if (openArtifact) {
|
||
var artUrl = openArtifact.getAttribute('data-page-ai-pi-lab-artifact-open') || openArtifact.getAttribute('href') || '';
|
||
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,
|
||
},
|
||
}));
|
||
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;
|
||
}
|
||
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;
|
||
}
|
||
var historyTarget = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-history-session]') : null;
|
||
if (historyTarget) {
|
||
openHistorySession(historyTarget.getAttribute('data-page-ai-pi-lab-history-session')).catch(function (error) {
|
||
updateDiagnostics(error.message || '历史会话加载失败');
|
||
});
|
||
return;
|
||
}
|
||
var target = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-example]') : null;
|
||
if (!target) return;
|
||
setComposerText(target.getAttribute('data-page-ai-pi-lab-example') || target.textContent || '');
|
||
});
|
||
Array.prototype.slice.call(piLabPanelEl.querySelectorAll('[data-page-ai-pi-lab-quick]')).forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
handleQuickAction(btn.getAttribute('data-page-ai-pi-lab-quick'));
|
||
});
|
||
});
|
||
document.addEventListener('pointerdown', function (event) {
|
||
if (!piLabState.active || !piLabPanelEl) return;
|
||
var target = event.target;
|
||
var controlSurface = target && target.closest ? target.closest(
|
||
'[data-page-ai-pi-lab-action-menu-toggle], [data-page-ai-pi-lab-action-menu], ' +
|
||
'[data-page-ai-pi-lab-model-menu-wrap], [data-page-ai-pi-lab-thinking-menu-wrap], ' +
|
||
'[data-page-ai-pi-lab-permission-wrap]'
|
||
) : null;
|
||
if (!controlSurface) closeComposerControlMenus();
|
||
}, true);
|
||
}
|
||
|
||
function setupPostMessageListener() {
|
||
window.addEventListener('message', function (event) {
|
||
if (!event.data || event.data.source !== 'mnote-sidebar') return;
|
||
if (event.data.type === 'mnote:pi-lab-status') checkStatus();
|
||
if (event.data.type === 'mnote:pi-lab-show') showPiLab();
|
||
if (event.data.type === 'mnote:pi-lab-hide') hidePiLab();
|
||
});
|
||
}
|
||
|
||
function setupSelectionListener() {
|
||
document.addEventListener('selectionchange', function () {
|
||
if (!piLabState.active) return;
|
||
refreshSelectionSummary();
|
||
updateContextStrip();
|
||
}, true);
|
||
window.addEventListener('mnote:leptos-tiptap-spike:selection', function () {
|
||
if (!piLabState.active) return;
|
||
refreshSelectionSummary();
|
||
updateContextStrip();
|
||
}, true);
|
||
}
|
||
|
||
function createSidebarPageAiPiLabRuntime(context) {
|
||
if (piLabInstalled) return piLabState;
|
||
piLabInstalled = true;
|
||
injectStyles();
|
||
piLabState.standalone = !!(context && context.standalone);
|
||
ensureDrawer();
|
||
if (piLabState.standalone) {
|
||
setDrawerVisible(true);
|
||
piLabState.active = true;
|
||
} else {
|
||
installLauncher();
|
||
}
|
||
setupPostMessageListener();
|
||
setupSelectionListener();
|
||
updateConfig();
|
||
updateReceiptDisplay();
|
||
checkStatus();
|
||
return piLabState;
|
||
}
|
||
|
||
if (typeof window !== 'undefined') {
|
||
window.createSidebarPageAiPiLabRuntime = createSidebarPageAiPiLabRuntime;
|
||
if (window.__MNOTE_PI_LAB_TEST__ === true) window.__mnotePiLabReducerTest = {
|
||
createPiRunViewModel: createPiRunViewModel,
|
||
reducePiRunViewModel: reducePiRunViewModel,
|
||
rebuildPiRunToolIndex: rebuildPiRunToolIndex,
|
||
};
|
||
if (window.__MNOTE_PI_LAB_TEST__ === true) window.__mnotePiLabTest = {
|
||
emitRpcEvent: handlePiRpcEvent,
|
||
emitToolCall: handleToolCallEventPayload,
|
||
showToast: showPiToast,
|
||
closeDialog: closePiUiDialog,
|
||
getCurrentContext: currentPageContext,
|
||
getState: function () { return piLabState; },
|
||
};
|
||
}
|
||
})();
|