// InsightFlow Frontend - Phase 6 (API Platform) const API_BASE = '/api/v1'; let currentProject = null; let currentData = null; let selectedEntity = null; let projectRelations = []; let projectEntities = []; let entityDetailsCache = {}; // Graph Analysis State let graphStats = null; let centralityData = null; let communitiesData = null; let currentPathData = null; // Init document.addEventListener('DOMContentLoaded', () => { const isWorkbench = window.location.pathname.includes('workbench'); if (isWorkbench) { initWorkbench(); } }); // Initialize workbench async function initWorkbench() { const projectId = localStorage.getItem('currentProject'); if (!projectId) { window.location.href = '/'; return; } try { const projects = await fetchProjects(); currentProject = projects.find(p => p.id === projectId); if (!currentProject) { localStorage.removeItem('currentProject'); window.location.href = '/'; return; } const nameEl = document.getElementById('projectName'); if (nameEl) nameEl.textContent = currentProject.name; initUpload(); initAgentPanel(); initEntityCard(); await loadProjectData(); } catch (err) { console.error('Init failed:', err); alert('加载失败,请返回项目列表'); } } // ==================== API Calls ==================== async function fetchProjects() { const res = await fetch(`${API_BASE}/projects`); if (!res.ok) throw new Error('Failed to fetch projects'); return await res.json(); } async function uploadAudio(file) { const formData = new FormData(); formData.append('file', file); const res = await fetch(`${API_BASE}/projects/${currentProject.id}/upload`, { method: 'POST', body: formData }); if (!res.ok) throw new Error('Upload failed'); return await res.json(); } async function loadProjectData() { try { const [entitiesRes, relationsRes] = await Promise.all([ fetch(`${API_BASE}/projects/${currentProject.id}/entities`), fetch(`${API_BASE}/projects/${currentProject.id}/relations`) ]); if (entitiesRes.ok) { projectEntities = await entitiesRes.json(); } if (relationsRes.ok) { projectRelations = await relationsRes.json(); } // 预加载实体详情 await preloadEntityDetails(); currentData = { transcript_id: 'project_view', project_id: currentProject.id, segments: [], entities: projectEntities, full_text: '', relations: projectRelations }; renderTranscript(); renderGraph(); renderEntityList(); } catch (err) { console.error('Load project data failed:', err); } } async function preloadEntityDetails() { const promises = projectEntities.slice(0, 20).map(async entity => { try { const res = await fetch(`${API_BASE}/entities/${entity.id}/details`); if (res.ok) { entityDetailsCache[entity.id] = await res.json(); } } catch (e) { // Ignore errors } }); await Promise.all(promises); } // ==================== View Switching ==================== window.switchView = function(viewName) { // Update sidebar buttons document.querySelectorAll('.sidebar-btn').forEach(btn => { btn.classList.remove('active'); }); const views = { 'workbench': 'workbenchView', 'knowledge-base': 'knowledgeBaseView', 'timeline': 'timelineView', 'reasoning': 'reasoningView', 'graph-analysis': 'graphAnalysisView', 'api-keys': 'apiKeysView' }; // Hide all views Object.values(views).forEach(id => { const el = document.getElementById(id); if (el) { el.style.display = 'none'; el.classList.remove('active', 'show'); } }); // Show selected view const targetId = views[viewName]; if (targetId) { const targetEl = document.getElementById(targetId); if (targetEl) { targetEl.style.display = 'flex'; targetEl.classList.add('active', 'show'); } } // Update active button const btnMap = { 'workbench': 0, 'knowledge-base': 1, 'timeline': 2, 'reasoning': 3, 'graph-analysis': 4, 'api-keys': 5 }; const buttons = document.querySelectorAll('.sidebar-btn'); if (buttons[btnMap[viewName]]) { buttons[btnMap[viewName]].classList.add('active'); } // Load view-specific data if (viewName === 'knowledge-base') { loadKnowledgeBase(); } else if (viewName === 'timeline') { loadTimeline(); } else if (viewName === 'graph-analysis') { initGraphAnalysis(); } else if (viewName === 'api-keys') { loadApiKeys(); } }; // ==================== Phase 6: API Key Management ==================== let apiKeysData = []; let currentApiKeyId = null; // Load API Keys async function loadApiKeys() { try { const res = await fetch(`${API_BASE}/api-keys`); if (!res.ok) throw new Error('Failed to fetch API keys'); const data = await res.json(); apiKeysData = data.keys || []; renderApiKeys(); updateApiKeyStats(); } catch (err) { console.error('Failed to load API keys:', err); document.getElementById('apiKeysListContent').innerHTML = `

加载失败: ${err.message}

`; } } // Update API Key Stats function updateApiKeyStats() { const total = apiKeysData.length; const active = apiKeysData.filter(k => k.status === 'active').length; const revoked = apiKeysData.filter(k => k.status === 'revoked').length; const totalCalls = apiKeysData.reduce((sum, k) => sum + (k.total_calls || 0), 0); document.getElementById('apiKeyTotalCount').textContent = total; document.getElementById('apiKeyActiveCount').textContent = active; document.getElementById('apiKeyRevokedCount').textContent = revoked; document.getElementById('apiKeyTotalCalls').textContent = totalCalls.toLocaleString(); } // Render API Keys List function renderApiKeys() { const container = document.getElementById('apiKeysListContent'); if (apiKeysData.length === 0) { container.innerHTML = `

暂无 API Keys

