document.addEventListener('DOMContentLoaded', function () { var state = { items: [], workspaceId: null, loading: false, error: '', openId: null, }; var list = document.getElementById('stream-sessions-list'); var summary = document.getElementById('stream-sessions-summary'); var refreshButton = document.getElementById('stream-sessions-refresh'); var statusFilter = document.getElementById('stream-sessions-status'); var modeFilter = document.getElementById('stream-sessions-mode'); function tKey(key, vars) { if (!window.t) return key; return t(key, vars); } function currentWorkspaceId() { var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; return workspace ? workspace.id : null; } function formatJson(value) { if (value === null || value === undefined) return ''; if (typeof value === 'string') return value; return JSON.stringify(value, null, 2); } function formatDateTime(value) { if (!value) return '—'; return new Date(value).toLocaleString(); } 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 renderEmpty(title, body) { list.innerHTML = ''; var empty = element('div', 'empty-state'); empty.appendChild(element('div', 'empty-state-title', title)); empty.appendChild(element('div', 'empty-state-text', body)); list.appendChild(empty); } function appendMetaItem(parent, label, value) { var item = element('div', 'resource-meta-item'); item.appendChild(element('div', 'resource-meta-label', label)); item.appendChild(element('div', 'resource-meta-value', value)); parent.appendChild(item); } function appendDetailBlock(parent, title, value) { var block = element('div', 'resource-detail-block'); block.appendChild(element('div', 'resource-detail-title', title)); block.appendChild(element('pre', 'resource-detail-pre', formatJson(value))); parent.appendChild(block); } function renderSessionCard(item) { var open = state.openId === item.id; var card = element('div', 'resource-card'); card.setAttribute('data-session-id', item.id); var header = element('div', 'resource-card-header'); var headerMain = element('div'); headerMain.appendChild(element('div', 'resource-card-title', item.id)); headerMain.appendChild(element('div', 'resource-card-subtitle', tKey('stream_sessions.operation', { operation: item.operation_id }))); var pillRow = element('div', 'resource-pill-row'); var statusPill = element('span', 'resource-status-pill ' + String(item.status || '').toLowerCase(), tKey('stream_sessions.status.' + item.status)); pillRow.appendChild(statusPill); pillRow.appendChild(element('span', 'resource-status-pill', item.mode)); pillRow.appendChild(element('span', 'resource-status-pill', item.protocol)); headerMain.appendChild(pillRow); header.appendChild(headerMain); var actions = element('div', 'resource-card-actions'); if (item.status === 'created' || item.status === 'running') { var stopButton = element('button', 'btn-secondary', tKey('stream_sessions.stop')); stopButton.setAttribute('data-action', 'stop'); stopButton.setAttribute('data-session-id', item.id); actions.appendChild(stopButton); } var toggleButton = element( 'button', 'btn-secondary', open ? tKey('stream_sessions.hide_details') : tKey('stream_sessions.show_details') ); toggleButton.setAttribute('data-action', 'toggle'); toggleButton.setAttribute('data-session-id', item.id); actions.appendChild(toggleButton); header.appendChild(actions); card.appendChild(header); var metaGrid = element('div', 'resource-meta-grid'); appendMetaItem(metaGrid, tKey('stream_sessions.meta.created'), formatDateTime(item.created_at)); appendMetaItem(metaGrid, tKey('stream_sessions.meta.last_poll'), formatDateTime(item.last_poll_at)); appendMetaItem(metaGrid, tKey('stream_sessions.meta.expires'), formatDateTime(item.expires_at)); appendMetaItem(metaGrid, tKey('stream_sessions.meta.agent'), item.agent_id || '—'); card.appendChild(metaGrid); if (open) { appendDetailBlock(card, tKey('stream_sessions.cursor'), item.cursor); } return card; } function render() { summary.textContent = tKey('stream_sessions.summary', { count: state.items.length }); if (state.loading && state.items.length === 0) { renderEmpty(tKey('stream_sessions.loading.title'), tKey('stream_sessions.loading.body')); return; } if (state.error) { renderEmpty(tKey('stream_sessions.error.title'), state.error); return; } if (!state.items.length) { renderEmpty(tKey('stream_sessions.empty.title'), tKey('stream_sessions.empty.body')); return; } list.innerHTML = ''; state.items.forEach(function(item) { list.appendChild(renderSessionCard(item)); }); list.querySelectorAll('[data-action="toggle"]').forEach(function(button) { button.addEventListener('click', function () { var sessionId = this.getAttribute('data-session-id'); state.openId = state.openId === sessionId ? null : sessionId; if (state.openId) { void loadDetail(sessionId); } else { render(); } }); }); list.querySelectorAll('[data-action="stop"]').forEach(function(button) { button.addEventListener('click', async function () { try { var sessionId = this.getAttribute('data-session-id'); await window.CrankApi.stopStreamSession(state.workspaceId, sessionId); if (window.CrankUi) { window.CrankUi.success(tKey('stream_sessions.toast.stop.body'), tKey('stream_sessions.toast.stop.title')); } await load(); } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || tKey('stream_sessions.toast.stop_error.body'), tKey('stream_sessions.toast.stop_error.title')); } } }); }); } async function loadDetail(sessionId) { var detail = await window.CrankApi.getStreamSession(state.workspaceId, sessionId); state.items = state.items.map(function(item) { return item.id === sessionId ? detail : item; }); render(); } async function load() { state.workspaceId = currentWorkspaceId(); if (!state.workspaceId) { state.items = []; state.error = tKey('stream_sessions.error.workspace'); render(); return; } state.loading = true; state.error = ''; render(); try { var response = await window.CrankApi.listStreamSessions(state.workspaceId, { status: statusFilter.value || null, mode: modeFilter.value || null, page: 1, page_size: 50, }); state.items = response.items || []; } catch (error) { state.error = error.message || tKey('stream_sessions.error.load'); } finally { state.loading = false; render(); } } refreshButton.addEventListener('click', load); statusFilter.addEventListener('change', load); modeFilter.addEventListener('change', load); document.addEventListener('workspace:changed', load); void load(); });