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")
|
logger.info("Application is starting up")
|
||||||
|
|
||||||
# Initialize root interface to ensure database is properly set up
|
# Initialize root interface to ensure database is properly set up
|
||||||
from modules.interfaces.serviceAppClass import getRootInterface
|
from modules.interfaces.interfaceAppObjects import getRootInterface
|
||||||
getRootInterface()
|
getRootInterface()
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ def loadConfigData():
|
||||||
"maxTokens": int(APP_CONFIG.get('Connector_AiAnthropic_MAX_TOKENS'))
|
"maxTokens": int(APP_CONFIG.get('Connector_AiAnthropic_MAX_TOKENS'))
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatService:
|
class AiAnthropic:
|
||||||
"""Connector for communication with the Anthropic API."""
|
"""Connector for communication with the Anthropic API."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -39,7 +39,7 @@ class ChatService:
|
||||||
|
|
||||||
logger.info(f"Anthropic Connector initialized with model: {self.modelName}")
|
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.
|
Calls the Anthropic API with the given messages.
|
||||||
|
|
||||||
|
|
@ -49,15 +49,12 @@ class ChatService:
|
||||||
maxTokens: Maximum number of tokens in the response
|
maxTokens: Maximum number of tokens in the response
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The response converted to OpenAI format
|
The response in OpenAI format
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
HTTPException: For errors in API communication
|
HTTPException: For errors in API communication
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Convert OpenAI format to Anthropic format
|
|
||||||
formattedMessages = self._convertToAnthropicFormat(messages)
|
|
||||||
|
|
||||||
# Use parameters from configuration if none were overridden
|
# Use parameters from configuration if none were overridden
|
||||||
if temperature is None:
|
if temperature is None:
|
||||||
temperature = self.config.get("temperature", 0.2)
|
temperature = self.config.get("temperature", 0.2)
|
||||||
|
|
@ -68,7 +65,7 @@ class ChatService:
|
||||||
# Create Anthropic API payload
|
# Create Anthropic API payload
|
||||||
payload = {
|
payload = {
|
||||||
"model": self.modelName,
|
"model": self.modelName,
|
||||||
"messages": formattedMessages,
|
"messages": messages,
|
||||||
"temperature": temperature,
|
"temperature": temperature,
|
||||||
"max_tokens": maxTokens
|
"max_tokens": maxTokens
|
||||||
}
|
}
|
||||||
|
|
@ -82,110 +79,44 @@ class ChatService:
|
||||||
logger.error(f"Anthropic API error: {response.status_code} - {response.text}")
|
logger.error(f"Anthropic API error: {response.status_code} - {response.text}")
|
||||||
raise HTTPException(status_code=500, detail="Error communicating with Anthropic API")
|
raise HTTPException(status_code=500, detail="Error communicating with Anthropic API")
|
||||||
|
|
||||||
# Convert response from Anthropic format to OpenAI format
|
# Parse response
|
||||||
anthropicResponse = response.json()
|
anthropicResponse = response.json()
|
||||||
openaiFormattedResponse = self._convertToOpenaiFormat(anthropicResponse)
|
|
||||||
|
|
||||||
return openaiFormattedResponse
|
# Extract content from response
|
||||||
|
content = ""
|
||||||
|
if "content" in anthropicResponse:
|
||||||
|
if isinstance(anthropicResponse["content"], list):
|
||||||
|
# Content is a list of parts (in newer API versions)
|
||||||
|
for part in anthropicResponse["content"]:
|
||||||
|
if part.get("type") == "text":
|
||||||
|
content += part.get("text", "")
|
||||||
|
else:
|
||||||
|
# Direct content as string (in older API versions)
|
||||||
|
content = anthropicResponse["content"]
|
||||||
|
|
||||||
|
# Return in OpenAI format
|
||||||
|
return {
|
||||||
|
"id": anthropicResponse.get("id", ""),
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": anthropicResponse.get("created", 0),
|
||||||
|
"model": anthropicResponse.get("model", self.modelName),
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": content
|
||||||
|
},
|
||||||
|
"index": 0,
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error calling Anthropic API: {str(e)}")
|
logger.error(f"Error calling Anthropic API: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=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]]:
|
async def callAiImage(self, prompt: str, imageData: Union[str, bytes], mimeType: str = None) -> str:
|
||||||
"""
|
|
||||||
Converts messages from OpenAI format to Anthropic format.
|
|
||||||
|
|
||||||
OpenAI uses:
|
|
||||||
[{"role": "system", "content": "..."},
|
|
||||||
{"role": "user", "content": "..."},
|
|
||||||
{"role": "assistant", "content": "..."}]
|
|
||||||
|
|
||||||
Anthropic uses:
|
|
||||||
[{"role": "user", "content": "..."},
|
|
||||||
{"role": "assistant", "content": "..."}]
|
|
||||||
|
|
||||||
Note: Anthropic has no direct system message equivalent,
|
|
||||||
so we add system messages to the first user message.
|
|
||||||
"""
|
|
||||||
anthropicMessages = []
|
|
||||||
systemContent = ""
|
|
||||||
|
|
||||||
# First extract all system messages
|
|
||||||
for msg in openaiMessages:
|
|
||||||
if msg.get("role") == "system":
|
|
||||||
systemContent += msg.get("content", "") + "\n\n"
|
|
||||||
|
|
||||||
# Convert the remaining messages
|
|
||||||
for msg in openaiMessages:
|
|
||||||
role = msg.get("role")
|
|
||||||
content = msg.get("content", "")
|
|
||||||
|
|
||||||
# Skip system messages (already extracted)
|
|
||||||
if role == "system":
|
|
||||||
continue
|
|
||||||
|
|
||||||
# For the first user message: prepend system content if available
|
|
||||||
if role == "user" and systemContent and not any(m.get("role") == "user" for m in anthropicMessages):
|
|
||||||
if isinstance(content, str):
|
|
||||||
content = systemContent + content
|
|
||||||
elif isinstance(content, list):
|
|
||||||
# If content is an array (for multimodal messages)
|
|
||||||
textParts = []
|
|
||||||
for part in content:
|
|
||||||
if part.get("type") == "text":
|
|
||||||
textParts.append(part)
|
|
||||||
|
|
||||||
if textParts:
|
|
||||||
# Create a new text part with combined content
|
|
||||||
textParts[0] = {
|
|
||||||
"type": "text",
|
|
||||||
"text": systemContent + textParts[0].get("text", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
# Anthropic only supports "user" and "assistant" roles
|
|
||||||
if role not in ["user", "assistant"]:
|
|
||||||
role = "user"
|
|
||||||
|
|
||||||
anthropicMessages.append({"role": role, "content": content})
|
|
||||||
|
|
||||||
return anthropicMessages
|
|
||||||
|
|
||||||
def _convertToOpenaiFormat(self, anthropicResponse: Dict[str, Any]) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Converts a response from Anthropic format to OpenAI format.
|
|
||||||
"""
|
|
||||||
# Extract content from Anthropic response
|
|
||||||
content = ""
|
|
||||||
if "content" in anthropicResponse:
|
|
||||||
if isinstance(anthropicResponse["content"], list):
|
|
||||||
# Content is a list of parts (in newer API versions)
|
|
||||||
for part in anthropicResponse["content"]:
|
|
||||||
if part.get("type") == "text":
|
|
||||||
content += part.get("text", "")
|
|
||||||
else:
|
|
||||||
# Direct content as string (in older API versions)
|
|
||||||
content = anthropicResponse["content"]
|
|
||||||
|
|
||||||
# Create OpenAI-formatted response
|
|
||||||
return {
|
|
||||||
"id": anthropicResponse.get("id", ""),
|
|
||||||
"object": "chat.completion",
|
|
||||||
"created": anthropicResponse.get("created", 0),
|
|
||||||
"model": anthropicResponse.get("model", self.modelName),
|
|
||||||
"choices": [
|
|
||||||
{
|
|
||||||
"message": {
|
|
||||||
"role": "assistant",
|
|
||||||
"content": content
|
|
||||||
},
|
|
||||||
"index": 0,
|
|
||||||
"finish_reason": "stop"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async def analyzeImage(self, imageData: Union[str, bytes], mimeType: str = None, prompt: str = "Describe this image") -> str:
|
|
||||||
"""
|
"""
|
||||||
Analyzes an image using Anthropic's vision capabilities.
|
Analyzes an image using Anthropic's vision capabilities.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ def loadConfigData():
|
||||||
"maxTokens": int(APP_CONFIG.get('Connector_AiOpenai_MAX_TOKENS'))
|
"maxTokens": int(APP_CONFIG.get('Connector_AiOpenai_MAX_TOKENS'))
|
||||||
}
|
}
|
||||||
|
|
||||||
class AiConnector:
|
class AiOpenai:
|
||||||
"""Connector for communication with the OpenAI API."""
|
"""Connector for communication with the OpenAI API."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -85,7 +85,7 @@ class AiConnector:
|
||||||
logger.error(f"Error calling OpenAI API: {str(e)}")
|
logger.error(f"Error calling OpenAI API: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=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.
|
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
|
import logging
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from modules.interfaces.serviceAppModel import UserPrivilege, Session, User
|
from modules.interfaces.interfaceAppModel import UserPrivilege, Session, User
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -14,8 +14,8 @@ import uuid
|
||||||
|
|
||||||
from modules.connectors.connectorDbJson import DatabaseConnector
|
from modules.connectors.connectorDbJson import DatabaseConnector
|
||||||
from modules.shared.configuration import APP_CONFIG
|
from modules.shared.configuration import APP_CONFIG
|
||||||
from modules.interfaces.serviceAppAccess import AppAccess
|
from modules.interfaces.interfaceAppAccess import AppAccess
|
||||||
from modules.interfaces.serviceAppModel import (
|
from modules.interfaces.interfaceAppModel import (
|
||||||
User, Mandate, UserInDB, UserConnection,
|
User, Mandate, UserInDB, UserConnection,
|
||||||
Session, AuthEvent, AuthAuthority, UserPrivilege,
|
Session, AuthEvent, AuthAuthority, UserPrivilege,
|
||||||
ConnectionStatus, Token, LocalToken, GoogleToken, MsftToken
|
ConnectionStatus, Token, LocalToken, GoogleToken, MsftToken
|
||||||
|
|
@ -24,16 +24,16 @@ from modules.shared.attributeUtils import ModelMixin
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Singleton factory for GatewayInterface instances per context
|
# Singleton factory for AppObjects instances per context
|
||||||
_gatewayInterfaces = {}
|
_gatewayInterfaces = {}
|
||||||
|
|
||||||
# Root interface instance
|
# Root interface instance
|
||||||
_rootGatewayInterface = None
|
_rootAppObjects = None
|
||||||
|
|
||||||
# Password-Hashing
|
# Password-Hashing
|
||||||
pwdContext = CryptContext(schemes=["argon2"], deprecated="auto")
|
pwdContext = CryptContext(schemes=["argon2"], deprecated="auto")
|
||||||
|
|
||||||
class GatewayInterface:
|
class AppObjects:
|
||||||
"""
|
"""
|
||||||
Interface to the Gateway system.
|
Interface to the Gateway system.
|
||||||
Manages users and mandates.
|
Manages users and mandates.
|
||||||
|
|
@ -793,9 +793,9 @@ class GatewayInterface:
|
||||||
|
|
||||||
# Public Methods
|
# 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.
|
Handles initialization of database and records.
|
||||||
"""
|
"""
|
||||||
if not currentUser:
|
if not currentUser:
|
||||||
|
|
@ -806,7 +806,7 @@ def getInterface(currentUser: User) -> GatewayInterface:
|
||||||
|
|
||||||
# Create new instance if not exists
|
# Create new instance if not exists
|
||||||
if contextKey not in _gatewayInterfaces:
|
if contextKey not in _gatewayInterfaces:
|
||||||
_gatewayInterfaces[contextKey] = GatewayInterface(currentUser)
|
_gatewayInterfaces[contextKey] = AppObjects(currentUser)
|
||||||
|
|
||||||
return _gatewayInterfaces[contextKey]
|
return _gatewayInterfaces[contextKey]
|
||||||
|
|
||||||
|
|
@ -817,7 +817,7 @@ def getRootUser() -> User:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Create a temporary interface without user context
|
# Create a temporary interface without user context
|
||||||
tempInterface = GatewayInterface()
|
tempInterface = AppObjects()
|
||||||
|
|
||||||
# Get the initial user directly
|
# Get the initial user directly
|
||||||
initialUserId = tempInterface.db.getInitialId("users")
|
initialUserId = tempInterface.db.getInitialId("users")
|
||||||
|
|
@ -835,15 +835,15 @@ def getRootUser() -> User:
|
||||||
logger.error(f"Error getting root user: {str(e)}")
|
logger.error(f"Error getting root user: {str(e)}")
|
||||||
raise ValueError(f"Failed to get 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.
|
This is used for initial setup and user creation.
|
||||||
"""
|
"""
|
||||||
global _rootGatewayInterface
|
global _rootAppObjects
|
||||||
|
|
||||||
if _rootGatewayInterface is None:
|
if _rootAppObjects is None:
|
||||||
rootUser = getRootUser()
|
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 typing import Dict, Any, List, Optional
|
||||||
from modules.interfaces.serviceAppModel import User, UserPrivilege
|
from modules.interfaces.interfaceAppModel import User, UserPrivilege
|
||||||
|
|
||||||
class ChatAccess:
|
class ChatAccess:
|
||||||
"""
|
"""
|
||||||
|
|
@ -12,11 +12,11 @@ from typing import Dict, Any, List, Optional, Union
|
||||||
import hashlib
|
import hashlib
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from modules.interfaces.serviceChatAccess import ChatAccess
|
from modules.interfaces.interfaceChatAccess import ChatAccess
|
||||||
from modules.interfaces.serviceChatModel import (
|
from modules.interfaces.interfaceChatModel import (
|
||||||
TaskStatus, UserInputRequest, ChatDocument, TaskItem, ChatStat, ChatLog, ChatMessage, ChatWorkflow, TaskAction
|
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
|
# DYNAMIC PART: Connectors to the Interface
|
||||||
from modules.connectors.connectorDbJson import DatabaseConnector
|
from modules.connectors.connectorDbJson import DatabaseConnector
|
||||||
|
|
@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
|
||||||
# Singleton factory for Chat instances
|
# Singleton factory for Chat instances
|
||||||
_chatInterfaces = {}
|
_chatInterfaces = {}
|
||||||
|
|
||||||
class ChatInterface:
|
class ChatObjects:
|
||||||
"""
|
"""
|
||||||
Interface to Chat database and AI Connectors.
|
Interface to Chat database and AI Connectors.
|
||||||
Uses the JSON connector for data access with added language support.
|
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)}")
|
logger.error(f"Error deleting task: {str(e)}")
|
||||||
return False
|
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.
|
Handles initialization of database and records.
|
||||||
"""
|
"""
|
||||||
if not currentUser:
|
if not currentUser:
|
||||||
|
|
@ -1038,6 +1038,6 @@ def getInterface(currentUser: Optional[User] = None) -> 'ChatInterface':
|
||||||
|
|
||||||
# Create new instance if not exists
|
# Create new instance if not exists
|
||||||
if contextKey not in _chatInterfaces:
|
if contextKey not in _chatInterfaces:
|
||||||
_chatInterfaces[contextKey] = ChatInterface(currentUser)
|
_chatInterfaces[contextKey] = ChatObjects(currentUser)
|
||||||
|
|
||||||
return _chatInterfaces[contextKey]
|
return _chatInterfaces[contextKey]
|
||||||
|
|
@ -5,12 +5,12 @@ Handles user access management and permission checks.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from modules.interfaces.serviceAppModel import User
|
from modules.interfaces.interfaceAppModel import User
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ManagementAccess:
|
class ComponentAccess:
|
||||||
"""
|
"""
|
||||||
Access control class for Management interface.
|
Access control class for Management interface.
|
||||||
Handles user access management and permission checks.
|
Handles user access management and permission checks.
|
||||||
|
|
@ -11,11 +11,11 @@ from typing import Dict, Any, List, Optional, Union
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from modules.interfaces.serviceManagementAccess import ManagementAccess
|
from modules.interfaces.interfaceComponentAccess import ComponentAccess
|
||||||
from modules.interfaces.serviceManagementModel import (
|
from modules.interfaces.interfaceComponentModel import (
|
||||||
FilePreview, Prompt, FileItem, FileData
|
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
|
# DYNAMIC PART: Connectors to the Interface
|
||||||
from modules.connectors.connectorDbJson import DatabaseConnector
|
from modules.connectors.connectorDbJson import DatabaseConnector
|
||||||
|
|
@ -49,7 +49,7 @@ class FileDeletionError(FileError):
|
||||||
"""Exception raised when there's an error deleting a file."""
|
"""Exception raised when there's an error deleting a file."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class ServiceManagement:
|
class ComponentObjects:
|
||||||
"""
|
"""
|
||||||
Interface to Management database and AI Connectors.
|
Interface to Management database and AI Connectors.
|
||||||
Uses the JSON connector for data access with added language support.
|
Uses the JSON connector for data access with added language support.
|
||||||
|
|
@ -60,7 +60,7 @@ class ServiceManagement:
|
||||||
# Initialize variables first
|
# Initialize variables first
|
||||||
self.currentUser: Optional[User] = None
|
self.currentUser: Optional[User] = None
|
||||||
self.userId: Optional[str] = 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
|
self.aiService: Optional[ChatService] = None # Will be set when user context is provided
|
||||||
|
|
||||||
# Initialize database
|
# Initialize database
|
||||||
|
|
@ -85,7 +85,7 @@ class ServiceManagement:
|
||||||
self.userLanguage = currentUser.language # Default user language
|
self.userLanguage = currentUser.language # Default user language
|
||||||
|
|
||||||
# Initialize access control with user context
|
# Initialize access control with user context
|
||||||
self.access = ManagementAccess(self.currentUser, self.db)
|
self.access = ComponentAccess(self.currentUser, self.db)
|
||||||
|
|
||||||
# Initialize AI service
|
# Initialize AI service
|
||||||
self.aiService = ChatService()
|
self.aiService = ChatService()
|
||||||
|
|
@ -143,7 +143,7 @@ class ServiceManagement:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get the root interface to access the initial mandate ID
|
# Get the root interface to access the initial mandate ID
|
||||||
from modules.interfaces.serviceAppClass import getRootInterface
|
from modules.interfaces.interfaceAppObjects import getRootInterface
|
||||||
rootInterface = getRootInterface()
|
rootInterface = getRootInterface()
|
||||||
|
|
||||||
# Get initial mandate ID through the root interface
|
# Get initial mandate ID through the root interface
|
||||||
|
|
@ -887,15 +887,15 @@ class ServiceManagement:
|
||||||
raise FileError(f"Error downloading file: {str(e)}")
|
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.
|
If currentUser is provided, initializes with user context.
|
||||||
Otherwise, returns an instance with only database access.
|
Otherwise, returns an instance with only database access.
|
||||||
"""
|
"""
|
||||||
# Create new instance if not exists
|
# Create new instance if not exists
|
||||||
if "default" not in _instancesManagement:
|
if "default" not in _instancesManagement:
|
||||||
_instancesManagement["default"] = ServiceManagement()
|
_instancesManagement["default"] = ComponentObjects()
|
||||||
|
|
||||||
interface = _instancesManagement["default"]
|
interface = _instancesManagement["default"]
|
||||||
|
|
||||||
|
|
@ -3,10 +3,19 @@ from typing import Dict, List, Optional, Any, Literal
|
||||||
from datetime import datetime, UTC
|
from datetime import datetime, UTC
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
import logging
|
import logging
|
||||||
from modules.interfaces.serviceChatModel import MethodResult
|
from modules.interfaces.interfaceChatModel import MethodResult
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
class MethodBase:
|
||||||
"""Base class for all methods"""
|
"""Base class for all methods"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,85 +3,20 @@ import logging
|
||||||
import ast
|
import ast
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from modules.methods.methodBase import MethodBase, MethodResult
|
from modules.methods.methodBase import MethodBase, MethodResult, action
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MethodCoder(MethodBase):
|
class MethodCoder(MethodBase):
|
||||||
"""Coder method implementation for code operations"""
|
"""Coder method implementation for code operations"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, serviceContainer: Any):
|
||||||
super().__init__()
|
super().__init__(serviceContainer)
|
||||||
self.name = "coder"
|
self.name = "coder"
|
||||||
self.description = "Handle code operations like analysis, generation, and refactoring"
|
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:
|
@action
|
||||||
"""Execute coder method"""
|
async def analyze(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||||
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:
|
|
||||||
"""Analyze code structure and quality"""
|
"""Analyze code structure and quality"""
|
||||||
try:
|
try:
|
||||||
code = parameters["code"]
|
code = parameters["code"]
|
||||||
|
|
@ -173,7 +108,8 @@ class MethodCoder(MethodBase):
|
||||||
data={"error": f"Analysis failed: {str(e)}"}
|
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"""
|
"""Generate code based on requirements"""
|
||||||
try:
|
try:
|
||||||
requirements = parameters["requirements"]
|
requirements = parameters["requirements"]
|
||||||
|
|
@ -216,7 +152,8 @@ class MethodCoder(MethodBase):
|
||||||
data={"error": f"Generation failed: {str(e)}"}
|
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"""
|
"""Refactor code for better quality"""
|
||||||
try:
|
try:
|
||||||
code = parameters["code"]
|
code = parameters["code"]
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,14 @@ import logging
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from modules.interfaces.serviceChatModel import (
|
from modules.interfaces.interfaceChatModel import (
|
||||||
ChatDocument,
|
ChatDocument,
|
||||||
TaskDocument,
|
TaskDocument,
|
||||||
ExtractedContent,
|
ExtractedContent,
|
||||||
ContentItem
|
ContentItem
|
||||||
)
|
)
|
||||||
from modules.workflow.managerDocument import DocumentManager
|
from modules.workflow.managerDocument import DocumentManager
|
||||||
from modules.methods.methodBase import MethodBase
|
from modules.methods.methodBase import MethodBase, MethodResult, action
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -25,100 +25,80 @@ class MethodDocument(MethodBase):
|
||||||
"""Initialize the document method"""
|
"""Initialize the document method"""
|
||||||
super().__init__(serviceContainer)
|
super().__init__(serviceContainer)
|
||||||
self.documentManager = DocumentManager(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:
|
Args:
|
||||||
action: The action to perform
|
parameters:
|
||||||
parameters: Action parameters
|
documentId: ID of the document to extract from
|
||||||
|
documentType: Type of document
|
||||||
Returns:
|
extractionType: Type of extraction to perform
|
||||||
Dictionary containing the operation result
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If action is not supported
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if action == "extract":
|
documentId = parameters["documentId"]
|
||||||
return await self._extractContent(parameters)
|
documentType = parameters.get("documentType", "text")
|
||||||
elif action == "analyze":
|
extractionType = parameters.get("extractionType", "full")
|
||||||
return await self._analyzeDocument(parameters)
|
|
||||||
elif action == "summarize":
|
# Get document from service
|
||||||
return await self._summarizeDocument(parameters)
|
document = await self.service.interfaceComponent.getDocument(documentId)
|
||||||
|
if not document:
|
||||||
|
return self._createResult(
|
||||||
|
success=False,
|
||||||
|
data={"error": f"Document not found: {documentId}"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract content based on type
|
||||||
|
if documentType == "text":
|
||||||
|
content = await self.documentManager.extractTextContent(document, extractionType)
|
||||||
|
elif documentType == "table":
|
||||||
|
content = await self.documentManager.extractTableContent(document, extractionType)
|
||||||
|
elif documentType == "image":
|
||||||
|
content = await self.documentManager.extractImageContent(document, extractionType)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported action: {action}")
|
return self._createResult(
|
||||||
except Exception as e:
|
success=False,
|
||||||
logger.error(f"Error processing document action {action}: {str(e)}")
|
data={"error": f"Unsupported document type: {documentType}"}
|
||||||
raise
|
)
|
||||||
|
|
||||||
async def _extractContent(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Extract content from a document
|
|
||||||
|
|
||||||
Args:
|
|
||||||
parameters: Dictionary containing:
|
|
||||||
- documentId: ID of the document to process
|
|
||||||
- documentType: Type of document ('ChatDocument' or 'TaskDocument')
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary containing extracted content
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
documentId = parameters.get("documentId")
|
|
||||||
documentType = parameters.get("documentType", "ChatDocument")
|
|
||||||
|
|
||||||
if not documentId:
|
return self._createResult(
|
||||||
raise ValueError("documentId is required")
|
success=True,
|
||||||
|
data={
|
||||||
# Get document from database
|
"documentId": documentId,
|
||||||
if documentType == "ChatDocument":
|
"type": documentType,
|
||||||
document = await self._getChatDocument(documentId)
|
"content": content
|
||||||
if not document:
|
}
|
||||||
raise ValueError(f"ChatDocument {documentId} not found")
|
)
|
||||||
extracted = await self.documentManager.extractFromChatDocument(document)
|
|
||||||
else:
|
|
||||||
document = await self._getTaskDocument(documentId)
|
|
||||||
if not document:
|
|
||||||
raise ValueError(f"TaskDocument {documentId} not found")
|
|
||||||
extracted = await self.documentManager.extractFromTaskDocument(document)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"content": extracted.dict(),
|
|
||||||
"metadata": await self.documentManager.getDocumentMetadata(document)
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error extracting content: {str(e)}")
|
logger.error(f"Error extracting content: {str(e)}")
|
||||||
return {
|
return self._createResult(
|
||||||
"success": False,
|
success=False,
|
||||||
"error": str(e)
|
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
|
Analyze document content
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parameters: Dictionary containing:
|
parameters:
|
||||||
- documentId: ID of the document to analyze
|
documentId: ID of the document to analyze
|
||||||
- documentType: Type of document
|
documentType: Type of document
|
||||||
- analysisType: Type of analysis to perform
|
analysisType: Type of analysis to perform
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary containing analysis results
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Extract content first
|
# Extract content first
|
||||||
contentResult = await self._extractContent(parameters)
|
contentResult = await self.extract(parameters)
|
||||||
if not contentResult["success"]:
|
if not contentResult.success:
|
||||||
return contentResult
|
return contentResult
|
||||||
|
|
||||||
# Perform analysis based on type
|
# Perform analysis based on type
|
||||||
analysisType = parameters.get("analysisType", "basic")
|
analysisType = parameters.get("analysisType", "basic")
|
||||||
content = ExtractedContent(**contentResult["content"])
|
content = ExtractedContent(**contentResult.data["content"])
|
||||||
|
|
||||||
if analysisType == "basic":
|
if analysisType == "basic":
|
||||||
# Basic analysis: count items, calculate statistics
|
# Basic analysis: count items, calculate statistics
|
||||||
|
|
@ -134,64 +114,71 @@ class MethodDocument(MethodBase):
|
||||||
stats["itemTypes"][itemType] = 0
|
stats["itemTypes"][itemType] = 0
|
||||||
stats["itemTypes"][itemType] += 1
|
stats["itemTypes"][itemType] += 1
|
||||||
|
|
||||||
return {
|
return self._createResult(
|
||||||
"success": True,
|
success=True,
|
||||||
"analysis": stats
|
data={
|
||||||
}
|
"documentId": parameters["documentId"],
|
||||||
|
"analysis": stats
|
||||||
|
}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported analysis type: {analysisType}")
|
return self._createResult(
|
||||||
|
success=False,
|
||||||
|
data={"error": f"Unsupported analysis type: {analysisType}"}
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error analyzing document: {str(e)}")
|
logger.error(f"Error analyzing document: {str(e)}")
|
||||||
return {
|
return self._createResult(
|
||||||
"success": False,
|
success=False,
|
||||||
"error": str(e)
|
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:
|
Args:
|
||||||
parameters: Dictionary containing:
|
parameters:
|
||||||
- documentId: ID of the document to summarize
|
documentId: ID of the document to summarize
|
||||||
- documentType: Type of document
|
documentType: Type of document
|
||||||
- summaryType: Type of summary to generate
|
summaryType: Type of summary to generate
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary containing summary
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Extract content first
|
# Extract content first
|
||||||
contentResult = await self._extractContent(parameters)
|
contentResult = await self.extract(parameters)
|
||||||
if not contentResult["success"]:
|
if not contentResult.success:
|
||||||
return contentResult
|
return contentResult
|
||||||
|
|
||||||
# Generate summary based on type
|
# Generate summary based on type
|
||||||
summaryType = parameters.get("summaryType", "basic")
|
summaryType = parameters.get("summaryType", "basic")
|
||||||
content = ExtractedContent(**contentResult["content"])
|
content = ExtractedContent(**contentResult.data["content"])
|
||||||
|
|
||||||
if summaryType == "basic":
|
if summaryType == "basic":
|
||||||
# Basic summary: concatenate all text content
|
# Basic summary: concatenate all text content
|
||||||
summary = "\n".join(
|
summary = "\n".join(item.content for item in content.contents if item.content)
|
||||||
item.data for item in content.contents
|
|
||||||
if item.label == "main"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return self._createResult(
|
||||||
"success": True,
|
success=True,
|
||||||
"summary": summary
|
data={
|
||||||
}
|
"documentId": parameters["documentId"],
|
||||||
|
"summary": summary
|
||||||
|
}
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported summary type: {summaryType}")
|
return self._createResult(
|
||||||
|
success=False,
|
||||||
|
data={"error": f"Unsupported summary type: {summaryType}"}
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error summarizing document: {str(e)}")
|
logger.error(f"Error summarizing document: {str(e)}")
|
||||||
return {
|
return self._createResult(
|
||||||
"success": False,
|
success=False,
|
||||||
"error": str(e)
|
data={"error": str(e)}
|
||||||
}
|
)
|
||||||
|
|
||||||
async def _getChatDocument(self, documentId: str) -> Optional[ChatDocument]:
|
async def _getChatDocument(self, documentId: str) -> Optional[ChatDocument]:
|
||||||
"""Get ChatDocument from database"""
|
"""Get ChatDocument from database"""
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
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
|
from datetime import datetime, UTC
|
||||||
import logging
|
import logging
|
||||||
from .methodBase import MethodBase
|
from .methodBase import MethodBase
|
||||||
from modules.interfaces.serviceChatModel import MethodResult
|
from modules.interfaces.interfaceChatModel import MethodResult
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -159,7 +159,7 @@ class MethodOperator(MethodBase):
|
||||||
full_prompt += f"\nDocument: {content['document']}\n{content['content']}\n"
|
full_prompt += f"\nDocument: {content['document']}\n{content['content']}\n"
|
||||||
|
|
||||||
# Call AI service
|
# Call AI service
|
||||||
response = await self.service.callAiBasic(full_prompt)
|
response = await self.service.callAiTextBasic(full_prompt)
|
||||||
|
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
|
|
|
||||||
|
|
@ -1,202 +1,176 @@
|
||||||
from typing import Dict, Any, Optional
|
"""
|
||||||
import logging
|
Outlook method module.
|
||||||
from datetime import datetime, UTC
|
Handles Outlook operations using the Outlook service.
|
||||||
from O365 import Account, MSGraphProtocol
|
"""
|
||||||
|
|
||||||
from modules.methods.methodBase import MethodBase, MethodResult
|
import logging
|
||||||
from modules.models.userConnection import UserConnection
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MethodOutlook(MethodBase):
|
class MethodOutlook(MethodBase):
|
||||||
"""Outlook method implementation for email operations"""
|
"""Outlook method implementation"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, serviceContainer):
|
||||||
super().__init__()
|
"""Initialize the Outlook method"""
|
||||||
self.name = "outlook"
|
super().__init__(serviceContainer)
|
||||||
self.description = "Handle Outlook email operations like reading and sending emails"
|
self.outlookService = OutlookService(serviceContainer)
|
||||||
|
|
||||||
|
@action
|
||||||
|
async def readMails(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||||
|
"""
|
||||||
|
Read emails from Outlook
|
||||||
|
|
||||||
@property
|
Args:
|
||||||
def actions(self) -> Dict[str, Dict[str, Any]]:
|
parameters:
|
||||||
"""Available actions and their parameters"""
|
folder: Folder to read from (default: inbox)
|
||||||
return {
|
query: Search query
|
||||||
"readMails": {
|
maxResults: Maximum number of results
|
||||||
"description": "Read emails from Outlook",
|
includeAttachments: Whether to include attachments
|
||||||
"retryMax": 2,
|
"""
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"folder": {"type": "string", "required": False},
|
|
||||||
"query": {"type": "string", "required": False},
|
|
||||||
"maxResults": {"type": "number", "required": False},
|
|
||||||
"includeAttachments": {"type": "boolean", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sendMail": {
|
|
||||||
"description": "Send email through Outlook",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"to": {"type": "array", "items": "string", "required": True},
|
|
||||||
"subject": {"type": "string", "required": True},
|
|
||||||
"body": {"type": "string", "required": True},
|
|
||||||
"cc": {"type": "array", "items": "string", "required": False},
|
|
||||||
"bcc": {"type": "array", "items": "string", "required": False},
|
|
||||||
"attachments": {"type": "array", "items": "string", "required": False}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async def execute(self, action: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
|
||||||
"""Execute Outlook method"""
|
|
||||||
try:
|
|
||||||
# Validate parameters
|
|
||||||
if not await self.validateParameters(action, parameters):
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": f"Invalid parameters for {action}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get UserConnection from auth_data
|
|
||||||
if not authData or "userConnection" not in authData:
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": "UserConnection required for Outlook operations"}
|
|
||||||
)
|
|
||||||
|
|
||||||
userConnection: UserConnection = authData["userConnection"]
|
|
||||||
|
|
||||||
# Execute action
|
|
||||||
if action == "readMails":
|
|
||||||
return await self._readMails(parameters, userConnection)
|
|
||||||
elif action == "sendMail":
|
|
||||||
return await self._sendMail(parameters, userConnection)
|
|
||||||
else:
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": f"Unknown action: {action}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error executing Outlook {action}: {e}")
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": str(e)}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _readMails(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
|
|
||||||
"""Read emails from Outlook"""
|
|
||||||
try:
|
try:
|
||||||
folder = parameters.get("folder", "inbox")
|
folder = parameters.get("folder", "inbox")
|
||||||
query = parameters.get("query")
|
query = parameters.get("query")
|
||||||
maxResults = parameters.get("maxResults", 10)
|
maxResults = parameters.get("maxResults", 10)
|
||||||
includeAttachments = parameters.get("includeAttachments", False)
|
includeAttachments = parameters.get("includeAttachments", False)
|
||||||
|
|
||||||
# Create Outlook account
|
# Read emails
|
||||||
account = Account(
|
emails = await self.outlookService.readEmails(
|
||||||
credentials=(userConnection.authToken, userConnection.refreshToken),
|
folder=folder,
|
||||||
protocol=MSGraphProtocol()
|
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(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"folder": folder,
|
"folder": folder,
|
||||||
"query": query,
|
"query": query,
|
||||||
"messages": results
|
"emails": emails
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error reading Outlook emails: {e}")
|
logger.error(f"Error reading emails: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"Read failed: {str(e)}"}
|
data={"error": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _sendMail(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
|
@action
|
||||||
"""Send email through Outlook"""
|
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:
|
try:
|
||||||
toAddresses = parameters["to"]
|
to = parameters["to"]
|
||||||
subject = parameters["subject"]
|
subject = parameters["subject"]
|
||||||
body = parameters["body"]
|
body = parameters["body"]
|
||||||
ccAddresses = parameters.get("cc", [])
|
|
||||||
bccAddresses = parameters.get("bcc", [])
|
|
||||||
attachments = parameters.get("attachments", [])
|
attachments = parameters.get("attachments", [])
|
||||||
|
|
||||||
# Create Outlook account
|
# Send email
|
||||||
account = Account(
|
messageId = await self.outlookService.sendEmail(
|
||||||
credentials=(userConnection.authToken, userConnection.refreshToken),
|
to=to,
|
||||||
protocol=MSGraphProtocol()
|
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(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"to": toAddresses,
|
"messageId": messageId,
|
||||||
"subject": subject,
|
"to": to,
|
||||||
"sent": datetime.now(UTC).isoformat()
|
"subject": subject
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending Outlook email: {e}")
|
logger.error(f"Error sending email: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
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
|
PowerPoint method module.
|
||||||
import os
|
Handles PowerPoint operations using the PowerPoint service.
|
||||||
from pathlib import Path
|
"""
|
||||||
|
|
||||||
from modules.methods.methodBase import MethodBase, MethodResult
|
import logging
|
||||||
from modules.models.userConnection import UserConnection
|
from typing import Dict, Any, List, Optional
|
||||||
from modules.models.account import Account
|
from datetime import datetime
|
||||||
from modules.protocols.msGraphProtocol import MSGraphProtocol
|
|
||||||
|
from modules.interfaces.interfacePowerpoint import PowerpointService
|
||||||
|
from modules.methods.methodBase import MethodBase, MethodResult, action
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MethodPowerpoint(MethodBase):
|
class MethodPowerpoint(MethodBase):
|
||||||
"""Powerpoint method implementation for PowerPoint operations"""
|
"""PowerPoint method implementation"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, serviceContainer):
|
||||||
super().__init__()
|
"""Initialize the PowerPoint method"""
|
||||||
self.name = "powerpoint"
|
super().__init__(serviceContainer)
|
||||||
self.description = "Handle PowerPoint operations like reading, writing, and converting presentations"
|
self.powerpointService = PowerpointService(serviceContainer)
|
||||||
|
|
||||||
|
@action
|
||||||
|
async def read(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||||
|
"""
|
||||||
|
Read PowerPoint presentation
|
||||||
|
|
||||||
@property
|
Args:
|
||||||
def actions(self) -> Dict[str, Dict[str, Any]]:
|
parameters:
|
||||||
"""Available actions and their parameters"""
|
fileId: ID of the PowerPoint file
|
||||||
return {
|
includeSlides: Whether to include slide content
|
||||||
"read": {
|
"""
|
||||||
"description": "Read PowerPoint presentation content",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"path": {"type": "string", "required": True},
|
|
||||||
"format": {"type": "string", "required": False},
|
|
||||||
"includeNotes": {"type": "boolean", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"write": {
|
|
||||||
"description": "Write content to PowerPoint presentation",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 60,
|
|
||||||
"parameters": {
|
|
||||||
"path": {"type": "string", "required": True},
|
|
||||||
"content": {"type": "object", "required": True},
|
|
||||||
"template": {"type": "string", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"convert": {
|
|
||||||
"description": "Convert PowerPoint presentation between formats",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 60,
|
|
||||||
"parameters": {
|
|
||||||
"sourcePath": {"type": "string", "required": True},
|
|
||||||
"targetPath": {"type": "string", "required": True},
|
|
||||||
"sourceFormat": {"type": "string", "required": False},
|
|
||||||
"targetFormat": {"type": "string", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"createPresentation": {
|
|
||||||
"description": "Create a new PowerPoint presentation",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 60,
|
|
||||||
"parameters": {
|
|
||||||
"title": {"type": "string", "required": True},
|
|
||||||
"template": {"type": "string", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"addSlide": {
|
|
||||||
"description": "Add a new slide to presentation",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 60,
|
|
||||||
"parameters": {
|
|
||||||
"presentationId": {"type": "string", "required": True},
|
|
||||||
"layout": {"type": "string", "required": False},
|
|
||||||
"title": {"type": "string", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"addContent": {
|
|
||||||
"description": "Add content to a slide",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 60,
|
|
||||||
"parameters": {
|
|
||||||
"presentationId": {"type": "string", "required": True},
|
|
||||||
"slideId": {"type": "string", "required": True},
|
|
||||||
"contentType": {"type": "string", "required": True},
|
|
||||||
"content": {"type": "object", "required": True},
|
|
||||||
"position": {"type": "object", "required": False}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async def execute(self, action: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
|
||||||
"""Execute PowerPoint method"""
|
|
||||||
try:
|
try:
|
||||||
# Validate parameters
|
fileId = parameters["fileId"]
|
||||||
if not await self.validateParameters(action, parameters):
|
includeSlides = parameters.get("includeSlides", True)
|
||||||
|
|
||||||
|
# Get file from service
|
||||||
|
file = await self.service.interfaceComponent.getFile(fileId)
|
||||||
|
if not file:
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"Invalid parameters for {action}"}
|
data={"error": f"File not found: {fileId}"}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get UserConnection from auth_data
|
# Read presentation
|
||||||
if not authData or "userConnection" not in authData:
|
presentation = await self.powerpointService.readPresentation(file, includeSlides)
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": "UserConnection required for PowerPoint operations"}
|
|
||||||
)
|
|
||||||
|
|
||||||
userConnection: UserConnection = authData["userConnection"]
|
return self._createResult(
|
||||||
|
success=True,
|
||||||
|
data={
|
||||||
|
"fileId": fileId,
|
||||||
|
"presentation": presentation
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Execute action
|
|
||||||
if action == "createPresentation":
|
|
||||||
return await self._createPresentation(parameters, userConnection)
|
|
||||||
elif action == "addSlide":
|
|
||||||
return await self._addSlide(parameters, userConnection)
|
|
||||||
elif action == "addContent":
|
|
||||||
return await self._addContent(parameters, userConnection)
|
|
||||||
else:
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": f"Unknown action: {action}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error executing PowerPoint {action}: {e}")
|
logger.error(f"Error reading PowerPoint: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": str(e)}
|
data={"error": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _read_presentation(self, parameters: Dict[str, Any], authData: Dict[str, Any]) -> MethodResult:
|
@action
|
||||||
"""Read PowerPoint presentation content"""
|
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:
|
try:
|
||||||
path = Path(parameters["path"])
|
fileId = parameters["fileId"]
|
||||||
if not path.exists():
|
slides = parameters["slides"]
|
||||||
|
|
||||||
|
# Get file from service
|
||||||
|
file = await self.service.interfaceComponent.getFile(fileId)
|
||||||
|
if not file:
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"File not found: {path}"}
|
data={"error": f"File not found: {fileId}"}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Determine format if not specified
|
# Write presentation
|
||||||
format = parameters.get("format")
|
await self.powerpointService.writePresentation(file, slides)
|
||||||
if not format:
|
|
||||||
format = path.suffix[1:] if path.suffix else "pptx"
|
|
||||||
|
|
||||||
# TODO: Implement PowerPoint reading using Microsoft Graph API
|
|
||||||
# This is a placeholder implementation
|
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"path": str(path),
|
"fileId": fileId,
|
||||||
"format": format,
|
"slideCount": len(slides)
|
||||||
"slides": [
|
|
||||||
{
|
|
||||||
"number": 1,
|
|
||||||
"title": "Example Slide",
|
|
||||||
"content": "Example content",
|
|
||||||
"notes": "Example notes" if parameters.get("includeNotes", False) else None
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error reading presentation: {e}")
|
logger.error(f"Error writing PowerPoint: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
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:
|
@action
|
||||||
"""Write content to PowerPoint presentation"""
|
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:
|
try:
|
||||||
path = Path(parameters["path"])
|
fileId = parameters["fileId"]
|
||||||
|
format = parameters["format"]
|
||||||
|
|
||||||
# Create directory if it doesn't exist
|
# Get file from service
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
file = await self.service.interfaceComponent.getFile(fileId)
|
||||||
|
if not file:
|
||||||
# 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(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"Source file not found: {source_path}"}
|
data={"error": f"File not found: {fileId}"}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Determine formats if not specified
|
# Convert presentation
|
||||||
source_format = parameters.get("sourceFormat")
|
convertedFile = await self.powerpointService.convertPresentation(file, format)
|
||||||
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(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"sourcePath": str(source_path),
|
"fileId": fileId,
|
||||||
"targetPath": str(target_path),
|
"format": format,
|
||||||
"sourceFormat": source_format,
|
"convertedFileId": convertedFile.id
|
||||||
"targetFormat": target_format
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error converting presentation: {e}")
|
logger.error(f"Error converting PowerPoint: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"Conversion failed: {str(e)}"}
|
data={"error": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _createPresentation(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
|
@action
|
||||||
"""Create a new PowerPoint presentation"""
|
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:
|
try:
|
||||||
title = parameters["title"]
|
fileName = parameters["fileName"]
|
||||||
template = parameters.get("template")
|
template = parameters.get("template")
|
||||||
|
|
||||||
# Create PowerPoint account
|
|
||||||
account = Account(
|
|
||||||
credentials=(userConnection.authToken, userConnection.refreshToken),
|
|
||||||
protocol=MSGraphProtocol()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get drive
|
|
||||||
drive = account.drive()
|
|
||||||
|
|
||||||
# Create presentation
|
# Create presentation
|
||||||
if template:
|
file = await self.powerpointService.createPresentation(fileName, 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"id": newFile.object_id,
|
"fileId": file.id,
|
||||||
"name": newFile.name,
|
"fileName": fileName,
|
||||||
"webUrl": newFile.web_url
|
"template": template
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating PowerPoint presentation: {e}")
|
logger.error(f"Error creating PowerPoint: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"Create failed: {str(e)}"}
|
data={"error": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _addSlide(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
|
@action
|
||||||
"""Add a new slide to presentation"""
|
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:
|
try:
|
||||||
presentationId = parameters["presentationId"]
|
fileId = parameters["fileId"]
|
||||||
layout = parameters.get("layout", "title")
|
layout = parameters.get("layout", "title")
|
||||||
title = parameters.get("title")
|
content = parameters.get("content", {})
|
||||||
|
|
||||||
# Create PowerPoint account
|
# Get file from service
|
||||||
account = Account(
|
file = await self.service.interfaceComponent.getFile(fileId)
|
||||||
credentials=(userConnection.authToken, userConnection.refreshToken),
|
if not file:
|
||||||
protocol=MSGraphProtocol()
|
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
|
# Add slide
|
||||||
slide = presentation.add_slide(layout=layout)
|
slideId = await self.powerpointService.addSlide(file, layout, content)
|
||||||
if title:
|
|
||||||
slide.title = title
|
|
||||||
|
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"slideId": slide.object_id,
|
"fileId": fileId,
|
||||||
"layout": layout,
|
"slideId": slideId,
|
||||||
"title": title
|
"layout": layout
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error adding PowerPoint slide: {e}")
|
logger.error(f"Error adding slide: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
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:
|
@action
|
||||||
"""Add content to a slide"""
|
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:
|
try:
|
||||||
presentationId = parameters["presentationId"]
|
fileId = parameters["fileId"]
|
||||||
slideId = parameters["slideId"]
|
slideId = parameters["slideId"]
|
||||||
contentType = parameters["contentType"]
|
|
||||||
content = parameters["content"]
|
content = parameters["content"]
|
||||||
position = parameters.get("position", {"x": 0, "y": 0})
|
|
||||||
|
|
||||||
# Create PowerPoint account
|
# Get file from service
|
||||||
account = Account(
|
file = await self.service.interfaceComponent.getFile(fileId)
|
||||||
credentials=(userConnection.authToken, userConnection.refreshToken),
|
if not file:
|
||||||
protocol=MSGraphProtocol()
|
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(
|
# Add content
|
||||||
image_path=content,
|
await self.powerpointService.addContent(file, slideId, 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}")
|
|
||||||
|
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"shapeId": shape.object_id,
|
"fileId": fileId,
|
||||||
"contentType": contentType,
|
"slideId": slideId
|
||||||
"position": position
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error adding PowerPoint content: {e}")
|
logger.error(f"Error adding content: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
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
|
SharePoint method module.
|
||||||
from datetime import datetime, UTC
|
Handles SharePoint operations using the SharePoint service.
|
||||||
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
|
|
||||||
|
|
||||||
from modules.methods.methodBase import MethodBase, MethodResult
|
import logging
|
||||||
from modules.models.userConnection import UserConnection
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MethodSharepoint(MethodBase):
|
class MethodSharepoint(MethodBase):
|
||||||
"""SharePoint method implementation for document operations"""
|
"""SharePoint method implementation"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, serviceContainer):
|
||||||
super().__init__()
|
"""Initialize the SharePoint method"""
|
||||||
self.name = "sharepoint"
|
super().__init__(serviceContainer)
|
||||||
self.description = "Handle SharePoint document operations like search, read, and write"
|
self.sharepointService = SharepointService(serviceContainer)
|
||||||
|
|
||||||
|
@action
|
||||||
|
async def search(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||||
|
"""
|
||||||
|
Search SharePoint content
|
||||||
|
|
||||||
@property
|
Args:
|
||||||
def actions(self) -> Dict[str, Dict[str, Any]]:
|
parameters:
|
||||||
"""Available actions and their parameters"""
|
query: Search query
|
||||||
return {
|
siteId: Site ID to search in
|
||||||
"search": {
|
contentType: Content type to search for
|
||||||
"description": "Search SharePoint documents",
|
maxResults: Maximum number of results
|
||||||
"retryMax": 3,
|
"""
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"query": {"type": "string", "required": True},
|
|
||||||
"siteUrl": {"type": "string", "required": True},
|
|
||||||
"listName": {"type": "string", "required": False},
|
|
||||||
"maxResults": {"type": "number", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"read": {
|
|
||||||
"description": "Read SharePoint document content",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"fileUrl": {"type": "string", "required": True},
|
|
||||||
"siteUrl": {"type": "string", "required": True}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"write": {
|
|
||||||
"description": "Write content to SharePoint document",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"fileUrl": {"type": "string", "required": True},
|
|
||||||
"siteUrl": {"type": "string", "required": True},
|
|
||||||
"content": {"type": "string", "required": True},
|
|
||||||
"contentType": {"type": "string", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"readList": {
|
|
||||||
"description": "Read items from SharePoint list",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"siteUrl": {"type": "string", "required": True},
|
|
||||||
"listName": {"type": "string", "required": True},
|
|
||||||
"query": {"type": "string", "required": False},
|
|
||||||
"fields": {"type": "array", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"writeList": {
|
|
||||||
"description": "Write items to SharePoint list",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"siteUrl": {"type": "string", "required": True},
|
|
||||||
"listName": {"type": "string", "required": True},
|
|
||||||
"items": {"type": "array", "required": True}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"createList": {
|
|
||||||
"description": "Create a new SharePoint list",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"siteUrl": {"type": "string", "required": True},
|
|
||||||
"listName": {"type": "string", "required": True},
|
|
||||||
"description": {"type": "string", "required": False},
|
|
||||||
"template": {"type": "string", "required": False},
|
|
||||||
"fields": {"type": "array", "required": False}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async def execute(self, action: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
|
||||||
"""Execute SharePoint method"""
|
|
||||||
try:
|
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"]
|
query = parameters["query"]
|
||||||
listName = parameters.get("listName")
|
siteId = parameters.get("siteId")
|
||||||
|
contentType = parameters.get("contentType")
|
||||||
maxResults = parameters.get("maxResults", 10)
|
maxResults = parameters.get("maxResults", 10)
|
||||||
|
|
||||||
# Create SharePoint context
|
# Search content
|
||||||
ctx = ClientContext(siteUrl).with_credentials(
|
results = await self.sharepointService.searchContent(
|
||||||
UserCredential(userConnection.authToken, userConnection.refreshToken)
|
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(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"query": query,
|
"query": query,
|
||||||
|
"siteId": siteId,
|
||||||
|
"contentType": contentType,
|
||||||
"results": results
|
"results": results
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error searching SharePoint documents: {e}")
|
logger.error(f"Error searching SharePoint: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
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:
|
@action
|
||||||
"""Read SharePoint document content"""
|
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:
|
try:
|
||||||
siteUrl = parameters["siteUrl"]
|
itemId = parameters["itemId"]
|
||||||
fileUrl = parameters["fileUrl"]
|
siteId = parameters.get("siteId")
|
||||||
|
listId = parameters.get("listId")
|
||||||
|
|
||||||
# Create SharePoint context
|
# Read item
|
||||||
ctx = ClientContext(siteUrl).with_credentials(
|
item = await self.sharepointService.readItem(
|
||||||
UserCredential(userConnection.authToken, userConnection.refreshToken)
|
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(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"url": fileUrl,
|
"itemId": itemId,
|
||||||
"content": file_content.content.decode('utf-8'),
|
"siteId": siteId,
|
||||||
"modified": file.properties["TimeLastModified"],
|
"listId": listId,
|
||||||
"size": file.properties["Length"]
|
"item": item
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error reading SharePoint document: {e}")
|
logger.error(f"Error reading SharePoint item: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
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:
|
@action
|
||||||
"""Write content to SharePoint document"""
|
async def write(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||||
|
"""
|
||||||
|
Write SharePoint item
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parameters:
|
||||||
|
siteId: Site ID to write to
|
||||||
|
listId: List ID to write to
|
||||||
|
item: Item data to write
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
siteUrl = parameters["siteUrl"]
|
siteId = parameters["siteId"]
|
||||||
fileUrl = parameters["fileUrl"]
|
listId = parameters["listId"]
|
||||||
content = parameters["content"]
|
item = parameters["item"]
|
||||||
contentType = parameters.get("contentType", "text/plain")
|
|
||||||
|
|
||||||
# Create SharePoint context
|
# Write item
|
||||||
ctx = ClientContext(siteUrl).with_credentials(
|
itemId = await self.sharepointService.writeItem(
|
||||||
UserCredential(userConnection.authToken, userConnection.refreshToken)
|
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(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"url": fileUrl,
|
"siteId": siteId,
|
||||||
"modified": datetime.now(UTC).isoformat(),
|
"listId": listId,
|
||||||
"size": len(content.encode('utf-8'))
|
"itemId": itemId
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error writing SharePoint document: {e}")
|
logger.error(f"Error writing SharePoint item: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"Write failed: {str(e)}"}
|
data={"error": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _readList(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
|
@action
|
||||||
"""Read items from SharePoint list"""
|
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:
|
try:
|
||||||
siteUrl = parameters["siteUrl"]
|
listId = parameters["listId"]
|
||||||
listName = parameters["listName"]
|
siteId = parameters.get("siteId")
|
||||||
query = parameters.get("query")
|
query = parameters.get("query")
|
||||||
fields = parameters.get("fields", ["*"])
|
maxResults = parameters.get("maxResults", 100)
|
||||||
|
|
||||||
# Create SharePoint account
|
# Read list
|
||||||
account = Account(
|
items = await self.sharepointService.readList(
|
||||||
credentials=(userConnection.authToken, userConnection.refreshToken),
|
listId=listId,
|
||||||
protocol=MSGraphProtocol()
|
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(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"siteUrl": siteUrl,
|
"listId": listId,
|
||||||
"listName": listName,
|
"siteId": siteId,
|
||||||
"items": items
|
"items": items
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error reading SharePoint list: {e}")
|
logger.error(f"Error reading SharePoint list: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"Read failed: {str(e)}"}
|
data={"error": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _writeList(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
|
@action
|
||||||
"""Write items to SharePoint list"""
|
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:
|
try:
|
||||||
siteUrl = parameters["siteUrl"]
|
siteId = parameters["siteId"]
|
||||||
listName = parameters["listName"]
|
listId = parameters["listId"]
|
||||||
items = parameters["items"]
|
items = parameters["items"]
|
||||||
|
|
||||||
# Create SharePoint account
|
# Write items
|
||||||
account = Account(
|
itemIds = await self.sharepointService.writeList(
|
||||||
credentials=(userConnection.authToken, userConnection.refreshToken),
|
siteId=siteId,
|
||||||
protocol=MSGraphProtocol()
|
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(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"siteUrl": siteUrl,
|
"siteId": siteId,
|
||||||
"listName": listName,
|
"listId": listId,
|
||||||
"results": results
|
"itemIds": itemIds
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
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(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"Write failed: {str(e)}"}
|
data={"error": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _createList(self, parameters: Dict[str, Any], userConnection: UserConnection) -> MethodResult:
|
@action
|
||||||
"""Create a new SharePoint list"""
|
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:
|
try:
|
||||||
siteUrl = parameters["siteUrl"]
|
siteId = parameters["siteId"]
|
||||||
listName = parameters["listName"]
|
name = parameters["name"]
|
||||||
description = parameters.get("description")
|
description = parameters.get("description")
|
||||||
template = parameters.get("template", "generic")
|
template = parameters.get("template", "genericList")
|
||||||
fields = parameters.get("fields", [])
|
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
|
# Create list
|
||||||
list = site.create_list(
|
listId = await self.sharepointService.createList(
|
||||||
name=listName,
|
siteId=siteId,
|
||||||
|
name=name,
|
||||||
description=description,
|
description=description,
|
||||||
template=template
|
template=template,
|
||||||
|
fields=fields
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add fields
|
|
||||||
for field in fields:
|
|
||||||
list.add_field(
|
|
||||||
name=field["name"],
|
|
||||||
field_type=field["type"],
|
|
||||||
required=field.get("required", False),
|
|
||||||
description=field.get("description")
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"siteUrl": siteUrl,
|
"siteId": siteId,
|
||||||
"listName": listName,
|
"listId": listId,
|
||||||
"id": list.id,
|
"name": name
|
||||||
"webUrl": list.web_url
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating SharePoint list: {e}")
|
logger.error(f"Error creating SharePoint list: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
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
|
Web method module.
|
||||||
import aiohttp
|
Handles web operations using the web service.
|
||||||
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
|
|
||||||
|
|
||||||
from modules.methods.methodBase import MethodBase, MethodResult
|
import logging
|
||||||
from modules.shared.configuration import APP_CONFIG
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MethodWeb(MethodBase):
|
class MethodWeb(MethodBase):
|
||||||
"""Web method implementation for web operations"""
|
"""Web method implementation"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, serviceContainer):
|
||||||
super().__init__()
|
"""Initialize the web method"""
|
||||||
self.name = "web"
|
super().__init__(serviceContainer)
|
||||||
self.description = "Handle web operations like search, crawl, and content extraction"
|
self.webService = WebService(serviceContainer)
|
||||||
|
|
||||||
|
@action
|
||||||
|
async def search(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||||
|
"""
|
||||||
|
Search web content
|
||||||
|
|
||||||
# Web crawling configuration from agentWebcrawler
|
Args:
|
||||||
self.srcApikey = APP_CONFIG.get("Agent_Webcrawler_SERPAPI_APIKEY", "")
|
parameters:
|
||||||
self.srcEngine = APP_CONFIG.get("Agent_Webcrawler_SERPAPI_ENGINE", "google")
|
query: Search query
|
||||||
self.srcCountry = APP_CONFIG.get("Agent_Webcrawler_SERPAPI_COUNTRY", "auto")
|
engine: Search engine to use (google, bing)
|
||||||
self.maxResults = int(APP_CONFIG.get("Agent_Webcrawler_SERPAPI_MAX_SEARCH_RESULTS", "5"))
|
maxResults: Maximum number of results
|
||||||
self.timeout = int(APP_CONFIG.get("Agent_Webcrawler_SERPAPI_TIMEOUT", "30"))
|
"""
|
||||||
self.userAgent = APP_CONFIG.get("Agent_Webcrawler_SERPAPI_USER_AGENT", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
|
||||||
|
|
||||||
if not self.srcApikey:
|
|
||||||
logger.error("SerpAPI key not configured")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def actions(self) -> Dict[str, Dict[str, Any]]:
|
|
||||||
"""Available actions and their parameters"""
|
|
||||||
return {
|
|
||||||
"search": {
|
|
||||||
"description": "Search web content",
|
|
||||||
"retryMax": 3,
|
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"query": {"type": "string", "required": True},
|
|
||||||
"maxResults": {"type": "number", "required": False},
|
|
||||||
"filters": {"type": "object", "required": False},
|
|
||||||
"searchEngine": {"type": "string", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"crawl": {
|
|
||||||
"description": "Crawl web pages",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 60,
|
|
||||||
"parameters": {
|
|
||||||
"url": {"type": "string", "required": True},
|
|
||||||
"depth": {"type": "number", "required": False},
|
|
||||||
"followLinks": {"type": "boolean", "required": False},
|
|
||||||
"includeImages": {"type": "boolean", "required": False},
|
|
||||||
"respectRobots": {"type": "boolean", "required": False}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extract": {
|
|
||||||
"description": "Extract content from web page",
|
|
||||||
"retryMax": 2,
|
|
||||||
"timeout": 30,
|
|
||||||
"parameters": {
|
|
||||||
"url": {"type": "string", "required": True},
|
|
||||||
"selectors": {"type": "array", "items": "string", "required": False},
|
|
||||||
"format": {"type": "string", "required": False},
|
|
||||||
"includeMetadata": {"type": "boolean", "required": False}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async def execute(self, action: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
|
||||||
"""Execute web method"""
|
|
||||||
try:
|
|
||||||
# Validate parameters
|
|
||||||
if not await self.validateParameters(action, parameters):
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": f"Invalid parameters for {action}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Execute action
|
|
||||||
if action == "fetchUrl":
|
|
||||||
return await self._fetchUrl(parameters)
|
|
||||||
elif action == "parseContent":
|
|
||||||
return await self._parseContent(parameters)
|
|
||||||
elif action == "extractData":
|
|
||||||
return await self._extractData(parameters)
|
|
||||||
else:
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": f"Unknown action: {action}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error executing web {action}: {e}")
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": str(e)}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _fetchUrl(self, parameters: Dict[str, Any]) -> MethodResult:
|
|
||||||
"""Fetch content from URL"""
|
|
||||||
try:
|
|
||||||
url = parameters["url"]
|
|
||||||
method = parameters.get("method", "GET")
|
|
||||||
headers = parameters.get("headers", {})
|
|
||||||
data = parameters.get("data")
|
|
||||||
timeout = parameters.get("timeout", 30)
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.request(
|
|
||||||
method=method,
|
|
||||||
url=url,
|
|
||||||
headers=headers,
|
|
||||||
data=data,
|
|
||||||
timeout=timeout
|
|
||||||
) as response:
|
|
||||||
content = await response.text()
|
|
||||||
return self._createResult(
|
|
||||||
success=True,
|
|
||||||
data={
|
|
||||||
"url": url,
|
|
||||||
"status": response.status,
|
|
||||||
"headers": dict(response.headers),
|
|
||||||
"content": content
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error fetching URL: {e}")
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": f"Fetch failed: {str(e)}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _parseContent(self, parameters: Dict[str, Any]) -> MethodResult:
|
|
||||||
"""Parse web content"""
|
|
||||||
try:
|
|
||||||
content = parameters["content"]
|
|
||||||
contentType = parameters.get("contentType", "html")
|
|
||||||
|
|
||||||
if contentType == "html":
|
|
||||||
soup = BeautifulSoup(content, "html.parser")
|
|
||||||
return self._createResult(
|
|
||||||
success=True,
|
|
||||||
data={
|
|
||||||
"type": "html",
|
|
||||||
"title": soup.title.string if soup.title else None,
|
|
||||||
"text": soup.get_text(),
|
|
||||||
"links": [a.get("href") for a in soup.find_all("a", href=True)],
|
|
||||||
"images": [img.get("src") for img in soup.find_all("img", src=True)]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif contentType == "json":
|
|
||||||
data = json.loads(content)
|
|
||||||
return self._createResult(
|
|
||||||
success=True,
|
|
||||||
data={
|
|
||||||
"type": "json",
|
|
||||||
"data": data
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unsupported content type: {contentType}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error parsing content: {e}")
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": f"Parse failed: {str(e)}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _extractData(self, parameters: Dict[str, Any]) -> MethodResult:
|
|
||||||
"""Extract data from web content"""
|
|
||||||
try:
|
|
||||||
content = parameters["content"]
|
|
||||||
contentType = parameters.get("contentType", "html")
|
|
||||||
selectors = parameters["selectors"]
|
|
||||||
|
|
||||||
if contentType == "html":
|
|
||||||
soup = BeautifulSoup(content, "html.parser")
|
|
||||||
results = {}
|
|
||||||
|
|
||||||
for key, selector in selectors.items():
|
|
||||||
elements = soup.select(selector)
|
|
||||||
if len(elements) == 1:
|
|
||||||
results[key] = elements[0].get_text().strip()
|
|
||||||
else:
|
|
||||||
results[key] = [el.get_text().strip() for el in elements]
|
|
||||||
|
|
||||||
return self._createResult(
|
|
||||||
success=True,
|
|
||||||
data={
|
|
||||||
"type": "html",
|
|
||||||
"results": results
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif contentType == "json":
|
|
||||||
data = json.loads(content)
|
|
||||||
results = {}
|
|
||||||
|
|
||||||
for key, path in selectors.items():
|
|
||||||
value = data
|
|
||||||
for part in path.split("."):
|
|
||||||
if isinstance(value, dict):
|
|
||||||
value = value.get(part)
|
|
||||||
elif isinstance(value, list) and part.isdigit():
|
|
||||||
value = value[int(part)]
|
|
||||||
else:
|
|
||||||
value = None
|
|
||||||
break
|
|
||||||
results[key] = value
|
|
||||||
|
|
||||||
return self._createResult(
|
|
||||||
success=True,
|
|
||||||
data={
|
|
||||||
"type": "json",
|
|
||||||
"results": results
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unsupported content type: {contentType}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error extracting data: {e}")
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": f"Extract failed: {str(e)}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _search_web(self, parameters: Dict[str, Any]) -> MethodResult:
|
|
||||||
"""Search web content"""
|
|
||||||
try:
|
try:
|
||||||
query = parameters["query"]
|
query = parameters["query"]
|
||||||
|
engine = parameters.get("engine", "google")
|
||||||
maxResults = parameters.get("maxResults", 10)
|
maxResults = parameters.get("maxResults", 10)
|
||||||
filters = parameters.get("filters", {})
|
|
||||||
searchEngine = parameters.get("searchEngine", "google")
|
|
||||||
|
|
||||||
# Implement search using different engines
|
# Search web
|
||||||
if searchEngine.lower() == "google":
|
results = await self.webService.searchContent(
|
||||||
# Use Google Custom Search API
|
query=query,
|
||||||
# TODO: Implement Google Custom Search API integration
|
engine=engine,
|
||||||
results = await self._google_search(query, maxResults, filters)
|
maxResults=maxResults
|
||||||
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}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=True,
|
success=True,
|
||||||
data={
|
data={
|
||||||
"query": query,
|
"query": query,
|
||||||
"engine": searchEngine,
|
"engine": engine,
|
||||||
"results": results
|
"results": results
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error searching web: {e}")
|
logger.error(f"Error searching web: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
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:
|
@action
|
||||||
"""Search using Google Custom Search API"""
|
async def crawl(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||||
# TODO: Implement Google Custom Search API
|
"""
|
||||||
# This is a placeholder implementation
|
Crawl web page
|
||||||
return [
|
|
||||||
{
|
Args:
|
||||||
"title": "Example Result",
|
parameters:
|
||||||
"url": "https://example.com",
|
url: URL to crawl
|
||||||
"snippet": "Example search result snippet",
|
depth: Crawl depth
|
||||||
"source": "google"
|
followLinks: Whether to follow links
|
||||||
}
|
extractContent: Whether to extract content
|
||||||
]
|
"""
|
||||||
|
|
||||||
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"""
|
|
||||||
try:
|
try:
|
||||||
url = parameters["url"]
|
url = parameters["url"]
|
||||||
depth = parameters.get("depth", 1)
|
depth = parameters.get("depth", 1)
|
||||||
followLinks = parameters.get("followLinks", False)
|
followLinks = parameters.get("followLinks", False)
|
||||||
includeImages = parameters.get("includeImages", False)
|
extractContent = parameters.get("extractContent", True)
|
||||||
respectRobots = parameters.get("respectRobots", True)
|
|
||||||
|
|
||||||
# Check robots.txt if required
|
# Crawl page
|
||||||
if respectRobots:
|
results = await self.webService.crawlPage(
|
||||||
if not await self._check_robots_txt(url):
|
url=url,
|
||||||
return self._createResult(
|
depth=depth,
|
||||||
success=False,
|
followLinks=followLinks,
|
||||||
data={"error": "Crawling not allowed by robots.txt"}
|
extractContent=extractContent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return self._createResult(
|
||||||
|
success=True,
|
||||||
|
data={
|
||||||
|
"url": url,
|
||||||
|
"depth": depth,
|
||||||
|
"results": results
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Crawl the page
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(url) as response:
|
|
||||||
if response.status == 200:
|
|
||||||
html = await response.text()
|
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
|
||||||
|
|
||||||
# Extract basic information
|
|
||||||
result = {
|
|
||||||
"url": url,
|
|
||||||
"title": soup.title.string if soup.title else None,
|
|
||||||
"description": self._get_meta_description(soup),
|
|
||||||
"links": [],
|
|
||||||
"images": [] if includeImages else None,
|
|
||||||
"text": soup.get_text(strip=True),
|
|
||||||
"crawled": datetime.now(UTC).isoformat()
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extract links if followLinks is True
|
|
||||||
if followLinks:
|
|
||||||
baseUrl = url
|
|
||||||
for link in soup.find_all('a'):
|
|
||||||
href = link.get('href')
|
|
||||||
if href:
|
|
||||||
absoluteUrl = urljoin(baseUrl, href)
|
|
||||||
if self._is_valid_url(absoluteUrl):
|
|
||||||
result["links"].append({
|
|
||||||
"url": absoluteUrl,
|
|
||||||
"text": link.get_text(strip=True)
|
|
||||||
})
|
|
||||||
|
|
||||||
# Extract images if includeImages is True
|
|
||||||
if includeImages:
|
|
||||||
for img in soup.find_all('img'):
|
|
||||||
src = img.get('src')
|
|
||||||
if src:
|
|
||||||
absoluteSrc = urljoin(url, src)
|
|
||||||
result["images"].append({
|
|
||||||
"url": absoluteSrc,
|
|
||||||
"alt": img.get('alt', ''),
|
|
||||||
"title": img.get('title', '')
|
|
||||||
})
|
|
||||||
|
|
||||||
return self._createResult(
|
|
||||||
success=True,
|
|
||||||
data=result
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return self._createResult(
|
|
||||||
success=False,
|
|
||||||
data={"error": f"Failed to fetch URL: {response.status}"}
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error crawling page: {e}")
|
logger.error(f"Error crawling web page: {str(e)}")
|
||||||
return self._createResult(
|
return self._createResult(
|
||||||
success=False,
|
success=False,
|
||||||
data={"error": f"Crawl failed: {str(e)}"}
|
data={"error": str(e)}
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_meta_description(self, soup: BeautifulSoup) -> Optional[str]:
|
@action
|
||||||
"""Extract meta description from HTML"""
|
async def extract(self, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||||
metaDesc = soup.find('meta', attrs={'name': 'description'})
|
"""
|
||||||
if metaDesc:
|
Extract content from web page
|
||||||
return metaDesc.get('content')
|
|
||||||
return None
|
Args:
|
||||||
|
parameters:
|
||||||
def _is_valid_url(self, url: str) -> bool:
|
url: URL to extract from
|
||||||
"""Check if URL is valid"""
|
selectors: CSS selectors to extract
|
||||||
|
format: Output format (text, html, json)
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
result = urlparse(url)
|
url = parameters["url"]
|
||||||
return all([result.scheme, result.netloc])
|
selectors = parameters.get("selectors", ["body"])
|
||||||
except:
|
format = parameters.get("format", "text")
|
||||||
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:
|
# Extract content
|
||||||
async with session.get(robotsUrl, headers={"User-Agent": self.userAgent}, timeout=self.timeout) as response:
|
content = await self.webService.extractContent(
|
||||||
if response.status == 200:
|
url=url,
|
||||||
robotsContent = await response.text()
|
selectors=selectors,
|
||||||
|
format=format
|
||||||
# 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
|
return self._createResult(
|
||||||
metaLang = soup.find('meta', attrs={'http-equiv': 'content-language'})
|
success=True,
|
||||||
if metaLang:
|
data={
|
||||||
return metaLang.get('content', 'en')
|
"url": url,
|
||||||
|
"format": format,
|
||||||
# Try to get language from meta charset
|
"content": content
|
||||||
metaCharset = soup.find('meta', attrs={'charset': True})
|
}
|
||||||
if metaCharset:
|
)
|
||||||
charset = metaCharset.get('charset', '').lower()
|
|
||||||
if 'utf-8' in charset:
|
|
||||||
return 'en' # Default to English for UTF-8
|
|
||||||
|
|
||||||
# Try to detect language from content
|
|
||||||
# This is a simple heuristic based on common words
|
|
||||||
text = soup.get_text().lower()
|
|
||||||
commonWords = {
|
|
||||||
'en': ['the', 'and', 'of', 'to', 'in', 'is', 'that', 'for', 'it', 'with'],
|
|
||||||
'es': ['el', 'la', 'los', 'las', 'de', 'y', 'en', 'que', 'por', 'con'],
|
|
||||||
'fr': ['le', 'la', 'les', 'de', 'et', 'en', 'que', 'pour', 'avec', 'dans'],
|
|
||||||
'de': ['der', 'die', 'das', 'und', 'in', 'den', 'von', 'zu', 'für', 'mit']
|
|
||||||
}
|
|
||||||
|
|
||||||
wordCounts = {lang: sum(1 for word in words if f' {word} ' in f' {text} ')
|
|
||||||
for lang, words in commonWords.items()}
|
|
||||||
|
|
||||||
if wordCounts:
|
|
||||||
return max(wordCounts.items(), key=lambda x: x[1])[0]
|
|
||||||
|
|
||||||
return 'en' # Default to English if no language detected
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error detecting language: {str(e)}")
|
logger.error(f"Error extracting web content: {str(e)}")
|
||||||
return 'en' # Default to English on error
|
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.shared.configuration import APP_CONFIG
|
||||||
from modules.security.auth import limiter, getCurrentUser
|
from modules.security.auth import limiter, getCurrentUser
|
||||||
from modules.interfaces.serviceAppModel import User
|
from modules.interfaces.interfaceAppModel import User
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="",
|
prefix="",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import logging
|
||||||
from modules.security.auth import limiter, getCurrentUser
|
from modules.security.auth import limiter, getCurrentUser
|
||||||
|
|
||||||
# Import the attribute definition and helper functions
|
# 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
|
from modules.shared.attributeUtils import getModelClasses, getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
import json
|
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.security.auth import getCurrentUser, limiter
|
||||||
from modules.interfaces.serviceAppClass import getInterface, getRootInterface
|
from modules.interfaces.interfaceAppObjects import getInterface, getRootInterface
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ from pydantic import BaseModel
|
||||||
from modules.security.auth import limiter, getCurrentUser
|
from modules.security.auth import limiter, getCurrentUser
|
||||||
|
|
||||||
# Import interfaces
|
# Import interfaces
|
||||||
import modules.interfaces.serviceManagementClass as serviceManagementClass
|
import modules.interfaces.interfaceComponentObjects as interfaceComponentObjects
|
||||||
from modules.interfaces.serviceManagementModel import FileItem, FilePreview
|
from modules.interfaces.interfaceComponentModel import FileItem, FilePreview
|
||||||
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
|
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
|
||||||
from modules.interfaces.serviceAppModel import User
|
from modules.interfaces.interfaceAppModel import User
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -46,7 +46,7 @@ async def get_files(
|
||||||
) -> List[FileItem]:
|
) -> List[FileItem]:
|
||||||
"""Get all files"""
|
"""Get all files"""
|
||||||
try:
|
try:
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Get all files generically - only metadata, no binary data
|
# Get all files generically - only metadata, no binary data
|
||||||
files = managementInterface.getAllFiles()
|
files = managementInterface.getAllFiles()
|
||||||
|
|
@ -70,17 +70,17 @@ async def upload_file(
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""Upload a file"""
|
"""Upload a file"""
|
||||||
try:
|
try:
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Read file
|
# Read file
|
||||||
fileContent = await file.read()
|
fileContent = await file.read()
|
||||||
|
|
||||||
# Check size limits
|
# 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:
|
if len(fileContent) > maxSize:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
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
|
# Save file via LucyDOM interface in the database
|
||||||
|
|
@ -101,7 +101,7 @@ async def upload_file(
|
||||||
"file": fileMeta
|
"file": fileMeta
|
||||||
})
|
})
|
||||||
|
|
||||||
except serviceManagementClass.FileStorageError as e:
|
except interfaceComponentObjects.FileStorageError as e:
|
||||||
logger.error(f"Error during file upload (storage): {str(e)}")
|
logger.error(f"Error during file upload (storage): {str(e)}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
|
@ -123,7 +123,7 @@ async def get_file(
|
||||||
) -> FileItem:
|
) -> FileItem:
|
||||||
"""Get a file"""
|
"""Get a file"""
|
||||||
try:
|
try:
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Get file via LucyDOM interface from the database
|
# Get file via LucyDOM interface from the database
|
||||||
fileData = managementInterface.getFile(fileId)
|
fileData = managementInterface.getFile(fileId)
|
||||||
|
|
@ -135,19 +135,19 @@ async def get_file(
|
||||||
|
|
||||||
return FileItem(**fileData)
|
return FileItem(**fileData)
|
||||||
|
|
||||||
except serviceManagementClass.FileNotFoundError as e:
|
except interfaceComponentObjects.FileNotFoundError as e:
|
||||||
logger.warning(f"File not found: {str(e)}")
|
logger.warning(f"File not found: {str(e)}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail=str(e)
|
detail=str(e)
|
||||||
)
|
)
|
||||||
except serviceManagementClass.FilePermissionError as e:
|
except interfaceComponentObjects.FilePermissionError as e:
|
||||||
logger.warning(f"No permission for file: {str(e)}")
|
logger.warning(f"No permission for file: {str(e)}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail=str(e)
|
detail=str(e)
|
||||||
)
|
)
|
||||||
except serviceManagementClass.FileError as e:
|
except interfaceComponentObjects.FileError as e:
|
||||||
logger.error(f"Error retrieving file: {str(e)}")
|
logger.error(f"Error retrieving file: {str(e)}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
|
@ -170,7 +170,7 @@ async def update_file(
|
||||||
) -> FileItem:
|
) -> FileItem:
|
||||||
"""Update file info"""
|
"""Update file info"""
|
||||||
try:
|
try:
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Get the file from the database
|
# Get the file from the database
|
||||||
file = managementInterface.getFile(fileId)
|
file = managementInterface.getFile(fileId)
|
||||||
|
|
@ -216,7 +216,7 @@ async def delete_file(
|
||||||
currentUser: User = Depends(getCurrentUser)
|
currentUser: User = Depends(getCurrentUser)
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Delete a file"""
|
"""Delete a file"""
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Check if the file exists
|
# Check if the file exists
|
||||||
existingFile = managementInterface.getFile(fileId)
|
existingFile = managementInterface.getFile(fileId)
|
||||||
|
|
@ -243,7 +243,7 @@ async def get_file_stats(
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Returns statistics about the stored files"""
|
"""Returns statistics about the stored files"""
|
||||||
try:
|
try:
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Get all files - metadata only
|
# Get all files - metadata only
|
||||||
allFiles = managementInterface.getAllFiles()
|
allFiles = managementInterface.getAllFiles()
|
||||||
|
|
@ -282,7 +282,7 @@ async def download_file(
|
||||||
) -> Response:
|
) -> Response:
|
||||||
"""Download a file"""
|
"""Download a file"""
|
||||||
try:
|
try:
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Get file data
|
# Get file data
|
||||||
fileData = managementInterface.getFile(fileId)
|
fileData = managementInterface.getFile(fileId)
|
||||||
|
|
@ -326,7 +326,7 @@ async def preview_file(
|
||||||
) -> FilePreview:
|
) -> FilePreview:
|
||||||
"""Preview a file's content"""
|
"""Preview a file's content"""
|
||||||
try:
|
try:
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Get file preview
|
# Get file preview
|
||||||
preview = managementInterface.getFilePreview(fileId)
|
preview = managementInterface.getFilePreview(fileId)
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ from pydantic import BaseModel
|
||||||
from modules.security.auth import limiter, getCurrentUser
|
from modules.security.auth import limiter, getCurrentUser
|
||||||
|
|
||||||
# Import interfaces
|
# Import interfaces
|
||||||
import modules.interfaces.serviceAppClass as serviceAppClass
|
import modules.interfaces.interfaceAppObjects as interfaceAppObjects
|
||||||
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
|
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
|
||||||
|
|
||||||
# Import the model classes
|
# Import the model classes
|
||||||
from modules.interfaces.serviceAppModel import Mandate, User
|
from modules.interfaces.interfaceAppModel import Mandate, User
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -44,7 +44,7 @@ async def get_mandates(
|
||||||
) -> List[Mandate]:
|
) -> List[Mandate]:
|
||||||
"""Get all mandates"""
|
"""Get all mandates"""
|
||||||
try:
|
try:
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
mandates = appInterface.getAllMandates()
|
mandates = appInterface.getAllMandates()
|
||||||
return mandates
|
return mandates
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -63,7 +63,7 @@ async def get_mandate(
|
||||||
) -> Mandate:
|
) -> Mandate:
|
||||||
"""Get a specific mandate by ID"""
|
"""Get a specific mandate by ID"""
|
||||||
try:
|
try:
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
mandate = appInterface.getMandate(mandateId)
|
mandate = appInterface.getMandate(mandateId)
|
||||||
|
|
||||||
if not mandate:
|
if not mandate:
|
||||||
|
|
@ -91,7 +91,7 @@ async def create_mandate(
|
||||||
) -> Mandate:
|
) -> Mandate:
|
||||||
"""Create a new mandate"""
|
"""Create a new mandate"""
|
||||||
try:
|
try:
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Create mandate
|
# Create mandate
|
||||||
newMandate = appInterface.createMandate(
|
newMandate = appInterface.createMandate(
|
||||||
|
|
@ -125,7 +125,7 @@ async def update_mandate(
|
||||||
) -> Mandate:
|
) -> Mandate:
|
||||||
"""Update an existing mandate"""
|
"""Update an existing mandate"""
|
||||||
try:
|
try:
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Check if mandate exists
|
# Check if mandate exists
|
||||||
existingMandate = appInterface.getMandate(mandateId)
|
existingMandate = appInterface.getMandate(mandateId)
|
||||||
|
|
@ -163,7 +163,7 @@ async def delete_mandate(
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Delete a mandate"""
|
"""Delete a mandate"""
|
||||||
try:
|
try:
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Check if mandate exists
|
# Check if mandate exists
|
||||||
existingMandate = appInterface.getMandate(mandateId)
|
existingMandate = appInterface.getMandate(mandateId)
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ from pydantic import BaseModel
|
||||||
from modules.security.auth import limiter, getCurrentUser
|
from modules.security.auth import limiter, getCurrentUser
|
||||||
|
|
||||||
# Import interfaces
|
# Import interfaces
|
||||||
import modules.interfaces.serviceManagementClass as serviceManagementClass
|
import modules.interfaces.interfaceComponentObjects as interfaceComponentObjects
|
||||||
from modules.interfaces.serviceManagementModel import Prompt
|
from modules.interfaces.interfaceComponentModel import Prompt
|
||||||
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
|
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse, AttributeDefinition
|
||||||
from modules.interfaces.serviceAppModel import User
|
from modules.interfaces.interfaceAppModel import User
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -34,7 +34,7 @@ async def get_prompts(
|
||||||
currentUser: User = Depends(getCurrentUser)
|
currentUser: User = Depends(getCurrentUser)
|
||||||
) -> List[Prompt]:
|
) -> List[Prompt]:
|
||||||
"""Get all prompts"""
|
"""Get all prompts"""
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
prompts = managementInterface.getAllPrompts()
|
prompts = managementInterface.getAllPrompts()
|
||||||
return prompts
|
return prompts
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ async def create_prompt(
|
||||||
currentUser: User = Depends(getCurrentUser)
|
currentUser: User = Depends(getCurrentUser)
|
||||||
) -> Prompt:
|
) -> Prompt:
|
||||||
"""Create a new prompt"""
|
"""Create a new prompt"""
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Convert Prompt to dict for interface
|
# Convert Prompt to dict for interface
|
||||||
prompt_data = prompt.dict()
|
prompt_data = prompt.dict()
|
||||||
|
|
@ -64,7 +64,7 @@ async def get_prompt(
|
||||||
currentUser: User = Depends(getCurrentUser)
|
currentUser: User = Depends(getCurrentUser)
|
||||||
) -> Prompt:
|
) -> Prompt:
|
||||||
"""Get a specific prompt"""
|
"""Get a specific prompt"""
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Get prompt
|
# Get prompt
|
||||||
prompt = managementInterface.getPrompt(promptId)
|
prompt = managementInterface.getPrompt(promptId)
|
||||||
|
|
@ -85,7 +85,7 @@ async def update_prompt(
|
||||||
currentUser: User = Depends(getCurrentUser)
|
currentUser: User = Depends(getCurrentUser)
|
||||||
) -> Prompt:
|
) -> Prompt:
|
||||||
"""Update an existing prompt"""
|
"""Update an existing prompt"""
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Check if the prompt exists
|
# Check if the prompt exists
|
||||||
existingPrompt = managementInterface.getPrompt(promptId)
|
existingPrompt = managementInterface.getPrompt(promptId)
|
||||||
|
|
@ -117,7 +117,7 @@ async def delete_prompt(
|
||||||
currentUser: User = Depends(getCurrentUser)
|
currentUser: User = Depends(getCurrentUser)
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Delete a prompt"""
|
"""Delete a prompt"""
|
||||||
managementInterface = serviceManagementClass.getInterface(currentUser)
|
managementInterface = interfaceComponentObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Check if the prompt exists
|
# Check if the prompt exists
|
||||||
existingPrompt = managementInterface.getPrompt(promptId)
|
existingPrompt = managementInterface.getPrompt(promptId)
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@ import os
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
# Import interfaces and models
|
# Import interfaces and models
|
||||||
import modules.interfaces.serviceAppClass as serviceAppClass
|
import modules.interfaces.interfaceAppObjects as interfaceAppObjects
|
||||||
from modules.security.auth import getCurrentUser, limiter, getCurrentUser
|
from modules.security.auth import getCurrentUser, limiter, getCurrentUser
|
||||||
|
|
||||||
# Import the attribute definition and helper functions
|
# 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
|
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
|
|
@ -39,7 +39,7 @@ async def get_users(
|
||||||
) -> List[User]:
|
) -> List[User]:
|
||||||
"""Get all users in the current mandate"""
|
"""Get all users in the current mandate"""
|
||||||
try:
|
try:
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
# If mandateId is provided, use it, otherwise use the current user's mandate
|
# If mandateId is provided, use it, otherwise use the current user's mandate
|
||||||
targetMandateId = mandateId or currentUser.mandateId
|
targetMandateId = mandateId or currentUser.mandateId
|
||||||
# Get all users without filtering by enabled status
|
# Get all users without filtering by enabled status
|
||||||
|
|
@ -61,7 +61,7 @@ async def get_user(
|
||||||
) -> User:
|
) -> User:
|
||||||
"""Get a specific user by ID"""
|
"""Get a specific user by ID"""
|
||||||
try:
|
try:
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
# Get user without filtering by enabled status
|
# Get user without filtering by enabled status
|
||||||
user = appInterface.getUser(userId)
|
user = appInterface.getUser(userId)
|
||||||
|
|
||||||
|
|
@ -89,7 +89,7 @@ async def create_user(
|
||||||
currentUser: User = Depends(getCurrentUser)
|
currentUser: User = Depends(getCurrentUser)
|
||||||
) -> User:
|
) -> User:
|
||||||
"""Create a new user"""
|
"""Create a new user"""
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Convert User to dict for interface
|
# Convert User to dict for interface
|
||||||
user_dict = user_data.dict()
|
user_dict = user_data.dict()
|
||||||
|
|
@ -108,7 +108,7 @@ async def update_user(
|
||||||
currentUser: User = Depends(getCurrentUser)
|
currentUser: User = Depends(getCurrentUser)
|
||||||
) -> User:
|
) -> User:
|
||||||
"""Update an existing user"""
|
"""Update an existing user"""
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Check if the user exists
|
# Check if the user exists
|
||||||
existingUser = appInterface.getUser(userId)
|
existingUser = appInterface.getUser(userId)
|
||||||
|
|
@ -140,7 +140,7 @@ async def delete_user(
|
||||||
currentUser: User = Depends(getCurrentUser)
|
currentUser: User = Depends(getCurrentUser)
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Delete a user"""
|
"""Delete a user"""
|
||||||
appInterface = serviceAppClass.getInterface(currentUser)
|
appInterface = interfaceAppObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Check if the user exists
|
# Check if the user exists
|
||||||
existingUser = appInterface.getUser(userId)
|
existingUser = appInterface.getUser(userId)
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ from google.auth.transport.requests import Request as GoogleRequest
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
|
|
||||||
from modules.shared.configuration import APP_CONFIG
|
from modules.shared.configuration import APP_CONFIG
|
||||||
from modules.interfaces.serviceAppClass import getInterface, getRootInterface
|
from modules.interfaces.interfaceAppObjects import getInterface, getRootInterface
|
||||||
from modules.interfaces.serviceAppModel import AuthAuthority, User, Token, ConnectionStatus, UserConnection
|
from modules.interfaces.interfaceAppModel import AuthAuthority, User, Token, ConnectionStatus, UserConnection
|
||||||
from modules.security.auth import getCurrentUser, limiter
|
from modules.security.auth import getCurrentUser, limiter
|
||||||
from modules.shared.attributeUtils import ModelMixin
|
from modules.shared.attributeUtils import ModelMixin
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ from pydantic import BaseModel
|
||||||
|
|
||||||
# Import auth modules
|
# Import auth modules
|
||||||
from modules.security.auth import createAccessToken, getCurrentUser, limiter
|
from modules.security.auth import createAccessToken, getCurrentUser, limiter
|
||||||
from modules.interfaces.serviceAppClass import getInterface, getRootInterface
|
from modules.interfaces.interfaceAppObjects import getInterface, getRootInterface
|
||||||
from modules.interfaces.serviceAppModel import User, UserInDB, AuthAuthority, UserPrivilege, Token
|
from modules.interfaces.interfaceAppModel import User, UserInDB, AuthAuthority, UserPrivilege, Token
|
||||||
from modules.shared.attributeUtils import ModelMixin
|
from modules.shared.attributeUtils import ModelMixin
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import msal
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from modules.shared.configuration import APP_CONFIG
|
from modules.shared.configuration import APP_CONFIG
|
||||||
from modules.interfaces.serviceAppClass import getInterface, getRootInterface
|
from modules.interfaces.interfaceAppObjects import getInterface, getRootInterface
|
||||||
from modules.interfaces.serviceAppModel import AuthAuthority, User, Token, ConnectionStatus, UserConnection
|
from modules.interfaces.interfaceAppModel import AuthAuthority, User, Token, ConnectionStatus, UserConnection
|
||||||
from modules.security.auth import getCurrentUser, limiter, createAccessToken
|
from modules.security.auth import getCurrentUser, limiter, createAccessToken
|
||||||
from modules.shared.attributeUtils import ModelMixin
|
from modules.shared.attributeUtils import ModelMixin
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ from datetime import datetime, timedelta
|
||||||
from modules.security.auth import limiter, getCurrentUser
|
from modules.security.auth import limiter, getCurrentUser
|
||||||
|
|
||||||
# Import interfaces
|
# Import interfaces
|
||||||
import modules.interfaces.serviceChatClass as serviceChatClass
|
import modules.interfaces.interfaceChatObjects as interfaceChatObjects
|
||||||
from modules.interfaces.serviceChatClass import getInterface
|
from modules.interfaces.interfaceChatObjects import getInterface
|
||||||
|
|
||||||
# Import models
|
# Import models
|
||||||
from modules.interfaces.serviceChatModel import (
|
from modules.interfaces.interfaceChatModel import (
|
||||||
ChatWorkflow,
|
ChatWorkflow,
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
ChatLog,
|
ChatLog,
|
||||||
|
|
@ -28,7 +28,7 @@ from modules.interfaces.serviceChatModel import (
|
||||||
UserInputRequest
|
UserInputRequest
|
||||||
)
|
)
|
||||||
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse
|
from modules.shared.attributeUtils import getModelAttributeDefinitions, AttributeResponse
|
||||||
from modules.interfaces.serviceAppModel import User
|
from modules.interfaces.interfaceAppModel import User
|
||||||
|
|
||||||
# Configure logger
|
# Configure logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -44,7 +44,7 @@ router = APIRouter(
|
||||||
)
|
)
|
||||||
|
|
||||||
def getServiceChat(currentUser: User):
|
def getServiceChat(currentUser: User):
|
||||||
return serviceChatClass.getInterface(currentUser)
|
return interfaceChatObjects.getInterface(currentUser)
|
||||||
|
|
||||||
# Consolidated endpoint for getting all workflows
|
# Consolidated endpoint for getting all workflows
|
||||||
@router.get("/", response_model=List[ChatWorkflow])
|
@router.get("/", response_model=List[ChatWorkflow])
|
||||||
|
|
@ -104,10 +104,10 @@ async def get_workflow_status(
|
||||||
"""Get the current status of a workflow."""
|
"""Get the current status of a workflow."""
|
||||||
try:
|
try:
|
||||||
# Get service container
|
# Get service container
|
||||||
serviceChat = getServiceChat(currentUser)
|
interfaceChat = getServiceChat(currentUser)
|
||||||
|
|
||||||
# Retrieve workflow
|
# Retrieve workflow
|
||||||
workflow = serviceChat.getWorkflow(workflowId)
|
workflow = interfaceChat.getWorkflow(workflowId)
|
||||||
if not workflow:
|
if not workflow:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
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."""
|
"""Get logs for a workflow with support for selective data transfer."""
|
||||||
try:
|
try:
|
||||||
# Get service container
|
# Get service container
|
||||||
serviceChat = getServiceChat(currentUser)
|
interfaceChat = getServiceChat(currentUser)
|
||||||
|
|
||||||
# Verify workflow exists
|
# Verify workflow exists
|
||||||
workflow = serviceChat.getWorkflow(workflowId)
|
workflow = interfaceChat.getWorkflow(workflowId)
|
||||||
if not workflow:
|
if not workflow:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
|
@ -147,7 +147,7 @@ async def get_workflow_logs(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get all logs
|
# Get all logs
|
||||||
allLogs = serviceChat.getWorkflowLogs(workflowId)
|
allLogs = interfaceChat.getWorkflowLogs(workflowId)
|
||||||
|
|
||||||
# Apply selective data transfer if logId is provided
|
# Apply selective data transfer if logId is provided
|
||||||
if logId:
|
if logId:
|
||||||
|
|
@ -179,10 +179,10 @@ async def get_workflow_messages(
|
||||||
"""Get messages for a workflow with support for selective data transfer."""
|
"""Get messages for a workflow with support for selective data transfer."""
|
||||||
try:
|
try:
|
||||||
# Get service container
|
# Get service container
|
||||||
serviceChat = getServiceChat(currentUser)
|
interfaceChat = getServiceChat(currentUser)
|
||||||
|
|
||||||
# Verify workflow exists
|
# Verify workflow exists
|
||||||
workflow = serviceChat.getWorkflow(workflowId)
|
workflow = interfaceChat.getWorkflow(workflowId)
|
||||||
if not workflow:
|
if not workflow:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
|
@ -190,7 +190,7 @@ async def get_workflow_messages(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get all messages
|
# Get all messages
|
||||||
allMessages = serviceChat.getWorkflowMessages(workflowId)
|
allMessages = interfaceChat.getWorkflowMessages(workflowId)
|
||||||
|
|
||||||
# Apply selective data transfer if messageId is provided
|
# Apply selective data transfer if messageId is provided
|
||||||
if messageId:
|
if messageId:
|
||||||
|
|
@ -225,10 +225,10 @@ async def start_workflow(
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Get service container
|
# Get service container
|
||||||
serviceChat = getServiceChat(currentUser)
|
interfaceChat = getServiceChat(currentUser)
|
||||||
|
|
||||||
# Start or continue workflow using ChatInterface
|
# Start or continue workflow using ChatObjects
|
||||||
workflow = await serviceChat.workflowStart(currentUser, userInput, workflowId)
|
workflow = await interfaceChat.workflowStart(currentUser, userInput, workflowId)
|
||||||
|
|
||||||
return ChatWorkflow(**workflow)
|
return ChatWorkflow(**workflow)
|
||||||
|
|
||||||
|
|
@ -250,10 +250,10 @@ async def stop_workflow(
|
||||||
"""Stops a running workflow."""
|
"""Stops a running workflow."""
|
||||||
try:
|
try:
|
||||||
# Get service container
|
# Get service container
|
||||||
serviceChat = getServiceChat(currentUser)
|
interfaceChat = getServiceChat(currentUser)
|
||||||
|
|
||||||
# Stop workflow using ChatInterface
|
# Stop workflow using ChatObjects
|
||||||
workflow = await serviceChat.workflowStop(workflowId)
|
workflow = await interfaceChat.workflowStop(workflowId)
|
||||||
|
|
||||||
return ChatWorkflow(**workflow)
|
return ChatWorkflow(**workflow)
|
||||||
|
|
||||||
|
|
@ -275,10 +275,10 @@ async def delete_workflow(
|
||||||
"""Deletes a workflow and its associated data."""
|
"""Deletes a workflow and its associated data."""
|
||||||
try:
|
try:
|
||||||
# Get service container
|
# Get service container
|
||||||
serviceChat = getServiceChat(currentUser)
|
interfaceChat = getServiceChat(currentUser)
|
||||||
|
|
||||||
# Verify workflow exists
|
# Verify workflow exists
|
||||||
workflow = serviceChat.getWorkflow(workflowId)
|
workflow = interfaceChat.getWorkflow(workflowId)
|
||||||
if not workflow:
|
if not workflow:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
|
@ -293,7 +293,7 @@ async def delete_workflow(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete workflow
|
# Delete workflow
|
||||||
success = serviceChat.deleteWorkflow(workflowId)
|
success = interfaceChat.deleteWorkflow(workflowId)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -328,10 +328,10 @@ async def delete_workflow_message(
|
||||||
"""Delete a message from a workflow."""
|
"""Delete a message from a workflow."""
|
||||||
try:
|
try:
|
||||||
# Get service container
|
# Get service container
|
||||||
serviceChat = getServiceChat(currentUser)
|
interfaceChat = getServiceChat(currentUser)
|
||||||
|
|
||||||
# Verify workflow exists
|
# Verify workflow exists
|
||||||
workflow = serviceChat.getWorkflow(workflowId)
|
workflow = interfaceChat.getWorkflow(workflowId)
|
||||||
if not workflow:
|
if not workflow:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
|
@ -339,7 +339,7 @@ async def delete_workflow_message(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete the message
|
# Delete the message
|
||||||
success = serviceChat.deleteWorkflowMessage(workflowId, messageId)
|
success = interfaceChat.deleteWorkflowMessage(workflowId, messageId)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -351,7 +351,7 @@ async def delete_workflow_message(
|
||||||
messageIds = workflow.get("messageIds", [])
|
messageIds = workflow.get("messageIds", [])
|
||||||
if messageId in messageIds:
|
if messageId in messageIds:
|
||||||
messageIds.remove(messageId)
|
messageIds.remove(messageId)
|
||||||
serviceChat.updateWorkflow(workflowId, {"messageIds": messageIds})
|
interfaceChat.updateWorkflow(workflowId, {"messageIds": messageIds})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"workflowId": workflowId,
|
"workflowId": workflowId,
|
||||||
|
|
@ -379,10 +379,10 @@ async def delete_file_from_message(
|
||||||
"""Delete a file reference from a message in a workflow."""
|
"""Delete a file reference from a message in a workflow."""
|
||||||
try:
|
try:
|
||||||
# Get service container
|
# Get service container
|
||||||
serviceChat = getServiceChat(currentUser)
|
interfaceChat = getServiceChat(currentUser)
|
||||||
|
|
||||||
# Verify workflow exists
|
# Verify workflow exists
|
||||||
workflow = serviceChat.getWorkflow(workflowId)
|
workflow = interfaceChat.getWorkflow(workflowId)
|
||||||
if not workflow:
|
if not workflow:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
|
@ -390,7 +390,7 @@ async def delete_file_from_message(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete file reference from message
|
# Delete file reference from message
|
||||||
success = serviceChat.deleteFileFromMessage(workflowId, messageId, fileId)
|
success = interfaceChat.deleteFileFromMessage(workflowId, messageId, fileId)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ from slowapi import Limiter
|
||||||
from slowapi.util import get_remote_address
|
from slowapi.util import get_remote_address
|
||||||
|
|
||||||
from modules.shared.configuration import APP_CONFIG
|
from modules.shared.configuration import APP_CONFIG
|
||||||
from modules.interfaces.serviceAppClass import getRootInterface
|
from modules.interfaces.interfaceAppObjects import getRootInterface
|
||||||
from modules.interfaces.serviceAppModel import Session, AuthEvent, UserPrivilege, User
|
from modules.interfaces.interfaceAppModel import Session, AuthEvent, UserPrivilege, User
|
||||||
|
|
||||||
# Get Config Data
|
# Get Config Data
|
||||||
SECRET_KEY = APP_CONFIG.get("APP_JWT_SECRET_SECRET")
|
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 logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from modules.interfaces.serviceChatModel import (
|
from modules.interfaces.interfaceChatModel import (
|
||||||
ChatDocument,
|
ChatDocument,
|
||||||
ExtractedContent
|
ExtractedContent
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import logging
|
||||||
from datetime import datetime, UTC
|
from datetime import datetime, UTC
|
||||||
import uuid
|
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.interfaceChatModel import (UserInputRequest, ChatMessage, ChatWorkflow)
|
||||||
from modules.interfaces.serviceChatClass import ChatInterface
|
from modules.interfaces.interfaceChatObjects import ChatObjects
|
||||||
from modules.workflow.managerChat import ChatManager
|
from modules.workflow.managerChat import ChatManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -18,7 +18,7 @@ class WorkflowStoppedException(Exception):
|
||||||
class WorkflowManager:
|
class WorkflowManager:
|
||||||
"""Manager for workflow processing and coordination"""
|
"""Manager for workflow processing and coordination"""
|
||||||
|
|
||||||
def __init__(self, chatInterface: ChatInterface, currentUser: User):
|
def __init__(self, chatInterface: ChatObjects, currentUser: User):
|
||||||
self.chatInterface = chatInterface
|
self.chatInterface = chatInterface
|
||||||
self.chatManager = ChatManager(currentUser)
|
self.chatManager = ChatManager(currentUser)
|
||||||
self.currentUser = currentUser
|
self.currentUser = currentUser
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from pathlib import Path
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from modules.interfaces.serviceChatModel import (
|
from modules.interfaces.interfaceChatModel import (
|
||||||
ChatDocument,
|
ChatDocument,
|
||||||
ExtractedContent,
|
ExtractedContent,
|
||||||
ContentItem,
|
ContentItem,
|
||||||
|
|
@ -578,7 +578,7 @@ class DocumentProcessor:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get AI response
|
# 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": "system", "content": "You are an expert at extracting relevant information from documents."},
|
||||||
{"role": "user", "content": aiPrompt}
|
{"role": "user", "content": aiPrompt}
|
||||||
])
|
])
|
||||||
|
|
@ -813,69 +813,4 @@ class DocumentProcessor:
|
||||||
except Exception:
|
except Exception:
|
||||||
return [content]
|
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 pkgutil
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from modules.interfaces.serviceAppClass import User
|
from modules.interfaces.interfaceAppModel import User, UserConnection
|
||||||
from modules.interfaces.serviceChatModel import (
|
from modules.interfaces.interfaceChatModel import (
|
||||||
TaskStatus, ChatDocument, TaskItem, TaskAction, TaskResult,
|
TaskStatus, ChatDocument, TaskItem, TaskAction, TaskResult,
|
||||||
ChatStat, ChatLog, ChatMessage, ChatWorkflow, UserConnection
|
ChatStat, ChatLog, ChatMessage, ChatWorkflow
|
||||||
)
|
)
|
||||||
from modules.interfaces.interfaceAi import interfaceAi
|
from modules.interfaces.interfaceAiCalls import interfaceAiCalls
|
||||||
from modules.interfaces.serviceChatClass import getInterface as getChatInterface
|
from modules.interfaces.interfaceChatObjects import getInterface as getChatObjects
|
||||||
from modules.interfaces.serviceManagementClass import getInterface as getFileInterface
|
from modules.interfaces.interfaceComponentObjects import getInterface as getComponentObjects
|
||||||
from modules.workflow.managerDocument import DocumentManager
|
from modules.workflow.managerDocument import DocumentManager
|
||||||
from modules.methods.methodBase import MethodBase
|
from modules.methods.methodBase import MethodBase
|
||||||
import uuid
|
import uuid
|
||||||
|
|
@ -30,18 +30,18 @@ class ServiceContainer:
|
||||||
self.currentTask = None # Initialize current task as None
|
self.currentTask = None # Initialize current task as None
|
||||||
|
|
||||||
# Initialize managers
|
# Initialize managers
|
||||||
self.interfaceChat = getChatInterface(currentUser)
|
self.interfaceChat = getChatObjects(currentUser)
|
||||||
self.interfaceFiles = getFileInterface(currentUser)
|
self.interfaceComponent = getComponentObjects(currentUser)
|
||||||
self.interfaceAi = interfaceAi()
|
self.interfaceAiCalls = interfaceAiCalls()
|
||||||
self.documentManager = DocumentManager(self)
|
self.documentManager = DocumentManager(self)
|
||||||
|
|
||||||
# Initialize methods catalog
|
# Initialize methods catalog
|
||||||
self.methods = None
|
self.methods = {}
|
||||||
# Discover additional methods
|
# Discover additional methods
|
||||||
self._discoverMethods()
|
self._discoverMethods()
|
||||||
|
|
||||||
def _discoverMethods(self):
|
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:
|
try:
|
||||||
# Import the methods package
|
# Import the methods package
|
||||||
methodsPackage = importlib.import_module('modules.methods')
|
methodsPackage = importlib.import_module('modules.methods')
|
||||||
|
|
@ -58,10 +58,41 @@ class ServiceContainer:
|
||||||
if (inspect.isclass(item) and
|
if (inspect.isclass(item) and
|
||||||
issubclass(item, MethodBase) and
|
issubclass(item, MethodBase) and
|
||||||
item != MethodBase):
|
item != MethodBase):
|
||||||
# Instantiate the method and add to service
|
# Instantiate the method
|
||||||
methodInstance = item()
|
methodInstance = item(self)
|
||||||
self.methods[methodInstance.name] = methodInstance
|
|
||||||
logger.info(f"Discovered method: {methodInstance.name}")
|
# 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:
|
except Exception as e:
|
||||||
logger.error(f"Error loading method module {name}: {str(e)}")
|
logger.error(f"Error loading method module {name}: {str(e)}")
|
||||||
|
|
@ -76,23 +107,35 @@ class ServiceContainer:
|
||||||
return self.documentManager.extractContent(prompt, document)
|
return self.documentManager.extractContent(prompt, document)
|
||||||
|
|
||||||
def getMethodsCatalog(self) -> Dict[str, Any]:
|
def getMethodsCatalog(self) -> Dict[str, Any]:
|
||||||
"""Get catalog of available methods"""
|
"""Get catalog of available methods and their actions"""
|
||||||
return self.methods
|
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]:
|
def getMethodsList(self) -> List[str]:
|
||||||
"""Get list of available methods with their signatures"""
|
"""Get list of available methods with their signatures"""
|
||||||
methodList = []
|
methodList = []
|
||||||
for methodName, method in self.methods.items():
|
for methodName, method in self.methods.items():
|
||||||
for actionName, action in method.actions.items():
|
for actionName, action in method['actions'].items():
|
||||||
# Get parameter types and return type from action signature
|
# Get parameter types from action signature
|
||||||
paramTypes = []
|
paramTypes = []
|
||||||
for paramName, param in action.parameters.items():
|
for paramName, param in action['parameters'].items():
|
||||||
paramTypes.append(f"{paramName}:{param.type}")
|
paramTypes.append(f"{paramName}:{param['type']}")
|
||||||
|
|
||||||
# Format: method.action([param1:type, param2:type])->resultLabel:type # description
|
# Format: method.action([param1:type, param2:type]) # description
|
||||||
signature = f"{methodName}.{actionName}([{', '.join(paramTypes)}])->{action.resultLabel}:{action.resultType}"
|
signature = f"{methodName}.{actionName}([{', '.join(paramTypes)}])"
|
||||||
if action.description:
|
if action['description']:
|
||||||
signature += f" # {action.description}"
|
signature += f" # {action['description']}"
|
||||||
methodList.append(signature)
|
methodList.append(signature)
|
||||||
return methodList
|
return methodList
|
||||||
|
|
||||||
|
|
@ -205,35 +248,137 @@ class ServiceContainer:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def getConnectionReferenceList(self) -> List[Dict[str, str]]:
|
def getConnectionReferenceList(self) -> List[Dict[str, str]]:
|
||||||
"""Get list of connection references sorted by authority"""
|
"""Get list of all UserConnection objects as references"""
|
||||||
return self._getConnectionReferences()
|
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:
|
def getConnectionReferenceFromUserConnection(self, connection: UserConnection) -> str:
|
||||||
"""Get connection reference from UserConnection"""
|
"""Get connection reference from UserConnection"""
|
||||||
return f"connection_{connection.id}_{connection.authority}"
|
return f"connection_{connection.id}_{connection.authority}"
|
||||||
|
|
||||||
def getUserConnectionFromConnectionReference(self, reference: str) -> UserConnection:
|
def getUserConnectionFromConnectionReference(self, connectionReference: str) -> Optional[UserConnection]:
|
||||||
"""Get UserConnection from connection reference"""
|
"""Get UserConnection from reference string"""
|
||||||
return self._getUserConnectionByReference(reference)
|
try:
|
||||||
|
# Parse reference format: connection_{id}_{authority}
|
||||||
def getMessageSummary(self, message: ChatMessage) -> Dict[str, List[Dict[str, Any]]]:
|
parts = connectionReference.split('_')
|
||||||
"""Get message summary"""
|
if len(parts) != 3 or parts[0] != "connection":
|
||||||
return {
|
return None
|
||||||
"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:
|
def getFileData(self, fileId: str) -> bytes:
|
||||||
"""Get file data by ID"""
|
"""Get file data by ID"""
|
||||||
return self.interfaceFiles.getFileData(fileId)
|
return self.interfaceComponent.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)
|
|
||||||
|
|
||||||
def createFile(self, fileName: str, mimeType: str, content: str, base64encoded: bool = False) -> str:
|
def createFile(self, fileName: str, mimeType: str, content: str, base64encoded: bool = False) -> str:
|
||||||
"""Create new file and return its ID"""
|
"""Create new file and return its ID"""
|
||||||
|
|
@ -244,14 +389,14 @@ class ServiceContainer:
|
||||||
content_bytes = content.encode('utf-8')
|
content_bytes = content.encode('utf-8')
|
||||||
|
|
||||||
# First create the file metadata
|
# First create the file metadata
|
||||||
file_item = self.interfaceFiles.createFile(
|
file_item = self.interfaceComponent.createFile(
|
||||||
name=fileName,
|
name=fileName,
|
||||||
mimeType=mimeType,
|
mimeType=mimeType,
|
||||||
size=len(content_bytes)
|
size=len(content_bytes)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Then store the file data
|
# 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
|
return file_item.id
|
||||||
|
|
||||||
|
|
@ -261,7 +406,7 @@ class ServiceContainer:
|
||||||
file_id = self.createFile(fileName, mimeType, content, base64encoded)
|
file_id = self.createFile(fileName, mimeType, content, base64encoded)
|
||||||
|
|
||||||
# Get file info for metadata
|
# Get file info for metadata
|
||||||
file_info = self.interfaceFiles.getFile(file_id)
|
file_info = self.interfaceComponent.getFile(file_id)
|
||||||
|
|
||||||
# Create document with file reference
|
# Create document with file reference
|
||||||
return ChatDocument(
|
return ChatDocument(
|
||||||
|
|
@ -271,85 +416,25 @@ class ServiceContainer:
|
||||||
fileSize=file_info.fileSize,
|
fileSize=file_info.fileSize,
|
||||||
mimeType=mimeType
|
mimeType=mimeType
|
||||||
)
|
)
|
||||||
|
|
||||||
def getFileInfo(self, fileId: str) -> Dict[str, Any]:
|
async def executeMethod(self, methodName: str, actionName: str, parameters: Dict[str, Any], authData: Optional[Dict[str, Any]] = None) -> MethodResult:
|
||||||
"""Get file information"""
|
"""Execute a method action"""
|
||||||
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:
|
|
||||||
try:
|
|
||||||
result = action(item)
|
|
||||||
results.append(result)
|
|
||||||
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:
|
try:
|
||||||
# Process each document
|
if methodName not in self.methods:
|
||||||
results = []
|
raise ValueError(f"Unknown method: {methodName}")
|
||||||
for doc in documents:
|
|
||||||
content = self.extractContent(prompt, doc)
|
method = self.methods[methodName]
|
||||||
results.append(content)
|
if actionName not in method['actions']:
|
||||||
return results
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Error executing AI call: {str(e)}")
|
logger.error(f"Error executing method {methodName}.{actionName}: {str(e)}")
|
||||||
return []
|
raise
|
||||||
|
|
||||||
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 []
|
|
||||||
|
|
||||||
# Create singleton instance
|
# Create singleton instance
|
||||||
serviceObject = None
|
serviceObject = None
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,8 @@
|
||||||
TODO
|
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
|
- neutralizer to put back placeholders to the returned data
|
||||||
- prompt for task definition to fix
|
- referenceHandling and authentication for connections in the method actions
|
||||||
- implement all functions from service object correctly
|
- check methods
|
||||||
- result parsing: execute task actions by using data references stepwise, all documents in TaskResults to save as ChatDocuments, to be available for next action.
|
- test for workflow backend with userdata
|
||||||
- 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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
********************
|
********************
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ class DocumentService:
|
||||||
## Implementation Steps
|
## Implementation Steps
|
||||||
|
|
||||||
1. **Model Cleanup**
|
1. **Model Cleanup**
|
||||||
- Create new model classes in `serviceChatModel.py`
|
- Create new model classes in `interfaceChatModel.py`
|
||||||
- Remove deprecated models:
|
- Remove deprecated models:
|
||||||
- DocumentExtraction
|
- DocumentExtraction
|
||||||
- DocumentContext
|
- DocumentContext
|
||||||
|
|
@ -113,7 +113,7 @@ class DocumentService:
|
||||||
|
|
||||||
3. **UserInput Processing**
|
3. **UserInput Processing**
|
||||||
- Update `UserInputRequest` processing to use `ChatMessage`
|
- Update `UserInputRequest` processing to use `ChatMessage`
|
||||||
- Implement `processFileIds` in `serviceChatClass`
|
- Implement `processFileIds` in `interfaceChatObjects`
|
||||||
- Update all references to use new model structure
|
- Update all references to use new model structure
|
||||||
|
|
||||||
4. **Method Module Updates**
|
4. **Method Module Updates**
|
||||||
|
|
@ -130,14 +130,14 @@ class DocumentService:
|
||||||
## Files to be Removed/Modified
|
## Files to be Removed/Modified
|
||||||
|
|
||||||
### To be Removed
|
### To be Removed
|
||||||
1. `DocumentExtraction` class from serviceChatModel.py
|
1. `DocumentExtraction` class from interfaceChatModel.py
|
||||||
2. `DocumentContext` class from serviceChatModel.py
|
2. `DocumentContext` class from interfaceChatModel.py
|
||||||
3. `ProcessedDocument` class from serviceChatModel.py
|
3. `ProcessedDocument` class from interfaceChatModel.py
|
||||||
4. `ChatContent` class from serviceChatModel.py
|
4. `ChatContent` class from interfaceChatModel.py
|
||||||
5. Direct file access methods from method*.py modules
|
5. Direct file access methods from method*.py modules
|
||||||
|
|
||||||
### To be Modified
|
### To be Modified
|
||||||
1. `serviceChatModel.py`
|
1. `interfaceChatModel.py`
|
||||||
- Add new model classes
|
- Add new model classes
|
||||||
- Remove deprecated classes
|
- Remove deprecated classes
|
||||||
- Update existing classes
|
- Update existing classes
|
||||||
|
|
@ -152,7 +152,7 @@ class DocumentService:
|
||||||
- Remove direct file access
|
- Remove direct file access
|
||||||
- Update error handling
|
- Update error handling
|
||||||
|
|
||||||
4. `serviceChatClass.py`
|
4. `interfaceChatObjects.py`
|
||||||
- Implement processFileIds
|
- Implement processFileIds
|
||||||
- Update document handling
|
- Update document handling
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -881,8 +881,8 @@ gateway/
|
||||||
│ │ └── methodPowerpoint.py # PowerPoint operations
|
│ │ └── methodPowerpoint.py # PowerPoint operations
|
||||||
│ │
|
│ │
|
||||||
│ └── interfaces/
|
│ └── interfaces/
|
||||||
│ ├── serviceChatModel.py # Chat system models and enums
|
│ ├── interfaceChatModel.py # Chat system models and enums
|
||||||
│ └── serviceAppModel.py # Application models including UserConnection
|
│ └── interfaceAppModel.py # Application models including UserConnection
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6.2 Implementation Plan
|
### 6.2 Implementation Plan
|
||||||
|
|
@ -894,8 +894,8 @@ gateway/
|
||||||
- Create new `methods` directory
|
- Create new `methods` directory
|
||||||
|
|
||||||
2. **Model Updates**
|
2. **Model Updates**
|
||||||
- Update `serviceChatModel.py` with new enums and models
|
- Update `interfaceChatModel.py` with new enums and models
|
||||||
- Integrate `UserConnection` from `serviceAppModel.py`
|
- Integrate `UserConnection` from `interfaceAppModel.py`
|
||||||
- Update validation logic in respective modules
|
- Update validation logic in respective modules
|
||||||
|
|
||||||
#### Phase 2: Method Migration
|
#### Phase 2: Method Migration
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue