This commit is contained in:
beabigegg
2025-08-17 18:34:38 +08:00
parent 9a263b6afb
commit 788e2409df
7 changed files with 35 additions and 30 deletions

Binary file not shown.

View File

@@ -213,8 +213,11 @@ def handle_meeting_detail(meeting_id):
return jsonify(meeting.to_dict()) return jsonify(meeting.to_dict())
if request.method == 'DELETE': if request.method == 'DELETE':
if get_jwt().get('role') != 'admin': current_user_id = get_jwt_identity()
return jsonify({"msg": "Administration rights required"}), 403 is_admin = get_jwt().get('role') == 'admin'
if not is_admin and str(meeting.created_by_id) != str(current_user_id):
return jsonify({"msg": "Only the meeting creator or an admin can delete this meeting."}), 403
db.session.delete(meeting) db.session.delete(meeting)
db.session.commit() db.session.commit()
return jsonify({"msg": "Meeting and associated action items deleted"}), 200 return jsonify({"msg": "Meeting and associated action items deleted"}), 200
@@ -235,9 +238,9 @@ def summarize_meeting(meeting_id):
@jwt_required() @jwt_required()
def preview_actions(meeting_id): def preview_actions(meeting_id):
meeting = Meeting.query.get_or_404(meeting_id) meeting = Meeting.query.get_or_404(meeting_id)
text_content = meeting.summary or meeting.transcript text_content = meeting.transcript # Always use the full transcript
if not text_content: if not text_content:
return jsonify({'error': 'Meeting has no summary or transcript to analyze.'}), 400 return jsonify({'error': 'Meeting has no transcript to analyze.'}), 400
task = preview_action_items_task.delay(text_content) task = preview_action_items_task.delay(text_content)
return jsonify({'task_id': task.id, 'status_url': f'/status/{task.id}'}), 202 return jsonify({'task_id': task.id, 'status_url': f'/status/{task.id}'}), 202

View File

