From 8fe1c75288dddaaa1ab2f88a69a478c11b1635d8 Mon Sep 17 00:00:00 2001 From: ValueOn AG
Please try again.
+ + + + """, + status_code=400 ) - # Store user information - user_info = {} - if "id_token_claims" in result: - user_info = { - "name": result["id_token_claims"].get("name", ""), - "email": result["id_token_claims"].get("preferred_username", ""), - } - - # If we have user info from the token, use that for user_id - token_user_id = result["id_token_claims"].get("oid") or result["id_token_claims"].get("sub") - if token_user_id: - user_id = token_user_id - elif not user_id and user_info.get("email"): - # Fall back to email-based ID if no other ID is available - user_id = user_info.get("email", "user").replace("@", "_").replace(".", "_") - - # Save tokens to file - token_data = { - "access_token": result["access_token"], - "refresh_token": result.get("refresh_token", ""), - "user_info": user_info, - "timestamp": datetime.now().isoformat() - } - - # Ensure token directory exists - if not os.path.exists(TOKEN_DIR): - os.makedirs(TOKEN_DIR) - - # Save token to file - token_file = os.path.join(TOKEN_DIR, f"{user_id}.json") - with open(token_file, 'w') as f: - json.dump(token_data, f) - - logger.info(f"User authenticated: {user_info.get('email', 'unknown')}") - - # Create a success page - html_content = """ - + # Get user info from token + user_info = get_user_info_from_token(token_response["access_token"]) + if not user_info: + logger.error("Failed to get user info from token") + return HTMLResponse( + content=""" + + +Could not retrieve user information.
+ + + + """, + status_code=400 + ) + + # Add user info to token data + token_response["user_info"] = user_info + + # Store tokens in session storage for the frontend to pick up + response = HTMLResponse( + content=f""" - - -You have successfully authenticated with Microsoft.
-You can now close this tab and return to the application.
-Your email templates will now be able to create drafts in your mailbox.
- Close Window -Welcome, {user_info.get('name', 'User')}!
+This window will close automatically.
+ + """ - - return HTMLResponse(content=html_content) + ) - else: - logger.warning("No id_token_claims found in result") - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content={"message": "Failed to retrieve user information"} - ) + return response except Exception as e: - logger.error(f"Error in auth callback: {str(e)}", exc_info=True) - return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content={"message": f"Error in auth callback: {str(e)}"} + logger.error(f"Authentication failed: {str(e)}") + return HTMLResponse( + content=""" + + +An error occurred during authentication.
+ + + + """, + status_code=500 ) - + @router.get("/status") -async def auth_status( - msft_user_id: Optional[str] = Cookie(None), - currentUser: Dict[str, Any] = Depends(getCurrentActiveUser) -): +async def auth_status(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): """Check Microsoft authentication status""" try: - # Get user ID - if not msft_user_id: - mandateId, userId = await getUserContext(currentUser) - user_id = str(userId) - else: - user_id = msft_user_id + # Get current user context + mandateId, userId = await getUserContext(currentUser) + if not mandateId or not userId: + logger.info("No user context found") + return JSONResponse({ + "authenticated": False, + "message": "Not authenticated with Microsoft" + }) + + # Check if we have a token for the current user + token_data = await load_token_from_file(currentUser) - # Check if user has a token - token_data = load_token_from_file(user_id) if not token_data: - return JSONResponse( - content={"authenticated": False, "message": "Not authenticated with Microsoft"} + logger.info(f"No token data found for user {userId}") + return JSONResponse({ + "authenticated": False, + "message": "Not authenticated with Microsoft" + }) + + # Verify token is still valid + if not verify_token(token_data["access_token"]): + logger.info("Token invalid, attempting refresh") + # Try to refresh the token + if not await refresh_token(userId, currentUser): + logger.info("Token refresh failed") + return JSONResponse({ + "authenticated": False, + "message": "Token expired and refresh failed" + }) + # Reload token data after refresh + token_data = await load_token_from_file(currentUser) + + # Get user info from token data + user_info = token_data.get("user_info") + if not user_info: + logger.info("No user info found in token data") + return JSONResponse({ + "authenticated": False, + "message": "No user information available" + }) + + logger.info(f"User {user_info.get('name')} is authenticated") + return JSONResponse({ + "authenticated": True, + "user": user_info + }) + + except Exception as e: + logger.error(f"Error checking authentication status: {str(e)}") + return JSONResponse({ + "authenticated": False, + "message": f"Error checking authentication status: {str(e)}" + }) + +@router.post("/logout") +async def logout(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): + """Logout from Microsoft""" + try: + # Get current user context + mandateId, userId = await getUserContext(currentUser) + if not mandateId or not userId: + return JSONResponse({ + "message": "Not authenticated with Microsoft" + }) + + # Get LucyDOM interface for current user + mydom = getLucydomInterface( + mandateId=mandateId, + userId=userId + ) + if not mydom: + return JSONResponse({ + "message": "Not authenticated with Microsoft" + }) + + # Remove token from database + tokens = mydom.db.getRecordset("msftTokens", recordFilter={ + "mandateId": mandateId, + "userId": userId + }) + + if tokens and len(tokens) > 0: + mydom.db.recordDelete("msftTokens", tokens[0]["id"]) + logger.info(f"Removed Microsoft token for user {userId}") + + return JSONResponse({ + "message": "Successfully logged out from Microsoft" + }) + + except Exception as e: + logger.error(f"Error during logout: {str(e)}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Logout failed: {str(e)}" + ) + +@router.get("/token") +async def get_access_token(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): + """Get the current user's access token for Microsoft Graph API""" + try: + # Check if we have a token for the current user + token_data = await load_token_from_file(currentUser) + + if not token_data: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated with Microsoft" ) - # Check if token is valid - if not verify_token(token_data.get("access_token", "")): - # Try to refresh token - if refresh_token(user_id): - token_data = load_token_from_file(user_id) - user_info = token_data.get("user_info", {}) - return JSONResponse( - content={ - "authenticated": True, - "message": "Token refreshed successfully", - "user": user_info - } - ) - else: - return JSONResponse( - content={ - "authenticated": False, - "message": "Token expired and couldn't be refreshed" - } + # Verify token is still valid + if not verify_token(token_data["access_token"]): + # Try to refresh the token + if not await refresh_token(currentUser["id"], currentUser): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Token expired and refresh failed" ) + # Reload token data after refresh + token_data = await load_token_from_file(currentUser) + + return JSONResponse({ + "access_token": token_data["access_token"] + }) - # Token is valid, return user info - user_info = token_data.get("user_info", {}) - return JSONResponse( - content={ - "authenticated": True, - "message": "Authenticated with Microsoft", - "user": user_info - } - ) - except Exception as e: - logger.error(f"Error checking auth status: {str(e)}") - return JSONResponse( + logger.error(f"Error getting access token: {str(e)}") + raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content={"message": f"Error checking auth status: {str(e)}"} + detail=f"Error getting access token: {str(e)}" ) + +@router.post("/save-token") +async def save_token(token_data: Dict[str, Any], currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): + """Save Microsoft token data from frontend""" + try: + # Save token to database + success = await save_token_to_file(token_data, currentUser) + + if success: + return JSONResponse({ + "success": True, + "message": "Token saved successfully" + }) + else: + return JSONResponse({ + "success": False, + "message": "Failed to save token" + }) + + except Exception as e: + logger.error(f"Error saving token: {str(e)}") + return JSONResponse({ + "success": False, + "message": f"Error saving token: {str(e)}" + }) + +async def generateFinalMessage(self, objUserResponse: str, objFinalDocuments: List[str], objResults: List[Dict[str, Any]]) -> Dict[str, Any]: + """Generate the final message for the workflow""" + try: + # Get list of delivered documents + matchingDocuments = [] + for result in objResults: + if "documents" in result: + for doc in result["documents"]: + if doc.get("label") in objFinalDocuments: + matchingDocuments.append(doc.get("label")) + + # Use the mydom for language-aware AI calls + finalPrompt = await self.mydom.callAi([ + {"role": "system", "content": "You are a project manager, who delivers results to a user."}, + {"role": "user", "content": f""" +Give a brief summary of what has been accomplished, referencing the initial request (objUserResponse). List only the files that have been successfully delivered (filesDelivered). Keep the message concise and professional. + +Here the data: +objUserResponse = {self.parseJson2text(objUserResponse)} +filesDelivered = {self.parseJson2text(matchingDocuments)} +""" + } + ], produceUserAnswer=True) + + # Create basic message structure with proper fields + logger.debug(f"FINAL PROMPT = {self.parseJson2text(finalPrompt)}.") + finalMessage = { + "role": "assistant", + "agentName": "Project Manager", + "content": finalPrompt, + "documents": [] # DO NOT include the results documents, already with agents + } + + logger.debug(f"FINAL MESSAGE = {self.parseJson2text(finalMessage)}.") + return finalMessage + + except Exception as e: + logger.error(f"Error generating final message: {str(e)}") + return { + "role": "assistant", + "agentName": "Project Manager", + "content": "I apologize, but there was an error generating the final message. Please check the logs for more details.", + "documents": [] + } diff --git a/static/31_email_preview.html b/static/31_email_preview.html new file mode 100644 index 00000000..2e367670 --- /dev/null +++ b/static/31_email_preview.html @@ -0,0 +1,42 @@ + + + + + +Sehr geehrter Herr Muster,
ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob Ihnen dieser neue Termin passt.
Vielen Dank für Ihr Verständnis.
Mit freundlichen Grüßen,
[Ihr Name]
Sehr geehrter Herr Muster,
ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob Ihnen dieser neue Termin passt.
Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.
Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]
Sehr geehrter Herr Motsch,
ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob Ihnen dieser neue Termin passt.
Vielen Dank für Ihr Verständnis.
Mit freundlichen Grüßen,
[Ihr Name]
Sehr geehrter Herr Motsch,
ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob Ihnen dieser neue Termin passt.
Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.
Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]
Sehr geehrter Empfänger,
im Anhang finden Sie die beiden Python-Skripte, die Sie angefordert haben. Bitte zögern Sie nicht, sich bei Fragen oder weiteren Anliegen an mich zu wenden.
Mit freundlichen Grüßen,
Ihr Name
Sehr geehrter Empf\u00e4nger,
im Anhang finden Sie die beiden Python-Skripte, die Sie angefordert haben. Bitte z\u00f6gern Sie nicht, sich bei Fragen oder weiteren Anliegen an mich zu wenden.
Mit freundlichen Gr\u00fc\u00dfen,
Ihr Name
Hello Development Team,
+Please find attached the Python modules 'defAttributes.py' and 'gatewayInterface.py'. The 'defAttributes.py' file defines a Pydantic model for attribute definitions and includes a function to convert it into a list of AttributeDefinition objects. The 'gatewayInterface.py' file serves as an interface to the Gateway system, managing users and mandates.
Kindly review the attached files and provide your feedback.
+Best regards,
[Your Name]
Hello Development Team,
\nPlease find attached the Python modules 'defAttributes.py' and 'gatewayInterface.py'. The 'defAttributes.py' file defines a Pydantic model for attribute definitions and includes a function to convert it into a list of AttributeDefinition objects. The 'gatewayInterface.py' file serves as an interface to the Gateway system, managing users and mandates.
Kindly review the attached files and provide your feedback.
\nBest regards,
[Your Name]
Sehr geehrter Herr Muster,
ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unseren geplanten Termin um 10 Uhr zu verschieben. Könnten wir den Termin stattdessen auf Freitag verlegen?
Bitte lassen Sie mich wissen, ob dieser neue Termin für Sie passt.
Vielen Dank für Ihr Verständnis.
Mit freundlichen Grüßen,
[Ihr Name]
Sehr geehrter Herr Muster,
ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unseren geplanten Termin um 10 Uhr zu verschieben. K\u00f6nnten wir den Termin stattdessen auf Freitag verlegen?
Bitte lassen Sie mich wissen, ob dieser neue Termin f\u00fcr Sie passt.
Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.
Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]
Sehr geehrter Herr Muster,
ich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es möglich wäre, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die Dokumente 'gatewayInterface.py' und 'defAttributes.py'.
Ich freue mich auf Ihre Rückmeldung.
Mit freundlichen Grüßen,
[Ihr Name]
Sehr geehrter Herr Muster,
ich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es m\u00f6glich w\u00e4re, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die Dokumente 'gatewayInterface.py' und 'defAttributes.py'.
Ich freue mich auf Ihre R\u00fcckmeldung.
Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]
Sehr geehrte Damen und Herren,
anbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte überprüfen Sie die Anhänge und lassen Sie uns wissen, falls Sie weitere Informationen benötigen.
Mit freundlichen Grüßen,
Ihr Team
Sehr geehrte Damen und Herren,
anbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte \u00fcberpr\u00fcfen Sie die Anh\u00e4nge und lassen Sie uns wissen, falls Sie weitere Informationen ben\u00f6tigen.
Mit freundlichen Gr\u00fc\u00dfen,
Ihr Team
Sehr geehrte/r Empfänger/in,
anbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte lassen Sie mich wissen, falls Sie weitere Informationen benötigen.
Mit freundlichen Grüßen,
Ihr Team
Sehr geehrte/r Empf\u00e4nger/in,
anbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte lassen Sie mich wissen, falls Sie weitere Informationen ben\u00f6tigen.
Mit freundlichen Gr\u00fc\u00dfen,
Ihr Team
PO}-ebdI#TIuA|cJwG=+ocSOm>^6+Q$dd2x&a=|@)>zj~EqvKtSo*{1a
z9@|*^X0gc}#5`@Ddb5j)Y{}?S$GYZu&FKZjle;5khT-fSmrqK=uZcQS+XUVz{&QKT
z0J}GEZTg&hd1?yYT0x9pg7ct~0s=kcK00_7Ufz)px)bxQI-M!vaG3_{zTF$g3Qz8W
zYcI|*!lXK#hDaRl*#}Yrkk>|7-kT