(function bootstrapAuth() {
const params = new URLSearchParams(window.location.search);
const fromQuery = params.get('token');
if (fromQuery) {
try { localStorage.setItem('iself_token', fromQuery); } catch (_) { }
params.delete('token');
const cleaned = params.toString();
history.replaceState(
null,
'',
window.location.pathname + (cleaned ? '?' + cleaned : '') + window.location.hash
);
}
})();
function authHeader() {
try {
const t = localStorage.getItem('iself_token');
return t ? { 'Authorization': 'Bearer ' + t } : {};
} catch (_) {
return {};
}
}
let promptOpen = false;
async function iselfFetch(url, options) {
options = options || {};
options.headers = Object.assign({}, options.headers || {}, authHeader());
const resp = await fetch(url, options);
if (resp.status === 401 && !promptOpen) {
promptOpen = true;
try { localStorage.removeItem('iself_token'); } catch (_) {}
const newToken = window.prompt(
'i-self API token required.\n\n' +
'Paste your bearer token (from `ISELF_API_TOKEN` env or ~/.i-self/api_token):\n\n' +
'Cancel to use the dashboard in degraded (no-API) mode.'
);
if (newToken && newToken.trim()) {
try { localStorage.setItem('iself_token', newToken.trim()); } catch (_) {}
window.location.reload();
}
}
return resp;
}
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
btn.classList.add('active');
const tabId = btn.getAttribute('data-tab');
document.getElementById(tabId).classList.add('active');
});
});
document.addEventListener('DOMContentLoaded', () => {
loadStats();
loadProfile();
loadTeams();
});
async function loadStats() {
try {
const response = await iselfFetch('/api/stats');
const stats = await response.json();
document.getElementById('profile-status').textContent =
stats.has_profile ? '✓ Loaded' : '✗ Not found';
const embedderEl = document.getElementById('embedder-id');
const banner = document.getElementById('embedder-degraded-banner');
if (embedderEl && stats.embedder) {
embedderEl.textContent = stats.embedder;
embedderEl.title = stats.embedder_is_semantic
? 'Real semantic embeddings'
: 'Hash-bucket keyword fallback (not semantic)';
if (banner) {
banner.style.display = stats.embedder_is_semantic ? 'none' : 'block';
}
} else if (embedderEl) {
embedderEl.textContent = '?';
}
} catch (error) {
console.error('Failed to load stats:', error);
}
}
async function loadProfile() {
try {
const response = await iselfFetch('/api/profile');
if (!response.ok) return;
const profile = await response.json();
document.getElementById('repo-count').textContent =
profile.github.total_repositories;
document.getElementById('language-count').textContent =
profile.github.primary_languages.length;
loadSearchStats();
} catch (error) {
console.error('Failed to load profile:', error);
}
}
async function loadSearchStats() {
try {
const response = await iselfFetch('/api/search/stats');
if (!response.ok) {
document.getElementById('embedding-count').textContent = '0';
return;
}
const stats = await response.json();
document.getElementById('embedding-count').textContent =
stats.total_embeddings || 0;
} catch (error) {
document.getElementById('embedding-count').textContent = '0';
}
}
document.getElementById('search-btn').addEventListener('click', performSearch);
document.getElementById('search-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') performSearch();
});
async function performSearch() {
const query = document.getElementById('search-input').value.trim();
if (!query) return;
const language = document.getElementById('language-filter').value;
const resultsContainer = document.getElementById('search-results');
resultsContainer.innerHTML = '<div class="loading"></div>';
try {
const response = await iselfFetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query,
top_k: 10,
language: language || undefined
})
});
if (!response.ok) {
throw new Error('Search failed');
}
const data = await response.json();
displaySearchResults(data.results);
} catch (error) {
resultsContainer.innerHTML = `<p class="error">Error: ${error.message}</p>`;
}
}
function displaySearchResults(results) {
const container = document.getElementById('search-results');
if (results.length === 0) {
container.innerHTML = '<p>No results found.</p>';
return;
}
container.innerHTML = results.map(result => `
<div class="result-item">
<div class="result-header">
<span class="result-file">${result.source_file} (${result.language})</span>
<span class="result-score">Score: ${(result.score * 100).toFixed(1)}%</span>
</div>
<div class="result-content">
<pre><code>${escapeHtml(result.content.substring(0, 500))}${result.content.length > 500 ? '...' : ''}</code></pre>
</div>
</div>
`).join('');
}
document.getElementById('ai-send').addEventListener('click', sendMessage);
document.getElementById('ai-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
async function sendMessage() {
const input = document.getElementById('ai-input');
const message = input.value.trim();
if (!message) return;
addMessage('user', message);
input.value = '';
try {
const response = await iselfFetch('/api/ai/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question: message })
});
if (!response.ok) {
throw new Error('AI request failed');
}
const data = await response.json();
addMessage('assistant', data.answer);
} catch (error) {
addMessage('assistant', `Error: ${error.message}`);
}
}
function addMessage(role, content) {
const container = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
messageDiv.innerHTML = `<p>${escapeHtml(content)}</p>`;
container.appendChild(messageDiv);
container.scrollTop = container.scrollHeight;
}
document.querySelectorAll('.action-btn').forEach(btn => {
btn.addEventListener('click', () => {
const action = btn.getAttribute('data-action');
handleQuickAction(action);
});
});
function handleQuickAction(action) {
const input = document.getElementById('ai-input');
switch(action) {
case 'explain':
input.value = 'Explain my typical coding style and patterns';
break;
case 'patterns':
input.value = 'What are my most common design patterns?';
break;
case 'improve':
input.value = 'How can I improve my code quality?';
break;
case 'generate':
input.value = 'Generate a Rust function for error handling following my patterns';
break;
}
sendMessage();
}
async function loadTeams() {
try {
const response = await iselfFetch('/api/teams');
if (!response.ok) return;
const teams = await response.json();
const select = document.getElementById('team-select');
teams.forEach(team => {
const option = document.createElement('option');
option.value = team;
option.textContent = team;
select.appendChild(option);
});
} catch (error) {
console.error('Failed to load teams:', error);
}
}
document.getElementById('load-team').addEventListener('click', async () => {
const teamName = document.getElementById('team-select').value;
if (!teamName) return;
try {
const response = await iselfFetch(`/api/teams/${teamName}`);
if (!response.ok) {
throw new Error('Team not found');
}
const team = await response.json();
displayTeam(team);
} catch (error) {
alert(`Error: ${error.message}`);
}
});
function displayTeam(team) {
document.getElementById('team-content').classList.remove('hidden');
document.getElementById('team-member-count').textContent =
team.config.members.length;
document.getElementById('skill-coverage').textContent =
Object.keys(team.language_coverage).length;
document.getElementById('knowledge-silos').textContent =
team.knowledge_distribution.knowledge_silos.length;
const recsContainer = document.getElementById('team-recommendations');
if (team.recommendations.length > 0) {
recsContainer.innerHTML = team.recommendations.map(rec => `
<div class="recommendation ${rec.priority.toLowerCase()}">
<h4>${rec.title}</h4>
<p>${rec.description}</p>
<ul>
${rec.action_items.map(item => `<li>${item}</li>`).join('')}
</ul>
</div>
`).join('');
} else {
recsContainer.innerHTML = '<p>No recommendations at this time.</p>';
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
setInterval(loadStats, 30000);