Phase 7 Task 7: 插件与集成系统
- 创建 plugin_manager.py 模块
- PluginManager: 插件管理主类
- ChromeExtensionHandler: Chrome 插件处理
- BotHandler: 飞书/钉钉/Slack 机器人处理
- WebhookIntegration: Zapier/Make Webhook 集成
- WebDAVSync: WebDAV 同步管理
- 创建完整的 Chrome 扩展代码
- manifest.json, background.js, content.js, content.css
- popup.html/js: 弹出窗口界面
- options.html/js: 设置页面
- 支持网页剪藏、选中文本保存、项目选择
- 更新 schema.sql 添加插件相关数据库表
- plugins: 插件配置表
- bot_sessions: 机器人会话表
- webhook_endpoints: Webhook 端点表
- webdav_syncs: WebDAV 同步配置表
- plugin_activity_logs: 插件活动日志表
- 更新 main.py 添加插件相关 API 端点
- GET/POST /api/v1/plugins - 插件管理
- POST /api/v1/plugins/chrome/clip - Chrome 插件保存网页
- POST /api/v1/bots/webhook/{platform} - 接收机器人消息
- GET /api/v1/bots/sessions - 机器人会话列表
- POST /api/v1/webhook-endpoints - 创建 Webhook 端点
- POST /webhook/{type}/{token} - 接收外部 Webhook
- POST /api/v1/webdav-syncs - WebDAV 同步配置
- POST /api/v1/webdav-syncs/{id}/test - 测试 WebDAV 连接
- POST /api/v1/webdav-syncs/{id}/sync - 触发 WebDAV 同步
- 更新 requirements.txt 添加插件依赖
- beautifulsoup4: HTML 解析
- webdavclient3: WebDAV 客户端
- 更新 STATUS.md 和 README.md 开发进度
This commit is contained in:
@@ -878,6 +878,310 @@ class DatabaseManager:
|
||||
filtered.append(entity)
|
||||
return filtered
|
||||
|
||||
# ==================== Phase 7: Multimodal Support ====================
|
||||
|
||||
def create_video(self, video_id: str, project_id: str, filename: str,
|
||||
duration: float = 0, fps: float = 0, resolution: Dict = None,
|
||||
audio_transcript_id: str = None, full_ocr_text: str = "",
|
||||
extracted_entities: List[Dict] = None,
|
||||
extracted_relations: List[Dict] = None) -> str:
|
||||
"""创建视频记录"""
|
||||
conn = self.get_conn()
|
||||
now = datetime.now().isoformat()
|
||||
|
||||
conn.execute(
|
||||
"""INSERT INTO videos
|
||||
(id, project_id, filename, duration, fps, resolution,
|
||||
audio_transcript_id, full_ocr_text, extracted_entities,
|
||||
extracted_relations, status, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(video_id, project_id, filename, duration, fps,
|
||||
json.dumps(resolution) if resolution else None,
|
||||
audio_transcript_id, full_ocr_text,
|
||||
json.dumps(extracted_entities or []),
|
||||
json.dumps(extracted_relations or []),
|
||||
'completed', now, now)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return video_id
|
||||
|
||||
def get_video(self, video_id: str) -> Optional[Dict]:
|
||||
"""获取视频信息"""
|
||||
conn = self.get_conn()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM videos WHERE id = ?", (video_id,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
|
||||
if row:
|
||||
data = dict(row)
|
||||
data['resolution'] = json.loads(data['resolution']) if data['resolution'] else None
|
||||
data['extracted_entities'] = json.loads(data['extracted_entities']) if data['extracted_entities'] else []
|
||||
data['extracted_relations'] = json.loads(data['extracted_relations']) if data['extracted_relations'] else []
|
||||
return data
|
||||
return None
|
||||
|
||||
def list_project_videos(self, project_id: str) -> List[Dict]:
|
||||
"""获取项目的所有视频"""
|
||||
conn = self.get_conn()
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM videos WHERE project_id = ? ORDER BY created_at DESC",
|
||||
(project_id,)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
videos = []
|
||||
for row in rows:
|
||||
data = dict(row)
|
||||
data['resolution'] = json.loads(data['resolution']) if data['resolution'] else None
|
||||
data['extracted_entities'] = json.loads(data['extracted_entities']) if data['extracted_entities'] else []
|
||||
data['extracted_relations'] = json.loads(data['extracted_relations']) if data['extracted_relations'] else []
|
||||
videos.append(data)
|
||||
return videos
|
||||
|
||||
def create_video_frame(self, frame_id: str, video_id: str, frame_number: int,
|
||||
timestamp: float, image_url: str = None,
|
||||
ocr_text: str = None, extracted_entities: List[Dict] = None) -> str:
|
||||
"""创建视频帧记录"""
|
||||
conn = self.get_conn()
|
||||
now = datetime.now().isoformat()
|
||||
|
||||
conn.execute(
|
||||
"""INSERT INTO video_frames
|
||||
(id, video_id, frame_number, timestamp, image_url, ocr_text, extracted_entities, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(frame_id, video_id, frame_number, timestamp, image_url, ocr_text,
|
||||
json.dumps(extracted_entities or []), now)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return frame_id
|
||||
|
||||
def get_video_frames(self, video_id: str) -> List[Dict]:
|
||||
"""获取视频的所有帧"""
|
||||
conn = self.get_conn()
|
||||
rows = conn.execute(
|
||||
"""SELECT * FROM video_frames WHERE video_id = ? ORDER BY timestamp""",
|
||||
(video_id,)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
frames = []
|
||||
for row in rows:
|
||||
data = dict(row)
|
||||
data['extracted_entities'] = json.loads(data['extracted_entities']) if data['extracted_entities'] else []
|
||||
frames.append(data)
|
||||
return frames
|
||||
|
||||
def create_image(self, image_id: str, project_id: str, filename: str,
|
||||
ocr_text: str = "", description: str = "",
|
||||
extracted_entities: List[Dict] = None,
|
||||
extracted_relations: List[Dict] = None) -> str:
|
||||
"""创建图片记录"""
|
||||
conn = self.get_conn()
|
||||
now = datetime.now().isoformat()
|
||||
|
||||
conn.execute(
|
||||
"""INSERT INTO images
|
||||
(id, project_id, filename, ocr_text, description,
|
||||
extracted_entities, extracted_relations, status, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(image_id, project_id, filename, ocr_text, description,
|
||||
json.dumps(extracted_entities or []),
|
||||
json.dumps(extracted_relations or []),
|
||||
'completed', now, now)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return image_id
|
||||
|
||||
def get_image(self, image_id: str) -> Optional[Dict]:
|
||||
"""获取图片信息"""
|
||||
conn = self.get_conn()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM images WHERE id = ?", (image_id,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
|
||||
if row:
|
||||
data = dict(row)
|
||||
data['extracted_entities'] = json.loads(data['extracted_entities']) if data['extracted_entities'] else []
|
||||
data['extracted_relations'] = json.loads(data['extracted_relations']) if data['extracted_relations'] else []
|
||||
return data
|
||||
return None
|
||||
|
||||
def list_project_images(self, project_id: str) -> List[Dict]:
|
||||
"""获取项目的所有图片"""
|
||||
conn = self.get_conn()
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM images WHERE project_id = ? ORDER BY created_at DESC",
|
||||
(project_id,)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
images = []
|
||||
for row in rows:
|
||||
data = dict(row)
|
||||
data['extracted_entities'] = json.loads(data['extracted_entities']) if data['extracted_entities'] else []
|
||||
data['extracted_relations'] = json.loads(data['extracted_relations']) if data['extracted_relations'] else []
|
||||
images.append(data)
|
||||
return images
|
||||
|
||||
def create_multimodal_mention(self, mention_id: str, project_id: str,
|
||||
entity_id: str, modality: str, source_id: str,
|
||||
source_type: str, text_snippet: str = "",
|
||||
confidence: float = 1.0) -> str:
|
||||
"""创建多模态实体提及记录"""
|
||||
conn = self.get_conn()
|
||||
now = datetime.now().isoformat()
|
||||
|
||||
conn.execute(
|
||||
"""INSERT OR REPLACE INTO multimodal_mentions
|
||||
(id, project_id, entity_id, modality, source_id, source_type,
|
||||
text_snippet, confidence, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(mention_id, project_id, entity_id, modality, source_id,
|
||||
source_type, text_snippet, confidence, now)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return mention_id
|
||||
|
||||
def get_entity_multimodal_mentions(self, entity_id: str) -> List[Dict]:
|
||||
"""获取实体的多模态提及"""
|
||||
conn = self.get_conn()
|
||||
rows = conn.execute(
|
||||
"""SELECT m.*, e.name as entity_name
|
||||
FROM multimodal_mentions m
|
||||
JOIN entities e ON m.entity_id = e.id
|
||||
WHERE m.entity_id = ? ORDER BY m.created_at DESC""",
|
||||
(entity_id,)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def get_project_multimodal_mentions(self, project_id: str,
|
||||
modality: str = None) -> List[Dict]:
|
||||
"""获取项目的多模态提及"""
|
||||
conn = self.get_conn()
|
||||
|
||||
if modality:
|
||||
rows = conn.execute(
|
||||
"""SELECT m.*, e.name as entity_name
|
||||
FROM multimodal_mentions m
|
||||
JOIN entities e ON m.entity_id = e.id
|
||||
WHERE m.project_id = ? AND m.modality = ?
|
||||
ORDER BY m.created_at DESC""",
|
||||
(project_id, modality)
|
||||
).fetchall()
|
||||
else:
|
||||
rows = conn.execute(
|
||||
"""SELECT m.*, e.name as entity_name
|
||||
FROM multimodal_mentions m
|
||||
JOIN entities e ON m.entity_id = e.id
|
||||
WHERE m.project_id = ? ORDER BY m.created_at DESC""",
|
||||
(project_id,)
|
||||
).fetchall()
|
||||
|
||||
conn.close()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def create_multimodal_entity_link(self, link_id: str, entity_id: str,
|
||||
linked_entity_id: str, link_type: str,
|
||||
confidence: float = 1.0,
|
||||
evidence: str = "",
|
||||
modalities: List[str] = None) -> str:
|
||||
"""创建多模态实体关联"""
|
||||
conn = self.get_conn()
|
||||
now = datetime.now().isoformat()
|
||||
|
||||
conn.execute(
|
||||
"""INSERT OR REPLACE INTO multimodal_entity_links
|
||||
(id, entity_id, linked_entity_id, link_type, confidence,
|
||||
evidence, modalities, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(link_id, entity_id, linked_entity_id, link_type, confidence,
|
||||
evidence, json.dumps(modalities or []), now)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return link_id
|
||||
|
||||
def get_entity_multimodal_links(self, entity_id: str) -> List[Dict]:
|
||||
"""获取实体的多模态关联"""
|
||||
conn = self.get_conn()
|
||||
rows = conn.execute(
|
||||
"""SELECT l.*, e1.name as entity_name, e2.name as linked_entity_name
|
||||
FROM multimodal_entity_links l
|
||||
JOIN entities e1 ON l.entity_id = e1.id
|
||||
JOIN entities e2 ON l.linked_entity_id = e2.id
|
||||
WHERE l.entity_id = ? OR l.linked_entity_id = ?""",
|
||||
(entity_id, entity_id)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
links = []
|
||||
for row in rows:
|
||||
data = dict(row)
|
||||
data['modalities'] = json.loads(data['modalities']) if data['modalities'] else []
|
||||
links.append(data)
|
||||
return links
|
||||
|
||||
def get_project_multimodal_stats(self, project_id: str) -> Dict:
|
||||
"""获取项目多模态统计信息"""
|
||||
conn = self.get_conn()
|
||||
|
||||
stats = {
|
||||
'video_count': 0,
|
||||
'image_count': 0,
|
||||
'multimodal_entity_count': 0,
|
||||
'cross_modal_links': 0,
|
||||
'modality_distribution': {}
|
||||
}
|
||||
|
||||
# 视频数量
|
||||
row = conn.execute(
|
||||
"SELECT COUNT(*) as count FROM videos WHERE project_id = ?",
|
||||
(project_id,)
|
||||
).fetchone()
|
||||
stats['video_count'] = row['count']
|
||||
|
||||
# 图片数量
|
||||
row = conn.execute(
|
||||
"SELECT COUNT(*) as count FROM images WHERE project_id = ?",
|
||||
(project_id,)
|
||||
).fetchone()
|
||||
stats['image_count'] = row['count']
|
||||
|
||||
# 多模态实体数量
|
||||
row = conn.execute(
|
||||
"""SELECT COUNT(DISTINCT entity_id) as count
|
||||
FROM multimodal_mentions WHERE project_id = ?""",
|
||||
(project_id,)
|
||||
).fetchone()
|
||||
stats['multimodal_entity_count'] = row['count']
|
||||
|
||||
# 跨模态关联数量
|
||||
row = conn.execute(
|
||||
"""SELECT COUNT(*) as count FROM multimodal_entity_links
|
||||
WHERE entity_id IN (SELECT id FROM entities WHERE project_id = ?)""",
|
||||
(project_id,)
|
||||
).fetchone()
|
||||
stats['cross_modal_links'] = row['count']
|
||||
|
||||
# 模态分布
|
||||
for modality in ['audio', 'video', 'image', 'document']:
|
||||
row = conn.execute(
|
||||
"""SELECT COUNT(*) as count FROM multimodal_mentions
|
||||
WHERE project_id = ? AND modality = ?""",
|
||||
(project_id, modality)
|
||||
).fetchone()
|
||||
stats['modality_distribution'][modality] = row['count']
|
||||
|
||||
conn.close()
|
||||
return stats
|
||||
|
||||
|
||||
# Singleton instance
|
||||
_db_manager = None
|
||||
|
||||
Reference in New Issue
Block a user