202 lines
6.6 KiB
Python
202 lines
6.6 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
from .schemas import BrowserCaptureRequest, FeishuCaptureRequest, GitHubCaptureRequest
|
|
|
|
@router.post("/project-from-text")
|
|
async def create_project_from_text():
|
|
return JSONResponse(
|
|
status_code=410,
|
|
content=error_response(410, "旧项目创建入口已下线,请使用新项目启动台生成草案并确认创建。"),
|
|
)
|
|
|
|
@router.post("/signal/capture-feishu")
|
|
@audited_write(caller="ai")
|
|
async def capture_feishu_signal(
|
|
request: FeishuCaptureRequest,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
tenant_id: int = Depends(get_tenant_id)
|
|
):
|
|
"""
|
|
从飞书群聊捕获信号
|
|
|
|
AI 会自动判断消息是否包含有价值的项目或技术信息,
|
|
如果是,则创建信号并自动关联到相关项目
|
|
"""
|
|
try:
|
|
service = ProactiveSignalCaptureService(db)
|
|
result = await service.capture_from_feishu(
|
|
group_chat_id=request.group_chat_id,
|
|
message_content=request.message_content,
|
|
sender=request.sender,
|
|
tenant_id=tenant_id,
|
|
feishu_message_id=request.feishu_message_id,
|
|
)
|
|
response_payload = dict(result) if isinstance(result, dict) else {"result": result}
|
|
|
|
should_enqueue_reply = bool(response_payload.get("direct_reply_required"))
|
|
reply_text = str(response_payload.get("direct_reply_text") or "").strip() or "已收到,正在处理。"
|
|
source_message_id = (str(request.feishu_message_id or "").strip()[:128] or None)
|
|
normalized_chat_id = str(request.group_chat_id or "").strip()
|
|
if should_enqueue_reply and normalized_chat_id:
|
|
try:
|
|
outbox_id = await FeishuOutboxService.enqueue(
|
|
db,
|
|
tenant_id=int(tenant_id),
|
|
chat_id=normalized_chat_id,
|
|
text=reply_text,
|
|
source_message_id=source_message_id,
|
|
dedupe_seed=f"capture-feishu:{normalized_chat_id}:{str(request.sender or '').strip()[:64]}",
|
|
trace_id=str(response_payload.get("ai_trace_id") or "").strip()[:64] or None,
|
|
)
|
|
await db.commit()
|
|
response_payload["reply_outbox_enqueued"] = bool(outbox_id)
|
|
response_payload["reply_outbox_id"] = int(outbox_id) if outbox_id else None
|
|
except Exception as outbox_exc:
|
|
await db.rollback()
|
|
response_payload["reply_outbox_enqueued"] = False
|
|
response_payload["reply_outbox_error"] = str(outbox_exc)[:200]
|
|
return response_payload
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"飞书信号捕获失败: {str(e)}")
|
|
|
|
@router.post("/signal/capture-browser")
|
|
async def capture_browser_signal(
|
|
request: BrowserCaptureRequest,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
tenant_id: int = Depends(get_tenant_id)
|
|
):
|
|
"""
|
|
从浏览器标签页捕获信号
|
|
|
|
监控用户访问的技术文档、竞品页面、GitHub issues等
|
|
"""
|
|
try:
|
|
service = ProactiveSignalCaptureService(db)
|
|
result = await service.capture_from_browser(
|
|
user_id=request.user_id,
|
|
url=request.url,
|
|
title=request.title,
|
|
content_preview=request.content_preview,
|
|
tenant_id=tenant_id
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"浏览器信号捕获失败: {str(e)}")
|
|
|
|
@router.post("/signal/capture-github")
|
|
async def capture_github_signal(
|
|
request: GitHubCaptureRequest,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
tenant_id: int = Depends(get_tenant_id)
|
|
):
|
|
"""
|
|
从GitHub事件捕获信号
|
|
|
|
监控竞品仓库更新、上游依赖更新等
|
|
"""
|
|
try:
|
|
service = ProactiveSignalCaptureService(db)
|
|
result = await service.capture_from_github(
|
|
repo_url=request.repo_url,
|
|
event_type=request.event_type,
|
|
event_data=request.event_data,
|
|
tenant_id=tenant_id
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"GitHub信号捕获失败: {str(e)}")
|
|
|
|
@router.post("/signal/start-hn-monitor")
|
|
async def start_hn_monitor(
|
|
background_tasks: BackgroundTasks,
|
|
interval_minutes: int = 30,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
tenant_id: int = Depends(get_tenant_id)
|
|
):
|
|
"""
|
|
启动 HackerNews 定时监控(后台任务)
|
|
|
|
定期抓取 HackerNews 首页,分析技术趋势
|
|
"""
|
|
async def run_monitor():
|
|
service = ProactiveSignalCaptureService(db)
|
|
await service.start_hackernews_monitor(interval_minutes=interval_minutes, tenant_id=tenant_id)
|
|
|
|
background_tasks.add_task(run_monitor)
|
|
return {
|
|
"status": "started",
|
|
"message": f"HackerNews 监控已启动,间隔: {interval_minutes} 分钟"
|
|
}
|
|
|
|
@router.post("/sync-signal")
|
|
async def sync_external_signal(
|
|
source: str,
|
|
content: str,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
tenant_id: int = Depends(get_tenant_id)
|
|
):
|
|
"""
|
|
手动同步外部信号
|
|
|
|
支持从以下来源同步:
|
|
- 飞书群聊
|
|
- GitHub commits/PRs
|
|
- 浏览器标签页
|
|
"""
|
|
from ...models import Signal
|
|
from ...services.ai_steward_service import get_ai_steward_service
|
|
|
|
service = get_ai_steward_service(db)
|
|
|
|
# AI 分析信号重要性
|
|
prompt = f"""分析以下信号的重要性:
|
|
|
|
来源:{source}
|
|
内容:{content}
|
|
|
|
请评估:
|
|
1. impact_score (0-1)
|
|
2. priority (low/medium/high/critical)
|
|
3. 是否需要立即行动
|
|
|
|
以 JSON 格式返回。"""
|
|
|
|
try:
|
|
from ...services.llm_service import LLMService
|
|
llm = LLMService()
|
|
response = await llm.generate(
|
|
prompt,
|
|
db=db,
|
|
tenant_id=tenant_id,
|
|
)
|
|
|
|
# 创建信号
|
|
signal = Signal(
|
|
source=source,
|
|
content=content,
|
|
impact_score=0.7,
|
|
priority='medium',
|
|
processed=False,
|
|
tenant_id=tenant_id
|
|
)
|
|
db.add(signal)
|
|
await db.commit()
|
|
|
|
return {
|
|
'signal_id': str(signal.id),
|
|
'analysis': response,
|
|
'status': 'captured'
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"信号同步失败: {str(e)}"
|
|
)
|