From b82cbf919463ea46c29f575bcfbc2a91d0c9724a Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Tue, 17 Feb 2026 18:32:20 +0800 Subject: [PATCH] feat: add project management page --- frontend/app.js | 118 ++++++------ frontend/index.html | 402 +++++++++++++++++++++++++--------------- frontend/workbench.html | 244 ++++++++++++++++++++++++ 3 files changed, 547 insertions(+), 217 deletions(-) create mode 100644 frontend/workbench.html diff --git a/frontend/app.js b/frontend/app.js index 54dfac2..55543a9 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -7,30 +7,39 @@ let selectedEntity = null; // Init document.addEventListener('DOMContentLoaded', () => { - initApp(); + const isWorkbench = window.location.pathname.includes('workbench'); + if (isWorkbench) { + initWorkbench(); + } }); -// Initialize app - load projects -async function initApp() { +// Initialize workbench +async function initWorkbench() { + const projectId = localStorage.getItem('currentProject'); + if (!projectId) { + window.location.href = '/'; + return; + } + try { const projects = await fetchProjects(); - if (projects.length === 0) { - // Create default project - await createProject('默认项目', '自动创建的默认项目'); - location.reload(); + currentProject = projects.find(p => p.id === projectId); + + if (!currentProject) { + localStorage.removeItem('currentProject'); + window.location.href = '/'; return; } - currentProject = projects[0]; - renderProjectSelector(projects); - initUpload(); + const nameEl = document.getElementById('projectName'); + if (nameEl) nameEl.textContent = currentProject.name; - // Load existing entities for this project + initUpload(); await loadProjectEntities(); } catch (err) { console.error('Init failed:', err); - alert('加载失败,请刷新重试'); + alert('加载失败,请返回项目列表'); } } @@ -41,16 +50,6 @@ async function fetchProjects() { return await res.json(); } -async function createProject(name, description) { - const res = await fetch(`${API_BASE}/projects`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, description }) - }); - if (!res.ok) throw new Error('Failed to create project'); - return await res.json(); -} - async function uploadAudio(file) { const formData = new FormData(); formData.append('file', file); @@ -70,7 +69,6 @@ async function loadProjectEntities() { if (!res.ok) return; const entities = await res.json(); - // Convert to currentData format currentData = { transcript_id: 'project_view', project_id: currentProject.id, @@ -93,30 +91,7 @@ async function loadProjectEntities() { } } -// Render project selector -function renderProjectSelector(projects) { - // Simple project selector in header - const header = document.querySelector('.header'); - const selector = document.createElement('select'); - selector.style.cssText = 'background:#222;color:#fff;border:1px solid #333;padding:4px 12px;border-radius:4px;margin-left:20px;'; - - projects.forEach(p => { - const opt = document.createElement('option'); - opt.value = p.id; - opt.textContent = p.name; - if (p.id === currentProject.id) opt.selected = true; - selector.appendChild(opt); - }); - - selector.onchange = (e) => { - currentProject = projects.find(p => p.id === e.target.value); - loadProjectEntities(); - }; - - header.appendChild(selector); -} - -// Render transcript with entity highlighting +// Render transcript function renderTranscript() { const container = document.getElementById('transcriptContent'); if (!container || !currentData || !currentData.segments) return; @@ -130,7 +105,6 @@ function renderTranscript() { let text = seg.text; const entities = findEntitiesInSegment(seg, idx); - entities.sort((a, b) => b.start - a.start); entities.forEach(ent => { @@ -149,7 +123,6 @@ function renderTranscript() { }); } -// Find entities within a segment function findEntitiesInSegment(seg, segIndex) { if (!currentData || !currentData.entities) return []; @@ -167,7 +140,7 @@ function findEntitiesInSegment(seg, segIndex) { })); } -// Render D3 force-directed graph +// Render D3 graph function renderGraph() { const svg = d3.select('#graph-svg'); svg.selectAll('*').remove(); @@ -318,21 +291,36 @@ window.selectEntity = function(entityId) { console.log('Selected:', entity.name); }; +// Show/hide upload +window.showUpload = function() { + const el = document.getElementById('uploadOverlay'); + if (el) el.classList.add('show'); +}; + +window.hideUpload = function() { + const el = document.getElementById('uploadOverlay'); + if (el) el.classList.remove('show'); +}; + // Upload handling function initUpload() { const input = document.getElementById('fileInput'); const overlay = document.getElementById('uploadOverlay'); + if (!input) return; + input.addEventListener('change', async (e) => { if (!e.target.files.length) return; const file = e.target.files[0]; - overlay.innerHTML = ` -
-

正在分析...

-

${file.name}

-
- `; + if (overlay) { + overlay.innerHTML = ` +
+

正在分析...

+

${file.name}

+
+ `; + } try { const result = await uploadAudio(file); @@ -342,17 +330,19 @@ function initUpload() { renderGraph(); renderEntityList(); - overlay.classList.add('hidden'); + if (overlay) overlay.classList.remove('show'); } catch (err) { console.error('Upload failed:', err); - overlay.innerHTML = ` -
-

分析失败

-

${err.message}

- -
- `; + if (overlay) { + overlay.innerHTML = ` +
+

分析失败

+

${err.message}

+ +
+ `; + } } }); } diff --git a/frontend/index.html b/frontend/index.html index e891f59..58419c0 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,206 +3,302 @@ - InsightFlow - 知识工作台 - + InsightFlow - 项目管理
-

InsightFlow 知识工作台

- 连接音频与知识图谱 +

📁 InsightFlow 项目管理

+ 进入工作台 →
-
-
-
- 📄 转录文本 - 点击实体高亮 -
-
-

请上传音频文件开始分析

-
+
+
+

我的项目

+
-
-
- 🔗 知识图谱 - 拖拽节点查看关系 -
- -
-

项目实体

-

暂无实体数据

-
+
+
-
-
上传音频开始分析 -

支持 MP3, WAV, M4A (最大 500MB)

- - -
+ +