diff --git a/__pycache__/api_routes.cpython-312.pyc b/__pycache__/api_routes.cpython-312.pyc index ea9fd4c..2e8c734 100644 Binary files a/__pycache__/api_routes.cpython-312.pyc and b/__pycache__/api_routes.cpython-312.pyc differ diff --git a/__pycache__/tasks.cpython-312.pyc b/__pycache__/tasks.cpython-312.pyc index 6dfca82..9c6deca 100644 Binary files a/__pycache__/tasks.cpython-312.pyc and b/__pycache__/tasks.cpython-312.pyc differ diff --git a/api_routes.py b/api_routes.py index 4c5aacf..95cd975 100644 --- a/api_routes.py +++ b/api_routes.py @@ -213,8 +213,11 @@ def handle_meeting_detail(meeting_id): return jsonify(meeting.to_dict()) if request.method == 'DELETE': - if get_jwt().get('role') != 'admin': - return jsonify({"msg": "Administration rights required"}), 403 + current_user_id = get_jwt_identity() + 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.commit() return jsonify({"msg": "Meeting and associated action items deleted"}), 200 @@ -235,9 +238,9 @@ def summarize_meeting(meeting_id): @jwt_required() def preview_actions(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: - 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) return jsonify({'task_id': task.id, 'status_url': f'/status/{task.id}'}), 202 diff --git a/frontend/src/pages/MeetingDetailPage.jsx b/frontend/src/pages/MeetingDetailPage.jsx index 73ce933..8ce97b8 100644 --- a/frontend/src/pages/MeetingDetailPage.jsx +++ b/frontend/src/pages/MeetingDetailPage.jsx @@ -40,6 +40,7 @@ const MeetingDetailPage = () => { const [isAddActionItemOpen, setIsAddActionItemOpen] = useState(false); const [newActionItem, setNewActionItem] = useState({ action: '', owner_id: '', due_date: '', item: '' }); const [previewedItems, setPreviewedItems] = useState([]); + const [previewMessage, setPreviewMessage] = useState(''); // State for the preview result message const fetchMeetingData = useCallback(async () => { try { @@ -94,7 +95,11 @@ const MeetingDetailPage = () => { } else if (previewTask) { // Handle preview success setPreviewTask(null); 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 () => { + setPreviewedItems([]); // Clear previous results + setPreviewMessage(''); // Clear previous messages setPreviewTask({ state: 'PENDING', info: 'Initializing preview task...' }); try { // This now calls the async task endpoint @@ -244,9 +251,10 @@ const MeetingDetailPage = () => { )} {canManageMeeting && ( - + {previewMessage && {previewMessage}} {previewedItems.length > 0 && ( Context/ItemActionOwnerDue Date diff --git a/services/__pycache__/dify_client.cpython-312.pyc b/services/__pycache__/dify_client.cpython-312.pyc index 39614f1..2861621 100644 Binary files a/services/__pycache__/dify_client.cpython-312.pyc and b/services/__pycache__/dify_client.cpython-312.pyc differ diff --git a/services/dify_client.py b/services/dify_client.py index d2f80c1..e45b3f7 100644 --- a/services/dify_client.py +++ b/services/dify_client.py @@ -1,6 +1,7 @@ # services/dify_client.py import os, json, re, requests from dotenv import load_dotenv +from flask import current_app 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 - 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) + current_app.logger.debug(f"Dify API Response ({resp.status_code}): {resp.text}") + if resp.status_code != 200: - print(f"Dify API Error Response: {resp.text}") resp.raise_for_status() 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]: api_key = os.getenv("DIFY_ACTION_EXTRACTOR_API_KEY") + + query = f"請從以下會議記錄中,提取所有行動項目(action items),並嚴格以JSON格式返回。會議記錄如下:\n\n{text}" + payload = { "inputs": {}, "response_mode": "blocking", "user": user_id, - "query": text, + "query": query, } 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"], "due_date": i["duedate"], }) - return normalized \ No newline at end of file + return normalized diff --git a/tasks.py b/tasks.py index c204708..7434fc5 100644 --- a/tasks.py +++ b/tasks.py @@ -362,37 +362,26 @@ def summarize_text_task(self, meeting_id): def preview_action_items_task(self, text_content): from app import app with app.app_context(): + from services.dify_client import extract_action_items try: 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) if not plain_text.strip(): + self.update_progress(100, 100, "Preview skipped, content is empty.") return {'status': 'Success', 'items': []} - response = ask_dify(api_key, plain_text) - answer_text = response.get("answer", "") - self.update_progress(80, 100, "Parsing response...") + # Use the robust client function + parsed_items = extract_action_items(plain_text) - 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.") return {'status': 'Success', 'items': parsed_items} 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( state='FAILURE', meta={'exc_type': type(e).__name__, 'exc_message': str(e)}