methods done
This commit is contained in:
parent
eebd995d64
commit
03c17d7506
43 changed files with 1761 additions and 2522 deletions
2
app.py
2
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
|
||||
|
|
|
|||
|
|
@ -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,80 +79,10 @@ 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
|
||||
|
||||
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
|
||||
# Extract content from response
|
||||
content = ""
|
||||
if "content" in anthropicResponse:
|
||||
if isinstance(anthropicResponse["content"], list):
|
||||
|
|
@ -167,7 +94,7 @@ class ChatService:
|
|||
# Direct content as string (in older API versions)
|
||||
content = anthropicResponse["content"]
|
||||
|
||||
# Create OpenAI-formatted response
|
||||
# Return in OpenAI format
|
||||
return {
|
||||
"id": anthropicResponse.get("id", ""),
|
||||
"object": "chat.completion",
|
||||
|
|
@ -185,7 +112,11 @@ class ChatService:
|
|||
]
|
||||
}
|
||||
|
||||
async def analyzeImage(self, imageData: Union[str, bytes], mimeType: str = None, prompt: str = "Describe this image") -> str:
|
||||
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)}")
|
||||
|
||||
async def callAiImage(self, prompt: str, imageData: Union[str, bytes], mimeType: str = None) -> str:
|
||||
"""
|
||||
Analyzes an image using Anthropic's vision capabilities.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
141
modules/interfaces/interfaceAiCalls.py
Normal file
141
modules/interfaces/interfaceAiCalls.py
Normal file
|
|
@ -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)}"
|
||||
|
||||
|
|
@ -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__)
|
||||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
"""
|
||||
|
|
@ -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]
|
||||
|
|
@ -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.
|
||||
|
|
@ -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"]
|
||||
|
||||
|
|
@ -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"""
|
||||
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
||||
|
|
@ -26,99 +26,79 @@ class MethodDocument(MethodBase):
|
|||
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)
|
||||
else:
|
||||
raise ValueError(f"Unsupported action: {action}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing document action {action}: {str(e)}")
|
||||
raise
|
||||
documentId = parameters["documentId"]
|
||||
documentType = parameters.get("documentType", "text")
|
||||
extractionType = parameters.get("extractionType", "full")
|
||||
|
||||
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")
|
||||
|
||||
if not documentId:
|
||||
raise ValueError("documentId is required")
|
||||
|
||||
# Get document from database
|
||||
if documentType == "ChatDocument":
|
||||
document = await self._getChatDocument(documentId)
|
||||
# Get document from service
|
||||
document = await self.service.interfaceComponent.getDocument(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 self._createResult(
|
||||
success=False,
|
||||
data={"error": f"Document not found: {documentId}"}
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"content": extracted.dict(),
|
||||
"metadata": await self.documentManager.getDocumentMetadata(document)
|
||||
# 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:
|
||||
return self._createResult(
|
||||
success=False,
|
||||
data={"error": f"Unsupported document type: {documentType}"}
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
return self._createResult(
|
||||
success=False,
|
||||
data={"error": str(e)}
|
||||
)
|
||||
|
||||
async def _analyzeDocument(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@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,63 +114,70 @@ class MethodDocument(MethodBase):
|
|||
stats["itemTypes"][itemType] = 0
|
||||
stats["itemTypes"][itemType] += 1
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
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)
|
||||
}
|
||||
return self._createResult(
|
||||
success=False,
|
||||
data={"error": str(e)}
|
||||
)
|
||||
|
||||
async def _summarizeDocument(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@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,
|
||||
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"""
|
||||
|
|
|
|||
188
modules/methods/methodExcel.py
Normal file
188
modules/methods/methodExcel.py
Normal file
|
|
@ -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)}
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
@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}
|
||||
}
|
||||
}
|
||||
}
|
||||
@action
|
||||
async def readMails(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||
"""
|
||||
Read emails from Outlook
|
||||
|
||||
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)}
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
||||
@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}
|
||||
}
|
||||
}
|
||||
}
|
||||
@action
|
||||
async def read(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||
"""
|
||||
Read PowerPoint presentation
|
||||
|
||||
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"]
|
||||
|
||||
# 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}"}
|
||||
success=True,
|
||||
data={
|
||||
"fileId": fileId,
|
||||
"presentation": presentation
|
||||
}
|
||||
)
|
||||
|
||||
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),
|
||||
"fileId": fileId,
|
||||
"slideCount": len(slides)
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error writing PowerPoint: {str(e)}")
|
||||
return self._createResult(
|
||||
success=False,
|
||||
data={"error": str(e)}
|
||||
)
|
||||
|
||||
@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:
|
||||
fileId = parameters["fileId"]
|
||||
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}"}
|
||||
)
|
||||
|
||||
# Convert presentation
|
||||
convertedFile = await self.powerpointService.convertPresentation(file, format)
|
||||
|
||||
return self._createResult(
|
||||
success=True,
|
||||
data={
|
||||
"fileId": fileId,
|
||||
"format": format,
|
||||
"slides": [
|
||||
{
|
||||
"number": 1,
|
||||
"title": "Example Slide",
|
||||
"content": "Example content",
|
||||
"notes": "Example notes" if parameters.get("includeNotes", False) else None
|
||||
}
|
||||
]
|
||||
"convertedFileId": convertedFile.id
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading presentation: {e}")
|
||||
logger.error(f"Error converting 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 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:
|
||||
path = Path(parameters["path"])
|
||||
|
||||
# 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():
|
||||
return self._createResult(
|
||||
success=False,
|
||||
data={"error": f"Source file not found: {source_path}"}
|
||||
)
|
||||
|
||||
# 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"
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error converting presentation: {e}")
|
||||
return self._createResult(
|
||||
success=False,
|
||||
data={"error": f"Conversion failed: {str(e)}"}
|
||||
)
|
||||
|
||||
async def _createPresentation(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
|
||||
"""Create a new PowerPoint presentation"""
|
||||
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 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}"}
|
||||
)
|
||||
|
||||
# Get drive
|
||||
drive = account.drive()
|
||||
|
||||
# Get presentation
|
||||
presentation = drive.get_item_by_id(presentationId)
|
||||
|
||||
# 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 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}"}
|
||||
)
|
||||
|
||||
# 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"]
|
||||
)
|
||||
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)}
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
||||
@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}
|
||||
}
|
||||
}
|
||||
}
|
||||
@action
|
||||
async def search(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||
"""
|
||||
Search SharePoint content
|
||||
|
||||
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"""
|
||||
try:
|
||||
siteUrl = parameters["siteUrl"]
|
||||
fileUrl = parameters["fileUrl"]
|
||||
content = parameters["content"]
|
||||
contentType = parameters.get("contentType", "text/plain")
|
||||
@action
|
||||
async def write(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||
"""
|
||||
Write SharePoint item
|
||||
|
||||
# Create SharePoint context
|
||||
ctx = ClientContext(siteUrl).with_credentials(
|
||||
UserCredential(userConnection.authToken, userConnection.refreshToken)
|
||||
Args:
|
||||
parameters:
|
||||
siteId: Site ID to write to
|
||||
listId: List ID to write to
|
||||
item: Item data to write
|
||||
"""
|
||||
try:
|
||||
siteId = parameters["siteId"]
|
||||
listId = parameters["listId"]
|
||||
item = parameters["item"]
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# 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")
|
||||
template=template,
|
||||
fields=fields
|
||||
)
|
||||
|
||||
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)}
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
||||
# 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")
|
||||
@action
|
||||
async def search(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||
"""
|
||||
Search web content
|
||||
|
||||
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"
|
||||
}
|
||||
]
|
||||
@action
|
||||
async def crawl(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||
"""
|
||||
Crawl web page
|
||||
|
||||
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"""
|
||||
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
|
||||
)
|
||||
|
||||
# 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}")
|
||||
return self._createResult(
|
||||
success=False,
|
||||
data={"error": f"Crawl failed: {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"""
|
||||
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"
|
||||
|
||||
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')
|
||||
|
||||
# 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']
|
||||
data={
|
||||
"url": url,
|
||||
"depth": depth,
|
||||
"results": results
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error detecting language: {str(e)}")
|
||||
return 'en' # Default to English on error
|
||||
logger.error(f"Error crawling web page: {str(e)}")
|
||||
return self._createResult(
|
||||
success=False,
|
||||
data={"error": str(e)}
|
||||
)
|
||||
|
||||
@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:
|
||||
url = parameters["url"]
|
||||
selectors = parameters.get("selectors", ["body"])
|
||||
format = parameters.get("format", "text")
|
||||
|
||||
# Extract content
|
||||
content = await self.webService.extractContent(
|
||||
url=url,
|
||||
selectors=selectors,
|
||||
format=format
|
||||
)
|
||||
|
||||
return self._createResult(
|
||||
success=True,
|
||||
data={
|
||||
"url": url,
|
||||
"format": format,
|
||||
"content": content
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
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)}
|
||||
)
|
||||
|
|
@ -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="",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,7 @@ import base64
|
|||
import logging
|
||||
import uuid
|
||||
|
||||
from modules.interfaces.serviceChatModel import (
|
||||
from modules.interfaces.interfaceChatModel import (
|
||||
ChatDocument,
|
||||
ExtractedContent
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import logging
|
|||
from datetime import datetime, UTC
|
||||
import uuid
|
||||
|
||||
from modules.interfaces.serviceAppClass import User
|
||||
from modules.interfaces.interfaceAppObjects import User
|
||||
|
||||
from modules.interfaces.serviceChatModel import (UserInputRequest, ChatMessage, ChatWorkflow)
|
||||
from modules.interfaces.serviceChatClass import ChatInterface
|
||||
from modules.interfaces.interfaceChatModel import (UserInputRequest, ChatMessage, ChatWorkflow)
|
||||
from modules.interfaces.interfaceChatObjects import ChatObjects
|
||||
from modules.workflow.managerChat import ChatManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -18,7 +18,7 @@ class WorkflowStoppedException(Exception):
|
|||
class WorkflowManager:
|
||||
"""Manager for workflow processing and coordination"""
|
||||
|
||||
def __init__(self, chatInterface: ChatInterface, currentUser: User):
|
||||
def __init__(self, chatInterface: ChatObjects, currentUser: User):
|
||||
self.chatInterface = chatInterface
|
||||
self.chatManager = ChatManager(currentUser)
|
||||
self.currentUser = currentUser
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from pathlib import Path
|
|||
import xml.etree.ElementTree as ET
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from modules.interfaces.serviceChatModel import (
|
||||
from modules.interfaces.interfaceChatModel import (
|
||||
ChatDocument,
|
||||
ExtractedContent,
|
||||
ContentItem,
|
||||
|
|
@ -578,7 +578,7 @@ class DocumentProcessor:
|
|||
"""
|
||||
|
||||
# Get AI response
|
||||
response = await self.serviceManagement.callAi([
|
||||
response = await self.interfaceComponent.callAi([
|
||||
{"role": "system", "content": "You are an expert at extracting relevant information from documents."},
|
||||
{"role": "user", "content": aiPrompt}
|
||||
])
|
||||
|
|
@ -813,69 +813,4 @@ class DocumentProcessor:
|
|||
except Exception:
|
||||
return [content]
|
||||
|
||||
async def _extractText(self, content: bytes, mimeType: str) -> str:
|
||||
"""Extract text content from various text formats"""
|
||||
try:
|
||||
textContent = content.decode('utf-8')
|
||||
return textContent
|
||||
except UnicodeDecodeError:
|
||||
logger.warning(f"Failed to decode text content as UTF-8 for {mimeType}")
|
||||
return ""
|
||||
|
||||
async def _extractImage(self, content: bytes, mimeType: str) -> str:
|
||||
"""Extract text from image content using OCR"""
|
||||
try:
|
||||
imageContent = Image.open(io.BytesIO(content))
|
||||
text = pytesseract.image_to_string(imageContent)
|
||||
return text
|
||||
except Exception as e:
|
||||
logger.error(f"Error extracting text from image: {str(e)}")
|
||||
return ""
|
||||
|
||||
async def _extractVideo(self, content: bytes, mimeType: str) -> str:
|
||||
"""Extract text from video content"""
|
||||
try:
|
||||
videoContent = io.BytesIO(content)
|
||||
# TODO: Implement video text extraction
|
||||
return "Video content extraction not implemented"
|
||||
except Exception as e:
|
||||
logger.error(f"Error extracting text from video: {str(e)}")
|
||||
return ""
|
||||
|
||||
async def _extractAudio(self, content: bytes, mimeType: str) -> str:
|
||||
"""Extract text from audio content using speech recognition"""
|
||||
try:
|
||||
audioContent = io.BytesIO(content)
|
||||
# TODO: Implement audio text extraction
|
||||
return "Audio content extraction not implemented"
|
||||
except Exception as e:
|
||||
logger.error(f"Error extracting text from audio: {str(e)}")
|
||||
return ""
|
||||
|
||||
async def _extractJson(self, content: bytes, mimeType: str) -> str:
|
||||
"""Extract text from JSON content"""
|
||||
try:
|
||||
jsonContent = json.loads(content.decode('utf-8'))
|
||||
return json.dumps(jsonContent, indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Error extracting text from JSON: {str(e)}")
|
||||
return ""
|
||||
|
||||
async def _extractXml(self, content: bytes, mimeType: str) -> str:
|
||||
"""Extract text from XML content"""
|
||||
try:
|
||||
xmlContent = content.decode('utf-8')
|
||||
return xmlContent
|
||||
except Exception as e:
|
||||
logger.error(f"Error extracting text from XML: {str(e)}")
|
||||
return ""
|
||||
|
||||
async def _extractCsv(self, content: bytes, mimeType: str) -> str:
|
||||
"""Extract text from CSV content"""
|
||||
try:
|
||||
csvContent = content.decode('utf-8')
|
||||
return csvContent
|
||||
except Exception as e:
|
||||
logger.error(f"Error extracting text from CSV: {str(e)}")
|
||||
return ""
|
||||
|
||||
|
|
@ -3,14 +3,14 @@ import importlib
|
|||
import pkgutil
|
||||
import inspect
|
||||
from typing import Dict, Any, List, Optional
|
||||
from modules.interfaces.serviceAppClass import User
|
||||
from modules.interfaces.serviceChatModel import (
|
||||
from modules.interfaces.interfaceAppModel import User, UserConnection
|
||||
from modules.interfaces.interfaceChatModel import (
|
||||
TaskStatus, ChatDocument, TaskItem, TaskAction, TaskResult,
|
||||
ChatStat, ChatLog, ChatMessage, ChatWorkflow, UserConnection
|
||||
ChatStat, ChatLog, ChatMessage, ChatWorkflow
|
||||
)
|
||||
from modules.interfaces.interfaceAi import interfaceAi
|
||||
from modules.interfaces.serviceChatClass import getInterface as getChatInterface
|
||||
from modules.interfaces.serviceManagementClass import getInterface as getFileInterface
|
||||
from modules.interfaces.interfaceAiCalls import interfaceAiCalls
|
||||
from modules.interfaces.interfaceChatObjects import getInterface as getChatObjects
|
||||
from modules.interfaces.interfaceComponentObjects import getInterface as getComponentObjects
|
||||
from modules.workflow.managerDocument import DocumentManager
|
||||
from modules.methods.methodBase import MethodBase
|
||||
import uuid
|
||||
|
|
@ -30,18 +30,18 @@ class ServiceContainer:
|
|||
self.currentTask = None # Initialize current task as None
|
||||
|
||||
# Initialize managers
|
||||
self.interfaceChat = getChatInterface(currentUser)
|
||||
self.interfaceFiles = getFileInterface(currentUser)
|
||||
self.interfaceAi = interfaceAi()
|
||||
self.interfaceChat = getChatObjects(currentUser)
|
||||
self.interfaceComponent = getComponentObjects(currentUser)
|
||||
self.interfaceAiCalls = interfaceAiCalls()
|
||||
self.documentManager = DocumentManager(self)
|
||||
|
||||
# Initialize methods catalog
|
||||
self.methods = None
|
||||
self.methods = {}
|
||||
# Discover additional methods
|
||||
self._discoverMethods()
|
||||
|
||||
def _discoverMethods(self):
|
||||
"""Dynamically discover all method classes in modules.methods package"""
|
||||
"""Dynamically discover all method classes and their actions in modules.methods package"""
|
||||
try:
|
||||
# Import the methods package
|
||||
methodsPackage = importlib.import_module('modules.methods')
|
||||
|
|
@ -58,10 +58,41 @@ class ServiceContainer:
|
|||
if (inspect.isclass(item) and
|
||||
issubclass(item, MethodBase) and
|
||||
item != MethodBase):
|
||||
# Instantiate the method and add to service
|
||||
methodInstance = item()
|
||||
self.methods[methodInstance.name] = methodInstance
|
||||
logger.info(f"Discovered method: {methodInstance.name}")
|
||||
# Instantiate the method
|
||||
methodInstance = item(self)
|
||||
|
||||
# Discover actions from public methods
|
||||
actions = {}
|
||||
for methodName, method in inspect.getmembers(methodInstance, predicate=inspect.isfunction):
|
||||
# Skip private methods and inherited methods
|
||||
if not methodName.startswith('_') and methodName not in ['execute', 'actions', 'validateParameters']:
|
||||
# Get method signature
|
||||
sig = inspect.signature(method)
|
||||
params = {}
|
||||
|
||||
# Convert parameters to action definition
|
||||
for paramName, param in sig.parameters.items():
|
||||
if paramName not in ['self', 'authData']:
|
||||
params[paramName] = {
|
||||
'type': param.annotation if param.annotation != param.empty else Any,
|
||||
'required': param.default == param.empty,
|
||||
'description': param.default.__doc__ if hasattr(param.default, '__doc__') else None
|
||||
}
|
||||
|
||||
# Add action definition
|
||||
actions[methodName] = {
|
||||
'description': method.__doc__ or '',
|
||||
'parameters': params,
|
||||
'method': method
|
||||
}
|
||||
|
||||
# Add method instance with discovered actions
|
||||
self.methods[methodInstance.name] = {
|
||||
'instance': methodInstance,
|
||||
'description': methodInstance.description,
|
||||
'actions': actions
|
||||
}
|
||||
logger.info(f"Discovered method: {methodInstance.name} with {len(actions)} actions")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading method module {name}: {str(e)}")
|
||||
|
|
@ -76,23 +107,35 @@ class ServiceContainer:
|
|||
return self.documentManager.extractContent(prompt, document)
|
||||
|
||||
def getMethodsCatalog(self) -> Dict[str, Any]:
|
||||
"""Get catalog of available methods"""
|
||||
return self.methods
|
||||
"""Get catalog of available methods and their actions"""
|
||||
catalog = {}
|
||||
for methodName, method in self.methods.items():
|
||||
catalog[methodName] = {
|
||||
'description': method['description'],
|
||||
'actions': {
|
||||
actionName: {
|
||||
'description': action['description'],
|
||||
'parameters': action['parameters']
|
||||
}
|
||||
for actionName, action in method['actions'].items()
|
||||
}
|
||||
}
|
||||
return catalog
|
||||
|
||||
def getMethodsList(self) -> List[str]:
|
||||
"""Get list of available methods with their signatures"""
|
||||
methodList = []
|
||||
for methodName, method in self.methods.items():
|
||||
for actionName, action in method.actions.items():
|
||||
# Get parameter types and return type from action signature
|
||||
for actionName, action in method['actions'].items():
|
||||
# Get parameter types from action signature
|
||||
paramTypes = []
|
||||
for paramName, param in action.parameters.items():
|
||||
paramTypes.append(f"{paramName}:{param.type}")
|
||||
for paramName, param in action['parameters'].items():
|
||||
paramTypes.append(f"{paramName}:{param['type']}")
|
||||
|
||||
# Format: method.action([param1:type, param2:type])->resultLabel:type # description
|
||||
signature = f"{methodName}.{actionName}([{', '.join(paramTypes)}])->{action.resultLabel}:{action.resultType}"
|
||||
if action.description:
|
||||
signature += f" # {action.description}"
|
||||
# Format: method.action([param1:type, param2:type]) # description
|
||||
signature = f"{methodName}.{actionName}([{', '.join(paramTypes)}])"
|
||||
if action['description']:
|
||||
signature += f" # {action['description']}"
|
||||
methodList.append(signature)
|
||||
return methodList
|
||||
|
||||
|
|
@ -205,35 +248,137 @@ class ServiceContainer:
|
|||
return []
|
||||
|
||||
def getConnectionReferenceList(self) -> List[Dict[str, str]]:
|
||||
"""Get list of connection references sorted by authority"""
|
||||
return self._getConnectionReferences()
|
||||
"""Get list of all UserConnection objects as references"""
|
||||
connections = []
|
||||
for conn in self.user.connections:
|
||||
connections.append({
|
||||
"connectionReference": f"connection_{conn.id}_{conn.authority}",
|
||||
"authority": conn.authority
|
||||
})
|
||||
# Sort by authority
|
||||
return sorted(connections, key=lambda x: x["authority"])
|
||||
|
||||
def getConnectionReferenceFromUserConnection(self, connection: UserConnection) -> str:
|
||||
"""Get connection reference from UserConnection"""
|
||||
return f"connection_{connection.id}_{connection.authority}"
|
||||
|
||||
def getUserConnectionFromConnectionReference(self, reference: str) -> UserConnection:
|
||||
"""Get UserConnection from connection reference"""
|
||||
return self._getUserConnectionByReference(reference)
|
||||
def getUserConnectionFromConnectionReference(self, connectionReference: str) -> Optional[UserConnection]:
|
||||
"""Get UserConnection from reference string"""
|
||||
try:
|
||||
# Parse reference format: connection_{id}_{authority}
|
||||
parts = connectionReference.split('_')
|
||||
if len(parts) != 3 or parts[0] != "connection":
|
||||
return None
|
||||
|
||||
def getMessageSummary(self, message: ChatMessage) -> Dict[str, List[Dict[str, Any]]]:
|
||||
"""Get message summary"""
|
||||
return {
|
||||
"chat": self._getChatMessageSummaries(),
|
||||
"history": self._getHistoryMessageSummaries()
|
||||
}
|
||||
conn_id = parts[1]
|
||||
authority = parts[2]
|
||||
|
||||
# Find matching connection
|
||||
for conn in self.user.connections:
|
||||
if str(conn.id) == conn_id and conn.authority == authority:
|
||||
return conn
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing connection reference: {str(e)}")
|
||||
return None
|
||||
|
||||
async def summarizeChat(self, messages: List[ChatMessage]) -> str:
|
||||
"""
|
||||
Summarize chat messages from last to first message with status="first"
|
||||
|
||||
Args:
|
||||
messages: List of chat messages to summarize
|
||||
|
||||
Returns:
|
||||
str: Summary of the chat in user's language
|
||||
"""
|
||||
try:
|
||||
# Get messages from last to first, stopping at first message with status="first"
|
||||
relevantMessages = []
|
||||
for msg in reversed(messages):
|
||||
relevantMessages.append(msg)
|
||||
if msg.status == "first":
|
||||
break
|
||||
|
||||
# Create prompt for AI
|
||||
prompt = f"""You are an AI assistant providing a summary of a chat conversation.
|
||||
Please respond in '{self.user.language}' language.
|
||||
|
||||
Chat History:
|
||||
{chr(10).join(f"- {msg.message}" for msg in reversed(relevantMessages))}
|
||||
|
||||
Instructions:
|
||||
1. Summarize the conversation's key points and outcomes
|
||||
2. Be concise but informative
|
||||
3. Use a professional but friendly tone
|
||||
4. Focus on important decisions and next steps if any
|
||||
|
||||
Please provide a comprehensive summary of this conversation."""
|
||||
|
||||
# Get summary using AI
|
||||
return await self.interfaceAiCalls.callAiTextBasic(prompt)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error summarizing chat: {str(e)}")
|
||||
return f"Error summarizing chat: {str(e)}"
|
||||
|
||||
async def summarizeMessage(self, message: ChatMessage) -> str:
|
||||
"""
|
||||
Summarize a single chat message
|
||||
|
||||
Args:
|
||||
message: Chat message to summarize
|
||||
|
||||
Returns:
|
||||
str: Summary of the message in user's language
|
||||
"""
|
||||
try:
|
||||
# Create prompt for AI
|
||||
prompt = f"""You are an AI assistant providing a summary of a chat message.
|
||||
Please respond in '{self.user.language}' language.
|
||||
|
||||
Message:
|
||||
{message.message}
|
||||
|
||||
Instructions:
|
||||
1. Summarize the key points of this message
|
||||
2. Be concise but informative
|
||||
3. Use a professional but friendly tone
|
||||
4. Focus on important information and any actions needed
|
||||
|
||||
Please provide a clear summary of this message."""
|
||||
|
||||
# Get summary using AI
|
||||
return await self.interfaceAiCalls.callAiTextBasic(prompt)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error summarizing message: {str(e)}")
|
||||
return f"Error summarizing message: {str(e)}"
|
||||
|
||||
def callAiTextBasic(self, prompt: str, context: str = None) -> str:
|
||||
"""Basic text processing using OpenAI"""
|
||||
return self.interfaceAiCalls.callAiTextBasic(prompt, context)
|
||||
|
||||
def callAiTextAdvanced(self, prompt: str, context: str = None) -> str:
|
||||
"""Advanced text processing using Anthropic"""
|
||||
return self.interfaceAiCalls.callAiTextAdvanced(prompt, context)
|
||||
|
||||
def callAiImageBasic(self, prompt: str, imageData: bytes, mimeType: str) -> str:
|
||||
"""Basic image processing using OpenAI"""
|
||||
return self.interfaceAiCalls.callAiImageBasic(prompt, imageData, mimeType)
|
||||
|
||||
def callAiImageAdvanced(self, prompt: str, imageData: bytes, mimeType: str) -> str:
|
||||
"""Advanced image processing using Anthropic"""
|
||||
return self.interfaceAiCalls.callAiImageAdvanced(prompt, imageData, mimeType)
|
||||
|
||||
def getFileInfo(self, fileId: str) -> Dict[str, Any]:
|
||||
"""Get file information"""
|
||||
return self.interfaceComponent.getFileInfo(fileId)
|
||||
|
||||
def getFileData(self, fileId: str) -> bytes:
|
||||
"""Get file data by ID"""
|
||||
return self.interfaceFiles.getFileData(fileId)
|
||||
|
||||
def callAiBasic(self, prompt: str, context: str = None, complexityFlag: bool = False) -> str:
|
||||
"""Call basic AI service"""
|
||||
return self.interfaceAi.callAiBasic(prompt, context, complexityFlag)
|
||||
|
||||
def callAiImage(self, imageData: bytes, mimeType: str, prompt: str) -> str:
|
||||
"""Call AI image service"""
|
||||
return self.interfaceAi.callAiImage(imageData, mimeType, prompt)
|
||||
return self.interfaceComponent.getFileData(fileId)
|
||||
|
||||
def createFile(self, fileName: str, mimeType: str, content: str, base64encoded: bool = False) -> str:
|
||||
"""Create new file and return its ID"""
|
||||
|
|
@ -244,14 +389,14 @@ class ServiceContainer:
|
|||
content_bytes = content.encode('utf-8')
|
||||
|
||||
# First create the file metadata
|
||||
file_item = self.interfaceFiles.createFile(
|
||||
file_item = self.interfaceComponent.createFile(
|
||||
name=fileName,
|
||||
mimeType=mimeType,
|
||||
size=len(content_bytes)
|
||||
)
|
||||
|
||||
# Then store the file data
|
||||
self.interfaceFiles.createFileData(file_item.id, content_bytes)
|
||||
self.interfaceComponent.createFileData(file_item.id, content_bytes)
|
||||
|
||||
return file_item.id
|
||||
|
||||
|
|
@ -261,7 +406,7 @@ class ServiceContainer:
|
|||
file_id = self.createFile(fileName, mimeType, content, base64encoded)
|
||||
|
||||
# Get file info for metadata
|
||||
file_info = self.interfaceFiles.getFile(file_id)
|
||||
file_info = self.interfaceComponent.getFile(file_id)
|
||||
|
||||
# Create document with file reference
|
||||
return ChatDocument(
|
||||
|
|
@ -272,84 +417,24 @@ class ServiceContainer:
|
|||
mimeType=mimeType
|
||||
)
|
||||
|
||||
def getFileInfo(self, fileId: str) -> Dict[str, Any]:
|
||||
"""Get file information"""
|
||||
return self.interfaceFiles.getFileInfo(fileId)
|
||||
|
||||
# ===== Private Methods =====
|
||||
|
||||
def _executeMethodAction(self, parameters: Dict[str, Any]) -> Any:
|
||||
"""Execute method action with parameters"""
|
||||
method = parameters.get('method')
|
||||
action = parameters.get('action')
|
||||
if method in self.methods and action in self.methods[method]:
|
||||
return self.methods[method][action](**parameters.get('parameters', {}))
|
||||
raise ValueError(f"Unknown method or action: {method}.{action}")
|
||||
|
||||
def _executeForEach(self, items: List[Any], action: callable) -> List[Any]:
|
||||
"""Execute forEach operation"""
|
||||
results = []
|
||||
for item in items:
|
||||
async def executeMethod(self, methodName: str, actionName: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||
"""Execute a method action"""
|
||||
try:
|
||||
result = action(item)
|
||||
results.append(result)
|
||||
if methodName not in self.methods:
|
||||
raise ValueError(f"Unknown method: {methodName}")
|
||||
|
||||
method = self.methods[methodName]
|
||||
if actionName not in method['actions']:
|
||||
raise ValueError(f"Unknown action: {actionName} for method {methodName}")
|
||||
|
||||
action = method['actions'][actionName]
|
||||
|
||||
# Execute the action
|
||||
return await action['method'](parameters, authData)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing forEach action: {str(e)}")
|
||||
results.append(None)
|
||||
return results
|
||||
|
||||
def _executeAiCall(self, prompt: str, documents: List[Dict[str, Any]]) -> List[Any]:
|
||||
"""Execute AI call with documents"""
|
||||
try:
|
||||
# Process each document
|
||||
results = []
|
||||
for doc in documents:
|
||||
content = self.extractContent(prompt, doc)
|
||||
results.append(content)
|
||||
return results
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing AI call: {str(e)}")
|
||||
return []
|
||||
|
||||
def _executeSharePointQuery(self, connection: str, site_query: str, file_query: str, content_query: str) -> List[Dict[str, str]]:
|
||||
"""Execute SharePoint query"""
|
||||
# TODO: Implement SharePoint query
|
||||
return []
|
||||
|
||||
def _executeSharePointDownload(self, connection: str, filepath: str) -> str:
|
||||
"""Execute SharePoint download"""
|
||||
# TODO: Implement SharePoint download
|
||||
return ""
|
||||
|
||||
def _getChatDocumentReferences(self) -> List[Dict[str, str]]:
|
||||
"""Get chat document references"""
|
||||
# TODO: Implement chat document references
|
||||
return []
|
||||
|
||||
def _getHistoryDocumentReferences(self) -> List[Dict[str, str]]:
|
||||
"""Get history document references"""
|
||||
# TODO: Implement history document references
|
||||
return []
|
||||
|
||||
def _getConnectionReferences(self) -> List[Dict[str, str]]:
|
||||
"""Get connection references"""
|
||||
# TODO: Implement connection references
|
||||
return []
|
||||
|
||||
def _getUserConnectionByReference(self, reference: str) -> UserConnection:
|
||||
"""Get user connection by reference"""
|
||||
# TODO: Implement user connection lookup
|
||||
pass
|
||||
|
||||
def _getChatMessageSummaries(self) -> List[Dict[str, Any]]:
|
||||
"""Get chat message summaries"""
|
||||
# TODO: Implement chat message summaries
|
||||
return []
|
||||
|
||||
def _getHistoryMessageSummaries(self) -> List[Dict[str, Any]]:
|
||||
"""Get history message summaries"""
|
||||
# TODO: Implement history message summaries
|
||||
return []
|
||||
logger.error(f"Error executing method {methodName}.{actionName}: {str(e)}")
|
||||
raise
|
||||
|
||||
# Create singleton instance
|
||||
serviceObject = None
|
||||
|
|
|
|||
|
|
@ -1,84 +1,8 @@
|
|||
TODO
|
||||
- documenthandling to review with document-file-filedata --> redundant and too complex --> to have ChatDocument with label and reference to fileid with function to get metadate directly from file object
|
||||
- prompt for task definition to fix
|
||||
- implement all functions from service object correctly
|
||||
- result parsing: execute task actions by using data references stepwise, all documents in TaskResults to save as ChatDocuments, to be available for next action.
|
||||
- action execution: to use conversion functions, user DocumentReference and ConnectionReference
|
||||
|
||||
ADAPT:
|
||||
|
||||
can you analyse the following specification:
|
||||
- clear what is required?
|
||||
- clear what to do?
|
||||
- questions?
|
||||
please give a summarized feedback before implementing
|
||||
|
||||
|
||||
How does the task process need to work:
|
||||
- specification for task-prompt below ensures, that a json with actions is produced, where references are clear, always having uuid integrated, only valid references
|
||||
- ai call result to be validated against json specification before proceeding, proper error handling for retry or abort depending on error (e.g. is ai down then to abort, if ai error to proceed with error to potentially fix it, etc.)
|
||||
- then to process the action list.
|
||||
|
||||
Resulting specification Prompt for task definition:
|
||||
- original user input (summary)
|
||||
- prompt for task to do
|
||||
- method list
|
||||
- workflow's history (list of messageSummary)
|
||||
- available documents (list of documentReference):
|
||||
- available connections (list of connectionReference):
|
||||
- instructions:
|
||||
- rules for result: status (enum), feedback (to tell what is done and what needed next. Only to to the tasks, which are possible with the available methods, referencing and data, rest to do in next round)
|
||||
- available data to use: list of documentReference, list of connectionReference
|
||||
- methods usage:
|
||||
- syntax: method.action([parameter:type])->resultLabel:type (reference note: resultLabel to be stored in TaskAction.execResultLabel for later use)
|
||||
- resultLabel to be in format documentList_<generated uuid>_<generated label>
|
||||
- sequence of method.action to be the sequence ot processing
|
||||
- as parameter only to use available items of documentReference or connectionReference or resultLabel from a previous method.action
|
||||
- required result format: json, nothing else
|
||||
|
||||
|
||||
FINALIZE:
|
||||
|
||||
service (ServiceContainer)
|
||||
user <-- currentUser
|
||||
workflow <- workflow
|
||||
tasks <- workflow.tasks
|
||||
statusEnums <- serviceChatModel.TaskStatus
|
||||
chatDatabase.* <-- serviceChatClass(user)
|
||||
|
||||
methods
|
||||
method.action([parameter:type])->resultLabel:type
|
||||
|
||||
operator.forEach([items:List[item], action:method.action])->resultList:List[]
|
||||
operator.aiCall([prompt:str, extractedDocumentContent:List[{"document":documentReference,"promptForContentExtraction":str}]])->resultList:List[]
|
||||
|
||||
sharepoint.query([connectionReference:str, site_query:str, file_query:str, content_query:str])->resultList:List[{"filepath":str,"extractedContent":str}]
|
||||
sharepoint.download([connectionReference:str, filepath:str])->documentReference:str
|
||||
|
||||
functions
|
||||
ok extractContent(prompt,documentReference):str <- managerDocument.extractContent(prompt,FilePreview)
|
||||
ok getMethodsCatalog():{...} <- to import dynamically all methods from the files "method*.py" in folder modules/methods
|
||||
ok getMethodsList():[str] <- to transform result from getMethodsCatalog into a list with the method items in format "method.action([parameter:type])->resultLabel:type # description"
|
||||
|
||||
ok getDocumentReferenceList():{"chat":[{"documentReference":str,"datetime":str}],"history":[{"documentReference":str,"datetime":str}]} sorted by datetime desc
|
||||
getDocumentReferenceFromChatDocument(ChatDocument):"document_"+ChatDocument.id+"_"+ChatDocument.filename
|
||||
getDocumentReferenceFromTaskResult(TaskResult):"documentList_"+TaskResult.id+"_"+TaskResult.documentsLabel
|
||||
getChatDocumentsFromDocumentReference(documentReference):List[ChatDocuments]
|
||||
|
||||
getConnectionReferenceList():List[{"connectionReference":str,"authority":str}] sorted by authority
|
||||
getConnectionReferenceFromUserConnection(UserConnection):"connection_"+UserConnection.id+"_"+UserConnection.authority
|
||||
getUserConnectionFromConnectionReference(documentReference):UserConnection
|
||||
|
||||
getMessageSummary(ChatMessage):{"chat":[{"messageSummary":str,"role":str,"success":bool,"sequenceNr":int}],"history":[{"messageSummary":str,"role":str,"success":bool,"sequenceNr":int}]} sorted by datetime desc
|
||||
|
||||
getFileData(fileId) <- seriveManagementClass.getFileData(fileid) --> used by ManagerData
|
||||
callAiBasic(prompt, context, complexityFlag) <- interfaceAi.callAiBasic --> used by managerChat
|
||||
callAiImage(...) <- interfaceAi.callAiImage --> used by processorDocument
|
||||
createFile(fileName, mimeType, content, base64encoded):FileItem <- seriveManagementClass.createFile, then seriveManagementClass.createFileData --> used by managerChat
|
||||
getFileInfo(id):FileItem <- serviceManagementClass.getFile(id) --> used by managerChat
|
||||
|
||||
|
||||
|
||||
- neutralizer to put back placeholders to the returned data
|
||||
- referenceHandling and authentication for connections in the method actions
|
||||
- check methods
|
||||
- test for workflow backend with userdata
|
||||
|
||||
********************
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class DocumentService:
|
|||
## Implementation Steps
|
||||
|
||||
1. **Model Cleanup**
|
||||
- Create new model classes in `serviceChatModel.py`
|
||||
- Create new model classes in `interfaceChatModel.py`
|
||||
- Remove deprecated models:
|
||||
- DocumentExtraction
|
||||
- DocumentContext
|
||||
|
|
@ -113,7 +113,7 @@ class DocumentService:
|
|||
|
||||
3. **UserInput Processing**
|
||||
- Update `UserInputRequest` processing to use `ChatMessage`
|
||||
- Implement `processFileIds` in `serviceChatClass`
|
||||
- Implement `processFileIds` in `interfaceChatObjects`
|
||||
- Update all references to use new model structure
|
||||
|
||||
4. **Method Module Updates**
|
||||
|
|
@ -130,14 +130,14 @@ class DocumentService:
|
|||
## Files to be Removed/Modified
|
||||
|
||||
### To be Removed
|
||||
1. `DocumentExtraction` class from serviceChatModel.py
|
||||
2. `DocumentContext` class from serviceChatModel.py
|
||||
3. `ProcessedDocument` class from serviceChatModel.py
|
||||
4. `ChatContent` class from serviceChatModel.py
|
||||
1. `DocumentExtraction` class from interfaceChatModel.py
|
||||
2. `DocumentContext` class from interfaceChatModel.py
|
||||
3. `ProcessedDocument` class from interfaceChatModel.py
|
||||
4. `ChatContent` class from interfaceChatModel.py
|
||||
5. Direct file access methods from method*.py modules
|
||||
|
||||
### To be Modified
|
||||
1. `serviceChatModel.py`
|
||||
1. `interfaceChatModel.py`
|
||||
- Add new model classes
|
||||
- Remove deprecated classes
|
||||
- Update existing classes
|
||||
|
|
@ -152,7 +152,7 @@ class DocumentService:
|
|||
- Remove direct file access
|
||||
- Update error handling
|
||||
|
||||
4. `serviceChatClass.py`
|
||||
4. `interfaceChatObjects.py`
|
||||
- Implement processFileIds
|
||||
- Update document handling
|
||||
|
||||
|
|
|
|||
|
|
@ -881,8 +881,8 @@ gateway/
|
|||
│ │ └── methodPowerpoint.py # PowerPoint operations
|
||||
│ │
|
||||
│ └── interfaces/
|
||||
│ ├── serviceChatModel.py # Chat system models and enums
|
||||
│ └── serviceAppModel.py # Application models including UserConnection
|
||||
│ ├── interfaceChatModel.py # Chat system models and enums
|
||||
│ └── interfaceAppModel.py # Application models including UserConnection
|
||||
```
|
||||
|
||||
### 6.2 Implementation Plan
|
||||
|
|
@ -894,8 +894,8 @@ gateway/
|
|||
- Create new `methods` directory
|
||||
|
||||
2. **Model Updates**
|
||||
- Update `serviceChatModel.py` with new enums and models
|
||||
- Integrate `UserConnection` from `serviceAppModel.py`
|
||||
- Update `interfaceChatModel.py` with new enums and models
|
||||
- Integrate `UserConnection` from `interfaceAppModel.py`
|
||||
- Update validation logic in respective modules
|
||||
|
||||
#### Phase 2: Method Migration
|
||||
|
|
|
|||
Loading…
Reference in a new issue