document.addEventListener('DOMContentLoaded', function () { var state = { logs: [], details: {}, level: 'all', search: '', period: '7d', openId: null, liveMode: true, timer: null, workspaceId: null, loading: false, loadError: '', }; var logList = document.getElementById('log-list'); var logSearch = document.getElementById('log-search'); var refreshBtn = document.getElementById('refresh-btn'); var timeRangeSel = document.getElementById('time-range'); var liveDot = document.querySelector('.live-dot'); var liveLabel = document.querySelector('.live-label'); function tKey(key) { return window.t ? t(key) : key; } function currentWorkspaceId() { var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; return workspace ? workspace.id : null; } function durationLabel(durationMs) { if (!durationMs) { return '0ms'; } if (durationMs >= 1000) { return (durationMs / 1000).toFixed(1) + 's'; } return durationMs + 'ms'; } function formatJson(value) { if (value === null || value === undefined) { return ''; } if (typeof value === 'string') { return value; } return JSON.stringify(value, null, 2); } function formatTime(timestamp) { var date = new Date(timestamp); return date.toISOString().slice(11, 23); } function element(tag, className, text) { var node = document.createElement(tag); if (className) node.className = className; if (text !== undefined && text !== null) node.textContent = text; return node; } function statusClass(statusCode) { if (!statusCode) { return ''; } if (statusCode < 300) { return 'ok'; } if (statusCode < 500) { return 'warn'; } return 'err'; } function normalizeLog(record) { var log = record.log; var agent = window.localizeDemoAgent ? window.localizeDemoAgent({ slug: record.agent_slug, display_name: record.agent_display_name, description: '', }) : null; var operation = window.localizeDemoOperation ? window.localizeDemoOperation({ name: record.operation_name, operation_display_name: record.operation_display_name, }) : null; return { id: log.id, createdAt: log.created_at, level: log.level, source: log.source, status: log.status, statusCode: log.status_code, durationMs: log.duration_ms, toolName: log.tool_name, message: log.message, operationName: record.operation_name, operationDisplayName: operation ? operation.operation_display_name : record.operation_display_name, agentSlug: record.agent_slug, agentDisplayName: agent ? agent.display_name : record.agent_display_name, requestPreview: log.request_preview, responsePreview: log.response_preview, errorKind: log.error_kind, requestId: log.request_id, }; } function renderEmpty(title, message) { logList.innerHTML = ''; var empty = element('div', 'empty-state'); empty.appendChild(element('div', 'empty-state-title', title)); empty.appendChild(element('div', 'empty-state-text', message)); logList.appendChild(empty); } function renderLogs() { if (state.loading && state.logs.length === 0) { renderEmpty(tKey('logs.loading.title'), tKey('logs.loading.sub')); return; } if (state.loadError) { renderEmpty(tKey('logs.error.title'), state.loadError); return; } if (!state.logs.length) { renderEmpty( tKey('logs.empty.title'), state.search || state.level !== 'all' ? tKey('logs.empty.filtered') : tKey('logs.empty.initial') ); return; } var fragment = document.createDocumentFragment(); state.logs.forEach(function (item) { var row = document.createElement('div'); row.className = 'log-entry'; row.setAttribute('data-id', item.id); var timeEl = document.createElement('span'); timeEl.className = 'log-time'; timeEl.textContent = formatTime(item.createdAt); row.appendChild(timeEl); var levelEl = document.createElement('span'); levelEl.className = 'log-level ' + item.level; levelEl.textContent = item.level.toUpperCase(); row.appendChild(levelEl); var msgDiv = document.createElement('div'); msgDiv.className = 'log-msg'; var opSpan = document.createElement('span'); opSpan.className = 'log-op-name'; opSpan.textContent = item.operationDisplayName || item.operationName; msgDiv.appendChild(opSpan); msgDiv.appendChild(document.createTextNode('\u00a0\u00a0')); if (item.statusCode) { var statusEl = document.createElement('span'); statusEl.className = 'log-status ' + statusClass(item.statusCode); statusEl.textContent = item.statusCode; msgDiv.appendChild(statusEl); msgDiv.appendChild(document.createTextNode(' \u00b7 ')); } msgDiv.appendChild(document.createTextNode(item.message)); row.appendChild(msgDiv); var durationEl = document.createElement('span'); var durationClass = item.level === 'error' ? ' error' : (item.durationMs >= 1000 ? ' slow' : ''); durationEl.className = 'log-duration' + durationClass; durationEl.textContent = durationLabel(item.durationMs); row.appendChild(durationEl); fragment.appendChild(row); var expanded = document.createElement('div'); expanded.className = 'log-entry-expanded' + (item.id === state.openId ? ' open' : ''); expanded.setAttribute('data-exp', item.id); if (item.id === state.openId) { var detail = state.details[item.id] || item; if (detail.agentDisplayName || detail.agentSlug) { var agentLabel = document.createElement('div'); agentLabel.className = 'log-detail-label'; agentLabel.textContent = tKey('logs.detail.agent'); expanded.appendChild(agentLabel); var agentPre = document.createElement('pre'); agentPre.className = 'log-detail-block'; agentPre.textContent = detail.agentDisplayName || detail.agentSlug; expanded.appendChild(agentPre); } var requestLabel = document.createElement('div'); requestLabel.className = 'log-detail-label'; requestLabel.textContent = tKey('logs.detail.request'); expanded.appendChild(requestLabel); var requestPre = document.createElement('pre'); requestPre.className = 'log-detail-block'; requestPre.textContent = formatJson(detail.requestPreview); expanded.appendChild(requestPre); var responseLabel = document.createElement('div'); responseLabel.className = 'log-detail-label'; responseLabel.textContent = tKey('logs.detail.response'); expanded.appendChild(responseLabel); var responsePre = document.createElement('pre'); responsePre.className = 'log-detail-block'; responsePre.textContent = formatJson(detail.responsePreview); expanded.appendChild(responsePre); if (detail.errorKind || detail.requestId) { var metaLabel = document.createElement('div'); metaLabel.className = 'log-detail-label'; metaLabel.textContent = tKey('logs.detail.meta'); expanded.appendChild(metaLabel); var metaPre = document.createElement('pre'); metaPre.className = 'log-detail-block'; metaPre.textContent = formatJson({ request_id: detail.requestId || null, error_kind: detail.errorKind || null, source: detail.source, status: detail.status, }); expanded.appendChild(metaPre); } } fragment.appendChild(expanded); }); logList.innerHTML = ''; logList.appendChild(fragment); logList.querySelectorAll('.log-entry').forEach(function (row) { row.addEventListener('click', async function () { var id = this.getAttribute('data-id'); state.openId = state.openId === id ? null : id; renderLogs(); if (state.openId) { await loadLogDetail(id); } }); }); } function queryParams() { var params = { period: state.period, limit: 100, }; if (state.level !== 'all') { params.level = state.level; } if (state.search) { params.search = state.search; } return params; } async function loadLogs() { if (!window.CrankApi) { state.loadError = tKey('logs.error.api'); renderLogs(); return; } state.workspaceId = currentWorkspaceId(); if (!state.workspaceId) { state.loadError = tKey('logs.error.workspace'); renderLogs(); return; } state.loading = true; state.loadError = ''; renderLogs(); try { var response = await window.CrankApi.listLogs(state.workspaceId, queryParams()); state.logs = (response && response.items ? response.items : []).map(normalizeLog); if (state.openId && !state.logs.some(function (item) { return item.id === state.openId; })) { state.openId = null; } } catch (error) { state.loadError = error.message || tKey('logs.error.load'); } finally { state.loading = false; renderLogs(); } } async function loadLogDetail(logId) { if (!window.CrankApi || !state.workspaceId || state.details[logId]) { return; } try { var record = await window.CrankApi.getLog(state.workspaceId, logId); state.details[logId] = normalizeLog(record); if (state.openId === logId) { renderLogs(); } } catch (_error) { } } function setLiveState() { if (liveDot) { liveDot.classList.toggle('is-paused', !state.liveMode); } if (liveLabel) { liveLabel.textContent = state.liveMode ? tKey('logs.live') : tKey('logs.paused'); liveLabel.classList.toggle('is-paused', !state.liveMode); } } function stopPolling() { if (state.timer) { clearInterval(state.timer); state.timer = null; } } function startPolling() { stopPolling(); if (!state.liveMode) { return; } state.timer = setInterval(loadLogs, 4000); } function toggleLive() { state.liveMode = !state.liveMode; setLiveState(); startPolling(); if (window.CrankUi) { window.CrankUi.info( state.liveMode ? tKey('logs.live.on.body') : tKey('logs.live.off.body'), state.liveMode ? tKey('logs.live.on.title') : tKey('logs.live.off.title') ); } } document.querySelectorAll('.filter-chip[data-level]').forEach(function (button) { button.addEventListener('click', function () { state.level = this.getAttribute('data-level'); document.querySelectorAll('.filter-chip[data-level]').forEach(function (item) { item.classList.remove('active'); }); this.classList.add('active'); loadLogs(); }); }); if (logSearch) { logSearch.addEventListener('input', function () { state.search = this.value.trim(); loadLogs(); }); } if (refreshBtn) { refreshBtn.addEventListener('click', function () { loadLogs().then(function () { if (!state.loadError && window.CrankUi) { window.CrankUi.info(tKey('logs.refresh.body'), tKey('logs.refresh.title')); } }); }); } if (timeRangeSel) { timeRangeSel.value = state.period; timeRangeSel.addEventListener('change', function () { state.period = this.value; loadLogs(); }); } if (liveDot) { liveDot.addEventListener('click', toggleLive); } if (liveLabel) { liveLabel.addEventListener('click', toggleLive); } window.addEventListener('crank:workspacechange', function () { state.details = {}; state.openId = null; loadLogs(); }); setLiveState(); startPolling(); if (window.whenWorkspacesReady) { window.whenWorkspacesReady().finally(loadLogs); } else { loadLogs(); } });