App._activeSession = null;
App._sessionMessages = [];
App.renderSessions = function() {
var self = this;
return Promise.all([
api('/api/sessions'),
api('/api/agent/status').catch(function() { return {}; })
]).then(function(results) {
var data = results[0] || {};
var agentStatus = results[1] || {};
if (agentStatus.name) setAgentDisplayName(agentStatus.name);
var sessions = data.sessions || [];
if (sessions.length > 0) {
dismissHint('sessions-helper');
try { window.localStorage.setItem('ic_sessions_helper_dismissed', '1'); } catch (_) {}
}
if (self._activeSession) {
return Promise.all([
api('/api/sessions/' + encodeURIComponent(self._activeSession.id) + '/messages').catch(function() { return { messages: [] }; }),
api('/api/sessions/' + encodeURIComponent(self._activeSession.id) + '/turns').catch(function() { return { turns: [] }; }),
api('/api/sessions/' + encodeURIComponent(self._activeSession.id) + '/feedback').catch(function() { return { feedback: [] }; })
]).then(function(results) {
var r = results[0], turnsData = results[1], fbData = results[2];
self._sessionMessages = r.messages || [];
var turns = turnsData.turns || [];
var feedbackByTurn = {};
(fbData.feedback || []).forEach(function(fb) { feedbackByTurn[fb.turn_id] = fb; });
var asstCount = 0;
var msgs = self._sessionMessages.map(function(m, idx) {
var side = m.role === 'user' ? 'user' : 'assistant';
var roleLabel = side === 'assistant'
? sessionAssistantLabel(self._activeSession, m.role || 'assistant')
: (m.role || 'user');
var expandBtn = side === 'assistant' ? '<button class="ctx-expand-btn" data-msg-idx="' + idx + '" data-session-id="' + esc(self._activeSession.id) + '" title="Toggle inline context details">> Context</button>' : '';
var gradeHtml = '';
if (side === 'assistant') {
var turnForMsg = turns[asstCount] || null;
asstCount++;
if (turnForMsg) {
var existingFb = feedbackByTurn[turnForMsg.id];
var existingGrade = existingFb ? existingFb.grade : 0;
gradeHtml = '<div class="grade-stars" data-turn-id="' + esc(turnForMsg.id) + '">';
for (var g = 1; g <= 5; g++) {
gradeHtml += '<span class="star' + (g <= existingGrade ? ' filled' : '') + '" data-grade="' + g + '">\u2605</span>';
}
gradeHtml += '<button class="grade-comment-toggle" data-turn-id="' + esc(turnForMsg.id) + '">comment</button>';
gradeHtml += '</div>';
if (existingFb && existingFb.comment) {
gradeHtml += '<div style="font-size:0.625rem;color:var(--muted);margin-top:0.125rem">\u201c' + esc(existingFb.comment) + '\u201d</div>';
}
}
}
var timeHtml = '';
if (m.created_at) {
var d = new Date(m.created_at);
var now = new Date();
var diffMs = now - d;
var diffMin = Math.floor(diffMs / 60000);
var relative = diffMin < 1 ? 'just now'
: diffMin < 60 ? diffMin + 'm ago'
: diffMin < 1440 ? Math.floor(diffMin / 60) + 'h ago'
: Math.floor(diffMin / 1440) + 'd ago';
timeHtml = '<div class="msg-time" title="' + esc(d.toISOString()) + '" style="font-size:0.625rem;color:var(--muted);text-align:right;margin-top:0.25rem">' + relative + '</div>';
}
return '<div class="message ' + side + '" id="msg-' + idx + '"><div class="message-role">' + esc(roleLabel) + expandBtn + '</div><div>' + renderSafeMarkdown(m.content || '') + '</div>' + gradeHtml + timeHtml + '<div class="ctx-detail" id="ctx-detail-' + idx + '" style="display:none"></div></div>';
}).join('') || '<p style="color:var(--muted);padding:1rem">Send a message to begin the conversation.</p>';
var chatLabel = self._activeSession.nickname || truncate(self._activeSession.id, 16);
var chatAgentLabel = sessionAssistantLabel(self._activeSession, self._activeSession.agent_id || 'default');
var isReadOnly = self._activeSession.non_interactive;
var inputArea = isReadOnly
? '<div style="padding:0.75rem 1rem;background:rgba(245,158,11,0.08);border-top:1px solid var(--border-ghost);font-size:0.8125rem;color:var(--muted);text-align:center">\ud83d\udd12 This session is read-only (cron/subagent)</div>'
: '<div class="session-chat-input"><textarea id="session-msg-input" placeholder="Type a message\u2026" rows="1" autocomplete="off"></textarea><button class="btn" id="btn-send-msg">' + uiBtnLabel('send', 'Send') + '</button></div>';
return '<div class="session-chat-wrap"><div class="session-chat-header"><button class="btn secondary" style="font-size:0.75rem;padding:0.3rem 0.75rem" id="btn-back-sessions">' + uiBtnLabel('back', 'Back') + '</button><span style="font-weight:600">' + esc(chatAgentLabel) + '</span><span class="session-nick" title="' + esc(self._activeSession.id) + '" style="color:var(--muted);font-size:0.75rem">' + esc(chatLabel) + '</span>' + copyIdBtn(self._activeSession.id) + (isReadOnly ? ' <span class="badge" style="font-size:0.5rem;padding:1px 5px;background:var(--warning);color:#000;vertical-align:middle">AUTO</span>' : '') + '</div><div class="message-thread">' + msgs + '</div>' + inputArea + '</div>';
});
}
// Separate active/closed from archived
var showArchived = self._showArchivedSessions || false;
var activeSessions = sessions.filter(function(s) { return s.status !== 'archived'; });
var archivedSessions = sessions.filter(function(s) { return s.status === 'archived'; });
var displaySessions = showArchived ? sessions : activeSessions;
var PAGE_SIZE = 25;
var page = self._sessionsPage || 0;
var totalPages = Math.ceil(displaySessions.length / PAGE_SIZE) || 1;
if (page >= totalPages) page = totalPages - 1;
var paged = displaySessions.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
var rows = paged.map(function(s) {
// Fallback label: nickname → agent name for subagents → truncated ID
var label = s.nickname || (s.agent_id && s.agent_id !== 'duncan' ? s.agent_id : '') || truncate(s.id, 16);
if (label === 'Untitled' && s.agent_id) label = s.agent_id;
var st = s.status || 'active';
var stCls = st === 'active' ? 'success' : (st === 'closed' ? '' : 'warning');
var isNonInteractive = s.non_interactive;
var openLabel = isNonInteractive ? 'View' : 'Open';
var archiveBtn = st === 'archived'
? '<button class="btn secondary" style="font-size:0.65rem;padding:0.2rem 0.5rem" disabled>Archived</button>'
: '<button class="btn secondary session-archive-btn" data-archive-session="' + esc(s.id || '') + '" style="font-size:0.65rem;padding:0.2rem 0.5rem" title="Archive">Archive</button>';
return '<tr data-id="' + esc(s.id || '') + '" style="cursor:pointer" title="' + esc(s.id || '') + '"><td><span class="session-nick">' + esc(label) + '</span>' + copyIdBtn(s.id || '') + '</td><td>' + esc(s.agent_id || '') + '</td><td><span class="badge ' + stCls + '" style="font-size:0.5625rem">' + esc(st) + '</span></td><td>' + esc(s.created_at || '') + '</td><td>' + esc(s.updated_at || '') + '</td><td style="display:flex;gap:0.25rem"><button class="btn secondary" style="font-size:0.65rem;padding:0.2rem 0.5rem">' + openLabel + '</button>' + archiveBtn + '<button class="btn secondary danger session-delete-btn" data-delete-session="' + esc(s.id || '') + '" style="font-size:0.65rem;padding:0.2rem 0.5rem" title="Delete">\u2715</button></td></tr>';
}).join('');
var emptyHint = displaySessions.length === 0 ? '<div class="card" style="margin-top:1rem;color:var(--muted)">No sessions yet. Click <strong>New session</strong> to start one.</div>' : '';
var pager = totalPages > 1 ? '<div style="display:flex;gap:0.5rem;align-items:center;margin-top:0.75rem;justify-content:center"><button class="btn secondary" id="sess-prev" style="font-size:0.75rem;padding:0.2rem 0.6rem"' + (page === 0 ? ' disabled' : '') + '>\u2190 Prev</button><span style="font-size:0.75rem;color:var(--muted)">Page ' + (page + 1) + ' of ' + totalPages + '</span><button class="btn secondary" id="sess-next" style="font-size:0.75rem;padding:0.2rem 0.6rem"' + (page >= totalPages - 1 ? ' disabled' : '') + '>Next \u2192</button></div>' : '';
var archiveToggle = archivedSessions.length > 0
? '<button class="btn secondary" id="toggle-archived" style="font-size:0.7rem;padding:0.25rem 0.6rem">' + (showArchived ? 'Hide archived (' + archivedSessions.length + ')' : 'Show archived (' + archivedSessions.length + ')') + '</button>'
: '';
return '<div style="display:flex;gap:0.75rem;align-items:center"><button class="btn" id="btn-new-session">' + uiBtnLabel('plus', 'New session') + '</button><span style="font-size:0.8125rem;color:var(--muted)">' + activeSessions.length + ' active</span>' + archiveToggle + '</div><div class="table-wrap" style="margin-top:1rem;max-height:calc(100vh - 56px - 8rem);overflow-y:auto"><table><thead><tr><th>Topic</th><th>Agent</th><th>Status</th><th>Created</th><th>Updated</th><th>Action</th></tr></thead><tbody>' + rows + '</tbody></table></div>' + pager + emptyHint;
});
};