`; return; } container.innerHTML = apiKeysData.map(key => `
${escapeHtml(key.name)}
${key.key_preview}
${key.permissions.map(p => `${p}`).join('')}
${key.rate_limit}/min
${key.status}
${key.total_calls || 0}
${key.status === 'active' ? ` ` : '已失效'}
`).join(''); } // Show Create API Key Modal window.showCreateApiKeyModal = function() { document.getElementById('apiKeyCreateModal').classList.add('show'); document.getElementById('apiKeyName').value = ''; document.getElementById('apiKeyName').focus(); }; // Hide Create API Key Modal window.hideCreateApiKeyModal = function() { document.getElementById('apiKeyCreateModal').classList.remove('show'); }; // Create API Key window.createApiKey = async function() { const name = document.getElementById('apiKeyName').value.trim(); if (!name) { alert('请输入 API Key 名称'); return; } const permissions = []; if (document.getElementById('permRead').checked) permissions.push('read'); if (document.getElementById('permWrite').checked) permissions.push('write'); if (document.getElementById('permDelete').checked) permissions.push('delete'); if (permissions.length === 0) { alert('请至少选择一个权限'); return; } const rateLimit = parseInt(document.getElementById('apiKeyRateLimit').value); const expiresDays = document.getElementById('apiKeyExpires').value; try { const res = await fetch(`${API_BASE}/api-keys`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, permissions, rate_limit: rateLimit, expires_days: expiresDays ? parseInt(expiresDays) : null }) }); if (!res.ok) throw new Error('Failed to create API key'); const data = await res.json(); hideCreateApiKeyModal(); // Show the created key document.getElementById('createdApiKeyValue').textContent = data.api_key; document.getElementById('apiKeyCreatedModal').classList.add('show'); // Refresh list await loadApiKeys(); } catch (err) { console.error('Failed to create API key:', err); alert('创建失败: ' + err.message); } }; // Copy API Key to clipboard window.copyApiKey = function() { const key = document.getElementById('createdApiKeyValue').textContent; navigator.clipboard.writeText(key).then(() => { showNotification('API Key 已复制到剪贴板', 'success'); }).catch(() => { // Fallback const textarea = document.createElement('textarea'); textarea.value = key; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); showNotification('API Key 已复制到剪贴板', 'success'); }); }; // Hide API Key Created Modal window.hideApiKeyCreatedModal = function() { document.getElementById('apiKeyCreatedModal').classList.remove('show'); }; // Show API Key Stats window.showApiKeyStats = async function(keyId, keyName) { currentApiKeyId = keyId; document.getElementById('apiKeyStatsTitle').textContent = `API Key 统计 - ${keyName}`; document.getElementById('apiKeyStatsModal').classList.add('show'); try { const res = await fetch(`${API_BASE}/api-keys/${keyId}/stats?days=30`); if (!res.ok) throw new Error('Failed to fetch stats'); const data = await res.json(); // Update stats document.getElementById('statsTotalCalls').textContent = data.summary.total_calls.toLocaleString(); document.getElementById('statsSuccessCalls').textContent = data.summary.success_calls.toLocaleString(); document.getElementById('statsErrorCalls').textContent = data.summary.error_calls.toLocaleString(); document.getElementById('statsAvgTime').textContent = Math.round(data.summary.avg_response_time_ms); // Render logs renderApiKeyLogs(data.logs || []); } catch (err) { console.error('Failed to load stats:', err); document.getElementById('apiKeyLogs').innerHTML = `

加载统计失败

`; } }; // Render API Key Logs function renderApiKeyLogs(logs) { const container = document.getElementById('apiKeyLogs'); if (logs.length === 0) { container.innerHTML = `

暂无调用记录

`; return; } container.innerHTML = logs.map(log => `
${escapeHtml(log.endpoint)}
${log.method}
${log.status_code}
${log.response_time_ms}ms
`).join(''); } // Hide API Key Stats Modal window.hideApiKeyStatsModal = function() { document.getElementById('apiKeyStatsModal').classList.remove('show'); currentApiKeyId = null; }; // Revoke API Key window.revokeApiKey = async function(keyId) { if (!confirm('确定要撤销此 API Key 吗?撤销后将无法恢复。')) { return; } try { const res = await fetch(`${API_BASE}/api-keys/${keyId}`, { method: 'DELETE' }); if (!res.ok) throw new Error('Failed to revoke API key'); showNotification('API Key 已撤销', 'success'); await loadApiKeys(); } catch (err) { console.error('Failed to revoke API key:', err); alert('撤销失败: ' + err.message); } }; // Escape HTML helper function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Show notification helper function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${type === 'success' ? 'rgba(0, 212, 255, 0.9)' : type === 'error' ? 'rgba(255, 107, 107, 0.9)' : '#333'}; color: ${type === 'success' || type === 'error' ? '#000' : '#fff'}; padding: 12px 20px; border-radius: 8px; z-index: 10000; font-size: 0.9rem; animation: slideIn 0.3s ease; `; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.style.animation = 'slideOut 0.3s ease'; setTimeout(() => { if (notification.parentNode) { document.body.removeChild(notification); } }, 300); }, 3000); } // Placeholder functions for other views function initUpload() {} function initAgentPanel() {} function initEntityCard() {} function renderTranscript() {} function renderGraph() {} function renderEntityList() {} function loadKnowledgeBase() {} function loadTimeline() {} function initGraphAnalysis() {}