ForcePilot/backend/package/yuxi/channel/extensions/confluence/page_writer.py
Kris f943c31ce4 feat(confluence): 新增Confluence渠道插件完整实现
新增了完整的Confluence集成插件,包含以下核心功能:
1.  基础认证与配置管理,支持API Token和OAuth2两种认证方式
2.  评论去重、权限控制与提及解析
3.  知识库检索与页面内容处理
4.  评论收发、编辑删除与流式回复支持
5.  附件与页面标签管理
6.  内容属性存储与AI元数据管理
7.  Webhook事件接收与处理
8.  完整的插件配置与状态检查
2026-05-21 10:43:42 +08:00

120 lines
4.1 KiB
Python

import asyncio
import logging
from yuxi.channel.extensions.confluence.format import ADFBuilder
from yuxi.channel.extensions.confluence.gateway import ConfluenceGateway
from yuxi.channel.extensions.confluence.types import OutboundResult
logger = logging.getLogger(__name__)
MAX_VERSION_RETRY = 3
RETRY_BASE_DELAY = 1.0
class ConfluencePageWriter:
def __init__(self, gateway: ConfluenceGateway):
self._gateway = gateway
async def create_page(
self,
space_id: str,
title: str,
content: str,
parent_page_id: str | None = None,
account_id: str = "default",
) -> OutboundResult:
client = self._gateway.get_client(account_id)
if client is None:
return OutboundResult(success=False, error="Client not found")
adf_body = ADFBuilder.from_markdown(content)
payload = {
"spaceId": space_id,
"status": "current",
"title": title,
"body": {
"representation": "atlas_doc_format",
"value": adf_body,
},
}
if parent_page_id:
payload["parentId"] = parent_page_id
try:
result = await client._request("POST", "/wiki/api/v2/pages", json=payload)
return OutboundResult(success=True, comment_id=result.get("id", ""))
except Exception as e:
return OutboundResult(success=False, error=str(e))
async def update_page_with_conflict_retry(
self,
page_id: str,
title: str,
content: str,
space_id: str,
version_comment: str = "AI 自动更新",
account_id: str = "default",
) -> OutboundResult:
client = self._gateway.get_client(account_id)
if client is None:
return OutboundResult(success=False, error="Client not found")
for attempt in range(MAX_VERSION_RETRY):
try:
page = await client.get_page(page_id)
except Exception as e:
return OutboundResult(success=False, error=f"Read page failed: {e}")
current_version = page.get("version", {}).get("number", 1)
adf_body = ADFBuilder.from_markdown(content)
payload = {
"id": page_id,
"status": "current",
"title": title,
"spaceId": space_id,
"body": {
"representation": "atlas_doc_format",
"value": adf_body,
},
"version": {
"number": current_version + 1,
"message": version_comment,
},
}
try:
result = await client._request(
"PUT", f"/wiki/api/v2/pages/{page_id}", json=payload
)
return OutboundResult(success=True, comment_id=result.get("id", ""))
except Exception as e:
status = getattr(getattr(e, "response", None), "status_code", 0)
if status == 409 and attempt < MAX_VERSION_RETRY - 1:
delay = RETRY_BASE_DELAY * (2 ** attempt)
logger.info(
"Page version conflict, retry %d/%d after %.1fs",
attempt + 1, MAX_VERSION_RETRY, delay,
)
await asyncio.sleep(delay)
continue
return OutboundResult(success=False, error=str(e))
return OutboundResult(success=False, error="Max retries exceeded")
async def generate_meeting_notes(
self,
space_id: str,
title: str,
transcript: str,
account_id: str = "default",
) -> OutboundResult:
prompt = f"""请根据以下会议记录生成结构化的会议纪要:
{transcript}
格式要求:
- 标题使用会议纪要标题
- 分节:参会人员、会议摘要、讨论要点、决议事项、待办事项
- 每个待办事项标注负责人和截止时间"""
return await self.create_page(space_id, title, prompt, account_id=account_id)