// 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 = `
`;
}
}
// 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() {}