208 lines
7.0 KiB
Python
208 lines
7.0 KiB
Python
from __future__ import annotations
|
||
|
||
from .shared import *
|
||
|
||
class SignalCaptureSourceAnalyzersMixin:
|
||
async def _analyze_webpage(self, url: str, title: str, content: str, tenant_id: int) -> Dict[str, Any]:
|
||
"""AI 分析网页价值"""
|
||
prompt = f"""分析以下网页内容是否有价值:
|
||
|
||
标题:{title}
|
||
URL: {url}
|
||
内容摘要:{content[:500]}
|
||
|
||
请判断:
|
||
1. 是否是技术文档、竞品信息、行业趋势?
|
||
2. 是否值得团队关注?
|
||
3. 影响程度(0-1)?
|
||
|
||
以 JSON 格式返回:
|
||
{{
|
||
"is_valuable": boolean,
|
||
"reason": "原因",
|
||
"impact_score": 0.8,
|
||
"priority": "high/medium/low",
|
||
"analysis": {{}},
|
||
"suggested_tasks": [{{"task_name": "任务", "type": "backend", "estimated_hours": 8}}]
|
||
}}
|
||
|
||
如果不是有价值内容,is_valuable 设为 false。"""
|
||
|
||
try:
|
||
response = await self.llm.generate(
|
||
prompt,
|
||
db=self.db,
|
||
tenant_id=tenant_id,
|
||
)
|
||
response = response.strip()
|
||
if response.startswith('```'):
|
||
response = response.split('```')[1]
|
||
if response.startswith('json'):
|
||
response = response[4:]
|
||
return json.loads(response.strip())
|
||
except:
|
||
return {'is_valuable': False, 'reason': 'AI 分析失败'}
|
||
|
||
async def _analyze_github_event(self, repo_url: str, event_type: str, event_data: Dict, tenant_id: int) -> Dict[str, Any]:
|
||
"""AI 分析 GitHub 事件"""
|
||
description = f"{event_type}: {event_data.get('title', event_data.get('message', ''))}"
|
||
|
||
prompt = f"""分析以下 GitHub 事件是否有价值:
|
||
|
||
仓库:{repo_url}
|
||
事件:{description}
|
||
|
||
请判断:
|
||
1. 是否是技术栈更新、安全修复、重要功能?
|
||
2. 是否影响我们的项目?
|
||
3. 影响程度(0-1)?
|
||
|
||
以 JSON 格式返回:
|
||
{{
|
||
"is_valuable": boolean,
|
||
"reason": "原因",
|
||
"impact_score": 0.8,
|
||
"priority": "high/medium/low",
|
||
"analysis": {{}},
|
||
"suggested_tasks": [{{"task_name": "任务", "type": "backend", "estimated_hours": 8}}]
|
||
}}
|
||
|
||
如果不是有价值内容,is_valuable 设为 false。"""
|
||
|
||
try:
|
||
response = await self.llm.generate(
|
||
prompt,
|
||
db=self.db,
|
||
tenant_id=tenant_id,
|
||
)
|
||
response = response.strip()
|
||
if response.startswith('```'):
|
||
response = response.split('```')[1]
|
||
if response.startswith('json'):
|
||
response = response[4:]
|
||
return json.loads(response.strip())
|
||
except:
|
||
return {'is_valuable': False, 'reason': 'AI 分析失败'}
|
||
|
||
async def _analyze_hackernews_story(self, story: Dict, tenant_id: int) -> Dict[str, Any]:
|
||
"""AI 分析 HackerNews 故事"""
|
||
prompt = f"""分析以下 HackerNews 故事是否是重要技术趋势:
|
||
|
||
标题:{story.get('title', '')}
|
||
链接:{story.get('url', '')}
|
||
得分:{story.get('points', 0)}
|
||
|
||
请判断:
|
||
1. 是否是行业趋势、技术突破?
|
||
2. 是否值得我们关注?
|
||
3. 影响程度(0-1)?
|
||
|
||
以 JSON 格式返回:
|
||
{{
|
||
"is_strategic": boolean,
|
||
"impact_score": 0.8,
|
||
"priority": "high/medium/low",
|
||
"analysis": {{}},
|
||
"suggested_tasks": [{{"task_name": "任务", "type": "backend", "estimated_hours": 8}}]
|
||
}}
|
||
|
||
如果不是战略性内容,is_strategic 设为 false。"""
|
||
|
||
try:
|
||
response = await self.llm.generate(
|
||
prompt,
|
||
db=self.db,
|
||
tenant_id=tenant_id,
|
||
)
|
||
response = response.strip()
|
||
if response.startswith('```'):
|
||
response = response.split('```')[1]
|
||
if response.startswith('json'):
|
||
response = response[4:]
|
||
result = json.loads(response.strip())
|
||
# 重命名字段以匹配其他方法
|
||
return {
|
||
'is_valuable': result.get('is_strategic', False),
|
||
'reason': result.get('reason', ''),
|
||
'impact_score': result.get('impact_score', 0.5),
|
||
'priority': result.get('priority', 'low'),
|
||
'analysis': result.get('analysis', {}),
|
||
'suggested_tasks': result.get('suggested_tasks', [])
|
||
}
|
||
except:
|
||
return {'is_valuable': False, 'reason': 'AI 分析失败'}
|
||
|
||
async def _auto_link_to_projects(
|
||
self,
|
||
signal_id: int,
|
||
analysis: Dict[str, Any],
|
||
tenant_id: int,
|
||
message_content: Optional[str] = None,
|
||
) -> List[int]:
|
||
"""自动关联信号到相关项目"""
|
||
_ = message_content
|
||
classification = str(analysis.get("classification") or "").lower()
|
||
if classification in {"new_project", "pending_classification", "non_requirement"}:
|
||
return []
|
||
|
||
if classification == "existing_project":
|
||
matched_ids: List[int] = []
|
||
for pid in analysis.get("matched_project_ids", []) or []:
|
||
try:
|
||
matched_ids.append(int(pid))
|
||
except Exception:
|
||
continue
|
||
matched_ids = sorted(set(matched_ids))
|
||
if not matched_ids:
|
||
return []
|
||
|
||
result = await self.db.execute(
|
||
select(Project.id).where(
|
||
and_(
|
||
Project.id.in_(matched_ids),
|
||
Project.status == "active",
|
||
Project.tenant_id == tenant_id,
|
||
)
|
||
)
|
||
)
|
||
valid_ids = {int(v) for v in result.scalars().all()}
|
||
if not valid_ids:
|
||
return []
|
||
confidence = float(analysis.get("confidence") or 0.0)
|
||
relevance = max(0.5, min(0.98, 0.55 + confidence * 0.4))
|
||
link_type = "ai_existing_project_signal"
|
||
else:
|
||
result = await self.db.execute(
|
||
select(Project.id).where(
|
||
and_(Project.status == "active", Project.tenant_id == tenant_id)
|
||
)
|
||
)
|
||
valid_ids = {int(v) for v in result.scalars().all()}
|
||
if not valid_ids:
|
||
return []
|
||
relevance = 0.7
|
||
link_type = "auto_captured"
|
||
|
||
linked_project_ids: List[int] = []
|
||
|
||
for project_id in sorted(valid_ids):
|
||
existing = await self.db.execute(
|
||
text("SELECT id FROM project_signal_links WHERE project_id = :project_id AND signal_id = :signal_id"),
|
||
{"project_id": project_id, "signal_id": signal_id},
|
||
)
|
||
if existing.first() is None:
|
||
from ...models import ProjectSignalLink
|
||
link = ProjectSignalLink(
|
||
project_id=project_id,
|
||
signal_id=signal_id,
|
||
link_type=link_type,
|
||
relevance_score=relevance,
|
||
tenant_id=tenant_id,
|
||
)
|
||
self.db.add(link)
|
||
linked_project_ids.append(project_id)
|
||
|
||
await self.db.commit()
|
||
return linked_project_ids
|
||
|