feat: Add AI report generation with DIFY integration
- Add Users table for display name resolution from AD authentication - Integrate DIFY AI service for report content generation - Create docx assembly service with image embedding from MinIO - Add REST API endpoints for report generation and download - Add WebSocket notifications for generation progress - Add frontend UI with progress modal and download functionality - Add integration tests for report generation flow Report sections (Traditional Chinese): - 事件摘要 (Summary) - 時間軸 (Timeline) - 參與人員 (Participants) - 處理過程 (Resolution Process) - 目前狀態 (Current Status) - 最終處置結果 (Final Resolution) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
199
app/modules/report_generation/prompts.py
Normal file
199
app/modules/report_generation/prompts.py
Normal file
@@ -0,0 +1,199 @@
|
||||
"""Prompt templates for DIFY AI report generation
|
||||
|
||||
Contains the prompt construction logic for building the user query
|
||||
sent to DIFY Chat API.
|
||||
"""
|
||||
from typing import List, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
INCIDENT_TYPE_MAP = {
|
||||
"equipment_failure": "設備故障",
|
||||
"material_shortage": "物料短缺",
|
||||
"quality_issue": "品質問題",
|
||||
"other": "其他",
|
||||
}
|
||||
|
||||
SEVERITY_MAP = {
|
||||
"low": "低",
|
||||
"medium": "中",
|
||||
"high": "高",
|
||||
"critical": "緊急",
|
||||
}
|
||||
|
||||
STATUS_MAP = {
|
||||
"active": "處理中",
|
||||
"resolved": "已解決",
|
||||
"archived": "已封存",
|
||||
}
|
||||
|
||||
MEMBER_ROLE_MAP = {
|
||||
"owner": "建立者",
|
||||
"editor": "編輯者",
|
||||
"viewer": "檢視者",
|
||||
}
|
||||
|
||||
|
||||
def build_report_prompt(
|
||||
room_data: Dict[str, Any],
|
||||
messages: List[Dict[str, Any]],
|
||||
members: List[Dict[str, Any]],
|
||||
files: List[Dict[str, Any]],
|
||||
) -> str:
|
||||
"""Build the complete prompt for DIFY report generation
|
||||
|
||||
Args:
|
||||
room_data: Room metadata (title, type, severity, status, etc.)
|
||||
messages: List of messages with sender_name, content, created_at
|
||||
members: List of members with display_name, role
|
||||
files: List of files with filename, file_type, uploaded_at, uploader_name
|
||||
|
||||
Returns:
|
||||
Formatted prompt string for DIFY API
|
||||
"""
|
||||
sections = []
|
||||
|
||||
# Section 1: Event Information
|
||||
sections.append(_format_room_info(room_data))
|
||||
|
||||
# Section 2: Participants
|
||||
sections.append(_format_members(members))
|
||||
|
||||
# Section 3: Message Timeline
|
||||
sections.append(_format_messages(messages))
|
||||
|
||||
# Section 4: File Attachments
|
||||
sections.append(_format_files(files))
|
||||
|
||||
# Section 5: Instructions
|
||||
sections.append(_format_instructions())
|
||||
|
||||
return "\n\n".join(sections)
|
||||
|
||||
|
||||
def _format_room_info(room_data: Dict[str, Any]) -> str:
|
||||
"""Format room metadata section"""
|
||||
incident_type = INCIDENT_TYPE_MAP.get(
|
||||
room_data.get("incident_type"), room_data.get("incident_type")
|
||||
)
|
||||
severity = SEVERITY_MAP.get(room_data.get("severity"), room_data.get("severity"))
|
||||
status = STATUS_MAP.get(room_data.get("status"), room_data.get("status"))
|
||||
|
||||
created_at = room_data.get("created_at")
|
||||
if isinstance(created_at, datetime):
|
||||
created_at = created_at.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
resolved_at = room_data.get("resolved_at")
|
||||
if isinstance(resolved_at, datetime):
|
||||
resolved_at = resolved_at.strftime("%Y-%m-%d %H:%M")
|
||||
elif resolved_at is None:
|
||||
resolved_at = "尚未解決"
|
||||
|
||||
lines = [
|
||||
"## 事件資訊",
|
||||
f"- 標題: {room_data.get('title', '未命名')}",
|
||||
f"- 類型: {incident_type}",
|
||||
f"- 嚴重程度: {severity}",
|
||||
f"- 目前狀態: {status}",
|
||||
f"- 發生地點: {room_data.get('location', '未指定')}",
|
||||
f"- 建立時間: {created_at}",
|
||||
f"- 解決時間: {resolved_at}",
|
||||
]
|
||||
|
||||
if room_data.get("description"):
|
||||
lines.append(f"- 事件描述: {room_data['description']}")
|
||||
|
||||
if room_data.get("resolution_notes"):
|
||||
lines.append(f"- 解決備註: {room_data['resolution_notes']}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _format_members(members: List[Dict[str, Any]]) -> str:
|
||||
"""Format participants section"""
|
||||
lines = ["## 參與人員"]
|
||||
|
||||
if not members:
|
||||
lines.append("無參與人員記錄")
|
||||
return "\n".join(lines)
|
||||
|
||||
for member in members:
|
||||
display_name = member.get("display_name") or member.get("user_id", "未知")
|
||||
role = MEMBER_ROLE_MAP.get(member.get("role"), member.get("role", "成員"))
|
||||
lines.append(f"- {display_name} ({role})")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _format_messages(messages: List[Dict[str, Any]]) -> str:
|
||||
"""Format message timeline section"""
|
||||
lines = ["## 對話記錄"]
|
||||
|
||||
if not messages:
|
||||
lines.append("無對話記錄")
|
||||
return "\n".join(lines)
|
||||
|
||||
for msg in messages:
|
||||
sender = msg.get("sender_name") or msg.get("sender_id", "未知")
|
||||
content = msg.get("content", "")
|
||||
msg_type = msg.get("message_type", "text")
|
||||
|
||||
created_at = msg.get("created_at")
|
||||
if isinstance(created_at, datetime):
|
||||
time_str = created_at.strftime("%Y-%m-%d %H:%M")
|
||||
else:
|
||||
time_str = str(created_at) if created_at else "未知時間"
|
||||
|
||||
# Handle different message types
|
||||
if msg_type == "file":
|
||||
file_name = msg.get("file_name", "附件")
|
||||
lines.append(f"[{time_str}] {sender}: [上傳檔案: {file_name}]")
|
||||
elif msg_type == "image":
|
||||
lines.append(f"[{time_str}] {sender}: [上傳圖片]")
|
||||
elif msg_type == "system":
|
||||
lines.append(f"[{time_str}] [系統]: {content}")
|
||||
else:
|
||||
lines.append(f"[{time_str}] {sender}: {content}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _format_files(files: List[Dict[str, Any]]) -> str:
|
||||
"""Format file attachments section"""
|
||||
lines = ["## 附件清單"]
|
||||
|
||||
if not files:
|
||||
lines.append("無附件")
|
||||
return "\n".join(lines)
|
||||
|
||||
for f in files:
|
||||
filename = f.get("filename", "未命名檔案")
|
||||
file_type = f.get("file_type", "file")
|
||||
uploader = f.get("uploader_name") or f.get("uploaded_by", "未知")
|
||||
|
||||
uploaded_at = f.get("uploaded_at")
|
||||
if isinstance(uploaded_at, datetime):
|
||||
time_str = uploaded_at.strftime("%Y-%m-%d %H:%M")
|
||||
else:
|
||||
time_str = str(uploaded_at) if uploaded_at else ""
|
||||
|
||||
type_label = "圖片" if file_type == "image" else "檔案"
|
||||
lines.append(f"- [{type_label}] {filename} (由 {uploader} 於 {time_str} 上傳)")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _format_instructions() -> str:
|
||||
"""Format generation instructions"""
|
||||
return """## 報告生成指示
|
||||
|
||||
請根據以上資料,生成一份結構化的事件報告。報告必須為 JSON 格式,包含以下欄位:
|
||||
|
||||
1. **summary**: 事件摘要 (50-100字)
|
||||
2. **timeline**: 按時間順序的事件時間軸
|
||||
3. **participants**: 參與人員及其角色
|
||||
4. **resolution_process**: 詳細的處理過程描述
|
||||
5. **current_status**: 目前狀態說明
|
||||
6. **final_resolution**: 最終處置結果(若已解決)
|
||||
|
||||
請直接輸出 JSON,不要包含其他說明文字。"""
|
||||
Reference in New Issue
Block a user