@@ -40,6 +40,7 @@ const MeetingDetailPage = () => {
const [isAddActionItemOpen, setIsAddActionItemOpen] = useState(false); const [isAddActionItemOpen, setIsAddActionItemOpen] = useState(false);
const [newActionItem, setNewActionItem] = useState({ action: '', owner_id: '', due_date: '', item: '' }); const [newActionItem, setNewActionItem] = useState({ action: '', owner_id: '', due_date: '', item: '' });
const [previewedItems, setPreviewedItems] = useState([]); const [previewedItems, setPreviewedItems] = useState([]);
const [previewMessage, setPreviewMessage] = useState(''); // State for the preview result message
const fetchMeetingData = useCallback(async () => { const fetchMeetingData = useCallback(async () => {
try { try {
@@ -94,7 +95,11 @@ const MeetingDetailPage = () => {
} else if (previewTask) { // Handle preview success } else if (previewTask) { // Handle preview success
setPreviewTask(null); setPreviewTask(null);
if (updatedTask.state === 'SUCCESS' && updatedTask.info.items) { if (updatedTask.state === 'SUCCESS' && updatedTask.info.items) {
setPreviewedItems(updatedTask.info.items); if (updatedTask.info.items.length > 0) {
setPreviewedItems(updatedTask.info.items);
} else {
setPreviewMessage('文本中未找到可提取的行動項目。');
}
} }
} }
} }
@@ -141,6 +146,8 @@ const MeetingDetailPage = () => {
}; };
const handlePreviewActionItems = async () => { const handlePreviewActionItems = async () => {
setPreviewedItems([]); // Clear previous results
setPreviewMessage(''); // Clear previous messages
setPreviewTask({ state: 'PENDING', info: 'Initializing preview task...' }); setPreviewTask({ state: 'PENDING', info: 'Initializing preview task...' });
try { try {
// This now calls the async task endpoint // This now calls the async task endpoint
@@ -244,9 +251,10 @@ const MeetingDetailPage = () => {
)} )}
{canManageMeeting && (<Box sx={{ mt: 3 }}> {canManageMeeting && (<Box sx={{ mt: 3 }}>
<Button variant="outlined" startIcon={<PreviewIcon />} onClick={handlePreviewActionItems} disabled={previewTask || summaryTask || isEditingSummary || (!meeting.summary && !meeting.transcript)}> <Button variant="outlined" startIcon={<PreviewIcon />} onClick={handlePreviewActionItems} disabled={previewTask || summaryTask || isEditingSummary || !meeting.transcript}>
{previewTask ? <CircularProgress size={24} /> : "Preview Action Items"} {previewTask ? <CircularProgress size={24} /> : "Preview Action Items"}
</Button> </Button>
{previewMessage && <Alert severity="info" sx={{ mt: 2 }}>{previewMessage}</Alert>}
{previewedItems.length > 0 && (<Box> {previewedItems.length > 0 && (<Box>
<TableContainer component={Paper} sx={{ mt: 2 }}><Table size="small"> <TableContainer component={Paper} sx={{ mt: 2 }}><Table size="small">
<TableHead><TableRow><TableCell>Context/Item</TableCell><TableCell>Action</TableCell><TableCell>Owner</TableCell><TableCell>Due Date</TableCell></TableRow></TableHead> <TableHead><TableRow><TableCell>Context/Item</TableCell><TableCell>Action</TableCell><TableCell>Owner</TableCell><TableCell>Due Date</TableCell></TableRow></TableHead>

View File

@@ -1,6 +1,7 @@
# services/dify_client.py # services/dify_client.py
import os, json, re, requests import os, json, re, requests
from dotenv import load_dotenv from dotenv import load_dotenv
from flask import current_app
load_dotenv() load_dotenv()
@@ -19,12 +20,13 @@ def _post_request(endpoint: str, api_key: str, payload: dict):
} }
# For debugging the exact payload sent to the API # For debugging the exact payload sent to the API
print(f"Sending Dify request to {url}: {json.dumps(payload, indent=2, ensure_ascii=False)}") current_app.logger.debug(f"Sending Dify request to {url}: {json.dumps(payload, indent=2, ensure_ascii=False)}")
resp = requests.post(url, headers=headers, json=payload, timeout=TIMEOUT) resp = requests.post(url, headers=headers, json=payload, timeout=TIMEOUT)
current_app.logger.debug(f"Dify API Response ({resp.status_code}): {resp.text}")
if resp.status_code != 200: if resp.status_code != 200:
print(f"Dify API Error Response: {resp.text}")
resp.raise_for_status() resp.raise_for_status()
data = resp.json() data = resp.json()
@@ -59,11 +61,14 @@ def summarize_text(text: str, user_id: str = "system") -> str:
def extract_action_items(text: str, user_id: str = "system") -> list[dict]: def extract_action_items(text: str, user_id: str = "system") -> list[dict]:
api_key = os.getenv("DIFY_ACTION_EXTRACTOR_API_KEY") api_key = os.getenv("DIFY_ACTION_EXTRACTOR_API_KEY")
query = f"請從以下會議記錄中,提取所有行動項目(action items)並嚴格以JSON格式返回。會議記錄如下\n\n{text}"
payload = { payload = {
"inputs": {}, "inputs": {},
"response_mode": "blocking", "response_mode": "blocking",
"user": user_id, "user": user_id,
"query": text, "query": query,
} }
raw = _post_request("/chat-messages", api_key, payload) raw = _post_request("/chat-messages", api_key, payload)
@@ -90,4 +95,4 @@ def extract_action_items(text: str, user_id: str = "system") -> list[dict]:
"owner": i["owner"], "owner": i["owner"],
"due_date": i["duedate"], "due_date": i["duedate"],
}) })
return normalized return normalized

View File

@@ -362,37 +362,26 @@ def summarize_text_task(self, meeting_id):
def preview_action_items_task(self, text_content): def preview_action_items_task(self, text_content):
from app import app from app import app
with app.app_context(): with app.app_context():
from services.dify_client import extract_action_items
try: try:
self.update_progress(10, 100, "Requesting Dify for action items...") self.update_progress(10, 100, "Requesting Dify for action items...")
api_key = app.config.get("DIFY_ACTION_EXTRACTOR_API_KEY")
# We can reuse the logic from the summarizer to strip timestamps
plain_text = re.sub(r'^(\s*\[.*?\])\s*', '', text_content, flags=re.MULTILINE) plain_text = re.sub(r'^(\s*\[.*?\])\s*', '', text_content, flags=re.MULTILINE)
if not plain_text.strip(): if not plain_text.strip():
self.update_progress(100, 100, "Preview skipped, content is empty.")
return {'status': 'Success', 'items': []} return {'status': 'Success', 'items': []}
response = ask_dify(api_key, plain_text) # Use the robust client function
answer_text = response.get("answer", "") parsed_items = extract_action_items(plain_text)
self.update_progress(80, 100, "Parsing response...")
parsed_items = []
try:
# Find the JSON array within the response string
match = re.search(r'\[.*\]', answer_text, re.DOTALL)
if match:
json_str = match.group(0)
parsed_items = json.loads(json_str)
# Ensure it's a list, otherwise reset to empty
if not isinstance(parsed_items, list):
parsed_items = []
except (json.JSONDecodeError, TypeError):
# If parsing fails, leave it as an empty list
parsed_items = []
self.update_progress(100, 100, "Action item preview generated.") self.update_progress(100, 100, "Action item preview generated.")
return {'status': 'Success', 'items': parsed_items} return {'status': 'Success', 'items': parsed_items}
except Exception as e: except Exception as e:
# Log the exception for better debugging
import logging
logging.error(f"Error in preview_action_items_task: {e}", exc_info=True)
self.update_state( self.update_state(
state='FAILURE', state='FAILURE',
meta={'exc_type': type(e).__name__, 'exc_message': str(e)} meta={'exc_type': type(e).__name__, 'exc_message': str(e)}