From 03c17d750617e1aca4cf10642ae4fd620688cda4 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Fri, 13 Jun 2025 00:41:51 +0200
Subject: [PATCH] methods done
---
app.py | 2 +-
modules/connectors/connectorAiAnthropic.py | 139 +--
modules/connectors/connectorAiOpenai.py | 4 +-
modules/interfaces/interfaceAi.py | 38 -
modules/interfaces/interfaceAiCalls.py | 141 +++
...viceAppAccess.py => interfaceAppAccess.py} | 2 +-
...erviceAppModel.py => interfaceAppModel.py} | 0
...viceAppClass.py => interfaceAppObjects.py} | 30 +-
...ceChatAccess.py => interfaceChatAccess.py} | 2 +-
...viceChatModel.py => interfaceChatModel.py} | 0
...ceChatClass.py => interfaceChatObjects.py} | 14 +-
...tAccess.py => interfaceComponentAccess.py} | 4 +-
...entModel.py => interfaceComponentModel.py} | 0
...tClass.py => interfaceComponentObjects.py} | 20 +-
modules/methods/methodBase.py | 11 +-
modules/methods/methodCoder.py | 81 +-
modules/methods/methodDocument.py | 209 ++--
modules/methods/methodExcel.py | 188 ++++
modules/methods/methodOperator.py | 4 +-
modules/methods/methodOutlook.py | 294 +++---
modules/methods/methodPowerpoint.py | 447 ++++-----
modules/methods/methodSharepoint.py | 466 ++++-----
modules/methods/methodWeb.py | 569 +++--------
modules/routes/routeAdmin.py | 2 +-
modules/routes/routeAttributes.py | 2 +-
modules/routes/routeDataConnections.py | 4 +-
modules/routes/routeDataFiles.py | 34 +-
modules/routes/routeDataMandates.py | 14 +-
modules/routes/routeDataPrompts.py | 16 +-
modules/routes/routeDataUsers.py | 14 +-
modules/routes/routeSecurityGoogle.py | 4 +-
modules/routes/routeSecurityLocal.py | 4 +-
modules/routes/routeSecurityMsft.py | 4 +-
modules/routes/routeWorkflows.py | 58 +-
modules/security/auth.py | 4 +-
modules/workflow/managerChat.py | 930 ++++++------------
modules/workflow/managerDocument.py | 2 +-
modules/workflow/managerWorkflow.py | 8 +-
modules/workflow/processorDocument.py | 71 +-
modules/workflow/serviceContainer.py | 339 ++++---
notes/changelog.txt | 84 +-
notes/data_specification.md | 16 +-
notes/methodbased_specification.md | 8 +-
43 files changed, 1761 insertions(+), 2522 deletions(-)
delete mode 100644 modules/interfaces/interfaceAi.py
create mode 100644 modules/interfaces/interfaceAiCalls.py
rename modules/interfaces/{serviceAppAccess.py => interfaceAppAccess.py} (99%)
rename modules/interfaces/{serviceAppModel.py => interfaceAppModel.py} (100%)
rename modules/interfaces/{serviceAppClass.py => interfaceAppObjects.py} (97%)
rename modules/interfaces/{serviceChatAccess.py => interfaceChatAccess.py} (98%)
rename modules/interfaces/{serviceChatModel.py => interfaceChatModel.py} (100%)
rename modules/interfaces/{serviceChatClass.py => interfaceChatObjects.py} (99%)
rename modules/interfaces/{serviceManagementAccess.py => interfaceComponentAccess.py} (99%)
rename modules/interfaces/{serviceManagementModel.py => interfaceComponentModel.py} (100%)
rename modules/interfaces/{serviceManagementClass.py => interfaceComponentObjects.py} (98%)
create mode 100644 modules/methods/methodExcel.py
diff --git a/app.py b/app.py
index ed80f158..05b54a9c 100644
--- a/app.py
+++ b/app.py
@@ -105,7 +105,7 @@ async def lifespan(app: FastAPI):
logger.info("Application is starting up")
# Initialize root interface to ensure database is properly set up
- from modules.interfaces.serviceAppClass import getRootInterface
+ from modules.interfaces.interfaceAppObjects import getRootInterface
getRootInterface()
yield
diff --git a/modules/connectors/connectorAiAnthropic.py b/modules/connectors/connectorAiAnthropic.py
index 9f452790..fce51e97 100644
--- a/modules/connectors/connectorAiAnthropic.py
+++ b/modules/connectors/connectorAiAnthropic.py
@@ -17,7 +17,7 @@ def loadConfigData():
"maxTokens": int(APP_CONFIG.get('Connector_AiAnthropic_MAX_TOKENS'))
}
-class ChatService:
+class AiAnthropic:
"""Connector for communication with the Anthropic API."""
def __init__(self):
@@ -39,7 +39,7 @@ class ChatService:
logger.info(f"Anthropic Connector initialized with model: {self.modelName}")
- async def callApi(self, messages: List[Dict[str, Any]], temperature: float = None, maxTokens: int = None) -> Dict[str, Any]:
+ async def callAiBasic(self, messages: List[Dict[str, Any]], temperature: float = None, maxTokens: int = None) -> Dict[str, Any]:
"""
Calls the Anthropic API with the given messages.
@@ -49,15 +49,12 @@ class ChatService:
maxTokens: Maximum number of tokens in the response
Returns:
- The response converted to OpenAI format
+ The response in OpenAI format
Raises:
HTTPException: For errors in API communication
"""
try:
- # Convert OpenAI format to Anthropic format
- formattedMessages = self._convertToAnthropicFormat(messages)
-
# Use parameters from configuration if none were overridden
if temperature is None:
temperature = self.config.get("temperature", 0.2)
@@ -68,7 +65,7 @@ class ChatService:
# Create Anthropic API payload
payload = {
"model": self.modelName,
- "messages": formattedMessages,
+ "messages": messages,
"temperature": temperature,
"max_tokens": maxTokens
}
@@ -82,110 +79,44 @@ class ChatService:
logger.error(f"Anthropic API error: {response.status_code} - {response.text}")
raise HTTPException(status_code=500, detail="Error communicating with Anthropic API")
- # Convert response from Anthropic format to OpenAI format
+ # Parse response
anthropicResponse = response.json()
- openaiFormattedResponse = self._convertToOpenaiFormat(anthropicResponse)
- return openaiFormattedResponse
+ # Extract content from response
+ content = ""
+ if "content" in anthropicResponse:
+ if isinstance(anthropicResponse["content"], list):
+ # Content is a list of parts (in newer API versions)
+ for part in anthropicResponse["content"]:
+ if part.get("type") == "text":
+ content += part.get("text", "")
+ else:
+ # Direct content as string (in older API versions)
+ content = anthropicResponse["content"]
+
+ # Return in OpenAI format
+ return {
+ "id": anthropicResponse.get("id", ""),
+ "object": "chat.completion",
+ "created": anthropicResponse.get("created", 0),
+ "model": anthropicResponse.get("model", self.modelName),
+ "choices": [
+ {
+ "message": {
+ "role": "assistant",
+ "content": content
+ },
+ "index": 0,
+ "finish_reason": "stop"
+ }
+ ]
+ }
except Exception as e:
logger.error(f"Error calling Anthropic API: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error calling Anthropic API: {str(e)}")
- def _convertToAnthropicFormat(self, openaiMessages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
- """
- Converts messages from OpenAI format to Anthropic format.
-
- OpenAI uses:
- [{"role": "system", "content": "..."},
- {"role": "user", "content": "..."},
- {"role": "assistant", "content": "..."}]
-
- Anthropic uses:
- [{"role": "user", "content": "..."},
- {"role": "assistant", "content": "..."}]
-
- Note: Anthropic has no direct system message equivalent,
- so we add system messages to the first user message.
- """
- anthropicMessages = []
- systemContent = ""
-
- # First extract all system messages
- for msg in openaiMessages:
- if msg.get("role") == "system":
- systemContent += msg.get("content", "") + "\n\n"
-
- # Convert the remaining messages
- for msg in openaiMessages:
- role = msg.get("role")
- content = msg.get("content", "")
-
- # Skip system messages (already extracted)
- if role == "system":
- continue
-
- # For the first user message: prepend system content if available
- if role == "user" and systemContent and not any(m.get("role") == "user" for m in anthropicMessages):
- if isinstance(content, str):
- content = systemContent + content
- elif isinstance(content, list):
- # If content is an array (for multimodal messages)
- textParts = []
- for part in content:
- if part.get("type") == "text":
- textParts.append(part)
-
- if textParts:
- # Create a new text part with combined content
- textParts[0] = {
- "type": "text",
- "text": systemContent + textParts[0].get("text", "")
- }
-
- # Anthropic only supports "user" and "assistant" roles
- if role not in ["user", "assistant"]:
- role = "user"
-
- anthropicMessages.append({"role": role, "content": content})
-
- return anthropicMessages
-
- def _convertToOpenaiFormat(self, anthropicResponse: Dict[str, Any]) -> Dict[str, Any]:
- """
- Converts a response from Anthropic format to OpenAI format.
- """
- # Extract content from Anthropic response
- content = ""
- if "content" in anthropicResponse:
- if isinstance(anthropicResponse["content"], list):
- # Content is a list of parts (in newer API versions)
- for part in anthropicResponse["content"]:
- if part.get("type") == "text":
- content += part.get("text", "")
- else:
- # Direct content as string (in older API versions)
- content = anthropicResponse["content"]
-
- # Create OpenAI-formatted response
- return {
- "id": anthropicResponse.get("id", ""),
- "object": "chat.completion",
- "created": anthropicResponse.get("created", 0),
- "model": anthropicResponse.get("model", self.modelName),
- "choices": [
- {
- "message": {
- "role": "assistant",
- "content": content
- },
- "index": 0,
- "finish_reason": "stop"
- }
- ]
- }
-
- async def analyzeImage(self, imageData: Union[str, bytes], mimeType: str = None, prompt: str = "Describe this image") -> str:
+ async def callAiImage(self, prompt: str, imageData: Union[str, bytes], mimeType: str = None) -> str:
"""
Analyzes an image using Anthropic's vision capabilities.
diff --git a/modules/connectors/connectorAiOpenai.py b/modules/connectors/connectorAiOpenai.py
index 1014137d..847380cd 100644
--- a/modules/connectors/connectorAiOpenai.py
+++ b/modules/connectors/connectorAiOpenai.py
@@ -18,7 +18,7 @@ def loadConfigData():
"maxTokens": int(APP_CONFIG.get('Connector_AiOpenai_MAX_TOKENS'))
}
-class AiConnector:
+class AiOpenai:
"""Connector for communication with the OpenAI API."""
def __init__(self):
@@ -85,7 +85,7 @@ class AiConnector:
logger.error(f"Error calling OpenAI API: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error calling OpenAI API: {str(e)}")
- async def callAiImage(self, imageData: Union[str, bytes], mimeType: str = None, prompt: str = "Describe this image") -> str:
+ async def callAiImage(self, prompt: str, imageData: Union[str, bytes], mimeType: str = None) -> str:
"""
Analyzes an image with the OpenAI Vision API.
diff --git a/modules/interfaces/interfaceAi.py b/modules/interfaces/interfaceAi.py
deleted file mode 100644
index 5c19ffb0..00000000
--- a/modules/interfaces/interfaceAi.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import logging
-from typing import Dict, Any, List, Union
-from modules.connectors.connectorAiOpenai import AiConnector
-
-logger = logging.getLogger(__name__)
-
-class AiInterface:
- """Interface for AI service interactions"""
-
- def __init__(self):
- self.service = AiConnector()
-
- async def callAiBasic(self, messages: List[Dict[str, str]], produceUserAnswer: bool = False, temperature: float = None) -> str:
- """Enhanced AI service call with language support."""
-
- # Add language instruction for user-facing responses
- if produceUserAnswer and hasattr(self, 'userLanguage') and self.userLanguage:
- ltext = f"Please respond in '{self.userLanguage}' language."
- if messages and messages[0]["role"] == "system":
- if "language" not in messages[0]["content"].lower():
- messages[0]["content"] = f"{ltext} {messages[0]['content']}"
- else:
- # Insert a system message with language instruction
- messages.insert(0, {
- "role": "system",
- "content": ltext
- })
-
- # Call the AI service
- return await self.service.callAiBasic(messages, temperature=temperature)
-
- async def callAiImage(self, imageData: Union[str, bytes], mimeType: str = None, prompt: str = "Describe this image") -> str:
- """Enhanced AI service call with language support."""
- if not self.service:
- logger.error("AI service not set in AiInterface")
- return "Error: AI service not available"
- return await self.service.callAiImage(imageData, mimeType, prompt)
-
diff --git a/modules/interfaces/interfaceAiCalls.py b/modules/interfaces/interfaceAiCalls.py
new file mode 100644
index 00000000..79a662af
--- /dev/null
+++ b/modules/interfaces/interfaceAiCalls.py
@@ -0,0 +1,141 @@
+import logging
+from typing import Dict, Any, List, Union, Optional
+from modules.connectors.connectorAiOpenai import AiOpenai
+from modules.connectors.connectorAiAnthropic import AiAnthropic
+
+logger = logging.getLogger(__name__)
+
+class AiCalls:
+ """Interface for AI service interactions"""
+
+ def __init__(self):
+ self.openaiService = AiOpenai()
+ self.anthropicService = AiAnthropic()
+
+ async def callAiTextBasic(self, prompt: str, context: Optional[str] = None) -> str:
+ """
+ Basic text processing using OpenAI.
+
+ Args:
+ prompt: The user prompt to process
+ context: Optional system context/prompt
+
+ Returns:
+ The AI response as text
+ """
+ # Prepare messages in OpenAI format
+ messages = []
+
+ # Add system message if context provided
+ if context:
+ messages.append({
+ "role": "system",
+ "content": context
+ })
+
+ # Add user message
+ messages.append({
+ "role": "user",
+ "content": prompt
+ })
+
+ # Add language instruction for user-facing responses
+ if hasattr(self, 'userLanguage') and self.userLanguage:
+ ltext = f"Please respond in '{self.userLanguage}' language."
+ if messages and messages[0]["role"] == "system":
+ if "language" not in messages[0]["content"].lower():
+ messages[0]["content"] = f"{ltext} {messages[0]['content']}"
+ else:
+ messages.insert(0, {
+ "role": "system",
+ "content": ltext
+ })
+
+ try:
+ return await self.openaiService.callAiBasic(messages)
+ except Exception as e:
+ logger.error(f"Error in OpenAI call: {str(e)}")
+ return f"Error: {str(e)}"
+
+ async def callAiTextAdvanced(self, prompt: str, context: Optional[str] = None) -> str:
+ """
+ Advanced text processing using Anthropic.
+
+ Args:
+ prompt: The user prompt to process
+ context: Optional system context/prompt
+
+ Returns:
+ The AI response as text
+ """
+ # Prepare messages in OpenAI format
+ messages = []
+
+ # Add system message if context provided
+ if context:
+ messages.append({
+ "role": "system",
+ "content": context
+ })
+
+ # Add user message
+ messages.append({
+ "role": "user",
+ "content": prompt
+ })
+
+ # Add language instruction for user-facing responses
+ if hasattr(self, 'userLanguage') and self.userLanguage:
+ ltext = f"Please respond in '{self.userLanguage}' language."
+ if messages and messages[0]["role"] == "system":
+ if "language" not in messages[0]["content"].lower():
+ messages[0]["content"] = f"{ltext} {messages[0]['content']}"
+ else:
+ messages.insert(0, {
+ "role": "system",
+ "content": ltext
+ })
+
+ try:
+ response = await self.anthropicService.callAiBasic(messages)
+ return response["choices"][0]["message"]["content"]
+ except Exception as e:
+ logger.error(f"Error in Anthropic call: {str(e)}")
+ return f"Error: {str(e)}"
+
+ async def callAiImageBasic(self, prompt: str, imageData: Union[str, bytes], mimeType: str = None) -> str:
+ """
+ Basic image processing using OpenAI.
+
+ Args:
+ prompt: The prompt for image analysis
+ imageData: The image data (file path or bytes)
+ mimeType: Optional MIME type of the image
+
+ Returns:
+ The AI response as text
+ """
+ try:
+ return await self.openaiService.callAiImage(imageData, mimeType, prompt)
+ except Exception as e:
+ logger.error(f"Error in OpenAI image call: {str(e)}")
+ return f"Error: {str(e)}"
+
+ async def callAiImageAdvanced(self, prompt: str, imageData: Union[str, bytes], mimeType: str = None) -> str:
+ """
+ Advanced image processing using Anthropic.
+
+ Args:
+ prompt: The prompt for image analysis
+ imageData: The image data (file path or bytes)
+ mimeType: Optional MIME type of the image
+
+ Returns:
+ The AI response as text
+ """
+ try:
+ return await self.anthropicService.callAiImage(prompt, imageData, mimeType)
+ except Exception as e:
+ logger.error(f"Error in Anthropic image call: {str(e)}")
+ return f"Error: {str(e)}"
+
diff --git a/modules/interfaces/serviceAppAccess.py b/modules/interfaces/interfaceAppAccess.py
similarity index 99%
rename from modules/interfaces/serviceAppAccess.py
rename to modules/interfaces/interfaceAppAccess.py
index a7b301f8..262ab3eb 100644
--- a/modules/interfaces/serviceAppAccess.py
+++ b/modules/interfaces/interfaceAppAccess.py
@@ -5,7 +5,7 @@ Access control for the Application.
import logging
from typing import Dict, Any, List, Optional
from datetime import datetime
-from modules.interfaces.serviceAppModel import UserPrivilege, Session, User
+from modules.interfaces.interfaceAppModel import UserPrivilege, Session, User
# Configure logger
logger = logging.getLogger(__name__)
diff --git a/modules/interfaces/serviceAppModel.py b/modules/interfaces/interfaceAppModel.py
similarity index 100%
rename from modules/interfaces/serviceAppModel.py
rename to modules/interfaces/interfaceAppModel.py
diff --git a/modules/interfaces/serviceAppClass.py b/modules/interfaces/interfaceAppObjects.py
similarity index 97%
rename from modules/interfaces/serviceAppClass.py
rename to modules/interfaces/interfaceAppObjects.py
index fc5e4791..92c55834 100644
--- a/modules/interfaces/serviceAppClass.py
+++ b/modules/interfaces/interfaceAppObjects.py
@@ -14,8 +14,8 @@ import uuid
from modules.connectors.connectorDbJson import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
-from modules.interfaces.serviceAppAccess import AppAccess
-from modules.interfaces.serviceAppModel import (
+from modules.interfaces.interfaceAppAccess import AppAccess
+from modules.interfaces.interfaceAppModel import (
User, Mandate, UserInDB, UserConnection,
Session, AuthEvent, AuthAuthority, UserPrivilege,
ConnectionStatus, Token, LocalToken, GoogleToken, MsftToken
@@ -24,16 +24,16 @@ from modules.shared.attributeUtils import ModelMixin
logger = logging.getLogger(__name__)
-# Singleton factory for GatewayInterface instances per context
+# Singleton factory for AppObjects instances per context
_gatewayInterfaces = {}
# Root interface instance
-_rootGatewayInterface = None
+_rootAppObjects = None
# Password-Hashing
pwdContext = CryptContext(schemes=["argon2"], deprecated="auto")
-class GatewayInterface:
+class AppObjects:
"""
Interface to the Gateway system.
Manages users and mandates.
@@ -793,9 +793,9 @@ class GatewayInterface:
# Public Methods
-def getInterface(currentUser: User) -> GatewayInterface:
+def getInterface(currentUser: User) -> AppObjects:
"""
- Returns a GatewayInterface instance for the current user.
+ Returns a AppObjects instance for the current user.
Handles initialization of database and records.
"""
if not currentUser:
@@ -806,7 +806,7 @@ def getInterface(currentUser: User) -> GatewayInterface:
# Create new instance if not exists
if contextKey not in _gatewayInterfaces:
- _gatewayInterfaces[contextKey] = GatewayInterface(currentUser)
+ _gatewayInterfaces[contextKey] = AppObjects(currentUser)
return _gatewayInterfaces[contextKey]
@@ -817,7 +817,7 @@ def getRootUser() -> User:
"""
try:
# Create a temporary interface without user context
- tempInterface = GatewayInterface()
+ tempInterface = AppObjects()
# Get the initial user directly
initialUserId = tempInterface.db.getInitialId("users")
@@ -835,15 +835,15 @@ def getRootUser() -> User:
logger.error(f"Error getting root user: {str(e)}")
raise ValueError(f"Failed to get root user: {str(e)}")
-def getRootInterface() -> GatewayInterface:
+def getRootInterface() -> AppObjects:
"""
- Returns a GatewayInterface instance with root privileges.
+ Returns a AppObjects instance with root privileges.
This is used for initial setup and user creation.
"""
- global _rootGatewayInterface
+ global _rootAppObjects
- if _rootGatewayInterface is None:
+ if _rootAppObjects is None:
rootUser = getRootUser()
- _rootGatewayInterface = GatewayInterface(rootUser)
+ _rootAppObjects = AppObjects(rootUser)
- return _rootGatewayInterface
+ return _rootAppObjects
diff --git a/modules/interfaces/serviceChatAccess.py b/modules/interfaces/interfaceChatAccess.py
similarity index 98%
rename from modules/interfaces/serviceChatAccess.py
rename to modules/interfaces/interfaceChatAccess.py
index cd9428ff..22961874 100644
--- a/modules/interfaces/serviceChatAccess.py
+++ b/modules/interfaces/interfaceChatAccess.py
@@ -4,7 +4,7 @@ Handles user access management and permission checks.
"""
from typing import Dict, Any, List, Optional
-from modules.interfaces.serviceAppModel import User, UserPrivilege
+from modules.interfaces.interfaceAppModel import User, UserPrivilege
class ChatAccess:
"""
diff --git a/modules/interfaces/serviceChatModel.py b/modules/interfaces/interfaceChatModel.py
similarity index 100%
rename from modules/interfaces/serviceChatModel.py
rename to modules/interfaces/interfaceChatModel.py
diff --git a/modules/interfaces/serviceChatClass.py b/modules/interfaces/interfaceChatObjects.py
similarity index 99%
rename from modules/interfaces/serviceChatClass.py
rename to modules/interfaces/interfaceChatObjects.py
index eb730e58..d939c394 100644
--- a/modules/interfaces/serviceChatClass.py
+++ b/modules/interfaces/interfaceChatObjects.py
@@ -12,11 +12,11 @@ from typing import Dict, Any, List, Optional, Union
import hashlib
import asyncio
-from modules.interfaces.serviceChatAccess import ChatAccess
-from modules.interfaces.serviceChatModel import (
+from modules.interfaces.interfaceChatAccess import ChatAccess
+from modules.interfaces.interfaceChatModel import (
TaskStatus, UserInputRequest, ChatDocument, TaskItem, ChatStat, ChatLog, ChatMessage, ChatWorkflow, TaskAction
)
-from modules.interfaces.serviceAppModel import User
+from modules.interfaces.interfaceAppModel import User
# DYNAMIC PART: Connectors to the Interface
from modules.connectors.connectorDbJson import DatabaseConnector
@@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
# Singleton factory for Chat instances
_chatInterfaces = {}
-class ChatInterface:
+class ChatObjects:
"""
Interface to Chat database and AI Connectors.
Uses the JSON connector for data access with added language support.
@@ -1025,9 +1025,9 @@ class ChatInterface:
logger.error(f"Error deleting task: {str(e)}")
return False
-def getInterface(currentUser: Optional[User] = None) -> 'ChatInterface':
+def getInterface(currentUser: Optional[User] = None) -> 'ChatObjects':
"""
- Returns a ChatInterface instance for the current user.
+ Returns a ChatObjects instance for the current user.
Handles initialization of database and records.
"""
if not currentUser:
@@ -1038,6 +1038,6 @@ def getInterface(currentUser: Optional[User] = None) -> 'ChatInterface':
# Create new instance if not exists
if contextKey not in _chatInterfaces:
- _chatInterfaces[contextKey] = ChatInterface(currentUser)
+ _chatInterfaces[contextKey] = ChatObjects(currentUser)
return _chatInterfaces[contextKey]
\ No newline at end of file
diff --git a/modules/interfaces/serviceManagementAccess.py b/modules/interfaces/interfaceComponentAccess.py
similarity index 99%
rename from modules/interfaces/serviceManagementAccess.py
rename to modules/interfaces/interfaceComponentAccess.py
index ef842b2f..6db839af 100644
--- a/modules/interfaces/serviceManagementAccess.py
+++ b/modules/interfaces/interfaceComponentAccess.py
@@ -5,12 +5,12 @@ Handles user access management and permission checks.
import logging
from typing import Dict, Any, List, Optional
-from modules.interfaces.serviceAppModel import User
+from modules.interfaces.interfaceAppModel import User
# Configure logger
logger = logging.getLogger(__name__)
-class ManagementAccess:
+class ComponentAccess:
"""
Access control class for Management interface.
Handles user access management and permission checks.
diff --git a/modules/interfaces/serviceManagementModel.py b/modules/interfaces/interfaceComponentModel.py
similarity index 100%
rename from modules/interfaces/serviceManagementModel.py
rename to modules/interfaces/interfaceComponentModel.py
diff --git a/modules/interfaces/serviceManagementClass.py b/modules/interfaces/interfaceComponentObjects.py
similarity index 98%
rename from modules/interfaces/serviceManagementClass.py
rename to modules/interfaces/interfaceComponentObjects.py
index e478ac18..87ec4e7c 100644
--- a/modules/interfaces/serviceManagementClass.py
+++ b/modules/interfaces/interfaceComponentObjects.py
@@ -11,11 +11,11 @@ from typing import Dict, Any, List, Optional, Union
import hashlib
-from modules.interfaces.serviceManagementAccess import ManagementAccess
-from modules.interfaces.serviceManagementModel import (
+from modules.interfaces.interfaceComponentAccess import ComponentAccess
+from modules.interfaces.interfaceComponentModel import (
FilePreview, Prompt, FileItem, FileData
)
-from modules.interfaces.serviceAppModel import User, Mandate, UserPrivilege
+from modules.interfaces.interfaceAppModel import User, Mandate, UserPrivilege
# DYNAMIC PART: Connectors to the Interface
from modules.connectors.connectorDbJson import DatabaseConnector
@@ -49,7 +49,7 @@ class FileDeletionError(FileError):
"""Exception raised when there's an error deleting a file."""
pass
-class ServiceManagement:
+class ComponentObjects:
"""
Interface to Management database and AI Connectors.
Uses the JSON connector for data access with added language support.
@@ -60,7 +60,7 @@ class ServiceManagement:
# Initialize variables first
self.currentUser: Optional[User] = None
self.userId: Optional[str] = None
- self.access: Optional[ManagementAccess] = None # Will be set when user context is provided
+ self.access: Optional[ComponentAccess] = None # Will be set when user context is provided
self.aiService: Optional[ChatService] = None # Will be set when user context is provided
# Initialize database
@@ -85,7 +85,7 @@ class ServiceManagement:
self.userLanguage = currentUser.language # Default user language
# Initialize access control with user context
- self.access = ManagementAccess(self.currentUser, self.db)
+ self.access = ComponentAccess(self.currentUser, self.db)
# Initialize AI service
self.aiService = ChatService()
@@ -143,7 +143,7 @@ class ServiceManagement:
return
# Get the root interface to access the initial mandate ID
- from modules.interfaces.serviceAppClass import getRootInterface
+ from modules.interfaces.interfaceAppObjects import getRootInterface
rootInterface = getRootInterface()
# Get initial mandate ID through the root interface
@@ -887,15 +887,15 @@ class ServiceManagement:
raise FileError(f"Error downloading file: {str(e)}")
-def getInterface(currentUser: Optional[User] = None) -> 'ServiceManagement':
+def getInterface(currentUser: Optional[User] = None) -> 'ComponentObjects':
"""
- Returns a ServiceManagement instance.
+ Returns a ComponentObjects instance.
If currentUser is provided, initializes with user context.
Otherwise, returns an instance with only database access.
"""
# Create new instance if not exists
if "default" not in _instancesManagement:
- _instancesManagement["default"] = ServiceManagement()
+ _instancesManagement["default"] = ComponentObjects()
interface = _instancesManagement["default"]
diff --git a/modules/methods/methodBase.py b/modules/methods/methodBase.py
index 7952b68e..824be505 100644
--- a/modules/methods/methodBase.py
+++ b/modules/methods/methodBase.py
@@ -3,10 +3,19 @@ from typing import Dict, List, Optional, Any, Literal
from datetime import datetime, UTC
from pydantic import BaseModel, Field
import logging
-from modules.interfaces.serviceChatModel import MethodResult
+from modules.interfaces.interfaceChatModel import MethodResult
+from functools import wraps
logger = logging.getLogger(__name__)
+def action(func):
+ """Decorator to mark a method as an available action"""
+ @wraps(func)
+ async def wrapper(self, *args, **kwargs):
+ return await func(self, *args, **kwargs)
+ wrapper.is_action = True
+ return wrapper
+
class MethodBase:
"""Base class for all methods"""
diff --git a/modules/methods/methodCoder.py b/modules/methods/methodCoder.py
index 7b9a1755..c99b93fb 100644
--- a/modules/methods/methodCoder.py
+++ b/modules/methods/methodCoder.py
@@ -3,85 +3,20 @@ import logging
import ast
import re
-from modules.methods.methodBase import MethodBase, MethodResult
+from modules.methods.methodBase import MethodBase, MethodResult, action
logger = logging.getLogger(__name__)
class MethodCoder(MethodBase):
"""Coder method implementation for code operations"""
- def __init__(self):
- super().__init__()
+ def __init__(self, serviceContainer: Any):
+ super().__init__(serviceContainer)
self.name = "coder"
self.description = "Handle code operations like analysis, generation, and refactoring"
-
- @property
- def actions(self) -> Dict[str, Dict[str, Any]]:
- """Available actions and their parameters"""
- return {
- "analyze": {
- "description": "Analyze code structure and quality",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "code": {"type": "string", "required": True},
- "language": {"type": "string", "required": False},
- "metrics": {"type": "array", "items": "string", "required": False}
- }
- },
- "generate": {
- "description": "Generate code based on requirements",
- "retryMax": 2,
- "timeout": 60,
- "parameters": {
- "requirements": {"type": "string", "required": True},
- "language": {"type": "string", "required": False},
- "style": {"type": "string", "required": False}
- }
- },
- "refactor": {
- "description": "Refactor code for better quality",
- "retryMax": 2,
- "timeout": 60,
- "parameters": {
- "code": {"type": "string", "required": True},
- "language": {"type": "string", "required": False},
- "improvements": {"type": "array", "items": "string", "required": False}
- }
- }
- }
- async def execute(self, action: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
- """Execute coder method"""
- try:
- # Validate parameters
- if not await self.validateParameters(action, parameters):
- return self._createResult(
- success=False,
- data={"error": f"Invalid parameters for {action}"}
- )
-
- # Execute action
- if action == "analyze":
- return await self._analyzeCode(parameters)
- elif action == "generate":
- return await self._generateCode(parameters)
- elif action == "refactor":
- return await self._refactorCode(parameters)
- else:
- return self._createResult(
- success=False,
- data={"error": f"Unknown action: {action}"}
- )
-
- except Exception as e:
- logger.error(f"Error executing coder {action}: {e}")
- return self._createResult(
- success=False,
- data={"error": str(e)}
- )
-
- async def _analyzeCode(self, parameters: Dict[str, Any]) -> MethodResult:
+ @action
+ async def analyze(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
"""Analyze code structure and quality"""
try:
code = parameters["code"]
@@ -173,7 +108,8 @@ class MethodCoder(MethodBase):
data={"error": f"Analysis failed: {str(e)}"}
)
- async def _generateCode(self, parameters: Dict[str, Any]) -> MethodResult:
+ @action
+ async def generate(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
"""Generate code based on requirements"""
try:
requirements = parameters["requirements"]
@@ -216,7 +152,8 @@ class MethodCoder(MethodBase):
data={"error": f"Generation failed: {str(e)}"}
)
- async def _refactorCode(self, parameters: Dict[str, Any]) -> MethodResult:
+ @action
+ async def refactor(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
"""Refactor code for better quality"""
try:
code = parameters["code"]
diff --git a/modules/methods/methodDocument.py b/modules/methods/methodDocument.py
index 1f669d21..34bafb43 100644
--- a/modules/methods/methodDocument.py
+++ b/modules/methods/methodDocument.py
@@ -7,14 +7,14 @@ import logging
from typing import Dict, Any, List, Optional
from datetime import datetime
-from modules.interfaces.serviceChatModel import (
+from modules.interfaces.interfaceChatModel import (
ChatDocument,
TaskDocument,
ExtractedContent,
ContentItem
)
from modules.workflow.managerDocument import DocumentManager
-from modules.methods.methodBase import MethodBase
+from modules.methods.methodBase import MethodBase, MethodResult, action
logger = logging.getLogger(__name__)
@@ -25,100 +25,80 @@ class MethodDocument(MethodBase):
"""Initialize the document method"""
super().__init__(serviceContainer)
self.documentManager = DocumentManager(serviceContainer)
-
- async def process(self, action: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
+
+ @action
+ async def extract(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
"""
- Process document operations
+ Extract content from document
Args:
- action: The action to perform
- parameters: Action parameters
-
- Returns:
- Dictionary containing the operation result
-
- Raises:
- ValueError: If action is not supported
+ parameters:
+ documentId: ID of the document to extract from
+ documentType: Type of document
+ extractionType: Type of extraction to perform
"""
try:
- if action == "extract":
- return await self._extractContent(parameters)
- elif action == "analyze":
- return await self._analyzeDocument(parameters)
- elif action == "summarize":
- return await self._summarizeDocument(parameters)
+ documentId = parameters["documentId"]
+ documentType = parameters.get("documentType", "text")
+ extractionType = parameters.get("extractionType", "full")
+
+ # Get document from service
+ document = await self.service.interfaceComponent.getDocument(documentId)
+ if not document:
+ return self._createResult(
+ success=False,
+ data={"error": f"Document not found: {documentId}"}
+ )
+
+ # Extract content based on type
+ if documentType == "text":
+ content = await self.documentManager.extractTextContent(document, extractionType)
+ elif documentType == "table":
+ content = await self.documentManager.extractTableContent(document, extractionType)
+ elif documentType == "image":
+ content = await self.documentManager.extractImageContent(document, extractionType)
else:
- raise ValueError(f"Unsupported action: {action}")
- except Exception as e:
- logger.error(f"Error processing document action {action}: {str(e)}")
- raise
-
- async def _extractContent(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
- """
- Extract content from a document
-
- Args:
- parameters: Dictionary containing:
- - documentId: ID of the document to process
- - documentType: Type of document ('ChatDocument' or 'TaskDocument')
-
- Returns:
- Dictionary containing extracted content
- """
- try:
- documentId = parameters.get("documentId")
- documentType = parameters.get("documentType", "ChatDocument")
+ return self._createResult(
+ success=False,
+ data={"error": f"Unsupported document type: {documentType}"}
+ )
- if not documentId:
- raise ValueError("documentId is required")
-
- # Get document from database
- if documentType == "ChatDocument":
- document = await self._getChatDocument(documentId)
- if not document:
- raise ValueError(f"ChatDocument {documentId} not found")
- extracted = await self.documentManager.extractFromChatDocument(document)
- else:
- document = await self._getTaskDocument(documentId)
- if not document:
- raise ValueError(f"TaskDocument {documentId} not found")
- extracted = await self.documentManager.extractFromTaskDocument(document)
-
- return {
- "success": True,
- "content": extracted.dict(),
- "metadata": await self.documentManager.getDocumentMetadata(document)
- }
+ return self._createResult(
+ success=True,
+ data={
+ "documentId": documentId,
+ "type": documentType,
+ "content": content
+ }
+ )
except Exception as e:
logger.error(f"Error extracting content: {str(e)}")
- return {
- "success": False,
- "error": str(e)
- }
-
- async def _analyzeDocument(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
+
+ @action
+ async def analyze(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
"""
Analyze document content
Args:
- parameters: Dictionary containing:
- - documentId: ID of the document to analyze
- - documentType: Type of document
- - analysisType: Type of analysis to perform
-
- Returns:
- Dictionary containing analysis results
+ parameters:
+ documentId: ID of the document to analyze
+ documentType: Type of document
+ analysisType: Type of analysis to perform
"""
try:
# Extract content first
- contentResult = await self._extractContent(parameters)
- if not contentResult["success"]:
+ contentResult = await self.extract(parameters)
+ if not contentResult.success:
return contentResult
# Perform analysis based on type
analysisType = parameters.get("analysisType", "basic")
- content = ExtractedContent(**contentResult["content"])
+ content = ExtractedContent(**contentResult.data["content"])
if analysisType == "basic":
# Basic analysis: count items, calculate statistics
@@ -134,64 +114,71 @@ class MethodDocument(MethodBase):
stats["itemTypes"][itemType] = 0
stats["itemTypes"][itemType] += 1
- return {
- "success": True,
- "analysis": stats
- }
+ return self._createResult(
+ success=True,
+ data={
+ "documentId": parameters["documentId"],
+ "analysis": stats
+ }
+ )
else:
- raise ValueError(f"Unsupported analysis type: {analysisType}")
+ return self._createResult(
+ success=False,
+ data={"error": f"Unsupported analysis type: {analysisType}"}
+ )
except Exception as e:
logger.error(f"Error analyzing document: {str(e)}")
- return {
- "success": False,
- "error": str(e)
- }
-
- async def _summarizeDocument(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
+
+ @action
+ async def summarize(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
"""
- Generate document summary
+ Summarize document content
Args:
- parameters: Dictionary containing:
- - documentId: ID of the document to summarize
- - documentType: Type of document
- - summaryType: Type of summary to generate
-
- Returns:
- Dictionary containing summary
+ parameters:
+ documentId: ID of the document to summarize
+ documentType: Type of document
+ summaryType: Type of summary to generate
"""
try:
# Extract content first
- contentResult = await self._extractContent(parameters)
- if not contentResult["success"]:
+ contentResult = await self.extract(parameters)
+ if not contentResult.success:
return contentResult
# Generate summary based on type
summaryType = parameters.get("summaryType", "basic")
- content = ExtractedContent(**contentResult["content"])
+ content = ExtractedContent(**contentResult.data["content"])
if summaryType == "basic":
# Basic summary: concatenate all text content
- summary = "\n".join(
- item.data for item in content.contents
- if item.label == "main"
- )
+ summary = "\n".join(item.content for item in content.contents if item.content)
- return {
- "success": True,
- "summary": summary
- }
+ return self._createResult(
+ success=True,
+ data={
+ "documentId": parameters["documentId"],
+ "summary": summary
+ }
+ )
else:
- raise ValueError(f"Unsupported summary type: {summaryType}")
+ return self._createResult(
+ success=False,
+ data={"error": f"Unsupported summary type: {summaryType}"}
+ )
except Exception as e:
logger.error(f"Error summarizing document: {str(e)}")
- return {
- "success": False,
- "error": str(e)
- }
-
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
+
async def _getChatDocument(self, documentId: str) -> Optional[ChatDocument]:
"""Get ChatDocument from database"""
try:
diff --git a/modules/methods/methodExcel.py b/modules/methods/methodExcel.py
new file mode 100644
index 00000000..898c25bd
--- /dev/null
+++ b/modules/methods/methodExcel.py
@@ -0,0 +1,188 @@
+"""
+Excel method module.
+Handles Excel operations using the Excel service.
+"""
+
+import logging
+from typing import Dict, Any, List, Optional
+from datetime import datetime
+
+from modules.interfaces.interfaceExcel import ExcelService
+from modules.methods.methodBase import MethodBase, MethodResult, action
+
+logger = logging.getLogger(__name__)
+
+class MethodExcel(MethodBase):
+ """Excel method implementation"""
+
+ def __init__(self, serviceContainer):
+ """Initialize the Excel method"""
+ super().__init__(serviceContainer)
+ self.excelService = ExcelService(serviceContainer)
+
+ @action
+ async def read(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Read data from Excel file
+
+ Args:
+ parameters:
+ fileId: ID of the Excel file
+ sheetName: Name of the sheet to read
+ range: Cell range to read (e.g. "A1:B10")
+ """
+ try:
+ fileId = parameters["fileId"]
+ sheetName = parameters.get("sheetName", "Sheet1")
+ range = parameters.get("range")
+
+ # Get file from service
+ file = await self.service.interfaceComponent.getFile(fileId)
+ if not file:
+ return self._createResult(
+ success=False,
+ data={"error": f"File not found: {fileId}"}
+ )
+
+ # Read data from Excel
+ data = await self.excelService.readData(file, sheetName, range)
+
+ return self._createResult(
+ success=True,
+ data={
+ "fileId": fileId,
+ "sheetName": sheetName,
+ "range": range,
+ "data": data
+ }
+ )
+
+ except Exception as e:
+ logger.error(f"Error reading Excel file: {str(e)}")
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
+
+ @action
+ async def write(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Write data to Excel file
+
+ Args:
+ parameters:
+ fileId: ID of the Excel file
+ sheetName: Name of the sheet to write to
+ range: Cell range to write to (e.g. "A1:B10")
+ data: Data to write
+ """
+ try:
+ fileId = parameters["fileId"]
+ sheetName = parameters.get("sheetName", "Sheet1")
+ range = parameters.get("range")
+ data = parameters["data"]
+
+ # Get file from service
+ file = await self.service.interfaceComponent.getFile(fileId)
+ if not file:
+ return self._createResult(
+ success=False,
+ data={"error": f"File not found: {fileId}"}
+ )
+
+ # Write data to Excel
+ await self.excelService.writeData(file, sheetName, range, data)
+
+ return self._createResult(
+ success=True,
+ data={
+ "fileId": fileId,
+ "sheetName": sheetName,
+ "range": range
+ }
+ )
+
+ except Exception as e:
+ logger.error(f"Error writing to Excel file: {str(e)}")
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
+
+ @action
+ async def create(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Create new Excel file
+
+ Args:
+ parameters:
+ fileName: Name of the new file
+ sheets: List of sheet configurations
+ """
+ try:
+ fileName = parameters["fileName"]
+ sheets = parameters.get("sheets", [{"name": "Sheet1"}])
+
+ # Create new Excel file
+ file = await self.excelService.createFile(fileName, sheets)
+
+ return self._createResult(
+ success=True,
+ data={
+ "fileId": file.id,
+ "fileName": fileName,
+ "sheets": sheets
+ }
+ )
+
+ except Exception as e:
+ logger.error(f"Error creating Excel file: {str(e)}")
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
+
+ @action
+ async def format(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Format Excel cells
+
+ Args:
+ parameters:
+ fileId: ID of the Excel file
+ sheetName: Name of the sheet to format
+ range: Cell range to format (e.g. "A1:B10")
+ format: Format configuration
+ """
+ try:
+ fileId = parameters["fileId"]
+ sheetName = parameters.get("sheetName", "Sheet1")
+ range = parameters.get("range")
+ format = parameters["format"]
+
+ # Get file from service
+ file = await self.service.interfaceComponent.getFile(fileId)
+ if not file:
+ return self._createResult(
+ success=False,
+ data={"error": f"File not found: {fileId}"}
+ )
+
+ # Apply formatting
+ await self.excelService.formatCells(file, sheetName, range, format)
+
+ return self._createResult(
+ success=True,
+ data={
+ "fileId": fileId,
+ "sheetName": sheetName,
+ "range": range
+ }
+ )
+
+ except Exception as e:
+ logger.error(f"Error formatting Excel cells: {str(e)}")
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
\ No newline at end of file
diff --git a/modules/methods/methodOperator.py b/modules/methods/methodOperator.py
index 9082ccf5..9e211272 100644
--- a/modules/methods/methodOperator.py
+++ b/modules/methods/methodOperator.py
@@ -2,7 +2,7 @@ from typing import Dict, List, Any, Optional
from datetime import datetime, UTC
import logging
from .methodBase import MethodBase
-from modules.interfaces.serviceChatModel import MethodResult
+from modules.interfaces.interfaceChatModel import MethodResult
logger = logging.getLogger(__name__)
@@ -159,7 +159,7 @@ class MethodOperator(MethodBase):
full_prompt += f"\nDocument: {content['document']}\n{content['content']}\n"
# Call AI service
- response = await self.service.callAiBasic(full_prompt)
+ response = await self.service.callAiTextBasic(full_prompt)
return self._createResult(
success=True,
diff --git a/modules/methods/methodOutlook.py b/modules/methods/methodOutlook.py
index 68dd07d0..48bfa702 100644
--- a/modules/methods/methodOutlook.py
+++ b/modules/methods/methodOutlook.py
@@ -1,202 +1,176 @@
-from typing import Dict, Any, Optional
-import logging
-from datetime import datetime, UTC
-from O365 import Account, MSGraphProtocol
+"""
+Outlook method module.
+Handles Outlook operations using the Outlook service.
+"""
-from modules.methods.methodBase import MethodBase, MethodResult
-from modules.models.userConnection import UserConnection
+import logging
+from typing import Dict, Any, List, Optional
+from datetime import datetime
+
+from modules.interfaces.interfaceOutlook import OutlookService
+from modules.methods.methodBase import MethodBase, MethodResult, action
logger = logging.getLogger(__name__)
class MethodOutlook(MethodBase):
- """Outlook method implementation for email operations"""
+ """Outlook method implementation"""
- def __init__(self):
- super().__init__()
- self.name = "outlook"
- self.description = "Handle Outlook email operations like reading and sending emails"
+ def __init__(self, serviceContainer):
+ """Initialize the Outlook method"""
+ super().__init__(serviceContainer)
+ self.outlookService = OutlookService(serviceContainer)
+
+ @action
+ async def readMails(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Read emails from Outlook
- @property
- def actions(self) -> Dict[str, Dict[str, Any]]:
- """Available actions and their parameters"""
- return {
- "readMails": {
- "description": "Read emails from Outlook",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "folder": {"type": "string", "required": False},
- "query": {"type": "string", "required": False},
- "maxResults": {"type": "number", "required": False},
- "includeAttachments": {"type": "boolean", "required": False}
- }
- },
- "sendMail": {
- "description": "Send email through Outlook",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "to": {"type": "array", "items": "string", "required": True},
- "subject": {"type": "string", "required": True},
- "body": {"type": "string", "required": True},
- "cc": {"type": "array", "items": "string", "required": False},
- "bcc": {"type": "array", "items": "string", "required": False},
- "attachments": {"type": "array", "items": "string", "required": False}
- }
- }
- }
-
- async def execute(self, action: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
- """Execute Outlook method"""
- try:
- # Validate parameters
- if not await self.validateParameters(action, parameters):
- return self._createResult(
- success=False,
- data={"error": f"Invalid parameters for {action}"}
- )
-
- # Get UserConnection from auth_data
- if not authData or "userConnection" not in authData:
- return self._createResult(
- success=False,
- data={"error": "UserConnection required for Outlook operations"}
- )
-
- userConnection: UserConnection = authData["userConnection"]
-
- # Execute action
- if action == "readMails":
- return await self._readMails(parameters, userConnection)
- elif action == "sendMail":
- return await self._sendMail(parameters, userConnection)
- else:
- return self._createResult(
- success=False,
- data={"error": f"Unknown action: {action}"}
- )
-
- except Exception as e:
- logger.error(f"Error executing Outlook {action}: {e}")
- return self._createResult(
- success=False,
- data={"error": str(e)}
- )
-
- async def _readMails(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Read emails from Outlook"""
+ Args:
+ parameters:
+ folder: Folder to read from (default: inbox)
+ query: Search query
+ maxResults: Maximum number of results
+ includeAttachments: Whether to include attachments
+ """
try:
folder = parameters.get("folder", "inbox")
query = parameters.get("query")
maxResults = parameters.get("maxResults", 10)
includeAttachments = parameters.get("includeAttachments", False)
- # Create Outlook account
- account = Account(
- credentials=(userConnection.authToken, userConnection.refreshToken),
- protocol=MSGraphProtocol()
+ # Read emails
+ emails = await self.outlookService.readEmails(
+ folder=folder,
+ query=query,
+ maxResults=maxResults,
+ includeAttachments=includeAttachments
)
- # Get mailbox
- mailbox = account.mailbox()
-
- # Get folder
- targetFolder = mailbox.folder(folder_name=folder)
-
- # Get messages
- if query:
- messages = targetFolder.get_messages(query=query, limit=maxResults)
- else:
- messages = targetFolder.get_messages(limit=maxResults)
-
- # Process messages
- results = []
- for message in messages:
- msgData = {
- "id": message.object_id,
- "subject": message.subject,
- "from": message.sender.address,
- "to": [to.address for to in message.to],
- "cc": [cc.address for cc in message.cc],
- "received": message.received.strftime("%Y-%m-%d %H:%M:%S"),
- "body": message.body,
- "hasAttachments": message.has_attachments
- }
-
- if includeAttachments and message.has_attachments:
- attachments = []
- for attachment in message.attachments:
- attachments.append({
- "name": attachment.name,
- "contentType": attachment.content_type,
- "size": attachment.size
- })
- msgData["attachments"] = attachments
-
- results.append(msgData)
-
return self._createResult(
success=True,
data={
"folder": folder,
"query": query,
- "messages": results
+ "emails": emails
}
)
+
except Exception as e:
- logger.error(f"Error reading Outlook emails: {e}")
+ logger.error(f"Error reading emails: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Read failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _sendMail(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Send email through Outlook"""
+
+ @action
+ async def sendMail(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Send email using Outlook
+
+ Args:
+ parameters:
+ to: List of recipient email addresses
+ subject: Email subject
+ body: Email body
+ attachments: List of attachment file IDs
+ """
try:
- toAddresses = parameters["to"]
+ to = parameters["to"]
subject = parameters["subject"]
body = parameters["body"]
- ccAddresses = parameters.get("cc", [])
- bccAddresses = parameters.get("bcc", [])
attachments = parameters.get("attachments", [])
- # Create Outlook account
- account = Account(
- credentials=(userConnection.authToken, userConnection.refreshToken),
- protocol=MSGraphProtocol()
+ # Send email
+ messageId = await self.outlookService.sendEmail(
+ to=to,
+ subject=subject,
+ body=body,
+ attachments=attachments
)
- # Get mailbox
- mailbox = account.mailbox()
-
- # Create new message
- message = mailbox.new_message()
- message.to.add(toAddresses)
- if ccAddresses:
- message.cc.add(ccAddresses)
- if bccAddresses:
- message.bcc.add(bccAddresses)
- message.subject = subject
- message.body = body
-
- # Add attachments
- for attachmentPath in attachments:
- message.attachments.add(attachmentPath)
-
- # Send message
- message.send()
-
return self._createResult(
success=True,
data={
- "to": toAddresses,
- "subject": subject,
- "sent": datetime.now(UTC).isoformat()
+ "messageId": messageId,
+ "to": to,
+ "subject": subject
}
)
+
except Exception as e:
- logger.error(f"Error sending Outlook email: {e}")
+ logger.error(f"Error sending email: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Send failed: {str(e)}"}
+ data={"error": str(e)}
+ )
+
+ @action
+ async def createFolder(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Create folder in Outlook
+
+ Args:
+ parameters:
+ name: Folder name
+ parentFolder: Parent folder ID (optional)
+ """
+ try:
+ name = parameters["name"]
+ parentFolder = parameters.get("parentFolder")
+
+ # Create folder
+ folderId = await self.outlookService.createFolder(
+ name=name,
+ parentFolder=parentFolder
+ )
+
+ return self._createResult(
+ success=True,
+ data={
+ "folderId": folderId,
+ "name": name,
+ "parentFolder": parentFolder
+ }
+ )
+
+ except Exception as e:
+ logger.error(f"Error creating folder: {str(e)}")
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
+
+ @action
+ async def moveMail(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Move email to different folder
+
+ Args:
+ parameters:
+ messageId: ID of the message to move
+ targetFolder: ID of the target folder
+ """
+ try:
+ messageId = parameters["messageId"]
+ targetFolder = parameters["targetFolder"]
+
+ # Move email
+ await self.outlookService.moveEmail(
+ messageId=messageId,
+ targetFolder=targetFolder
+ )
+
+ return self._createResult(
+ success=True,
+ data={
+ "messageId": messageId,
+ "targetFolder": targetFolder
+ }
+ )
+
+ except Exception as e:
+ logger.error(f"Error moving email: {str(e)}")
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
)
\ No newline at end of file
diff --git a/modules/methods/methodPowerpoint.py b/modules/methods/methodPowerpoint.py
index 26ccf458..d5a90a4e 100644
--- a/modules/methods/methodPowerpoint.py
+++ b/modules/methods/methodPowerpoint.py
@@ -1,375 +1,260 @@
-from typing import Dict, Any, Optional
-import logging
-import os
-from pathlib import Path
+"""
+PowerPoint method module.
+Handles PowerPoint operations using the PowerPoint service.
+"""
-from modules.methods.methodBase import MethodBase, MethodResult
-from modules.models.userConnection import UserConnection
-from modules.models.account import Account
-from modules.protocols.msGraphProtocol import MSGraphProtocol
+import logging
+from typing import Dict, Any, List, Optional
+from datetime import datetime
+
+from modules.interfaces.interfacePowerpoint import PowerpointService
+from modules.methods.methodBase import MethodBase, MethodResult, action
logger = logging.getLogger(__name__)
class MethodPowerpoint(MethodBase):
- """Powerpoint method implementation for PowerPoint operations"""
+ """PowerPoint method implementation"""
- def __init__(self):
- super().__init__()
- self.name = "powerpoint"
- self.description = "Handle PowerPoint operations like reading, writing, and converting presentations"
+ def __init__(self, serviceContainer):
+ """Initialize the PowerPoint method"""
+ super().__init__(serviceContainer)
+ self.powerpointService = PowerpointService(serviceContainer)
+
+ @action
+ async def read(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Read PowerPoint presentation
- @property
- def actions(self) -> Dict[str, Dict[str, Any]]:
- """Available actions and their parameters"""
- return {
- "read": {
- "description": "Read PowerPoint presentation content",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "path": {"type": "string", "required": True},
- "format": {"type": "string", "required": False},
- "includeNotes": {"type": "boolean", "required": False}
- }
- },
- "write": {
- "description": "Write content to PowerPoint presentation",
- "retryMax": 2,
- "timeout": 60,
- "parameters": {
- "path": {"type": "string", "required": True},
- "content": {"type": "object", "required": True},
- "template": {"type": "string", "required": False}
- }
- },
- "convert": {
- "description": "Convert PowerPoint presentation between formats",
- "retryMax": 2,
- "timeout": 60,
- "parameters": {
- "sourcePath": {"type": "string", "required": True},
- "targetPath": {"type": "string", "required": True},
- "sourceFormat": {"type": "string", "required": False},
- "targetFormat": {"type": "string", "required": False}
- }
- },
- "createPresentation": {
- "description": "Create a new PowerPoint presentation",
- "retryMax": 2,
- "timeout": 60,
- "parameters": {
- "title": {"type": "string", "required": True},
- "template": {"type": "string", "required": False}
- }
- },
- "addSlide": {
- "description": "Add a new slide to presentation",
- "retryMax": 2,
- "timeout": 60,
- "parameters": {
- "presentationId": {"type": "string", "required": True},
- "layout": {"type": "string", "required": False},
- "title": {"type": "string", "required": False}
- }
- },
- "addContent": {
- "description": "Add content to a slide",
- "retryMax": 2,
- "timeout": 60,
- "parameters": {
- "presentationId": {"type": "string", "required": True},
- "slideId": {"type": "string", "required": True},
- "contentType": {"type": "string", "required": True},
- "content": {"type": "object", "required": True},
- "position": {"type": "object", "required": False}
- }
- }
- }
-
- async def execute(self, action: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
- """Execute PowerPoint method"""
+ Args:
+ parameters:
+ fileId: ID of the PowerPoint file
+ includeSlides: Whether to include slide content
+ """
try:
- # Validate parameters
- if not await self.validateParameters(action, parameters):
+ fileId = parameters["fileId"]
+ includeSlides = parameters.get("includeSlides", True)
+
+ # Get file from service
+ file = await self.service.interfaceComponent.getFile(fileId)
+ if not file:
return self._createResult(
success=False,
- data={"error": f"Invalid parameters for {action}"}
+ data={"error": f"File not found: {fileId}"}
)
- # Get UserConnection from auth_data
- if not authData or "userConnection" not in authData:
- return self._createResult(
- success=False,
- data={"error": "UserConnection required for PowerPoint operations"}
- )
+ # Read presentation
+ presentation = await self.powerpointService.readPresentation(file, includeSlides)
- userConnection: UserConnection = authData["userConnection"]
+ return self._createResult(
+ success=True,
+ data={
+ "fileId": fileId,
+ "presentation": presentation
+ }
+ )
- # Execute action
- if action == "createPresentation":
- return await self._createPresentation(parameters, userConnection)
- elif action == "addSlide":
- return await self._addSlide(parameters, userConnection)
- elif action == "addContent":
- return await self._addContent(parameters, userConnection)
- else:
- return self._createResult(
- success=False,
- data={"error": f"Unknown action: {action}"}
- )
-
except Exception as e:
- logger.error(f"Error executing PowerPoint {action}: {e}")
+ logger.error(f"Error reading PowerPoint: {str(e)}")
return self._createResult(
success=False,
data={"error": str(e)}
)
-
- async def _read_presentation(self, parameters: Dict[str, Any], authData: Dict[str, Any]) -> MethodResult:
- """Read PowerPoint presentation content"""
+
+ @action
+ async def write(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Write PowerPoint presentation
+
+ Args:
+ parameters:
+ fileId: ID of the PowerPoint file
+ slides: List of slide configurations
+ """
try:
- path = Path(parameters["path"])
- if not path.exists():
+ fileId = parameters["fileId"]
+ slides = parameters["slides"]
+
+ # Get file from service
+ file = await self.service.interfaceComponent.getFile(fileId)
+ if not file:
return self._createResult(
success=False,
- data={"error": f"File not found: {path}"}
+ data={"error": f"File not found: {fileId}"}
)
- # Determine format if not specified
- format = parameters.get("format")
- if not format:
- format = path.suffix[1:] if path.suffix else "pptx"
+ # Write presentation
+ await self.powerpointService.writePresentation(file, slides)
- # TODO: Implement PowerPoint reading using Microsoft Graph API
- # This is a placeholder implementation
return self._createResult(
success=True,
data={
- "path": str(path),
- "format": format,
- "slides": [
- {
- "number": 1,
- "title": "Example Slide",
- "content": "Example content",
- "notes": "Example notes" if parameters.get("includeNotes", False) else None
- }
- ]
+ "fileId": fileId,
+ "slideCount": len(slides)
}
)
+
except Exception as e:
- logger.error(f"Error reading presentation: {e}")
+ logger.error(f"Error writing PowerPoint: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Read failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _write_presentation(self, parameters: Dict[str, Any], authData: Dict[str, Any]) -> MethodResult:
- """Write content to PowerPoint presentation"""
+
+ @action
+ async def convert(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Convert PowerPoint to other format
+
+ Args:
+ parameters:
+ fileId: ID of the PowerPoint file
+ format: Target format (pdf, png, etc.)
+ """
try:
- path = Path(parameters["path"])
+ fileId = parameters["fileId"]
+ format = parameters["format"]
- # Create directory if it doesn't exist
- path.parent.mkdir(parents=True, exist_ok=True)
-
- # Determine format if not specified
- format = parameters.get("format")
- if not format:
- format = path.suffix[1:] if path.suffix else "pptx"
-
- # TODO: Implement PowerPoint writing using Microsoft Graph API
- # This is a placeholder implementation
- return self._createResult(
- success=True,
- data={
- "path": str(path),
- "format": format,
- "slides": len(parameters["content"].get("slides", []))
- }
- )
- except Exception as e:
- logger.error(f"Error writing presentation: {e}")
- return self._createResult(
- success=False,
- data={"error": f"Write failed: {str(e)}"}
- )
-
- async def _convert_presentation(self, parameters: Dict[str, Any], authData: Dict[str, Any]) -> MethodResult:
- """Convert PowerPoint presentation between formats"""
- try:
- source_path = Path(parameters["sourcePath"])
- target_path = Path(parameters["targetPath"])
-
- if not source_path.exists():
+ # Get file from service
+ file = await self.service.interfaceComponent.getFile(fileId)
+ if not file:
return self._createResult(
success=False,
- data={"error": f"Source file not found: {source_path}"}
+ data={"error": f"File not found: {fileId}"}
)
- # Determine formats if not specified
- source_format = parameters.get("sourceFormat")
- if not source_format:
- source_format = source_path.suffix[1:] if source_path.suffix else "pptx"
+ # Convert presentation
+ convertedFile = await self.powerpointService.convertPresentation(file, format)
- target_format = parameters.get("targetFormat")
- if not target_format:
- target_format = target_path.suffix[1:] if target_path.suffix else "pptx"
-
- # TODO: Implement PowerPoint conversion using Microsoft Graph API
- # This is a placeholder implementation
return self._createResult(
success=True,
data={
- "sourcePath": str(source_path),
- "targetPath": str(target_path),
- "sourceFormat": source_format,
- "targetFormat": target_format
+ "fileId": fileId,
+ "format": format,
+ "convertedFileId": convertedFile.id
}
)
+
except Exception as e:
- logger.error(f"Error converting presentation: {e}")
+ logger.error(f"Error converting PowerPoint: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Conversion failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _createPresentation(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Create a new PowerPoint presentation"""
+
+ @action
+ async def createPresentation(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Create new PowerPoint presentation
+
+ Args:
+ parameters:
+ fileName: Name of the new file
+ template: Template ID (optional)
+ """
try:
- title = parameters["title"]
+ fileName = parameters["fileName"]
template = parameters.get("template")
- # Create PowerPoint account
- account = Account(
- credentials=(userConnection.authToken, userConnection.refreshToken),
- protocol=MSGraphProtocol()
- )
-
- # Get drive
- drive = account.drive()
-
# Create presentation
- if template:
- # Copy template
- templateFile = drive.get_item_by_path(template)
- newFile = templateFile.copy(f"{title}.pptx")
- else:
- # Create blank presentation
- newFile = drive.create_file(
- name=f"{title}.pptx",
- content_type="application/vnd.openxmlformats-officedocument.presentationml.presentation"
- )
+ file = await self.powerpointService.createPresentation(fileName, template)
return self._createResult(
success=True,
data={
- "id": newFile.object_id,
- "name": newFile.name,
- "webUrl": newFile.web_url
+ "fileId": file.id,
+ "fileName": fileName,
+ "template": template
}
)
+
except Exception as e:
- logger.error(f"Error creating PowerPoint presentation: {e}")
+ logger.error(f"Error creating PowerPoint: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Create failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _addSlide(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Add a new slide to presentation"""
+
+ @action
+ async def addSlide(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Add slide to presentation
+
+ Args:
+ parameters:
+ fileId: ID of the PowerPoint file
+ layout: Slide layout
+ content: Slide content
+ """
try:
- presentationId = parameters["presentationId"]
+ fileId = parameters["fileId"]
layout = parameters.get("layout", "title")
- title = parameters.get("title")
+ content = parameters.get("content", {})
- # Create PowerPoint account
- account = Account(
- credentials=(userConnection.authToken, userConnection.refreshToken),
- protocol=MSGraphProtocol()
- )
-
- # Get drive
- drive = account.drive()
-
- # Get presentation
- presentation = drive.get_item_by_id(presentationId)
+ # Get file from service
+ file = await self.service.interfaceComponent.getFile(fileId)
+ if not file:
+ return self._createResult(
+ success=False,
+ data={"error": f"File not found: {fileId}"}
+ )
# Add slide
- slide = presentation.add_slide(layout=layout)
- if title:
- slide.title = title
+ slideId = await self.powerpointService.addSlide(file, layout, content)
return self._createResult(
success=True,
data={
- "slideId": slide.object_id,
- "layout": layout,
- "title": title
+ "fileId": fileId,
+ "slideId": slideId,
+ "layout": layout
}
)
+
except Exception as e:
- logger.error(f"Error adding PowerPoint slide: {e}")
+ logger.error(f"Error adding slide: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Add slide failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _addContent(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Add content to a slide"""
+
+ @action
+ async def addContent(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Add content to slide
+
+ Args:
+ parameters:
+ fileId: ID of the PowerPoint file
+ slideId: ID of the slide
+ content: Content to add
+ """
try:
- presentationId = parameters["presentationId"]
+ fileId = parameters["fileId"]
slideId = parameters["slideId"]
- contentType = parameters["contentType"]
content = parameters["content"]
- position = parameters.get("position", {"x": 0, "y": 0})
- # Create PowerPoint account
- account = Account(
- credentials=(userConnection.authToken, userConnection.refreshToken),
- protocol=MSGraphProtocol()
- )
-
- # Get drive
- drive = account.drive()
-
- # Get presentation and slide
- presentation = drive.get_item_by_id(presentationId)
- slide = presentation.get_slide(slideId)
-
- # Add content based on type
- if contentType == "text":
- shape = slide.add_text_box(
- text=content,
- left=position["x"],
- top=position["y"]
+ # Get file from service
+ file = await self.service.interfaceComponent.getFile(fileId)
+ if not file:
+ return self._createResult(
+ success=False,
+ data={"error": f"File not found: {fileId}"}
)
- elif contentType == "image":
- shape = slide.add_picture(
- image_path=content,
- left=position["x"],
- top=position["y"]
- )
- elif contentType == "table":
- shape = slide.add_table(
- rows=content["rows"],
- cols=content["cols"],
- left=position["x"],
- top=position["y"]
- )
- else:
- raise ValueError(f"Unsupported content type: {contentType}")
+
+ # Add content
+ await self.powerpointService.addContent(file, slideId, content)
return self._createResult(
success=True,
data={
- "shapeId": shape.object_id,
- "contentType": contentType,
- "position": position
+ "fileId": fileId,
+ "slideId": slideId
}
)
+
except Exception as e:
- logger.error(f"Error adding PowerPoint content: {e}")
+ logger.error(f"Error adding content: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Add content failed: {str(e)}"}
+ data={"error": str(e)}
)
\ No newline at end of file
diff --git a/modules/methods/methodSharepoint.py b/modules/methods/methodSharepoint.py
index e75fbd6f..d4eda812 100644
--- a/modules/methods/methodSharepoint.py
+++ b/modules/methods/methodSharepoint.py
@@ -1,389 +1,269 @@
-from typing import Dict, Any, Optional
-import logging
-from datetime import datetime, UTC
-from office365.runtime.auth.user_credential import UserCredential
-from office365.sharepoint.client_context import ClientContext
-from office365.sharepoint.files.file import File
-from office365.sharepoint.lists.list import List
-from office365.sharepoint.lists.list_creation_information import ListCreationInformation
+"""
+SharePoint method module.
+Handles SharePoint operations using the SharePoint service.
+"""
-from modules.methods.methodBase import MethodBase, MethodResult
-from modules.models.userConnection import UserConnection
+import logging
+from typing import Dict, Any, List, Optional
+from datetime import datetime
+
+from modules.interfaces.interfaceSharepoint import SharepointService
+from modules.methods.methodBase import MethodBase, MethodResult, action
logger = logging.getLogger(__name__)
class MethodSharepoint(MethodBase):
- """SharePoint method implementation for document operations"""
+ """SharePoint method implementation"""
- def __init__(self):
- super().__init__()
- self.name = "sharepoint"
- self.description = "Handle SharePoint document operations like search, read, and write"
+ def __init__(self, serviceContainer):
+ """Initialize the SharePoint method"""
+ super().__init__(serviceContainer)
+ self.sharepointService = SharepointService(serviceContainer)
+
+ @action
+ async def search(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Search SharePoint content
- @property
- def actions(self) -> Dict[str, Dict[str, Any]]:
- """Available actions and their parameters"""
- return {
- "search": {
- "description": "Search SharePoint documents",
- "retryMax": 3,
- "timeout": 30,
- "parameters": {
- "query": {"type": "string", "required": True},
- "siteUrl": {"type": "string", "required": True},
- "listName": {"type": "string", "required": False},
- "maxResults": {"type": "number", "required": False}
- }
- },
- "read": {
- "description": "Read SharePoint document content",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "fileUrl": {"type": "string", "required": True},
- "siteUrl": {"type": "string", "required": True}
- }
- },
- "write": {
- "description": "Write content to SharePoint document",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "fileUrl": {"type": "string", "required": True},
- "siteUrl": {"type": "string", "required": True},
- "content": {"type": "string", "required": True},
- "contentType": {"type": "string", "required": False}
- }
- },
- "readList": {
- "description": "Read items from SharePoint list",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "siteUrl": {"type": "string", "required": True},
- "listName": {"type": "string", "required": True},
- "query": {"type": "string", "required": False},
- "fields": {"type": "array", "required": False}
- }
- },
- "writeList": {
- "description": "Write items to SharePoint list",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "siteUrl": {"type": "string", "required": True},
- "listName": {"type": "string", "required": True},
- "items": {"type": "array", "required": True}
- }
- },
- "createList": {
- "description": "Create a new SharePoint list",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "siteUrl": {"type": "string", "required": True},
- "listName": {"type": "string", "required": True},
- "description": {"type": "string", "required": False},
- "template": {"type": "string", "required": False},
- "fields": {"type": "array", "required": False}
- }
- }
- }
-
- async def execute(self, action: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
- """Execute SharePoint method"""
+ Args:
+ parameters:
+ query: Search query
+ siteId: Site ID to search in
+ contentType: Content type to search for
+ maxResults: Maximum number of results
+ """
try:
- # Validate parameters
- if not await self.validateParameters(action, parameters):
- return self._createResult(
- success=False,
- data={"error": f"Invalid parameters for {action}"}
- )
-
- # Get UserConnection from auth_data
- if not authData or "userConnection" not in authData:
- return self._createResult(
- success=False,
- data={"error": "UserConnection required for SharePoint operations"}
- )
-
- userConnection: UserConnection = authData["userConnection"]
-
- # Execute action
- if action == "search":
- return await self._search_documents(parameters, userConnection)
- elif action == "read":
- return await self._read_document(parameters, userConnection)
- elif action == "write":
- return await self._write_document(parameters, userConnection)
- elif action == "readList":
- return await self._readList(parameters, userConnection)
- elif action == "writeList":
- return await self._writeList(parameters, userConnection)
- elif action == "createList":
- return await self._createList(parameters, userConnection)
- else:
- return self._createResult(
- success=False,
- data={"error": f"Unknown action: {action}"}
- )
-
- except Exception as e:
- logger.error(f"Error executing SharePoint {action}: {e}")
- return self._createResult(
- success=False,
- data={"error": str(e)}
- )
-
- async def _search_documents(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Search SharePoint documents"""
- try:
- siteUrl = parameters["siteUrl"]
query = parameters["query"]
- listName = parameters.get("listName")
+ siteId = parameters.get("siteId")
+ contentType = parameters.get("contentType")
maxResults = parameters.get("maxResults", 10)
- # Create SharePoint context
- ctx = ClientContext(siteUrl).with_credentials(
- UserCredential(userConnection.authToken, userConnection.refreshToken)
+ # Search content
+ results = await self.sharepointService.searchContent(
+ query=query,
+ siteId=siteId,
+ contentType=contentType,
+ maxResults=maxResults
)
- # Search in specific list or entire site
- if listName:
- targetList = ctx.web.lists.get_by_title(listName)
- items = targetList.items.filter(f"Title eq '{query}'").top(maxResults).get().execute_query()
- results = [{
- "title": item.properties["Title"],
- "url": item.properties["FileRef"],
- "modified": item.properties["Modified"],
- "created": item.properties["Created"]
- } for item in items]
- else:
- # Search entire site
- search_results = ctx.search(query).execute_query()
- results = [{
- "title": result.properties["Title"],
- "url": result.properties["Path"],
- "modified": result.properties["LastModifiedTime"],
- "created": result.properties["Created"]
- } for result in search_results[:maxResults]]
-
return self._createResult(
success=True,
data={
"query": query,
+ "siteId": siteId,
+ "contentType": contentType,
"results": results
}
)
+
except Exception as e:
- logger.error(f"Error searching SharePoint documents: {e}")
+ logger.error(f"Error searching SharePoint: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Search failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _read_document(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Read SharePoint document content"""
+
+ @action
+ async def read(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Read SharePoint item
+
+ Args:
+ parameters:
+ itemId: ID of the item to read
+ siteId: Site ID containing the item
+ listId: List ID containing the item
+ """
try:
- siteUrl = parameters["siteUrl"]
- fileUrl = parameters["fileUrl"]
+ itemId = parameters["itemId"]
+ siteId = parameters.get("siteId")
+ listId = parameters.get("listId")
- # Create SharePoint context
- ctx = ClientContext(siteUrl).with_credentials(
- UserCredential(userConnection.authToken, userConnection.refreshToken)
+ # Read item
+ item = await self.sharepointService.readItem(
+ itemId=itemId,
+ siteId=siteId,
+ listId=listId
)
- # Get file
- file = ctx.web.get_file_by_server_relative_url(fileUrl)
- file_content = file.read().execute_query()
-
return self._createResult(
success=True,
data={
- "url": fileUrl,
- "content": file_content.content.decode('utf-8'),
- "modified": file.properties["TimeLastModified"],
- "size": file.properties["Length"]
+ "itemId": itemId,
+ "siteId": siteId,
+ "listId": listId,
+ "item": item
}
)
+
except Exception as e:
- logger.error(f"Error reading SharePoint document: {e}")
+ logger.error(f"Error reading SharePoint item: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Read failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _write_document(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Write content to SharePoint document"""
+
+ @action
+ async def write(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Write SharePoint item
+
+ Args:
+ parameters:
+ siteId: Site ID to write to
+ listId: List ID to write to
+ item: Item data to write
+ """
try:
- siteUrl = parameters["siteUrl"]
- fileUrl = parameters["fileUrl"]
- content = parameters["content"]
- contentType = parameters.get("contentType", "text/plain")
+ siteId = parameters["siteId"]
+ listId = parameters["listId"]
+ item = parameters["item"]
- # Create SharePoint context
- ctx = ClientContext(siteUrl).with_credentials(
- UserCredential(userConnection.authToken, userConnection.refreshToken)
+ # Write item
+ itemId = await self.sharepointService.writeItem(
+ siteId=siteId,
+ listId=listId,
+ item=item
)
- # Get or create file
- try:
- file = ctx.web.get_file_by_server_relative_url(fileUrl)
- except:
- # Create new file
- folderUrl = "/".join(fileUrl.split("/")[:-1])
- fileName = fileUrl.split("/")[-1]
- folder = ctx.web.get_folder_by_server_relative_url(folderUrl)
- file = folder.upload_file(fileName, content.encode('utf-8')).execute_query()
-
- # Update file content
- file.write(content.encode('utf-8')).execute_query()
-
return self._createResult(
success=True,
data={
- "url": fileUrl,
- "modified": datetime.now(UTC).isoformat(),
- "size": len(content.encode('utf-8'))
+ "siteId": siteId,
+ "listId": listId,
+ "itemId": itemId
}
)
+
except Exception as e:
- logger.error(f"Error writing SharePoint document: {e}")
+ logger.error(f"Error writing SharePoint item: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Write failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _readList(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Read items from SharePoint list"""
+
+ @action
+ async def readList(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Read SharePoint list
+
+ Args:
+ parameters:
+ listId: ID of the list to read
+ siteId: Site ID containing the list
+ query: Query to filter items
+ maxResults: Maximum number of results
+ """
try:
- siteUrl = parameters["siteUrl"]
- listName = parameters["listName"]
+ listId = parameters["listId"]
+ siteId = parameters.get("siteId")
query = parameters.get("query")
- fields = parameters.get("fields", ["*"])
+ maxResults = parameters.get("maxResults", 100)
- # Create SharePoint account
- account = Account(
- credentials=(userConnection.authToken, userConnection.refreshToken),
- protocol=MSGraphProtocol()
+ # Read list
+ items = await self.sharepointService.readList(
+ listId=listId,
+ siteId=siteId,
+ query=query,
+ maxResults=maxResults
)
- # Get site
- site = account.get_site(siteUrl)
-
- # Get list
- list = site.get_list(listName)
-
- # Get items
- if query:
- items = list.get_items(query=query, fields=fields)
- else:
- items = list.get_items(fields=fields)
-
return self._createResult(
success=True,
data={
- "siteUrl": siteUrl,
- "listName": listName,
+ "listId": listId,
+ "siteId": siteId,
"items": items
}
)
+
except Exception as e:
- logger.error(f"Error reading SharePoint list: {e}")
+ logger.error(f"Error reading SharePoint list: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Read failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _writeList(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Write items to SharePoint list"""
+
+ @action
+ async def writeList(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Write multiple items to SharePoint list
+
+ Args:
+ parameters:
+ siteId: Site ID to write to
+ listId: List ID to write to
+ items: List of item data to write
+ """
try:
- siteUrl = parameters["siteUrl"]
- listName = parameters["listName"]
+ siteId = parameters["siteId"]
+ listId = parameters["listId"]
items = parameters["items"]
- # Create SharePoint account
- account = Account(
- credentials=(userConnection.authToken, userConnection.refreshToken),
- protocol=MSGraphProtocol()
+ # Write items
+ itemIds = await self.sharepointService.writeList(
+ siteId=siteId,
+ listId=listId,
+ items=items
)
- # Get site
- site = account.get_site(siteUrl)
-
- # Get list
- list = site.get_list(listName)
-
- # Add items
- results = []
- for item in items:
- result = list.add_item(item)
- results.append({
- "id": result.id,
- "status": "success"
- })
-
return self._createResult(
success=True,
data={
- "siteUrl": siteUrl,
- "listName": listName,
- "results": results
+ "siteId": siteId,
+ "listId": listId,
+ "itemIds": itemIds
}
)
+
except Exception as e:
- logger.error(f"Error writing to SharePoint list: {e}")
+ logger.error(f"Error writing SharePoint list: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Write failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _createList(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
- """Create a new SharePoint list"""
+
+ @action
+ async def createList(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Create SharePoint list
+
+ Args:
+ parameters:
+ siteId: Site ID to create list in
+ name: Name of the list
+ description: List description
+ template: List template
+ fields: List field definitions
+ """
try:
- siteUrl = parameters["siteUrl"]
- listName = parameters["listName"]
+ siteId = parameters["siteId"]
+ name = parameters["name"]
description = parameters.get("description")
- template = parameters.get("template", "generic")
+ template = parameters.get("template", "genericList")
fields = parameters.get("fields", [])
- # Create SharePoint account
- account = Account(
- credentials=(userConnection.authToken, userConnection.refreshToken),
- protocol=MSGraphProtocol()
- )
-
- # Get site
- site = account.get_site(siteUrl)
-
# Create list
- list = site.create_list(
- name=listName,
+ listId = await self.sharepointService.createList(
+ siteId=siteId,
+ name=name,
description=description,
- template=template
+ template=template,
+ fields=fields
)
- # Add fields
- for field in fields:
- list.add_field(
- name=field["name"],
- field_type=field["type"],
- required=field.get("required", False),
- description=field.get("description")
- )
-
return self._createResult(
success=True,
data={
- "siteUrl": siteUrl,
- "listName": listName,
- "id": list.id,
- "webUrl": list.web_url
+ "siteId": siteId,
+ "listId": listId,
+ "name": name
}
)
+
except Exception as e:
- logger.error(f"Error creating SharePoint list: {e}")
+ logger.error(f"Error creating SharePoint list: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Create failed: {str(e)}"}
+ data={"error": str(e)}
)
\ No newline at end of file
diff --git a/modules/methods/methodWeb.py b/modules/methods/methodWeb.py
index 9398741c..13e4b2e8 100644
--- a/modules/methods/methodWeb.py
+++ b/modules/methods/methodWeb.py
@@ -1,468 +1,177 @@
-from typing import Dict, Any, Optional
-import logging
-import aiohttp
-import asyncio
-from bs4 import BeautifulSoup
-from urllib.parse import urljoin, urlparse
-import re
-from datetime import datetime, UTC
-import requests
-import time
-import json
+"""
+Web method module.
+Handles web operations using the web service.
+"""
-from modules.methods.methodBase import MethodBase, MethodResult
-from modules.shared.configuration import APP_CONFIG
+import logging
+from typing import Dict, Any, List, Optional
+from datetime import datetime
+
+from modules.interfaces.interfaceWeb import WebService
+from modules.methods.methodBase import MethodBase, MethodResult, action
logger = logging.getLogger(__name__)
class MethodWeb(MethodBase):
- """Web method implementation for web operations"""
+ """Web method implementation"""
- def __init__(self):
- super().__init__()
- self.name = "web"
- self.description = "Handle web operations like search, crawl, and content extraction"
+ def __init__(self, serviceContainer):
+ """Initialize the web method"""
+ super().__init__(serviceContainer)
+ self.webService = WebService(serviceContainer)
+
+ @action
+ async def search(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Search web content
- # Web crawling configuration from agentWebcrawler
- self.srcApikey = APP_CONFIG.get("Agent_Webcrawler_SERPAPI_APIKEY", "")
- self.srcEngine = APP_CONFIG.get("Agent_Webcrawler_SERPAPI_ENGINE", "google")
- self.srcCountry = APP_CONFIG.get("Agent_Webcrawler_SERPAPI_COUNTRY", "auto")
- self.maxResults = int(APP_CONFIG.get("Agent_Webcrawler_SERPAPI_MAX_SEARCH_RESULTS", "5"))
- self.timeout = int(APP_CONFIG.get("Agent_Webcrawler_SERPAPI_TIMEOUT", "30"))
- self.userAgent = APP_CONFIG.get("Agent_Webcrawler_SERPAPI_USER_AGENT", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
-
- if not self.srcApikey:
- logger.error("SerpAPI key not configured")
-
- @property
- def actions(self) -> Dict[str, Dict[str, Any]]:
- """Available actions and their parameters"""
- return {
- "search": {
- "description": "Search web content",
- "retryMax": 3,
- "timeout": 30,
- "parameters": {
- "query": {"type": "string", "required": True},
- "maxResults": {"type": "number", "required": False},
- "filters": {"type": "object", "required": False},
- "searchEngine": {"type": "string", "required": False}
- }
- },
- "crawl": {
- "description": "Crawl web pages",
- "retryMax": 2,
- "timeout": 60,
- "parameters": {
- "url": {"type": "string", "required": True},
- "depth": {"type": "number", "required": False},
- "followLinks": {"type": "boolean", "required": False},
- "includeImages": {"type": "boolean", "required": False},
- "respectRobots": {"type": "boolean", "required": False}
- }
- },
- "extract": {
- "description": "Extract content from web page",
- "retryMax": 2,
- "timeout": 30,
- "parameters": {
- "url": {"type": "string", "required": True},
- "selectors": {"type": "array", "items": "string", "required": False},
- "format": {"type": "string", "required": False},
- "includeMetadata": {"type": "boolean", "required": False}
- }
- }
- }
-
- async def execute(self, action: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
- """Execute web method"""
- try:
- # Validate parameters
- if not await self.validateParameters(action, parameters):
- return self._createResult(
- success=False,
- data={"error": f"Invalid parameters for {action}"}
- )
-
- # Execute action
- if action == "fetchUrl":
- return await self._fetchUrl(parameters)
- elif action == "parseContent":
- return await self._parseContent(parameters)
- elif action == "extractData":
- return await self._extractData(parameters)
- else:
- return self._createResult(
- success=False,
- data={"error": f"Unknown action: {action}"}
- )
-
- except Exception as e:
- logger.error(f"Error executing web {action}: {e}")
- return self._createResult(
- success=False,
- data={"error": str(e)}
- )
-
- async def _fetchUrl(self, parameters: Dict[str, Any]) -> MethodResult:
- """Fetch content from URL"""
- try:
- url = parameters["url"]
- method = parameters.get("method", "GET")
- headers = parameters.get("headers", {})
- data = parameters.get("data")
- timeout = parameters.get("timeout", 30)
-
- async with aiohttp.ClientSession() as session:
- async with session.request(
- method=method,
- url=url,
- headers=headers,
- data=data,
- timeout=timeout
- ) as response:
- content = await response.text()
- return self._createResult(
- success=True,
- data={
- "url": url,
- "status": response.status,
- "headers": dict(response.headers),
- "content": content
- }
- )
- except Exception as e:
- logger.error(f"Error fetching URL: {e}")
- return self._createResult(
- success=False,
- data={"error": f"Fetch failed: {str(e)}"}
- )
-
- async def _parseContent(self, parameters: Dict[str, Any]) -> MethodResult:
- """Parse web content"""
- try:
- content = parameters["content"]
- contentType = parameters.get("contentType", "html")
-
- if contentType == "html":
- soup = BeautifulSoup(content, "html.parser")
- return self._createResult(
- success=True,
- data={
- "type": "html",
- "title": soup.title.string if soup.title else None,
- "text": soup.get_text(),
- "links": [a.get("href") for a in soup.find_all("a", href=True)],
- "images": [img.get("src") for img in soup.find_all("img", src=True)]
- }
- )
- elif contentType == "json":
- data = json.loads(content)
- return self._createResult(
- success=True,
- data={
- "type": "json",
- "data": data
- }
- )
- else:
- raise ValueError(f"Unsupported content type: {contentType}")
- except Exception as e:
- logger.error(f"Error parsing content: {e}")
- return self._createResult(
- success=False,
- data={"error": f"Parse failed: {str(e)}"}
- )
-
- async def _extractData(self, parameters: Dict[str, Any]) -> MethodResult:
- """Extract data from web content"""
- try:
- content = parameters["content"]
- contentType = parameters.get("contentType", "html")
- selectors = parameters["selectors"]
-
- if contentType == "html":
- soup = BeautifulSoup(content, "html.parser")
- results = {}
-
- for key, selector in selectors.items():
- elements = soup.select(selector)
- if len(elements) == 1:
- results[key] = elements[0].get_text().strip()
- else:
- results[key] = [el.get_text().strip() for el in elements]
-
- return self._createResult(
- success=True,
- data={
- "type": "html",
- "results": results
- }
- )
- elif contentType == "json":
- data = json.loads(content)
- results = {}
-
- for key, path in selectors.items():
- value = data
- for part in path.split("."):
- if isinstance(value, dict):
- value = value.get(part)
- elif isinstance(value, list) and part.isdigit():
- value = value[int(part)]
- else:
- value = None
- break
- results[key] = value
-
- return self._createResult(
- success=True,
- data={
- "type": "json",
- "results": results
- }
- )
- else:
- raise ValueError(f"Unsupported content type: {contentType}")
- except Exception as e:
- logger.error(f"Error extracting data: {e}")
- return self._createResult(
- success=False,
- data={"error": f"Extract failed: {str(e)}"}
- )
-
- async def _search_web(self, parameters: Dict[str, Any]) -> MethodResult:
- """Search web content"""
+ Args:
+ parameters:
+ query: Search query
+ engine: Search engine to use (google, bing)
+ maxResults: Maximum number of results
+ """
try:
query = parameters["query"]
+ engine = parameters.get("engine", "google")
maxResults = parameters.get("maxResults", 10)
- filters = parameters.get("filters", {})
- searchEngine = parameters.get("searchEngine", "google")
- # Implement search using different engines
- if searchEngine.lower() == "google":
- # Use Google Custom Search API
- # TODO: Implement Google Custom Search API integration
- results = await self._google_search(query, maxResults, filters)
- elif searchEngine.lower() == "bing":
- # Use Bing Web Search API
- # TODO: Implement Bing Web Search API integration
- results = await self._bing_search(query, maxResults, filters)
- else:
- return self._createResult(
- success=False,
- data={"error": f"Unsupported search engine: {searchEngine}"}
- )
+ # Search web
+ results = await self.webService.searchContent(
+ query=query,
+ engine=engine,
+ maxResults=maxResults
+ )
return self._createResult(
success=True,
data={
"query": query,
- "engine": searchEngine,
+ "engine": engine,
"results": results
}
)
+
except Exception as e:
- logger.error(f"Error searching web: {e}")
+ logger.error(f"Error searching web: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Search failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- async def _google_search(self, query: str, max_results: int, filters: Dict[str, Any]) -> list:
- """Search using Google Custom Search API"""
- # TODO: Implement Google Custom Search API
- # This is a placeholder implementation
- return [
- {
- "title": "Example Result",
- "url": "https://example.com",
- "snippet": "Example search result snippet",
- "source": "google"
- }
- ]
-
- async def _bing_search(self, query: str, max_results: int, filters: Dict[str, Any]) -> list:
- """Search using Bing Web Search API"""
- # TODO: Implement Bing Web Search API
- # This is a placeholder implementation
- return [
- {
- "title": "Example Result",
- "url": "https://example.com",
- "snippet": "Example search result snippet",
- "source": "bing"
- }
- ]
-
- async def _crawl_page(self, parameters: Dict[str, Any]) -> MethodResult:
- """Crawl web pages"""
+
+ @action
+ async def crawl(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Crawl web page
+
+ Args:
+ parameters:
+ url: URL to crawl
+ depth: Crawl depth
+ followLinks: Whether to follow links
+ extractContent: Whether to extract content
+ """
try:
url = parameters["url"]
depth = parameters.get("depth", 1)
followLinks = parameters.get("followLinks", False)
- includeImages = parameters.get("includeImages", False)
- respectRobots = parameters.get("respectRobots", True)
+ extractContent = parameters.get("extractContent", True)
- # Check robots.txt if required
- if respectRobots:
- if not await self._check_robots_txt(url):
- return self._createResult(
- success=False,
- data={"error": "Crawling not allowed by robots.txt"}
- )
+ # Crawl page
+ results = await self.webService.crawlPage(
+ url=url,
+ depth=depth,
+ followLinks=followLinks,
+ extractContent=extractContent
+ )
+
+ return self._createResult(
+ success=True,
+ data={
+ "url": url,
+ "depth": depth,
+ "results": results
+ }
+ )
- # Crawl the page
- async with aiohttp.ClientSession() as session:
- async with session.get(url) as response:
- if response.status == 200:
- html = await response.text()
- soup = BeautifulSoup(html, 'html.parser')
-
- # Extract basic information
- result = {
- "url": url,
- "title": soup.title.string if soup.title else None,
- "description": self._get_meta_description(soup),
- "links": [],
- "images": [] if includeImages else None,
- "text": soup.get_text(strip=True),
- "crawled": datetime.now(UTC).isoformat()
- }
-
- # Extract links if followLinks is True
- if followLinks:
- baseUrl = url
- for link in soup.find_all('a'):
- href = link.get('href')
- if href:
- absoluteUrl = urljoin(baseUrl, href)
- if self._is_valid_url(absoluteUrl):
- result["links"].append({
- "url": absoluteUrl,
- "text": link.get_text(strip=True)
- })
-
- # Extract images if includeImages is True
- if includeImages:
- for img in soup.find_all('img'):
- src = img.get('src')
- if src:
- absoluteSrc = urljoin(url, src)
- result["images"].append({
- "url": absoluteSrc,
- "alt": img.get('alt', ''),
- "title": img.get('title', '')
- })
-
- return self._createResult(
- success=True,
- data=result
- )
- else:
- return self._createResult(
- success=False,
- data={"error": f"Failed to fetch URL: {response.status}"}
- )
except Exception as e:
- logger.error(f"Error crawling page: {e}")
+ logger.error(f"Error crawling web page: {str(e)}")
return self._createResult(
success=False,
- data={"error": f"Crawl failed: {str(e)}"}
+ data={"error": str(e)}
)
-
- def _get_meta_description(self, soup: BeautifulSoup) -> Optional[str]:
- """Extract meta description from HTML"""
- metaDesc = soup.find('meta', attrs={'name': 'description'})
- if metaDesc:
- return metaDesc.get('content')
- return None
-
- def _is_valid_url(self, url: str) -> bool:
- """Check if URL is valid"""
+
+ @action
+ async def extract(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Extract content from web page
+
+ Args:
+ parameters:
+ url: URL to extract from
+ selectors: CSS selectors to extract
+ format: Output format (text, html, json)
+ """
try:
- result = urlparse(url)
- return all([result.scheme, result.netloc])
- except:
- return False
-
- async def _check_robots_txt(self, url: str) -> bool:
- """Check if URL is allowed by robots.txt"""
- try:
- parsedUrl = urlparse(url)
- robotsUrl = f"{parsedUrl.scheme}://{parsedUrl.netloc}/robots.txt"
+ url = parameters["url"]
+ selectors = parameters.get("selectors", ["body"])
+ format = parameters.get("format", "text")
- async with aiohttp.ClientSession() as session:
- async with session.get(robotsUrl, headers={"User-Agent": self.userAgent}, timeout=self.timeout) as response:
- if response.status == 200:
- robotsContent = await response.text()
-
- # Parse robots.txt content
- userAgent = "*" # Default to all user agents
- disallowPaths = []
-
- for line in robotsContent.splitlines():
- line = line.strip().lower()
- if line.startswith("user-agent:"):
- userAgent = line[11:].strip()
- elif line.startswith("disallow:") and userAgent in ["*", self.userAgent.lower()]:
- path = line[9:].strip()
- if path:
- disallowPaths.append(path)
-
- # Check if URL path is disallowed
- urlPath = parsedUrl.path
- for disallowPath in disallowPaths:
- if urlPath.startswith(disallowPath):
- return False
-
- return True
- else:
- # If robots.txt doesn't exist, assume crawling is allowed
- return True
-
- except Exception as e:
- logger.warning(f"Error checking robots.txt for {url}: {str(e)}")
- # If there's an error, assume crawling is allowed
- return True
-
- def _detect_language(self, soup: BeautifulSoup) -> str:
- """Detect page language"""
- try:
- # Try to get language from HTML lang attribute
- if soup.html and soup.html.get('lang'):
- return soup.html.get('lang')
+ # Extract content
+ content = await self.webService.extractContent(
+ url=url,
+ selectors=selectors,
+ format=format
+ )
- # Try to get language from meta tag
- metaLang = soup.find('meta', attrs={'http-equiv': 'content-language'})
- if metaLang:
- return metaLang.get('content', 'en')
-
- # Try to get language from meta charset
- metaCharset = soup.find('meta', attrs={'charset': True})
- if metaCharset:
- charset = metaCharset.get('charset', '').lower()
- if 'utf-8' in charset:
- return 'en' # Default to English for UTF-8
-
- # Try to detect language from content
- # This is a simple heuristic based on common words
- text = soup.get_text().lower()
- commonWords = {
- 'en': ['the', 'and', 'of', 'to', 'in', 'is', 'that', 'for', 'it', 'with'],
- 'es': ['el', 'la', 'los', 'las', 'de', 'y', 'en', 'que', 'por', 'con'],
- 'fr': ['le', 'la', 'les', 'de', 'et', 'en', 'que', 'pour', 'avec', 'dans'],
- 'de': ['der', 'die', 'das', 'und', 'in', 'den', 'von', 'zu', 'für', 'mit']
- }
-
- wordCounts = {lang: sum(1 for word in words if f' {word} ' in f' {text} ')
- for lang, words in commonWords.items()}
-
- if wordCounts:
- return max(wordCounts.items(), key=lambda x: x[1])[0]
-
- return 'en' # Default to English if no language detected
+ return self._createResult(
+ success=True,
+ data={
+ "url": url,
+ "format": format,
+ "content": content
+ }
+ )
except Exception as e:
- logger.warning(f"Error detecting language: {str(e)}")
- return 'en' # Default to English on error
\ No newline at end of file
+ logger.error(f"Error extracting web content: {str(e)}")
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
+
+ @action
+ async def validate(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
+ """
+ Validate web page
+
+ Args:
+ parameters:
+ url: URL to validate
+ checks: List of checks to perform
+ """
+ try:
+ url = parameters["url"]
+ checks = parameters.get("checks", ["accessibility", "seo", "performance"])
+
+ # Validate page
+ results = await self.webService.validatePage(
+ url=url,
+ checks=checks
+ )
+
+ return self._createResult(
+ success=True,
+ data={
+ "url": url,
+ "checks": checks,
+ "results": results
+ }
+ )
+
+ except Exception as e:
+ logger.error(f"Error validating web page: {str(e)}")
+ return self._createResult(
+ success=False,
+ data={"error": str(e)}
+ )
\ No newline at end of file
diff --git a/modules/routes/routeAdmin.py b/modules/routes/routeAdmin.py
index 1e3156d7..f895521c 100644
--- a/modules/routes/routeAdmin.py
+++ b/modules/routes/routeAdmin.py
@@ -9,7 +9,7 @@ from fastapi import HTTPException, status
from modules.shared.configuration import APP_CONFIG
from modules.security.auth import limiter, getCurrentUser
-from modules.interfaces.serviceAppModel import User
+from modules.interfaces.interfaceAppModel import User
router = APIRouter(
prefix="",
diff --git a/modules/routes/routeAttributes.py b/modules/routes/routeAttributes.py
index 0f9fec0f..45a759e1 100644
--- a/modules/routes/routeAttributes.py
+++ b/modules/routes/routeAttributes.py
@@ -11,7 +11,7 @@ import logging
from modules.security.auth import limiter, getCurrentUser
# Import the attribute definition and helper functions
-from modules.interfaces.serviceAppModel import User
+from modules.interfaces.interfaceAppModel import User
from modules.shared.attributeUtils import getModelClasses, getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
# Configure logger
diff --git a/modules/routes/routeDataConnections.py b/modules/routes/routeDataConnections.py
index 0ac46005..d7831d44 100644
--- a/modules/routes/routeDataConnections.py
+++ b/modules/routes/routeDataConnections.py
@@ -10,9 +10,9 @@ from datetime import datetime
import logging
import json
-from modules.interfaces.serviceAppModel import User, UserConnection, AuthAuthority, ConnectionStatus
+from modules.interfaces.interfaceAppModel import User, UserConnection, AuthAuthority, ConnectionStatus
from modules.security.auth import getCurrentUser, limiter
-from modules.interfaces.serviceAppClass import getInterface, getRootInterface
+from modules.interfaces.interfaceAppObjects import getInterface, getRootInterface
# Configure logger
logger = logging.getLogger(__name__)
diff --git a/modules/routes/routeDataFiles.py b/modules/routes/routeDataFiles.py
index 7860fec3..26f63bd0 100644
--- a/modules/routes/routeDataFiles.py
+++ b/modules/routes/routeDataFiles.py
@@ -14,10 +14,10 @@ from pydantic import BaseModel
from modules.security.auth import limiter, getCurrentUser
# Import interfaces
-import modules.interfaces.serviceManagementClass as serviceManagementClass
-from modules.interfaces.serviceManagementModel import FileItem, FilePreview
+import modules.interfaces.interfaceComponentObjects as interfaceComponentObjects
+from modules.interfaces.interfaceComponentModel import FileItem, FilePreview
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
-from modules.interfaces.serviceAppModel import User
+from modules.interfaces.interfaceAppModel import User
# Configure logger
logger = logging.getLogger(__name__)
@@ -46,7 +46,7 @@ async def get_files(
) -> List[FileItem]:
"""Get all files"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Get all files generically - only metadata, no binary data
files = managementInterface.getAllFiles()
@@ -70,17 +70,17 @@ async def upload_file(
) -> JSONResponse:
"""Upload a file"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Read file
fileContent = await file.read()
# Check size limits
- maxSize = int(serviceManagementClass.APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # in bytes
+ maxSize = int(interfaceComponentObjects.APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # in bytes
if len(fileContent) > maxSize:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
- detail=f"File too large. Maximum size: {serviceManagementClass.APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
+ detail=f"File too large. Maximum size: {interfaceComponentObjects.APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
)
# Save file via LucyDOM interface in the database
@@ -101,7 +101,7 @@ async def upload_file(
"file": fileMeta
})
- except serviceManagementClass.FileStorageError as e:
+ except interfaceComponentObjects.FileStorageError as e:
logger.error(f"Error during file upload (storage): {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -123,7 +123,7 @@ async def get_file(
) -> FileItem:
"""Get a file"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Get file via LucyDOM interface from the database
fileData = managementInterface.getFile(fileId)
@@ -135,19 +135,19 @@ async def get_file(
return FileItem(**fileData)
- except serviceManagementClass.FileNotFoundError as e:
+ except interfaceComponentObjects.FileNotFoundError as e:
logger.warning(f"File not found: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(e)
)
- except serviceManagementClass.FilePermissionError as e:
+ except interfaceComponentObjects.FilePermissionError as e:
logger.warning(f"No permission for file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=str(e)
)
- except serviceManagementClass.FileError as e:
+ except interfaceComponentObjects.FileError as e:
logger.error(f"Error retrieving file: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -170,7 +170,7 @@ async def update_file(
) -> FileItem:
"""Update file info"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Get the file from the database
file = managementInterface.getFile(fileId)
@@ -216,7 +216,7 @@ async def delete_file(
currentUser: User = Depends(getCurrentUser)
) -> Dict[str, Any]:
"""Delete a file"""
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Check if the file exists
existingFile = managementInterface.getFile(fileId)
@@ -243,7 +243,7 @@ async def get_file_stats(
) -> Dict[str, Any]:
"""Returns statistics about the stored files"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Get all files - metadata only
allFiles = managementInterface.getAllFiles()
@@ -282,7 +282,7 @@ async def download_file(
) -> Response:
"""Download a file"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Get file data
fileData = managementInterface.getFile(fileId)
@@ -326,7 +326,7 @@ async def preview_file(
) -> FilePreview:
"""Preview a file's content"""
try:
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Get file preview
preview = managementInterface.getFilePreview(fileId)
diff --git a/modules/routes/routeDataMandates.py b/modules/routes/routeDataMandates.py
index c2aebea2..a6be8e8d 100644
--- a/modules/routes/routeDataMandates.py
+++ b/modules/routes/routeDataMandates.py
@@ -17,11 +17,11 @@ from pydantic import BaseModel
from modules.security.auth import limiter, getCurrentUser
# Import interfaces
-import modules.interfaces.serviceAppClass as serviceAppClass
+import modules.interfaces.interfaceAppObjects as interfaceAppObjects
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
# Import the model classes
-from modules.interfaces.serviceAppModel import Mandate, User
+from modules.interfaces.interfaceAppModel import Mandate, User
# Configure logger
logger = logging.getLogger(__name__)
@@ -44,7 +44,7 @@ async def get_mandates(
) -> List[Mandate]:
"""Get all mandates"""
try:
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
mandates = appInterface.getAllMandates()
return mandates
except Exception as e:
@@ -63,7 +63,7 @@ async def get_mandate(
) -> Mandate:
"""Get a specific mandate by ID"""
try:
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
mandate = appInterface.getMandate(mandateId)
if not mandate:
@@ -91,7 +91,7 @@ async def create_mandate(
) -> Mandate:
"""Create a new mandate"""
try:
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
# Create mandate
newMandate = appInterface.createMandate(
@@ -125,7 +125,7 @@ async def update_mandate(
) -> Mandate:
"""Update an existing mandate"""
try:
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
# Check if mandate exists
existingMandate = appInterface.getMandate(mandateId)
@@ -163,7 +163,7 @@ async def delete_mandate(
) -> Dict[str, Any]:
"""Delete a mandate"""
try:
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
# Check if mandate exists
existingMandate = appInterface.getMandate(mandateId)
diff --git a/modules/routes/routeDataPrompts.py b/modules/routes/routeDataPrompts.py
index 6f54fc9c..b4809151 100644
--- a/modules/routes/routeDataPrompts.py
+++ b/modules/routes/routeDataPrompts.py
@@ -12,10 +12,10 @@ from pydantic import BaseModel
from modules.security.auth import limiter, getCurrentUser
# Import interfaces
-import modules.interfaces.serviceManagementClass as serviceManagementClass
-from modules.interfaces.serviceManagementModel import Prompt
+import modules.interfaces.interfaceComponentObjects as interfaceComponentObjects
+from modules.interfaces.interfaceComponentModel import Prompt
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
-from modules.interfaces.serviceAppModel import User
+from modules.interfaces.interfaceAppModel import User
# Configure logger
logger = logging.getLogger(__name__)
@@ -34,7 +34,7 @@ async def get_prompts(
currentUser: User = Depends(getCurrentUser)
) -> List[Prompt]:
"""Get all prompts"""
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
prompts = managementInterface.getAllPrompts()
return prompts
@@ -46,7 +46,7 @@ async def create_prompt(
currentUser: User = Depends(getCurrentUser)
) -> Prompt:
"""Create a new prompt"""
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Convert Prompt to dict for interface
prompt_data = prompt.dict()
@@ -64,7 +64,7 @@ async def get_prompt(
currentUser: User = Depends(getCurrentUser)
) -> Prompt:
"""Get a specific prompt"""
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Get prompt
prompt = managementInterface.getPrompt(promptId)
@@ -85,7 +85,7 @@ async def update_prompt(
currentUser: User = Depends(getCurrentUser)
) -> Prompt:
"""Update an existing prompt"""
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Check if the prompt exists
existingPrompt = managementInterface.getPrompt(promptId)
@@ -117,7 +117,7 @@ async def delete_prompt(
currentUser: User = Depends(getCurrentUser)
) -> Dict[str, Any]:
"""Delete a prompt"""
- managementInterface = serviceManagementClass.getInterface(currentUser)
+ managementInterface = interfaceComponentObjects.getInterface(currentUser)
# Check if the prompt exists
existingPrompt = managementInterface.getPrompt(promptId)
diff --git a/modules/routes/routeDataUsers.py b/modules/routes/routeDataUsers.py
index bcaeead4..9d8be813 100644
--- a/modules/routes/routeDataUsers.py
+++ b/modules/routes/routeDataUsers.py
@@ -14,11 +14,11 @@ import os
from pydantic import BaseModel
# Import interfaces and models
-import modules.interfaces.serviceAppClass as serviceAppClass
+import modules.interfaces.interfaceAppObjects as interfaceAppObjects
from modules.security.auth import getCurrentUser, limiter, getCurrentUser
# Import the attribute definition and helper functions
-from modules.interfaces.serviceAppModel import User, AttributeDefinition
+from modules.interfaces.interfaceAppModel import User, AttributeDefinition
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse
# Configure logger
@@ -39,7 +39,7 @@ async def get_users(
) -> List[User]:
"""Get all users in the current mandate"""
try:
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
# If mandateId is provided, use it, otherwise use the current user's mandate
targetMandateId = mandateId or currentUser.mandateId
# Get all users without filtering by enabled status
@@ -61,7 +61,7 @@ async def get_user(
) -> User:
"""Get a specific user by ID"""
try:
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
# Get user without filtering by enabled status
user = appInterface.getUser(userId)
@@ -89,7 +89,7 @@ async def create_user(
currentUser: User = Depends(getCurrentUser)
) -> User:
"""Create a new user"""
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
# Convert User to dict for interface
user_dict = user_data.dict()
@@ -108,7 +108,7 @@ async def update_user(
currentUser: User = Depends(getCurrentUser)
) -> User:
"""Update an existing user"""
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
# Check if the user exists
existingUser = appInterface.getUser(userId)
@@ -140,7 +140,7 @@ async def delete_user(
currentUser: User = Depends(getCurrentUser)
) -> Dict[str, Any]:
"""Delete a user"""
- appInterface = serviceAppClass.getInterface(currentUser)
+ appInterface = interfaceAppObjects.getInterface(currentUser)
# Check if the user exists
existingUser = appInterface.getUser(userId)
diff --git a/modules/routes/routeSecurityGoogle.py b/modules/routes/routeSecurityGoogle.py
index 944b2fa7..d4f69108 100644
--- a/modules/routes/routeSecurityGoogle.py
+++ b/modules/routes/routeSecurityGoogle.py
@@ -14,8 +14,8 @@ from google.auth.transport.requests import Request as GoogleRequest
from googleapiclient.discovery import build
from modules.shared.configuration import APP_CONFIG
-from modules.interfaces.serviceAppClass import getInterface, getRootInterface
-from modules.interfaces.serviceAppModel import AuthAuthority, User, Token, ConnectionStatus, UserConnection
+from modules.interfaces.interfaceAppObjects import getInterface, getRootInterface
+from modules.interfaces.interfaceAppModel import AuthAuthority, User, Token, ConnectionStatus, UserConnection
from modules.security.auth import getCurrentUser, limiter
from modules.shared.attributeUtils import ModelMixin
diff --git a/modules/routes/routeSecurityLocal.py b/modules/routes/routeSecurityLocal.py
index 441975b8..4d53d216 100644
--- a/modules/routes/routeSecurityLocal.py
+++ b/modules/routes/routeSecurityLocal.py
@@ -12,8 +12,8 @@ from pydantic import BaseModel
# Import auth modules
from modules.security.auth import createAccessToken, getCurrentUser, limiter
-from modules.interfaces.serviceAppClass import getInterface, getRootInterface
-from modules.interfaces.serviceAppModel import User, UserInDB, AuthAuthority, UserPrivilege, Token
+from modules.interfaces.interfaceAppObjects import getInterface, getRootInterface
+from modules.interfaces.interfaceAppModel import User, UserInDB, AuthAuthority, UserPrivilege, Token
from modules.shared.attributeUtils import ModelMixin
# Configure logger
diff --git a/modules/routes/routeSecurityMsft.py b/modules/routes/routeSecurityMsft.py
index 8b54c580..59a2898e 100644
--- a/modules/routes/routeSecurityMsft.py
+++ b/modules/routes/routeSecurityMsft.py
@@ -12,8 +12,8 @@ import msal
import httpx
from modules.shared.configuration import APP_CONFIG
-from modules.interfaces.serviceAppClass import getInterface, getRootInterface
-from modules.interfaces.serviceAppModel import AuthAuthority, User, Token, ConnectionStatus, UserConnection
+from modules.interfaces.interfaceAppObjects import getInterface, getRootInterface
+from modules.interfaces.interfaceAppModel import AuthAuthority, User, Token, ConnectionStatus, UserConnection
from modules.security.auth import getCurrentUser, limiter, createAccessToken
from modules.shared.attributeUtils import ModelMixin
diff --git a/modules/routes/routeWorkflows.py b/modules/routes/routeWorkflows.py
index a96ca2b4..a5258f9d 100644
--- a/modules/routes/routeWorkflows.py
+++ b/modules/routes/routeWorkflows.py
@@ -15,11 +15,11 @@ from datetime import datetime, timedelta
from modules.security.auth import limiter, getCurrentUser
# Import interfaces
-import modules.interfaces.serviceChatClass as serviceChatClass
-from modules.interfaces.serviceChatClass import getInterface
+import modules.interfaces.interfaceChatObjects as interfaceChatObjects
+from modules.interfaces.interfaceChatObjects import getInterface
# Import models
-from modules.interfaces.serviceChatModel import (
+from modules.interfaces.interfaceChatModel import (
ChatWorkflow,
ChatMessage,
ChatLog,
@@ -28,7 +28,7 @@ from modules.interfaces.serviceChatModel import (
UserInputRequest
)
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse
-from modules.interfaces.serviceAppModel import User
+from modules.interfaces.interfaceAppModel import User
# Configure logger
logger = logging.getLogger(__name__)
@@ -44,7 +44,7 @@ router = APIRouter(
)
def getServiceChat(currentUser: User):
- return serviceChatClass.getInterface(currentUser)
+ return interfaceChatObjects.getInterface(currentUser)
# Consolidated endpoint for getting all workflows
@router.get("/", response_model=List[ChatWorkflow])
@@ -104,10 +104,10 @@ async def get_workflow_status(
"""Get the current status of a workflow."""
try:
# Get service container
- serviceChat = getServiceChat(currentUser)
+ interfaceChat = getServiceChat(currentUser)
# Retrieve workflow
- workflow = serviceChat.getWorkflow(workflowId)
+ workflow = interfaceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -136,10 +136,10 @@ async def get_workflow_logs(
"""Get logs for a workflow with support for selective data transfer."""
try:
# Get service container
- serviceChat = getServiceChat(currentUser)
+ interfaceChat = getServiceChat(currentUser)
# Verify workflow exists
- workflow = serviceChat.getWorkflow(workflowId)
+ workflow = interfaceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -147,7 +147,7 @@ async def get_workflow_logs(
)
# Get all logs
- allLogs = serviceChat.getWorkflowLogs(workflowId)
+ allLogs = interfaceChat.getWorkflowLogs(workflowId)
# Apply selective data transfer if logId is provided
if logId:
@@ -179,10 +179,10 @@ async def get_workflow_messages(
"""Get messages for a workflow with support for selective data transfer."""
try:
# Get service container
- serviceChat = getServiceChat(currentUser)
+ interfaceChat = getServiceChat(currentUser)
# Verify workflow exists
- workflow = serviceChat.getWorkflow(workflowId)
+ workflow = interfaceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -190,7 +190,7 @@ async def get_workflow_messages(
)
# Get all messages
- allMessages = serviceChat.getWorkflowMessages(workflowId)
+ allMessages = interfaceChat.getWorkflowMessages(workflowId)
# Apply selective data transfer if messageId is provided
if messageId:
@@ -225,10 +225,10 @@ async def start_workflow(
"""
try:
# Get service container
- serviceChat = getServiceChat(currentUser)
+ interfaceChat = getServiceChat(currentUser)
- # Start or continue workflow using ChatInterface
- workflow = await serviceChat.workflowStart(currentUser, userInput, workflowId)
+ # Start or continue workflow using ChatObjects
+ workflow = await interfaceChat.workflowStart(currentUser, userInput, workflowId)
return ChatWorkflow(**workflow)
@@ -250,10 +250,10 @@ async def stop_workflow(
"""Stops a running workflow."""
try:
# Get service container
- serviceChat = getServiceChat(currentUser)
+ interfaceChat = getServiceChat(currentUser)
- # Stop workflow using ChatInterface
- workflow = await serviceChat.workflowStop(workflowId)
+ # Stop workflow using ChatObjects
+ workflow = await interfaceChat.workflowStop(workflowId)
return ChatWorkflow(**workflow)
@@ -275,10 +275,10 @@ async def delete_workflow(
"""Deletes a workflow and its associated data."""
try:
# Get service container
- serviceChat = getServiceChat(currentUser)
+ interfaceChat = getServiceChat(currentUser)
# Verify workflow exists
- workflow = serviceChat.getWorkflow(workflowId)
+ workflow = interfaceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -293,7 +293,7 @@ async def delete_workflow(
)
# Delete workflow
- success = serviceChat.deleteWorkflow(workflowId)
+ success = interfaceChat.deleteWorkflow(workflowId)
if not success:
raise HTTPException(
@@ -328,10 +328,10 @@ async def delete_workflow_message(
"""Delete a message from a workflow."""
try:
# Get service container
- serviceChat = getServiceChat(currentUser)
+ interfaceChat = getServiceChat(currentUser)
# Verify workflow exists
- workflow = serviceChat.getWorkflow(workflowId)
+ workflow = interfaceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -339,7 +339,7 @@ async def delete_workflow_message(
)
# Delete the message
- success = serviceChat.deleteWorkflowMessage(workflowId, messageId)
+ success = interfaceChat.deleteWorkflowMessage(workflowId, messageId)
if not success:
raise HTTPException(
@@ -351,7 +351,7 @@ async def delete_workflow_message(
messageIds = workflow.get("messageIds", [])
if messageId in messageIds:
messageIds.remove(messageId)
- serviceChat.updateWorkflow(workflowId, {"messageIds": messageIds})
+ interfaceChat.updateWorkflow(workflowId, {"messageIds": messageIds})
return {
"workflowId": workflowId,
@@ -379,10 +379,10 @@ async def delete_file_from_message(
"""Delete a file reference from a message in a workflow."""
try:
# Get service container
- serviceChat = getServiceChat(currentUser)
+ interfaceChat = getServiceChat(currentUser)
# Verify workflow exists
- workflow = serviceChat.getWorkflow(workflowId)
+ workflow = interfaceChat.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
@@ -390,7 +390,7 @@ async def delete_file_from_message(
)
# Delete file reference from message
- success = serviceChat.deleteFileFromMessage(workflowId, messageId, fileId)
+ success = interfaceChat.deleteFileFromMessage(workflowId, messageId, fileId)
if not success:
raise HTTPException(
diff --git a/modules/security/auth.py b/modules/security/auth.py
index 6eeb7bde..4eb405f0 100644
--- a/modules/security/auth.py
+++ b/modules/security/auth.py
@@ -13,8 +13,8 @@ from slowapi import Limiter
from slowapi.util import get_remote_address
from modules.shared.configuration import APP_CONFIG
-from modules.interfaces.serviceAppClass import getRootInterface
-from modules.interfaces.serviceAppModel import Session, AuthEvent, UserPrivilege, User
+from modules.interfaces.interfaceAppObjects import getRootInterface
+from modules.interfaces.interfaceAppModel import Session, AuthEvent, UserPrivilege, User
# Get Config Data
SECRET_KEY = APP_CONFIG.get("APP_JWT_SECRET_SECRET")
diff --git a/modules/workflow/managerChat.py b/modules/workflow/managerChat.py
index a605bc91..ed91dade 100644
--- a/modules/workflow/managerChat.py
+++ b/modules/workflow/managerChat.py
@@ -5,8 +5,8 @@ import json
import uuid
import time
-from modules.interfaces.serviceAppModel import User
-from modules.interfaces.serviceChatModel import (
+from modules.interfaces.interfaceAppModel import User
+from modules.interfaces.interfaceChatModel import (
TaskStatus, ChatDocument, TaskItem, TaskAction, TaskResult, ChatStat, ChatLog, ChatMessage, ChatWorkflow
)
from modules.workflow.serviceContainer import ServiceContainer
@@ -19,6 +19,7 @@ class ChatManager:
def __init__(self, currentUser: User):
self.currentUser = currentUser
self.service: ServiceContainer = None
+ self.workflow: ChatWorkflow = None
# ===== Initialization and Setup =====
async def initialize(self, workflow: ChatWorkflow) -> None:
@@ -30,30 +31,62 @@ class ChatManager:
async def createInitialTask(self, workflow: ChatWorkflow, initialMessage: ChatMessage) -> Optional[TaskItem]:
"""Create the initial task from the first message"""
try:
- # Get available methods and their actions
- methodCatalog = self.service.getMethodsCatalog()
+ # Create task definition prompt
+ prompt = await self._createTaskDefinitionPrompt(initialMessage.message, workflow)
- # Process user input with AI
- processedInput = await self._processUserInput(initialMessage.message, methodCatalog)
+ # Get AI response
+ response = await self.service.callAiTextAdvanced(prompt)
- # Create actions from processed input
- actions = await self._createActions(processedInput['actions'])
+ # Parse response
+ try:
+ taskDef = json.loads(response)
+ except json.JSONDecodeError:
+ logger.error(f"Invalid JSON in task definition: {response}")
+ return None
- # Create task data
- taskData = {
- "workflowId": workflow.id,
- "userInput": processedInput['objective'],
- "dataList": initialMessage.documents,
- "actionList": [action.dict() for action in actions],
- "status": TaskStatus.PENDING,
- "startedAt": datetime.now(UTC).isoformat(),
- "updatedAt": datetime.now(UTC).isoformat()
- }
+ # Validate task definition
+ if not isinstance(taskDef, dict):
+ logger.error("Task definition must be a JSON object")
+ return None
+
+ requiredFields = ["status", "feedback", "actions"]
+ for field in requiredFields:
+ if field not in taskDef:
+ logger.error(f"Missing required field: {field}")
+ return None
+
+ if not isinstance(taskDef["actions"], list):
+ logger.error("Actions must be a list")
+ return None
+
+ # Create task
+ task = TaskItem(
+ id=str(uuid.uuid4()),
+ workflow=workflow,
+ userInput=initialMessage.message,
+ status=taskDef["status"],
+ feedback=taskDef["feedback"],
+ actions=[]
+ )
+
+ # Add actions
+ for actionDef in taskDef["actions"]:
+ if not isinstance(actionDef, dict):
+ continue
+
+ requiredFields = ["method", "action", "parameters"]
+ if not all(field in actionDef for field in requiredFields):
+ continue
+
+ action = TaskAction(
+ id=str(uuid.uuid4()),
+ method=actionDef["method"],
+ action=actionDef["action"],
+ parameters=actionDef["parameters"],
+ resultLabel=actionDef.get("resultLabel")
+ )
+ task.actions.append(action)
- # Create task using ChatInterface
- task = self.service.createTask(taskData)
- if task:
- self.service.currentTask = task
return task
except Exception as e:
@@ -68,369 +101,153 @@ class ChatManager:
logger.error(f"Previous task failed: {previousResult.error}")
return None
- # Extract task data from previous result
- taskData = previousResult.data
- if not taskData:
- logger.error("No task data in previous result")
- return None
-
- # Create task data
- taskData = {
- "workflowId": workflow.id,
- "userInput": taskData.get('objective', ''),
- "actionList": [action.dict() for action in await self._createActions(taskData.get('actions', []))],
- "status": TaskStatus.PENDING,
- "startedAt": datetime.now(UTC).isoformat(),
- "updatedAt": datetime.now(UTC).isoformat()
- }
+ # Create task definition prompt
+ prompt = await self._createTaskDefinitionPrompt(previousResult.feedback, workflow)
+
+ # Get AI response
+ response = await self.service.callAiTextAdvanced(prompt)
+
+ # Parse response
+ try:
+ taskDef = json.loads(response)
+ except json.JSONDecodeError:
+ logger.error(f"Invalid JSON in task definition: {response}")
+ return None
+
+ # Validate task definition
+ if not isinstance(taskDef, dict):
+ logger.error("Task definition must be a JSON object")
+ return None
+
+ requiredFields = ["status", "feedback", "actions"]
+ for field in requiredFields:
+ if field not in taskDef:
+ logger.error(f"Missing required field: {field}")
+ return None
+
+ if not isinstance(taskDef["actions"], list):
+ logger.error("Actions must be a list")
+ return None
+
+ # Create task
+ task = TaskItem(
+ id=str(uuid.uuid4()),
+ workflow=workflow,
+ userInput=previousResult.feedback,
+ status=taskDef["status"],
+ feedback=taskDef["feedback"],
+ actions=[]
+ )
+
+ # Add actions
+ for actionDef in taskDef["actions"]:
+ if not isinstance(actionDef, dict):
+ continue
+
+ requiredFields = ["method", "action", "parameters"]
+ if not all(field in actionDef for field in requiredFields):
+ continue
+
+ action = TaskAction(
+ id=str(uuid.uuid4()),
+ method=actionDef["method"],
+ action=actionDef["action"],
+ parameters=actionDef["parameters"],
+ resultLabel=actionDef.get("resultLabel")
+ )
+ task.actions.append(action)
- # Create task using ChatInterface
- task = self.service.createTask(taskData)
- if task:
- self.service.currentTask = task
return task
except Exception as e:
logger.error(f"Error creating next task: {str(e)}")
return None
- async def identifyNextTask(self, workflow: ChatWorkflow) -> TaskResult:
- """Identify the next task based on workflow state"""
- try:
- # Get workflow summary
- summary = await self._summarizeWorkflow()
-
- # Generate prompt for next task
- prompt = f"""Based on the workflow summary:
- {summary}
-
- Determine what the next task should be.
- Return a JSON object with:
- - objective: The main goal or task to accomplish
- - actions: List of required actions with method and parameters
- """
-
- # Get AI response
- response = await self.service.callAiBasic(prompt)
-
- # Parse response
- try:
- result = json.loads(response)
- return TaskResult(
- taskId=f"analysis_{datetime.now(UTC).timestamp()}",
- status=TaskStatus.COMPLETED,
- success=True,
- timestamp=datetime.now(UTC),
- data=result,
- documentsLabel="Task Results"
- )
- except json.JSONDecodeError as e:
- logger.error(f"Error parsing AI response: {str(e)}")
- return TaskResult(
- taskId=f"analysis_{datetime.now(UTC).timestamp()}",
- status=TaskStatus.FAILED,
- success=False,
- timestamp=datetime.now(UTC),
- error=f"Error parsing AI response: {str(e)}",
- documentsLabel="Task Error"
- )
-
- except Exception as e:
- logger.error(f"Error identifying next task: {str(e)}")
- return TaskResult(
- taskId=f"analysis_{datetime.now(UTC).timestamp()}",
- status=TaskStatus.FAILED,
- success=False,
- timestamp=datetime.now(UTC),
- error=f"Error identifying next task: {str(e)}",
- documentsLabel="Task Error"
- )
-
-
- async def generateWorkflowFeedback(self, workflow: ChatWorkflow) -> str:
- """
- Generates a final feedback message for the workflow in the user's language.
-
- Args:
- workflow: The completed workflow to generate feedback for
-
- Returns:
- str: The generated feedback message
- """
- try:
- # Get workflow summary
- workflowSummary = {
- "status": workflow.status,
- "totalMessages": len(workflow.messages),
- "totalDocuments": sum(len(msg.documents) for msg in workflow.messages),
- "duration": (datetime.now(UTC) - datetime.fromisoformat(workflow.startedAt)).total_seconds()
- }
-
- # Get user language from service
- userLanguage = self.service.user.language
-
- # Prepare messages for AI context
- messages = [
- {
- "role": "system",
- "content": f"You are an AI assistant providing a summary of a completed workflow. "
- f"Please respond in '{userLanguage}' language. "
- f"Summarize the workflow's activities, outcomes, and any important points. "
- f"Be concise but informative. Use a professional but friendly tone."
- },
- {
- "role": "user",
- "content": f"Please provide a summary of this workflow:\n"
- f"Status: {workflowSummary['status']}\n"
- f"Total Messages: {workflowSummary['totalMessages']}\n"
- f"Total Documents: {workflowSummary['totalDocuments']}\n"
- f"Duration: {workflowSummary['duration']:.1f} seconds"
- }
- ]
-
- # Add relevant workflow messages for context
- for msg in workflow.messages:
- if msg.role == "user" or msg.status in ["first", "last"]:
- messages.append({
- "role": msg.role,
- "content": msg.message
- })
-
- # Generate feedback using AI
- feedback = await self.service.callAi(messages, produceUserAnswer=True, temperature=0.7)
-
- return feedback
-
- except Exception as e:
- logger.error(f"Error generating workflow feedback: {str(e)}")
- return "Workflow completed successfully."
-
- def _generatePrompt(self, task: str, document: ChatDocument, examples: List[Dict[str, str]] = None) -> str:
- """Generate a prompt based on task and document"""
- try:
- # Create base prompt
- prompt = f"""Task: {task}
-Document: {document.filename} ({document.mimeType})
-
-"""
-
- # Add examples if provided
- if examples:
- prompt += "\nExamples:\n"
- for example in examples:
- prompt += f"Input: {example.get('input', '')}\n"
- prompt += f"Output: {example.get('output', '')}\n\n"
-
- return prompt
-
- except Exception as e:
- logger.error(f"Error generating prompt: {str(e)}")
- return ""
-
- # ===== Task Execution and Processing =====
async def executeTask(self, task: TaskItem) -> TaskItem:
- """Execute a task with its list of actions"""
+ """Execute a task's actions"""
try:
- # Start timing
- start_time = time.time()
- task.startedAt = datetime.now(UTC).isoformat()
- task.status = TaskStatus.RUNNING
+ # Execute each action
+ for action in task.actions:
+ # Create action prompt
+ prompt = f"""Execute the following action:
- # Execute each action in sequence
- for action in task.actionList:
+Action: {action.method}.{action.action}
+Parameters: {json.dumps(action.parameters)}
+
+Please provide a JSON response with:
+1. result: The result of the action
+2. resultLabel: A label for the result (format: documentList__