Phase 5: Add Timeline View feature
- Add backend API endpoints for timeline data:
- GET /api/v1/projects/{id}/timeline
- GET /api/v1/projects/{id}/timeline/summary
- GET /api/v1/entities/{id}/timeline
- Add database methods for timeline queries in db_manager.py
- Add frontend timeline view:
- New sidebar button for timeline view
- Timeline panel with date-grouped events
- Visual distinction between mentions and relations
- Entity filter dropdown
- Statistics cards
- Interactive event cards
- Update STATUS.md with Phase 5 progress
- Add view switching functions (switchView, switchKBTab)
- Add knowledge base loading functions
This commit is contained in:
@@ -598,6 +598,163 @@ class DatabaseManager:
|
||||
'recent_transcripts': [dict(t) for t in recent_transcripts],
|
||||
'top_entities': [dict(e) for e in top_entities]
|
||||
}
|
||||
|
||||
# Phase 5: Timeline operations
|
||||
def get_project_timeline(self, project_id: str, entity_id: str = None, start_date: str = None, end_date: str = None) -> List[dict]:
|
||||
"""获取项目时间线数据 - 按时间顺序的实体提及和事件"""
|
||||
conn = self.get_conn()
|
||||
|
||||
# 构建查询条件
|
||||
conditions = ["t.project_id = ?"]
|
||||
params = [project_id]
|
||||
|
||||
if entity_id:
|
||||
conditions.append("m.entity_id = ?")
|
||||
params.append(entity_id)
|
||||
|
||||
if start_date:
|
||||
conditions.append("t.created_at >= ?")
|
||||
params.append(start_date)
|
||||
|
||||
if end_date:
|
||||
conditions.append("t.created_at <= ?")
|
||||
params.append(end_date)
|
||||
|
||||
where_clause = " AND ".join(conditions)
|
||||
|
||||
# 获取实体提及时间线
|
||||
mentions = conn.execute(
|
||||
f"""SELECT m.*, e.name as entity_name, e.type as entity_type, e.definition,
|
||||
t.filename, t.created_at as event_date, t.type as source_type
|
||||
FROM entity_mentions m
|
||||
JOIN entities e ON m.entity_id = e.id
|
||||
JOIN transcripts t ON m.transcript_id = t.id
|
||||
WHERE {where_clause}
|
||||
ORDER BY t.created_at, m.start_pos""",
|
||||
params
|
||||
).fetchall()
|
||||
|
||||
# 获取关系创建时间线
|
||||
relation_conditions = ["r.project_id = ?"]
|
||||
relation_params = [project_id]
|
||||
|
||||
if start_date:
|
||||
relation_conditions.append("r.created_at >= ?")
|
||||
relation_params.append(start_date)
|
||||
|
||||
if end_date:
|
||||
relation_conditions.append("r.created_at <= ?")
|
||||
relation_params.append(end_date)
|
||||
|
||||
relation_where = " AND ".join(relation_conditions)
|
||||
|
||||
relations = conn.execute(
|
||||
f"""SELECT r.*,
|
||||
s.name as source_name, t.name as target_name,
|
||||
tr.filename, r.created_at as event_date
|
||||
FROM entity_relations r
|
||||
JOIN entities s ON r.source_entity_id = s.id
|
||||
JOIN entities t ON r.target_entity_id = t.id
|
||||
LEFT JOIN transcripts tr ON r.transcript_id = tr.id
|
||||
WHERE {relation_where}
|
||||
ORDER BY r.created_at""",
|
||||
relation_params
|
||||
).fetchall()
|
||||
|
||||
conn.close()
|
||||
|
||||
# 合并并格式化时间线事件
|
||||
timeline_events = []
|
||||
|
||||
for m in mentions:
|
||||
timeline_events.append({
|
||||
'id': m['id'],
|
||||
'type': 'mention',
|
||||
'event_date': m['event_date'],
|
||||
'entity_id': m['entity_id'],
|
||||
'entity_name': m['entity_name'],
|
||||
'entity_type': m['entity_type'],
|
||||
'entity_definition': m['definition'],
|
||||
'text_snippet': m['text_snippet'],
|
||||
'confidence': m['confidence'],
|
||||
'source': {
|
||||
'transcript_id': m['transcript_id'],
|
||||
'filename': m['filename'],
|
||||
'type': m['source_type']
|
||||
}
|
||||
})
|
||||
|
||||
for r in relations:
|
||||
timeline_events.append({
|
||||
'id': r['id'],
|
||||
'type': 'relation',
|
||||
'event_date': r['event_date'],
|
||||
'relation_type': r['relation_type'],
|
||||
'source_entity': r['source_name'],
|
||||
'target_entity': r['target_name'],
|
||||
'evidence': r['evidence'],
|
||||
'source': {
|
||||
'transcript_id': r.get('transcript_id'),
|
||||
'filename': r['filename']
|
||||
}
|
||||
})
|
||||
|
||||
# 按时间排序
|
||||
timeline_events.sort(key=lambda x: x['event_date'])
|
||||
|
||||
return timeline_events
|
||||
|
||||
def get_entity_timeline_summary(self, project_id: str) -> dict:
|
||||
"""获取项目实体时间线摘要统计"""
|
||||
conn = self.get_conn()
|
||||
|
||||
# 按日期统计提及数量
|
||||
daily_stats = conn.execute(
|
||||
"""SELECT DATE(t.created_at) as date, COUNT(*) as count
|
||||
FROM entity_mentions m
|
||||
JOIN transcripts t ON m.transcript_id = t.id
|
||||
WHERE t.project_id = ?
|
||||
GROUP BY DATE(t.created_at)
|
||||
ORDER BY date""",
|
||||
(project_id,)
|
||||
).fetchall()
|
||||
|
||||
# 按实体统计提及数量
|
||||
entity_stats = conn.execute(
|
||||
"""SELECT e.name, e.type, COUNT(m.id) as mention_count,
|
||||
MIN(t.created_at) as first_mentioned,
|
||||
MAX(t.created_at) as last_mentioned
|
||||
FROM entities e
|
||||
LEFT JOIN entity_mentions m ON e.id = m.entity_id
|
||||
LEFT JOIN transcripts t ON m.transcript_id = t.id
|
||||
WHERE e.project_id = ?
|
||||
GROUP BY e.id
|
||||
ORDER BY mention_count DESC
|
||||
LIMIT 20""",
|
||||
(project_id,)
|
||||
).fetchall()
|
||||
|
||||
# 获取活跃时间段
|
||||
active_periods = conn.execute(
|
||||
"""SELECT
|
||||
DATE(t.created_at) as date,
|
||||
COUNT(DISTINCT m.entity_id) as active_entities,
|
||||
COUNT(m.id) as total_mentions
|
||||
FROM transcripts t
|
||||
LEFT JOIN entity_mentions m ON t.id = m.transcript_id
|
||||
WHERE t.project_id = ?
|
||||
GROUP BY DATE(t.created_at)
|
||||
ORDER BY date""",
|
||||
(project_id,)
|
||||
).fetchall()
|
||||
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
'daily_activity': [dict(d) for d in daily_stats],
|
||||
'top_entities': [dict(e) for e in entity_stats],
|
||||
'active_periods': [dict(a) for a in active_periods]
|
||||
}
|
||||
|
||||
def get_transcript_context(self, transcript_id: str, position: int, context_chars: int = 200) -> str:
|
||||
"""获取转录文本的上下文"""
|
||||
|
||||
@@ -1265,3 +1265,72 @@ async def search_entities(project_id: str, q: str):
|
||||
db = get_db_manager()
|
||||
entities = db.search_entities(project_id, q)
|
||||
return [{"id": e.id, "name": e.name, "type": e.type, "definition": e.definition} for e in entities]
|
||||
|
||||
|
||||
# ==================== Phase 5: 时间线视图 API ====================
|
||||
|
||||
@app.get("/api/v1/projects/{project_id}/timeline")
|
||||
async def get_project_timeline(
|
||||
project_id: str,
|
||||
entity_id: str = None,
|
||||
start_date: str = None,
|
||||
end_date: str = None
|
||||
):
|
||||
"""获取项目时间线 - 按时间顺序的实体提及和关系事件"""
|
||||
if not DB_AVAILABLE:
|
||||
raise HTTPException(status_code=500, detail="Database not available")
|
||||
|
||||
db = get_db_manager()
|
||||
project = db.get_project(project_id)
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
|
||||
timeline = db.get_project_timeline(project_id, entity_id, start_date, end_date)
|
||||
|
||||
return {
|
||||
"project_id": project_id,
|
||||
"events": timeline,
|
||||
"total_count": len(timeline)
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/v1/projects/{project_id}/timeline/summary")
|
||||
async def get_timeline_summary(project_id: str):
|
||||
"""获取项目时间线摘要统计"""
|
||||
if not DB_AVAILABLE:
|
||||
raise HTTPException(status_code=500, detail="Database not available")
|
||||
|
||||
db = get_db_manager()
|
||||
project = db.get_project(project_id)
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
|
||||
summary = db.get_entity_timeline_summary(project_id)
|
||||
|
||||
return {
|
||||
"project_id": project_id,
|
||||
"project_name": project.name,
|
||||
**summary
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/v1/entities/{entity_id}/timeline")
|
||||
async def get_entity_timeline(entity_id: str):
|
||||
"""获取单个实体的时间线"""
|
||||
if not DB_AVAILABLE:
|
||||
raise HTTPException(status_code=500, detail="Database not available")
|
||||
|
||||
db = get_db_manager()
|
||||
entity = db.get_entity(entity_id)
|
||||
if not entity:
|
||||
raise HTTPException(status_code=404, detail="Entity not found")
|
||||
|
||||
timeline = db.get_project_timeline(entity.project_id, entity_id)
|
||||
|
||||
return {
|
||||
"entity_id": entity_id,
|
||||
"entity_name": entity.name,
|
||||
"entity_type": entity.type,
|
||||
"events": timeline,
|
||||
"total_count": len(timeline)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user