var _cachedConfig = null;
var _cachedCronJobs = [];
var _cachedWorkspace = null;
var App = {
page: 'overview',
_activeAgentId: null,
_memoryTab: 'episodic',
_skillsTab: 'installed',
_memorySessionId: '',
_routingProfileDraft: null,
_routingProfileDefaults: null,
_contextBudgetDraft: null,
_contextBudgetDefaults: null,
_modelGraphFocusTurn: null,
_modelGraphFocusModel: null,
_modelGraphFocusEdge: null,
_effTab: 'performance',
ws: null,
_resolveActiveAgentId: function() {
var self = this;
if (self._activeAgentId) return Promise.resolve(self._activeAgentId);
return api('/api/agent/status')
.then(function(s) {
if (s && s.name) setAgentDisplayName(s.name);
var aid = (s && s.agent_id) ? String(s.agent_id) : 'default';
self._activeAgentId = aid;
return aid;
})
.catch(function() { return 'default'; });
},
setPage: function(p) {
this.page = p;
document.querySelectorAll('.sidebar-nav a, .mobile-nav a').forEach(function(a) {
a.classList.toggle('active', a.getAttribute('data-page') === p);
});
var bc = document.getElementById('breadcrumb'); if (bc) bc.textContent = titles[p] || p;
},
refreshOverview: function() {
if (this.page !== 'overview') return;
var content = document.getElementById('content');
if (!content) return;
var self = this;
this.renderOverview().then(function(cards) {
self._applyOverviewCards(cards);
}).catch(function(e) {
toast(e.message || 'Overview refresh failed');
});
},
_renderOverviewShell: function() {
return ''
+ '<div class="card overview-status">'
+ ' <div class="overview-meta" id="ov-meta"></div>'
+ '</div>'
+ '<div class="card overview-attention" id="ov-attention" style="display:none"></div>'
+ '<div class="card overview-onboarding" id="ov-onboarding" style="display:none"></div>'
+ '<div class="overview-grid" id="overview-grid">'
+ ' <div id="ov-card-cost"></div>'
+ ' <div id="ov-card-tokens"></div>'
+ ' <div id="ov-card-cache"></div>'
+ ' <div id="ov-card-wallet"></div>'
+ ' <div id="ov-card-latency"></div>'
+ ' <div id="ov-card-sessions"></div>'
+ ' <div id="ov-card-system"></div>'
+ ' <div id="ov-card-errors"></div>'
+ ' <div id="ov-card-fleet" class="overview-fleet-slot" style="display:none"></div>'
+ '</div>'
+ '<div class="card" id="ov-integrations" style="margin-top:1rem"></div>';
},
_applyOverviewCards: function(cards) {
var self = this;
var slotMap = {
cost: 'ov-card-cost',
tokens: 'ov-card-tokens',
cache: 'ov-card-cache',
wallet: 'ov-card-wallet',
latency: 'ov-card-latency',
sessions: 'ov-card-sessions',
system: 'ov-card-system',
errors: 'ov-card-errors',
fleet: 'ov-card-fleet'
};
var meta = cards && cards.__meta ? cards.__meta : {};
var attention = cards && cards.__attention ? cards.__attention : [];
var failures = cards && cards.__failures ? cards.__failures : [];
var ovMeta = document.getElementById('ov-meta');
if (ovMeta) {
var staleBadge = failures.length > 0
? '<span class="badge warning">Partial data (' + failures.length + ' source' + (failures.length > 1 ? 's' : '') + ' unavailable)</span>'
: '<span class="badge success">All sources healthy</span>';
var updated = meta.updated_at ? esc(meta.updated_at) : 'unknown';
setHtml(ovMeta, staleBadge + '<span class="badge muted">Last updated: ' + updated + '</span>');
}
var ovAttention = document.getElementById('ov-attention');
if (ovAttention) {
if (attention.length > 0) {
var items = attention.map(function(item) {
return '<li>' + esc(item) + '</li>';
}).join('');
setHtml(ovAttention, '<strong>Attention needed</strong><ul style="margin:0.5rem 0 0 1rem">' + items + '</ul>');
ovAttention.style.display = '';
} else {
ovAttention.style.display = 'none';
}
}
var ovOnboarding = document.getElementById('ov-onboarding');
if (ovOnboarding && hintsEnabled() && !window.localStorage.getItem('ic_dash_onboarding_dismissed')) {
setHtml(
ovOnboarding,
'<div style="display:flex;justify-content:space-between;gap:1rem;align-items:flex-start">'
+ '<div><strong>Quick Start</strong><div style="margin-top:0.35rem;color:var(--muted);font-size:0.85rem">1) Open <em>Sessions</em> and start a conversation. 2) Use <em>Context</em> to inspect token and reasoning traces. 3) Use <em>Prompt Performance</em> to tune latency/cost.</div></div>'
+ '<button class="btn secondary" id="dismiss-onboarding" title="Dismiss hint" aria-label="Dismiss hint">x</button>'
+ '</div>'
);
ovOnboarding.style.display = '';
} else if (ovOnboarding) {
ovOnboarding.style.display = 'none';
}
Object.keys(slotMap).forEach(function(key) {
var el = document.getElementById(slotMap[key]);
if (!el) return;
var html = cards && cards[key] ? cards[key] : '';
if (html) {
setHtml(el, html);
el.style.display = '';
} else {
setHtml(el, '');
if (key === 'fleet') el.style.display = 'none';
}
});
var intEl = document.getElementById('ov-integrations');
if (intEl) {
var chStatuses = cards && cards.__channelStatuses ? cards.__channelStatuses : [];
var intHtml = '<div class="card-title">Integrations</div>';
if (Array.isArray(chStatuses) && chStatuses.length > 0) {
intHtml += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem">';
chStatuses.forEach(function(ch) {
var dot = ch.connected ? 'background:#22c55e' : 'background:#ef4444';
var statusText = ch.connected ? 'Connected' : (ch.last_error || 'Disconnected');
var stats = '';
if (ch.messages_received != null) stats += '<div style="font-size:0.65rem;color:var(--muted)">In: ' + (ch.messages_received || 0) + ' / Out: ' + (ch.messages_sent || 0) + '</div>';
intHtml += '<div style="padding:0.65rem;border:1px solid var(--border);border-radius:6px">'
+ '<div style="display:flex;align-items:center;gap:0.4rem;margin-bottom:0.3rem">'
+ '<span style="width:8px;height:8px;border-radius:50%;display:inline-block;' + dot + '"></span>'
+ '<span style="font-weight:600;font-size:0.8rem">' + esc(ch.name || 'unknown') + '</span>'
+ '</div>'
+ '<div style="font-size:0.7rem;color:' + (ch.connected ? 'var(--text)' : 'var(--warning, #f59e0b)') + '">' + esc(statusText.substring(0, 80)) + '</div>'
+ stats
+ '<button class="btn secondary channel-test-btn" data-test-channel="' + esc(ch.name || '') + '" style="font-size:0.65rem;padding:0.2rem 0.5rem;margin-top:0.35rem">Test</button>'
+ '</div>';
});
intHtml += '</div>';
} else {
intHtml += '<div style="font-size:0.75rem;color:var(--muted)">No integrations configured. Add channels in Settings.</div>';
}
setHtml(intEl, intHtml);
}
},
navigate: function(page) {
if (page === 'recommendations') {
this._effTab = 'recommendations';
page = 'efficiency';
}
this.setPage(page);
if (page !== 'overview') {
if (overviewRefreshTimer) clearInterval(overviewRefreshTimer);
}
if (page !== 'workspace') stopWorkspaceRefresh();
var content = document.getElementById('content');
if (content) {
if (page === 'workspace' || page === 'scheduler') { content.style.overflow = 'hidden'; content.style.padding = '0'; content.style.display = ''; }
else if (page === 'settings') { content.style.overflow = 'auto'; content.style.padding = '1.5rem'; content.style.display = 'flex'; content.style.flexDirection = 'column'; }
else { content.style.overflow = 'auto'; content.style.padding = '1.5rem'; content.style.display = ''; }
setHtml(content, '<div class="skeleton" style="height:200px"></div>');
}
var self = this;
var renderName = 'render' + page.charAt(0).toUpperCase() + page.slice(1);
if (this[renderName]) {
this[renderName]().then(function(html) {
if (page === 'overview') {
if (content) setHtml(content, self._renderOverviewShell());
self._applyOverviewCards(html);
startOverviewRefresh(self);
return;
}
if (content) {
var helperDefs = {
sessions: { id: 'sessions-helper', text: 'Review and continue conversations. Click a row to open details.' },
context: { id: 'context-helper', text: 'Inspect context budgets, token allocation, and reasoning traces.' },
memory: { id: 'memory-helper', text: 'Browse memory layers or search by query.' },
skills: { id: 'skills-helper', text: 'Toggle and manage enabled runtime skills.' },
agents: { id: 'agents-helper', text: 'Monitor subagents and their current state.' },
scheduler: { id: 'scheduler-helper', text: 'Manage recurring jobs and inspect run outcomes.' },
integrations: { id: 'integrations-helper', text: 'View channel health, message stats, and test connectivity.' },
metrics: { id: 'metrics-helper', text: 'Track costs, throughput, and provider capacity.' },
efficiency: { id: 'efficiency-helper', text: 'Find prompt and token optimization opportunities.' },
recommendations: { id: 'recommendations-helper', text: 'Generate and review improvement suggestions.' },
wallet: { id: 'wallet-helper', text: 'Inspect treasury balances and token positions.' },
workspace: { id: 'workspace-helper', text: 'See live runtime activity across systems.' },
settings: { id: 'settings-helper', text: 'Edit runtime configuration safely.' }
};
var helperDef = helperDefs[page];
var showHelper = !!helperDef && hintsEnabled() && !isHintDismissed(helperDef.id);
if (page === 'sessions') {
showHelper = showHelper && !self._activeSession;
}
var helper = showHelper ? renderHintBanner(helperDef.id, helperDef.text) : '';
setHtml(content, helper + html);
}
if (page === 'settings') setTimeout(syncSettingsHighlight, 0);
if (page === 'workspace') setTimeout(function() { startWorkspaceEngine(_cachedWorkspace || { agents: [], workstations: [] }); startWorkspaceRefresh(self); }, 0);
if (page === 'overview') startOverviewRefresh(self);
}).catch(function(e) {
if (content) setHtml(content, '<div class="card"><p style="color:var(--error)">' + esc(e.message || 'Failed to load') + '</p></div>');
});
}
},
refreshSkills: function() {
if (this.page !== 'skills') return;
var content = document.getElementById('content');
if (!content) return;
setHtml(content, '<div class="skeleton" style="height:200px"></div>');
this.renderSkills().then(function(html) {
setHtml(content, html);
}).catch(function(e) {
setHtml(content, '<div class="card"><p style="color:var(--error)">' + esc(e.message || 'Failed to load skills') + '</p></div>');
});
},
};