From 1ceb361274707648ccecbe83911a746720aff0b2 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Sat, 10 May 2025 21:36:18 +0200 Subject: [PATCH] prod azure 1.1.0 fix documents --- connectors/BACKUP-connectorDbJson.py | 569 --------- modules/agentAnalyst.py | 436 ++++--- modules/agentEmail.py | 171 ++- modules/agentWebcrawler.py | 37 +- modules/documentProcessor.py | 261 +++- modules/lucydomInterface.py | 24 +- modules/lucydomModel.py | 3 +- modules/workflowAgentsRegistry.py | 68 +- modules/workflowManager.py | 244 ++-- notes/changelog.txt | 16 +- static/10_email_preview.html | 42 - static/11_email_template.json | 6 - static/12_email_preview.html | 42 - static/13_email_template.json | 6 - static/14_microsoft_authentication.html | 47 - static/15_microsoft_authentication.html | 28 - static/16_email_preview.html | 42 - static/17_email_template.json | 6 - static/18_generated_code.py | 48 - static/19_execution_history.json | 19 - static/1_LF-Details.png | Bin 253009 -> 0 bytes static/20_prime_numbers.csv | 1000 ---------------- static/21_email_preview.html | 42 - static/22_email_template.json | 6 - static/23_documentProcessor.py | 933 --------------- static/24_defAttributes.py | 123 -- static/25_email_preview.html | 42 - static/26_email_template.json | 6 - static/27_email_preview.html | 42 - static/28_email_template.json | 6 - static/29_email_preview.html | 42 - static/2_LF-Details_Description.txt | 295 ----- static/30_email_template.json | 6 - static/31_email_preview.html | 42 - static/32_email_template.json | 6 - static/33_email_preview.html | 42 - static/34_email_template.json | 6 - static/35_email_preview.html | 42 - static/36_email_template.json | 6 - static/37_gatewayInterface.py | 522 -------- static/38_email_preview.html | 49 - static/39_email_template.json | 6 - static/3_generated_code.py | 38 - static/40_email_preview.html | 42 - static/41_email_template.json | 6 - static/42_gatewayInterface.py | 526 -------- static/43_defAttributes.py | 123 -- static/44_email_preview.html | 42 - static/45_email_template.json | 6 - static/46_email_preview.html | 42 - static/47_email_template.json | 6 - static/48_email_preview.html | 42 - static/49_email_template.json | 6 - static/4_execution_history.json | 19 - static/50_LF-Nutshell.png | Bin 52108 -> 0 bytes static/51_PowerOn NDA 2025.pdf | Bin 39612 -> 0 bytes static/52_email_preview.html | 42 - static/53_email_template.json | 6 - static/5_prime_numbers.txt | 1000 ---------------- static/6_email_preview.html | 42 - static/7_email_template.json | 6 - static/8_email_preview.html | 74 -- static/9_email_template.json | 6 - .../7d08aab9-a170-4975-8898-bc7e0a95488e.json | 1 - tool_testBackendSingle.py | 432 ------- tool_testData.py | 1064 ----------------- tool_testUser.py | 244 ---- 67 files changed, 802 insertions(+), 8392 deletions(-) delete mode 100644 connectors/BACKUP-connectorDbJson.py delete mode 100644 static/10_email_preview.html delete mode 100644 static/11_email_template.json delete mode 100644 static/12_email_preview.html delete mode 100644 static/13_email_template.json delete mode 100644 static/14_microsoft_authentication.html delete mode 100644 static/15_microsoft_authentication.html delete mode 100644 static/16_email_preview.html delete mode 100644 static/17_email_template.json delete mode 100644 static/18_generated_code.py delete mode 100644 static/19_execution_history.json delete mode 100644 static/1_LF-Details.png delete mode 100644 static/20_prime_numbers.csv delete mode 100644 static/21_email_preview.html delete mode 100644 static/22_email_template.json delete mode 100644 static/23_documentProcessor.py delete mode 100644 static/24_defAttributes.py delete mode 100644 static/25_email_preview.html delete mode 100644 static/26_email_template.json delete mode 100644 static/27_email_preview.html delete mode 100644 static/28_email_template.json delete mode 100644 static/29_email_preview.html delete mode 100644 static/2_LF-Details_Description.txt delete mode 100644 static/30_email_template.json delete mode 100644 static/31_email_preview.html delete mode 100644 static/32_email_template.json delete mode 100644 static/33_email_preview.html delete mode 100644 static/34_email_template.json delete mode 100644 static/35_email_preview.html delete mode 100644 static/36_email_template.json delete mode 100644 static/37_gatewayInterface.py delete mode 100644 static/38_email_preview.html delete mode 100644 static/39_email_template.json delete mode 100644 static/3_generated_code.py delete mode 100644 static/40_email_preview.html delete mode 100644 static/41_email_template.json delete mode 100644 static/42_gatewayInterface.py delete mode 100644 static/43_defAttributes.py delete mode 100644 static/44_email_preview.html delete mode 100644 static/45_email_template.json delete mode 100644 static/46_email_preview.html delete mode 100644 static/47_email_template.json delete mode 100644 static/48_email_preview.html delete mode 100644 static/49_email_template.json delete mode 100644 static/4_execution_history.json delete mode 100644 static/50_LF-Nutshell.png delete mode 100644 static/51_PowerOn NDA 2025.pdf delete mode 100644 static/52_email_preview.html delete mode 100644 static/53_email_template.json delete mode 100644 static/5_prime_numbers.txt delete mode 100644 static/6_email_preview.html delete mode 100644 static/7_email_template.json delete mode 100644 static/8_email_preview.html delete mode 100644 static/9_email_template.json delete mode 100644 token_storage/7d08aab9-a170-4975-8898-bc7e0a95488e.json delete mode 100644 tool_testBackendSingle.py delete mode 100644 tool_testData.py delete mode 100644 tool_testUser.py diff --git a/connectors/BACKUP-connectorDbJson.py b/connectors/BACKUP-connectorDbJson.py deleted file mode 100644 index f4bdea80..00000000 --- a/connectors/BACKUP-connectorDbJson.py +++ /dev/null @@ -1,569 +0,0 @@ -import json -import os -from typing import List, Dict, Any, Optional, Union -import logging - -logger = logging.getLogger(__name__) - -class DatabaseConnector: - """ - A connector for JSON-based data storage. - Provides generic database operations with tenant and user context support. - """ - def __init__(self, dbHost: str, dbDatabase: str, dbUser: str = None, dbPassword: str = None, - mandateId: int = None, userId: int = None, skipInitialIdLookup: bool = False): - """ - Initializes the JSON database connector. - - Args: - dbHost: Directory for the JSON files - dbDatabase: Database name - dbUser: Username for authentication (optional) - dbPassword: API key for authentication (optional) - mandateId: Context parameter for the tenant - userId: Context parameter for the user - skipInitialIdLookup: When True, skips looking up initial IDs for mandateId and userId - """ - # Store the input parameters - self.dbHost = dbHost - self.dbDatabase = dbDatabase - self.dbUser = dbUser - self.dbPassword = dbPassword - self.skipInitialIdLookup = skipInitialIdLookup - - # Check if context parameters are set - if mandateId is None or userId is None: - raise ValueError("mandateId and userId must be set") - - # Ensure the database directory exists - self.dbFolder = os.path.join(self.dbHost, self.dbDatabase) - os.makedirs(self.dbFolder, exist_ok=True) - - # Cache for loaded data - self._tablesCache = {} - - # Initialize system table - self._systemTableName = "_system" - self._initializeSystemTable() - - # Temporarily store mandateId and userId - self._mandateId = mandateId - self._userId = userId - - # If mandateId or userId are 0 and we're not skipping ID lookup, try to use the initial IDs - if not skipInitialIdLookup: - if mandateId == 0: - initialMandateId = self.getInitialId("mandates") - if initialMandateId is not None: - self._mandateId = initialMandateId - logger.info(f"Using initial mandateId: {initialMandateId} instead of 0") - - if userId == 0: - initialUserId = self.getInitialId("users") - if initialUserId is not None: - self._userId = initialUserId - logger.info(f"Using initial userId: {initialUserId} instead of 0") - - # Set the effective IDs as properties - self.mandateId = self._mandateId - self.userId = self._userId - - logger.info(f"DatabaseConnector initialized for directory: {self.dbFolder}") - logger.debug(f"Context: mandateId={self.mandateId}, userId={self.userId}") - - def _initializeSystemTable(self): - """Initializes the system table if it doesn't exist yet.""" - systemTablePath = self._getTablePath(self._systemTableName) - if not os.path.exists(systemTablePath): - emptySystemTable = {} - self._saveSystemTable(emptySystemTable) - logger.info(f"System table initialized in {systemTablePath}") - else: - # Load existing system table to ensure it's available - self._loadSystemTable() - logger.debug(f"Existing system table loaded from {systemTablePath}") - - def _loadSystemTable(self) -> Dict[str, int]: - """Loads the system table with the initial IDs.""" - # Check if system table is in cache - if f"_{self._systemTableName}" in self._tablesCache: - return self._tablesCache[f"_{self._systemTableName}"] - - systemTablePath = self._getTablePath(self._systemTableName) - try: - if os.path.exists(systemTablePath): - with open(systemTablePath, 'r', encoding='utf-8') as f: - data = json.load(f) - # Store in cache with special prefix to avoid collision with regular tables - self._tablesCache[f"_{self._systemTableName}"] = data - return data - else: - self._tablesCache[f"_{self._systemTableName}"] = {} - return {} - except Exception as e: - logger.error(f"Error loading the system table: {e}") - self._tablesCache[f"_{self._systemTableName}"] = {} - return {} - - def _saveSystemTable(self, data: Dict[str, int]) -> bool: - """Saves the system table with the initial IDs.""" - systemTablePath = self._getTablePath(self._systemTableName) - try: - with open(systemTablePath, 'w', encoding='utf-8') as f: - json.dump(data, f, indent=2, ensure_ascii=False) - # Update cache - self._tablesCache[f"_{self._systemTableName}"] = data - return True - except Exception as e: - logger.error(f"Error saving the system table: {e}") - return False - - def _getTablePath(self, table: str) -> str: - """Returns the full path to a table file""" - return os.path.join(self.dbFolder, f"{table}.json") - - def _loadTable(self, table: str) -> List[Dict[str, Any]]: - """Loads a table from the corresponding JSON file""" - path = self._getTablePath(table) - - # If the table is the system table, load it directly - if table == self._systemTableName: - return [] # The system table is not treated like normal tables - - # If the table is already in the cache, use the cache - if table in self._tablesCache: - return self._tablesCache[table] - - # Otherwise load the file - try: - if os.path.exists(path): - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - self._tablesCache[table] = data - - # If data was loaded and no initial ID is registered yet, - # register the ID of the first record (if available) - if data and not self.hasInitialId(table): - if "id" in data[0]: - self._registerInitialId(table, data[0]["id"]) - logger.info(f"Initial ID {data[0]['id']} for table {table} retroactively registered") - - return data - else: - # If the file doesn't exist, create an empty table - logger.info(f"New table {table}") - self._tablesCache[table] = [] - self._saveTable(table, []) - return [] - except Exception as e: - logger.error(f"Error loading table {table}: {e}") - return [] - - def _saveTable(self, table: str, data: List[Dict[str, Any]]) -> bool: - """Saves a table to the corresponding JSON file""" - # The system table is handled specially - if table == self._systemTableName: - return False - - path = self._getTablePath(table) - try: - with open(path, 'w', encoding='utf-8') as f: - json.dump(data, f, indent=2, ensure_ascii=False) - - # Update the cache - self._tablesCache[table] = data - return True - except Exception as e: - logger.error(f"Error saving table {table}: {e}") - return False - - def _filterByContext(self, records: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """ - Filters records by tenant and user context, - if these fields exist in the record. - """ - filteredRecords = [] - - for record in records: - # Check if mandateId exists in the record and is not null - hasMandate = "mandateId" in record and record["mandateId"] is not None and record["mandateId"] != "" - - # Check if userId exists in the record and is not null - hasUser = "userId" in record and record["userId"] is not None and record["userId"] != "" - - # If both exist, filter accordingly - if hasMandate and hasUser: - if record["mandateId"] == self.mandateId: - filteredRecords.append(record) - # If only mandateId exists - elif hasMandate and not hasUser: - if record["mandateId"] == self.mandateId: - filteredRecords.append(record) - # If neither mandateId nor userId exist, add the record - elif not hasMandate and not hasUser: - filteredRecords.append(record) - - return filteredRecords - - - def _applyRecordFilter(self, records: List[Dict[str, Any]], recordFilter: Dict[str, Any] = None) -> List[Dict[str, Any]]: - """Applies a record filter to the records""" - if not recordFilter: - return records - - filteredRecords = [] - - for record in records: - match = True - - for field, value in recordFilter.items(): - # Check if the field exists - if field not in record: - match = False - break - - # Handle type conversion for integer comparisons both ways - if isinstance(value, int) and isinstance(record[field], str) and record[field].isdigit(): - # Filter value is int, record value is string - if value != int(record[field]): - match = False - break - elif isinstance(value, str) and value.isdigit() and isinstance(record[field], int): - # Filter value is string, record value is int - if record[field] != int(value): - match = False - break - # Otherwise direct comparison - elif record[field] != value: - match = False - break - - if match: - filteredRecords.append(record) - - return filteredRecords - - def _registerInitialId(self, table: str, initialId: int) -> bool: - """ - Registers the initial ID for a table. - - Args: - table: Name of the table - initialId: The initial ID - - Returns: - True on success, False on error - """ - try: - # Load the current system table - systemData = self._loadSystemTable() - - # Only register if not already present - if table not in systemData: - systemData[table] = initialId - success = self._saveSystemTable(systemData) - if success: - logger.info(f"Initial ID {initialId} for table {table} registered") - return success - return True # If already present, this is not an error - except Exception as e: - logger.error(f"Error registering the initial ID for table {table}: {e}") - return False - - def _removeInitialId(self, table: str) -> bool: - """ - Removes the initial ID for a table from the system table. - - Args: - table: Name of the table - - Returns: - True on success, False on error - """ - try: - # Load the current system table - systemData = self._loadSystemTable() - - # Remove the entry if it exists - if table in systemData: - del systemData[table] - success = self._saveSystemTable(systemData) - if success: - logger.info(f"Initial ID for table {table} removed from system table") - return success - return True # If not present, this is not an error - except Exception as e: - logger.error(f"Error removing initial ID for table {table}: {e}") - return False - - # Public API - - def getTables(self) -> List[str]: - """ - Returns a list of all available tables. - - Returns: - List of table names - """ - tables = [] - - try: - for filename in os.listdir(self.dbFolder): - if filename.endswith('.json') and not filename.startswith('_'): - tableName = filename[:-5] # Remove the .json extension - tables.append(tableName) - except Exception as e: - logger.error(f"Error reading the database directory: {e}") - - return tables - - def getFields(self, table: str) -> List[str]: - """ - Returns a list of all fields in a table. - - Args: - table: Name of the table - - Returns: - List of field names - """ - # Load the table data - data = self._loadTable(table) - - if not data: - return [] - - # Take the first record as a reference for the fields - fields = list(data[0].keys()) if data else [] - - return fields - - def getSchema(self, table: str, language: str = None) -> Dict[str, Dict[str, Any]]: - """ - Returns a schema object for a table with data types and labels. - - Args: - table: Name of the table - language: Language for the labels (optional) - - Returns: - Schema object with fields, data types and labels - """ - # Load the table data - data = self._loadTable(table) - - schema = {} - - if not data: - return schema - - # Take the first record as a reference for the fields and data types - firstRecord = data[0] - - for field, value in firstRecord.items(): - # Determine the data type - dataType = type(value).__name__ - - # Create label (default is the field name) - label = field - - schema[field] = { - "type": dataType, - "label": label - } - - return schema - - def getRecordset(self, table: str, fieldFilter: List[str] = None, recordFilter: Dict[str, Any] = None) -> List[Dict[str, Any]]: - """ - Returns a list of records from a table, filtered by criteria. - - Args: - table: Name of the table - fieldFilter: Filter for fields (which fields should be returned) - recordFilter: Filter for records (which records should be returned) - - Returns: - List of filtered records - """ - # Load the table data - data = self._loadTable(table) - logger.debug(f"getRecordset: data volume of {len(data)} bytes") - - # Filter by tenant and user context - filteredData = self._filterByContext(data) - - # Apply recordFilter if available - if recordFilter: - filteredData = self._applyRecordFilter(filteredData, recordFilter) - - # If fieldFilter is available, reduce the fields - if fieldFilter and isinstance(fieldFilter, list): - result = [] - for record in filteredData: - filteredRecord = {} - for field in fieldFilter: - if field in record: - filteredRecord[field] = record[field] - result.append(filteredRecord) - return result - - return filteredData - - def recordCreate(self, table: str, recordData: Dict[str, Any]) -> Dict[str, Any]: - """ - Creates a new record in the table. - - Args: - table: Name of the table - recordData: Data for the new record - - Returns: - The created record - """ - # Load the table data - data = self._loadTable(table) - - # Add mandateId and userId if not present or 0 - if "mandateId" not in recordData or recordData["mandateId"] == 0: - recordData["mandateId"] = self.mandateId - - if "userId" not in recordData or recordData["userId"] == 0: - recordData["userId"] = self.userId - - # Determine the next ID if not present - if "id" not in recordData: - nextId = 1 - if data: - nextId = max(record["id"] for record in data if "id" in record) + 1 - recordData["id"] = nextId - - # If the table is empty and a system ID should be registered - if not data: - self._registerInitialId(table, recordData["id"]) - logger.info(f"Initial ID {recordData['id']} for table {table} has been registered") - - # Add the new record - data.append(recordData) - - # Save the updated table - if self._saveTable(table, data): - return recordData - else: - raise ValueError(f"Error creating the record in table {table}") - - def recordDelete(self, table: str, recordId: Union[str, int]) -> bool: - """ - Deletes a record from the table. - - Args: - table: Name of the table - recordId: ID of the record to delete - - Returns: - True on success, False on error - """ - # Load table data - data = self._loadTable(table) - - # Search for the record - for i, record in enumerate(data): - if "id" in record and record["id"] == recordId: - # Check if the record belongs to the current mandate - if "mandateId" in record and record["mandateId"] != self.mandateId: - raise ValueError("Not your mandate") - - # Check if it's an initial record - initialId = self.getInitialId(table) - if initialId is not None and initialId == recordId: - # Remove this entry from the system table - self._removeInitialId(table) - logger.info(f"Initial ID {recordId} for table {table} has been removed from the system table") - - # Delete the record - del data[i] - - # Save the updated table - return self._saveTable(table, data) - - # Record not found - return False - - def recordModify(self, table: str, recordId: Union[str, int], recordData: Dict[str, Any]) -> Dict[str, Any]: - """ - Modifies a record in the table. - - Args: - table: Name of the table - recordId: ID of the record to modify - recordData: New data for the record - - Returns: - The updated record - """ - # Load table data - data = self._loadTable(table) - - # Search for the record - for i, record in enumerate(data): - if "id" in record and record["id"] == recordId: - # Check if the record belongs to the current mandate - if "mandateId" in record and record["mandateId"] != self.mandateId: - raise ValueError("Not your mandate") - - # Prevent changing the ID - if "id" in recordData and recordData["id"] != recordId: - raise ValueError(f"The ID of a record in table {table} cannot be changed") - - # Update the record - for key, value in recordData.items(): - data[i][key] = value - - # Save the updated table - if self._saveTable(table, data): - return data[i] - else: - raise ValueError(f"Error updating record in table {table}") - - # Record not found - raise ValueError(f"Record with ID {recordId} not found in table {table}") - - def hasInitialId(self, table: str) -> bool: - """ - Checks if an initial ID is registered for a table. - - Args: - table: Name of the table - - Returns: - True if an initial ID is registered, otherwise False - """ - systemData = self._loadSystemTable() - return table in systemData - - def getInitialId(self, table: str) -> Optional[int]: - """ - Returns the initial ID for a table. - - Args: - table: Name of the table - - Returns: - The initial ID or None if not present - """ - systemData = self._loadSystemTable() - initialId = systemData.get(table) - logger.debug(f"Database '{self.dbDatabase}': Initial ID for table '{table}' is {initialId}") - if initialId is None: - logger.debug(f"No initial ID found for table {table}") - return initialId - - def getAllInitialIds(self) -> Dict[str, int]: - """ - Returns all registered initial IDs. - - Returns: - Dictionary with table names as keys and initial IDs as values - """ - systemData = self._loadSystemTable() - return systemData.copy() # Return a copy to protect the original \ No newline at end of file diff --git a/modules/agentAnalyst.py b/modules/agentAnalyst.py index 5e68c91e..b967af5f 100644 --- a/modules/agentAnalyst.py +++ b/modules/agentAnalyst.py @@ -64,9 +64,11 @@ class AgentAnalyst(AgentBase): } # Extract data from documents - focusing only on dataExtracted + self.mydom.logAdd(task["workflowId"], "Extracting data from documents...", level="info", progress=35) datasets, documentContext = self._extractData(inputDocuments) # Generate task analysis to understand what's needed + self.mydom.logAdd(task["workflowId"], "Analyzing task requirements...", level="info", progress=45) analysisPlan = await self._analyzeTask(prompt, documentContext, datasets, outputSpecs) # Generate all required output documents @@ -77,7 +79,11 @@ class AgentAnalyst(AgentBase): outputSpecs = [] # Process each output specification - for spec in outputSpecs: + totalSpecs = len(outputSpecs) + for i, spec in enumerate(outputSpecs): + progress = 45 + int((i / totalSpecs) * 45) # Progress from 45% to 90% + self.mydom.logAdd(task["workflowId"], f"Creating output {i+1}/{totalSpecs}...", level="info", progress=progress) + outputLabel = spec.get("label", "") outputDescription = spec.get("description", "") @@ -106,9 +112,9 @@ class AgentAnalyst(AgentBase): documents.append(document) # Generate feedback - feedback = f"{analysisPlan.get('analysisApproach')}" - if analysisPlan.get("keyInsights"): - feedback += f"\n\n{analysisPlan.get('keyInsights')}" + feedback = f"{analysisPlan.get('feedback')}" + if analysisPlan.get("insights"): + feedback += f"\n\n{analysisPlan.get('insights')}" return { "feedback": feedback, @@ -196,69 +202,74 @@ class AgentAnalyst(AgentBase): return datasets, documentContext - async def _analyzeTask(self, prompt: str, context: str, datasets: Dict, outputSpecs: List) -> Dict: + async def _analyzeTask(self, prompt: str, documentContext: str, datasets: Dict[str, Any], outputSpecs: List[Dict[str, Any]]) -> Dict[str, Any]: """ - Use AI to analyze the task and create a plan for analysis. + Analyze the task requirements using AI. Args: prompt: The task prompt - context: Document context text - datasets: Dictionary of extracted datasets + documentContext: Context from input documents + datasets: Available datasets outputSpecs: Output specifications Returns: Analysis plan dictionary """ - # Prepare dataset information - datasetInfo = {} - for name, df in datasets.items(): - try: - datasetInfo[name] = { - "shape": df.shape, - "columns": df.columns.tolist(), - "dtypes": {col: str(df[col].dtype) for col in df.columns}, - "sample": df.head(3).to_dict(orient='records') - } - except: - datasetInfo[name] = {"error": "Could not process dataset"} - + # Create analysis prompt analysisPrompt = f""" - Analyze this data analysis task and create a plan. + Analyze this data analysis task and create a detailed plan: TASK: {prompt} - AVAILABLE DATA: - {json.dumps(datasetInfo, indent=2)} - DOCUMENT CONTEXT: - {context[:1000]}... (truncated) + {documentContext} - OUTPUT REQUIREMENTS: + AVAILABLE DATASETS: + {json.dumps(datasets, indent=2)} + + REQUIRED OUTPUTS: {json.dumps(outputSpecs, indent=2)} - Create a detailed analysis plan in JSON format with the following structure: + Create a detailed analysis plan in JSON format with: {{ - "analysisType": "statistical|trend|comparative|predictive|cluster|general", - "keyQuestions": ["question1", "question2"], - "recommendedVisualizations": [{{ - "type": "chart_type", - "dataSource": "dataset_name", - "variables": ["col1", "col2"], - "purpose": "explanation" - }}], - "keyInsights": "brief summary of initial insights", - "analysisApproach": "brief description of recommended approach" + "analysisSteps": [ + {{ + "step": "step description", + "purpose": "why this step is needed", + "datasets": ["dataset1", "dataset2"], + "techniques": ["technique1", "technique2"], + "outputs": ["output1", "output2"] + }} + ], + "visualizations": [ + {{ + "type": "visualization type", + "purpose": "what it shows", + "datasets": ["dataset1"], + "settings": {{"key": "value"}} + }} + ], + "insights": [ + {{ + "type": "insight type", + "description": "what to look for", + "datasets": ["dataset1"] + }} + ], + "feedback": "explanation of the analysis approach" }} - Only return valid JSON. No preamble or explanations. + Respond with ONLY the JSON object, no additional text or explanations. """ + try: + # Get analysis plan from AI response = await self.mydom.callAi([ - {"role": "system", "content": "You are a data analysis expert. Respond with valid JSON only."}, + {"role": "system", "content": "You are a data analysis expert. Create detailed analysis plans. Respond with valid JSON only."}, {"role": "user", "content": analysisPrompt} - ], produceUserAnswer = True) + ], produceUserAnswer=True) - # Extract JSON from response + # Extract JSON jsonStart = response.find('{') jsonEnd = response.rfind('}') + 1 @@ -266,154 +277,249 @@ class AgentAnalyst(AgentBase): plan = json.loads(response[jsonStart:jsonEnd]) return plan else: - # Fallback if JSON not found + # Fallback plan + logger.warning(f"Not able creating analysis plan, generating fallback plan") return { - "analysisType": "general", - "keyQuestions": ["What insights can be extracted from this data?"], - "recommendedVisualizations": [], - "keyInsights": "Analysis plan could not be created", - "analysisApproach": "General exploratory analysis" + "analysisSteps": [ + { + "step": "Basic data analysis", + "purpose": "Understand the data structure and content", + "datasets": list(datasets.keys()), + "techniques": ["summary statistics", "data visualization"], + "outputs": ["summary report", "basic visualizations"] + } + ], + "visualizations": [ + { + "type": "basic charts", + "purpose": "Show data distribution and relationships", + "datasets": list(datasets.keys()), + "settings": {} + } + ], + "insights": [ + { + "type": "basic insights", + "description": "Key findings from the data", + "datasets": list(datasets.keys()) + } + ], + "feedback": f"I'll analyze the data and provide insights about {prompt}" } except Exception as e: logger.warning(f"Error creating analysis plan: {str(e)}") + # Simple fallback plan return { - "analysisType": "general", - "keyQuestions": ["What insights can be extracted from this data?"], - "recommendedVisualizations": [], - "keyInsights": "Analysis plan could not be created", - "analysisApproach": "General exploratory analysis" + "analysisSteps": [ + { + "step": "Basic data analysis", + "purpose": "Understand the data structure and content", + "datasets": list(datasets.keys()), + "techniques": ["summary statistics", "data visualization"], + "outputs": ["summary report", "basic visualizations"] + } + ], + "visualizations": [ + { + "type": "basic charts", + "purpose": "Show data distribution and relationships", + "datasets": list(datasets.keys()), + "settings": {} + } + ], + "insights": [ + { + "type": "basic insights", + "description": "Key findings from the data", + "datasets": list(datasets.keys()) + } + ], + "feedback": f"I'll analyze the data and provide insights about {prompt}" } async def _createVisualization(self, datasets: Dict, prompt: str, outputLabel: str, analysisPlan: Dict, description: str) -> Dict: """ - Create visualization document using AI guidance. + Create a visualization based on the analysis plan. Args: datasets: Dictionary of datasets prompt: Original task prompt - outputLabel: Output filename - analysisPlan: Analysis plan from AI + outputLabel: Output file label + analysisPlan: Analysis plan description: Output description Returns: - Visualization document + Document dictionary with visualization """ - # Determine format from filename - formatType = outputLabel.split('.')[-1].lower() - if formatType not in ['png', 'jpg', 'jpeg', 'svg']: - formatType = 'png' - - # If no datasets available, create error message image - if not datasets: - plt.figure(figsize=(10, 6)) - plt.text(0.5, 0.5, "No data available for visualization", - ha='center', va='center', fontsize=14) - plt.tight_layout() - imgData = self._getImageBase64(formatType) - plt.close() - - return { - "label": outputLabel, - "content": imgData, - "metadata": { - "contentType": f"image/{formatType}" - } - } - - # Get recommended visualization from plan - recommendedViz = analysisPlan.get("recommendedVisualizations", []) - - # Prepare dataset info for the first dataset if none specified - if not recommendedViz and datasets: - name, df = next(iter(datasets.items())) - recommendedViz = [{ - "type": "auto", - "dataSource": name, - "variables": df.columns.tolist()[:5], - "purpose": "general analysis" - }] - - # Create visualization code prompt - vizPrompt = f""" - Generate Python matplotlib/seaborn code to create a visualization for: - - TASK: {prompt} - - VISUALIZATION REQUIREMENTS: - - Output format: {formatType} - - Filename: {outputLabel} - - Description: {description} - - RECOMMENDED VISUALIZATION: - {json.dumps(recommendedViz, indent=2)} - - AVAILABLE DATASETS: - """ - - # Add dataset info for recommended sources - for viz in recommendedViz: - dataSource = viz.get("dataSource") - if dataSource in datasets: - df = datasets[dataSource] - vizPrompt += f"\nDataset '{dataSource}':\n" - vizPrompt += f"- Shape: {df.shape}\n" - vizPrompt += f"- Columns: {df.columns.tolist()}\n" - vizPrompt += f"- Sample data: {df.head(3).to_dict(orient='records')}\n" - - vizPrompt += """ - Generate ONLY Python code that: - 1. Uses matplotlib and/or seaborn to create a clear visualization - 2. Sets figure size to (10, 6) - 3. Includes appropriate titles, labels, and legend - 4. Uses professional color schemes - 5. Handles any missing data gracefully - - Return ONLY executable Python code, no explanations or markdown. - """ - try: - # Get visualization code from AI - vizCode = await self.mydom.callAi([ - {"role": "system", "content": "You are a data visualization expert. Provide only executable Python code."}, - {"role": "user", "content": vizPrompt} - ], produceUserAnswer = True) + # Get visualization recommendations + vizRecommendations = analysisPlan.get("visualizations", []) - # Clean code - vizCode = vizCode.replace("```python", "").replace("```", "").strip() - - # Execute visualization code - plt.figure(figsize=(10, 6)) - - # Make local variables available to the code - localVars = { - "plt": plt, - "sns": sns, - "pd": pd, - "np": __import__('numpy') - } - - # Add datasets to local variables - for name, df in datasets.items(): - # Create a sanitized variable name - varName = ''.join(c if c.isalnum() else '_' for c in name) - localVars[varName] = df + if not vizRecommendations: + # Generate visualization recommendations if none provided + self.mydom.logAdd(analysisPlan.get("workflowId"), "Generating visualization recommendations...", level="info", progress=50) + vizPrompt = f""" + Based on this data and task, recommend appropriate visualizations. - # Also add with standard names for simpler code - if "df" not in localVars: - localVars["df"] = df - elif "df2" not in localVars: - localVars["df2"] = df + TASK: {prompt} + DESCRIPTION: {description} + + DATASETS: + {json.dumps({name: {"shape": df.shape, "columns": df.columns.tolist()} + for name, df in datasets.items()}, indent=2)} + + Recommend visualizations in JSON format: + {{ + "visualizations": [ + {{ + "type": "chart_type", + "dataSource": "dataset_name", + "variables": ["col1", "col2"], + "purpose": "explanation" + }} + ] + }} + """ + + response = await self.mydom.callAi([ + {"role": "system", "content": "You are a data visualization expert. Recommend appropriate visualizations based on the data and task."}, + {"role": "user", "content": vizPrompt} + ]) + + # Extract JSON + jsonStart = response.find('{') + jsonEnd = response.rfind('}') + 1 + + if jsonStart >= 0 and jsonEnd > jsonStart: + vizData = json.loads(response[jsonStart:jsonEnd]) + vizRecommendations = vizData.get("visualizations", []) - # Execute the visualization code - exec(vizCode, globals(), localVars) + # Determine format from filename + formatType = outputLabel.split('.')[-1].lower() + if formatType not in ['png', 'jpg', 'jpeg', 'svg']: + formatType = 'png' - # Capture the image - imgData = self._getImageBase64(formatType) - plt.close() + # If no datasets available, create error message image + if not datasets: + plt.figure(figsize=(10, 6)) + plt.text(0.5, 0.5, "No data available for visualization", + ha='center', va='center', fontsize=14) + plt.tight_layout() + imgData = self._getImageBase64(formatType) + plt.close() + + return { + "label": outputLabel, + "content": imgData, + "metadata": { + "contentType": f"image/{formatType}" + } + } - return self.formatAgentDocumentOutput(outputLabel, imgData, f"image/{formatType}") + # Prepare dataset info for the first dataset if none specified + if not vizRecommendations and datasets: + name, df = next(iter(datasets.items())) + vizRecommendations = [{ + "type": "auto", + "dataSource": name, + "variables": df.columns.tolist()[:5], + "purpose": "general analysis" + }] + + # Create visualization code prompt + vizPrompt = f""" + Generate Python matplotlib/seaborn code to create a visualization for: + + TASK: {prompt} + + VISUALIZATION REQUIREMENTS: + - Output format: {formatType} + - Filename: {outputLabel} + - Description: {description} + + RECOMMENDED VISUALIZATION: + {json.dumps(vizRecommendations, indent=2)} + + AVAILABLE DATASETS: + """ + + # Add dataset info for recommended sources + for viz in vizRecommendations: + dataSource = viz.get("dataSource") + if dataSource in datasets: + df = datasets[dataSource] + vizPrompt += f"\nDataset '{dataSource}':\n" + vizPrompt += f"- Shape: {df.shape}\n" + vizPrompt += f"- Columns: {df.columns.tolist()}\n" + vizPrompt += f"- Sample data: {df.head(3).to_dict(orient='records')}\n" + + vizPrompt += """ + Generate ONLY Python code that: + 1. Uses matplotlib and/or seaborn to create a clear visualization + 2. Sets figure size to (10, 6) + 3. Includes appropriate titles, labels, and legend + 4. Uses professional color schemes + 5. Handles any missing data gracefully + + Return ONLY executable Python code, no explanations or markdown. + """ + + try: + # Get visualization code from AI + vizCode = await self.mydom.callAi([ + {"role": "system", "content": "You are a data visualization expert. Provide only executable Python code."}, + {"role": "user", "content": vizPrompt} + ], produceUserAnswer = True) + + # Clean code + vizCode = vizCode.replace("```python", "").replace("```", "").strip() + + # Execute visualization code + plt.figure(figsize=(10, 6)) + + # Make local variables available to the code + localVars = { + "plt": plt, + "sns": sns, + "pd": pd, + "np": __import__('numpy') + } + + # Add datasets to local variables + for name, df in datasets.items(): + # Create a sanitized variable name + varName = ''.join(c if c.isalnum() else '_' for c in name) + localVars[varName] = df + + # Also add with standard names for simpler code + if "df" not in localVars: + localVars["df"] = df + elif "df2" not in localVars: + localVars["df2"] = df + + # Execute the visualization code + exec(vizCode, globals(), localVars) + + # Capture the image + imgData = self._getImageBase64(formatType) + plt.close() + + return self.formatAgentDocumentOutput(outputLabel, imgData, f"image/{formatType}") + + except Exception as e: + logger.error(f"Error creating visualization: {str(e)}", exc_info=True) + + # Create error message image + plt.figure(figsize=(10, 6)) + plt.text(0.5, 0.5, f"Visualization error: {str(e)}", + ha='center', va='center', fontsize=12) + plt.tight_layout() + imgData = self._getImageBase64(formatType) + plt.close() + + return self.formatAgentDocumentOutput(outputLabel, imgData, f"image/{formatType}") except Exception as e: logger.error(f"Error creating visualization: {str(e)}", exc_info=True) diff --git a/modules/agentEmail.py b/modules/agentEmail.py index 0a6482ab..6686b725 100644 --- a/modules/agentEmail.py +++ b/modules/agentEmail.py @@ -81,6 +81,7 @@ class AgentEmail(AgentBase): # Extract task information prompt = task.get("prompt", "") inputDocuments = task.get("inputDocuments", []) + outputSpecs = task.get("outputSpecifications", []) # Check AI service if not self.mydom: @@ -128,22 +129,36 @@ class AgentEmail(AgentBase): # Prepare output documents documents = [] - # Add HTML preview document - previewDoc = self.formatAgentDocumentOutput( - "email_preview.html", - htmlPreview, - "text/html" - ) - documents.append(previewDoc) - - # Add email template as JSON for reference - templateJson = json.dumps(emailTemplate, indent=2) - templateDoc = self.formatAgentDocumentOutput( - "email_template.json", - templateJson, - "application/json" - ) - documents.append(templateDoc) + # Process output specifications + for spec in outputSpecs: + label = spec.get("label", "") + description = spec.get("description", "") + + if label.endswith(".html"): + # Create the HTML template file + templateDoc = self.formatAgentDocumentOutput( + label, + emailTemplate["htmlBody"], # Use the actual HTML body, not the preview + "text/html" + ) + documents.append(templateDoc) + elif label.endswith(".json"): + # Create JSON template if requested + templateJson = json.dumps(emailTemplate, indent=2) + templateDoc = self.formatAgentDocumentOutput( + label, + templateJson, + "application/json" + ) + documents.append(templateDoc) + else: + # Default to preview for other cases + previewDoc = self.formatAgentDocumentOutput( + label, + htmlPreview, + "text/html" + ) + documents.append(previewDoc) # Prepare feedback message if draft_result: @@ -230,18 +245,19 @@ class AgentEmail(AgentBase): # Add document name to contents documentContents.append(f"\n\n--- {docName} ---\n") - # Check if document has data to attach + # Process document data directly if doc.get("data"): - # Add to attachments + # Add to attachments with proper metadata attachments.append({ "name": docName, - "document": doc + "document": { + "data": doc["data"], + "mimeType": doc.get("mimeType", "application/octet-stream"), + "base64Encoded": doc.get("base64Encoded", False) + } }) - - # Add document name to contents documentContents.append(f"Document attached: {docName}") else: - # If no data, just add the name documentContents.append(f"Document referenced: {docName}") return "\n".join(documentContents), attachments @@ -282,7 +298,7 @@ class AgentEmail(AgentBase): try: response = await self.mydom.callAi([ - {"role": "system", "content": "You are an email template specialist. Respond with valid JSON only."}, + {"role": "system", "content": "You are an email template specialist. Create professional emails. Respond with valid JSON only."}, {"role": "user", "content": emailPrompt} ], produceUserAnswer=True) @@ -294,7 +310,8 @@ class AgentEmail(AgentBase): template = json.loads(response[jsonStart:jsonEnd]) return template else: - # Fallback if JSON not found + # Fallback plan + logger.warning(f"Not able creating email template, generating fallback plan") return { "recipient": "recipient@example.com", "subject": "Information Regarding Your Request", @@ -471,8 +488,8 @@ class AgentEmail(AgentBase): def _createGraphDraftEmail(self, access_token, recipient, subject, body, attachments=None): """ - Create a draft email using Microsoft Graph API with fixed attachment handling. - Uses the document data directly for attachments. + Create a draft email using Microsoft Graph API. + Treats all files as binary attachments without content analysis. Args: access_token: Microsoft Graph access token @@ -521,87 +538,69 @@ class AgentEmail(AgentBase): logger.warning(f"No data found for attachment: {file_name}") continue - # Get content type and base64 flag - content_type = doc.get('contentType', 'application/octet-stream') + # Get content type from document metadata + mime_type = doc.get('mimeType', 'application/octet-stream') is_base64 = doc.get('base64Encoded', False) - # Handle base64 encoding if needed - if not is_base64: - logger.info(f"Base64 encoding content for {file_name}") - if isinstance(file_content, str): - try: - # Check if already valid base64 - base64.b64decode(file_content) - logger.info("Content appears to be valid base64 already") - except: - # Not valid base64, encode it - logger.info("Encoding string content to base64") - file_content = base64.b64encode(file_content.encode('utf-8')).decode('utf-8') - elif isinstance(file_content, bytes): - logger.info("Encoding bytes content to base64") - file_content = base64.b64encode(file_content).decode('utf-8') - - # Calculate actual size from base64 content + # Handle content encoding try: - decoded_size = len(base64.b64decode(file_content)) + if is_base64: + # Content is already base64 encoded + content_bytes = file_content + else: + # Content needs to be base64 encoded + if isinstance(file_content, str): + # For text files, encode the string to bytes first + content_bytes = base64.b64encode(file_content.encode('utf-8')).decode('utf-8') + elif isinstance(file_content, bytes): + # For binary files, encode directly + content_bytes = base64.b64encode(file_content).decode('utf-8') + else: + logger.warning(f"Unexpected content type for {file_name}") + continue + + # Calculate size from decoded content + decoded_size = len(base64.b64decode(content_bytes)) + + # Add attachment to email data + logger.info(f"Adding attachment: {file_name} ({mime_type}, size: {decoded_size} bytes)") + attachment_data = { + '@odata.type': '#microsoft.graph.fileAttachment', + 'name': file_name, + 'contentType': mime_type, + 'contentBytes': content_bytes, + 'isInline': False, + 'size': decoded_size + } + email_data['attachments'].append(attachment_data) + logger.info(f"Successfully added attachment: {file_name}") + except Exception as e: - logger.error(f"Error calculating size for {file_name}: {str(e)}") - decoded_size = 0 - - # Add attachment to email data - logger.info(f"Adding attachment: {file_name} ({content_type}, size: {decoded_size} bytes)") - attachment_data = { - '@odata.type': '#microsoft.graph.fileAttachment', - 'name': file_name, - 'contentType': content_type, - 'contentBytes': file_content, - 'isInline': False, - 'size': decoded_size - } - email_data['attachments'].append(attachment_data) - logger.info(f"Successfully added attachment: {file_name}") + logger.error(f"Error processing attachment {file_name}: {str(e)}") + continue # Try to create draft using drafts folder endpoint try: - logger.info("Attempting to create draft email using drafts folder endpoint") + logger.info("Attempting to create draft email using messages endpoint") logger.info(f"Email data structure: subject={subject}, recipient={recipient}, " + f"has_attachments={bool(email_data.get('attachments'))}, " + f"attachment_count={len(email_data.get('attachments', []))}") - # Log the full email data structure for debugging - logger.debug(f"Full email data structure: {json.dumps(email_data, indent=2)}") - - # First create the draft message + # Create the draft message response = requests.post( - 'https://graph.microsoft.com/v1.0/me/mailFolders/drafts/messages', + 'https://graph.microsoft.com/v1.0/me/messages', headers=headers, json=email_data ) if response.status_code >= 200 and response.status_code < 300: - logger.info("Successfully created draft email using drafts folder endpoint") + logger.info("Successfully created draft email using messages endpoint") return response.json() else: - logger.error(f"Drafts folder method failed: {response.status_code} - {response.text}") + logger.error(f"Messages endpoint method failed: {response.status_code} - {response.text}") logger.error(f"Request headers: {headers}") logger.error(f"Request body: {json.dumps(email_data, indent=2)}") - - # Try fallback method with messages endpoint - logger.info("Trying fallback with messages endpoint") - response = requests.post( - 'https://graph.microsoft.com/v1.0/me/messages', - headers=headers, - json=email_data - ) - - if response.status_code >= 200 and response.status_code < 300: - logger.info("Successfully created draft email using messages endpoint") - return response.json() - else: - logger.error(f"Messages endpoint method also failed: {response.status_code} - {response.text}") - logger.error(f"Request headers: {headers}") - logger.error(f"Request body: {json.dumps(email_data, indent=2)}") - return None + return None except Exception as e: logger.error(f"Exception creating draft email: {str(e)}", exc_info=True) diff --git a/modules/agentWebcrawler.py b/modules/agentWebcrawler.py index 7f5cad09..7dd87825 100644 --- a/modules/agentWebcrawler.py +++ b/modules/agentWebcrawler.py @@ -77,6 +77,7 @@ class AgentWebcrawler(AgentBase): } # Create research plan + self.mydom.logAdd(task["workflowId"], "Creating research plan...", level="info", progress=35) researchPlan = await self._createResearchPlan(prompt) # Check if this is truly a web research task @@ -87,9 +88,11 @@ class AgentWebcrawler(AgentBase): } # Gather raw material through web research + self.mydom.logAdd(task["workflowId"], "Gathering research material...", level="info", progress=45) rawResults = await self._gatherResearchMaterial(researchPlan) # Format results into requested output documents + self.mydom.logAdd(task["workflowId"], "Creating output documents...", level="info", progress=55) documents = await self._createOutputDocuments( prompt, rawResults, @@ -142,9 +145,9 @@ class AgentWebcrawler(AgentBase): try: # Get research plan from AI response = await self.mydom.callAi([ - {"role": "system", "content": "You are a web research planning expert. Create precise research plans in JSON format only."}, + {"role": "system", "content": "You are a web research planning expert. Create precise research plans. Respond with valid JSON only."}, {"role": "user", "content": researchPrompt} - ]) + ], produceUserAnswer=True) # Extract JSON jsonStart = response.find('{') @@ -202,7 +205,9 @@ class AgentWebcrawler(AgentBase): # Process direct URLs directUrls = researchPlan.get("directUrls", [])[:self.maxUrl] - for url in directUrls: + for i, url in enumerate(directUrls): + progress = 45 + int((i / len(directUrls)) * 5) # Progress from 45% to 50% + self.mydom.logAdd(researchPlan.get("workflowId"), f"Processing direct URL {i+1}/{len(directUrls)}...", level="info", progress=progress) logger.info(f"Processing direct URL: {url}") try: # Fetch and extract content @@ -226,7 +231,9 @@ class AgentWebcrawler(AgentBase): # Process search terms searchTerms = researchPlan.get("searchTerms", [])[:self.maxSearchTerms] - for term in searchTerms: + for i, term in enumerate(searchTerms): + progress = 50 + int((i / len(searchTerms)) * 5) # Progress from 50% to 55% + self.mydom.logAdd(researchPlan.get("workflowId"), f"Searching term {i+1}/{len(searchTerms)}...", level="info", progress=progress) logger.info(f"Searching for: {term}") try: # Perform search @@ -302,19 +309,15 @@ class AgentWebcrawler(AgentBase): Only include information actually found in the content. No fabrications or assumptions. """ - if self.mydom: - summary = await self.mydom.callAi([ - {"role": "system", "content": "You summarize web content accurately and concisely, focusing only on what is actually in the content."}, - {"role": "user", "content": summaryPrompt} - ]) - - # Store the summary - result["summary"] = summary - else: - # Fallback if no AI service - logger.warning(f"Not able to summarize result, using fallback plan.") - result["summary"] = f"Content from {result['url']} ({len(content)} characters)" - + # Get summary from AI + summary = await self.mydom.callAi([ + {"role": "system", "content": "You are a web content summarization expert. Create concise summaries."}, + {"role": "user", "content": summaryPrompt} + ], produceUserAnswer=True) + + # Add summary to result + result["summary"] = summary.strip() + except Exception as e: logger.warning(f"Error summarizing result {i+1}: {str(e)}") result["summary"] = f"Error creating summary: {str(e)}" diff --git a/modules/documentProcessor.py b/modules/documentProcessor.py index d3b637e1..3e082578 100644 --- a/modules/documentProcessor.py +++ b/modules/documentProcessor.py @@ -38,8 +38,50 @@ def getDocumentContents(fileMetadata: Dict[str, Any], fileContent: bytes) -> Lis # Extract content based on MIME type contents = [] + # Try to detect actual file type from content for unknown MIME types + if mimeType == "application/octet-stream": + # Check file extension first + ext = os.path.splitext(fileName)[1].lower() + if ext: + # Map common extensions to MIME types + ext_to_mime = { + '.txt': 'text/plain', + '.md': 'text/markdown', + '.csv': 'text/csv', + '.json': 'application/json', + '.xml': 'application/xml', + '.js': 'application/javascript', + '.py': 'application/x-python', + '.svg': 'image/svg+xml', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.pdf': 'application/pdf', + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.doc': 'application/msword', + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.xls': 'application/vnd.ms-excel', + '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.ppt': 'application/vnd.ms-powerpoint' + } + if ext in ext_to_mime: + mimeType = ext_to_mime[ext] + logger.info(f"Detected MIME type {mimeType} from extension {ext}") + else: + logger.warning(f"Unknown file extension {ext} for file {fileName}") + + # Try to detect if it's text content + try: + text_content = fileContent.decode('utf-8') + logger.info(f"Successfully decoded file {fileName} as text") + contents.extend(extractTextContent(fileName, fileContent, "text/plain")) + except UnicodeDecodeError: + logger.info(f"File {fileName} is not text, treating as binary") + contents.extend(extractBinaryContent(fileName, fileContent, mimeType)) + # Text-based formats (excluding CSV which has its own handler) - if mimeType == "text/csv": + elif mimeType == "text/csv": contents.extend(extractCsvContent(fileName, fileContent)) # Then handle other text-based formats @@ -86,6 +128,7 @@ def getDocumentContents(fileMetadata: Dict[str, Any], fileContent: bytes) -> Lis # Binary data as fallback for unknown formats else: + logger.warning(f"Unknown MIME type {mimeType} for file {fileName}, treating as binary") contents.extend(extractBinaryContent(fileName, fileContent, mimeType)) # Fallback when no content could be extracted @@ -99,7 +142,7 @@ def getDocumentContents(fileMetadata: Dict[str, Any], fileContent: bytes) -> Lis "sequenceNr": 1, "name": '1_undefined', "ext": os.path.splitext(fileName)[1][1:] if os.path.splitext(fileName)[1] else "bin", - "contentType": mimeType, + "mimeType": mimeType, "data": encoded_data, "base64Encoded": True, "metadata": { @@ -130,13 +173,13 @@ def getDocumentContents(fileMetadata: Dict[str, Any], fileContent: bytes) -> Lis return contents except Exception as e: - logger.error(f"Error during content extraction: {str(e)}") + logger.error(f"Error during content extraction for file {fileMetadata.get('name', 'unknown')}: {str(e)}", exc_info=True) # Fallback on error - return original data return [{ "sequenceNr": 1, "name": fileMetadata.get("name", "unknown"), "ext": os.path.splitext(fileMetadata.get("name", ""))[1][1:] if os.path.splitext(fileMetadata.get("name", ""))[1] else "bin", - "contentType": fileMetadata.get("mimeType", "application/octet-stream"), + "mimeType": fileMetadata.get("mimeType", "application/octet-stream"), "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -206,7 +249,7 @@ def extractTextContent(fileName: str, fileContent: bytes, mimeType: str) -> List "sequenceNr": 1, "name": "1_text", # Simplified naming "ext": fileExtension, - "contentType": "text/plain", + "mimeType": "text/plain", "data": textContent, "base64Encoded": False, "metadata": { @@ -225,7 +268,7 @@ def extractTextContent(fileName: str, fileContent: bytes, mimeType: str) -> List "sequenceNr": 1, "name": "1_text", # Simplified naming "ext": fileExtension, - "contentType": "text/plain", + "mimeType": "text/plain", "data": textContent, "base64Encoded": False, "metadata": { @@ -242,7 +285,7 @@ def extractTextContent(fileName: str, fileContent: bytes, mimeType: str) -> List "sequenceNr": 1, "name": "1_binary", # Simplified naming "ext": fileExtension, - "contentType": mimeType, + "mimeType": mimeType, "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -256,7 +299,7 @@ def extractTextContent(fileName: str, fileContent: bytes, mimeType: str) -> List "sequenceNr": 1, "name": "1_binary", # Simplified naming "ext": fileExtension, - "contentType": mimeType, + "mimeType": mimeType, "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -282,7 +325,7 @@ def extractCsvContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_csv", # Simplified naming "ext": "csv", - "contentType": "text/csv", + "mimeType": "text/csv", "data": csvContent, "base64Encoded": False, "metadata": { @@ -302,7 +345,7 @@ def extractCsvContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_csv", # Simplified naming "ext": "csv", - "contentType": "text/csv", + "mimeType": "text/csv", "data": csvContent, "base64Encoded": False, "metadata": { @@ -319,7 +362,7 @@ def extractCsvContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_binary", # Simplified naming "ext": "csv", - "contentType": "text/csv", + "mimeType": "text/csv", "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -332,7 +375,7 @@ def extractCsvContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_binary", # Simplified naming "ext": "csv", - "contentType": "text/csv", + "mimeType": "text/csv", "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -364,7 +407,7 @@ def extractSvgContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_svg", # Simplified naming "ext": "svg", - "contentType": "image/svg+xml", + "mimeType": "image/svg+xml", "data": svgText, "base64Encoded": False, "metadata": { @@ -380,7 +423,7 @@ def extractSvgContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_text", "ext": "svg", - "contentType": "text/plain", + "mimeType": "text/plain", "data": svgText, "base64Encoded": False, "metadata": { @@ -401,7 +444,7 @@ def extractSvgContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_svg", # Simplified naming "ext": "svg", - "contentType": "image/svg+xml", + "mimeType": "image/svg+xml", "data": svgText, "base64Encoded": False, "metadata": { @@ -422,7 +465,7 @@ def extractSvgContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_binary", # Simplified naming "ext": "svg", - "contentType": "image/svg+xml", + "mimeType": "image/svg+xml", "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -438,7 +481,7 @@ def extractSvgContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_binary", # Simplified naming "ext": "svg", - "contentType": "image/svg+xml", + "mimeType": "image/svg+xml", "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -519,7 +562,7 @@ def extractImageContent(fileName: str, fileContent: bytes, mimeType: str) -> Lis "sequenceNr": 1, "name": "1_image", # Simplified naming "ext": fileExtension, - "contentType": mimeType, + "mimeType": mimeType, "data": encoded_data, "base64Encoded": True, "metadata": imageMetadata @@ -531,7 +574,7 @@ def extractImageContent(fileName: str, fileContent: bytes, mimeType: str) -> Lis "sequenceNr": 2, "name": "2_text_image_info", # Simplified naming with label "ext": "txt", - "contentType": "text/plain", + "mimeType": "text/plain", "data": imageDescription, "base64Encoded": False, "metadata": { @@ -566,7 +609,7 @@ def extractPdfContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_pdf", # Simplified naming "ext": "pdf", - "contentType": "application/pdf", + "mimeType": "application/pdf", "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -604,7 +647,7 @@ def extractPdfContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": len(contents) + 1, "name": f"{len(contents) + 1}_text", # Simplified naming "ext": "txt", - "contentType": "text/plain", + "mimeType": "text/plain", "data": extractedText, "base64Encoded": False, "metadata": { @@ -639,7 +682,7 @@ def extractPdfContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": len(contents) + 1, "name": f"{len(contents) + 1}_image_page{pageNum+1}_{imgIndex+1}", # Simplified naming with label "ext": imageExt, - "contentType": f"image/{imageExt}", + "mimeType": f"image/{imageExt}", "data": base64.b64encode(imageBytes).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -667,7 +710,7 @@ def extractPdfContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]] "sequenceNr": 1, "name": "1_pdf", # Simplified naming "ext": "pdf", - "contentType": "application/pdf", + "mimeType": "application/pdf", "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -706,7 +749,7 @@ def extractWordContent(fileName: str, fileContent: bytes, mimeType: str) -> List "sequenceNr": 1, "name": "1_word", # Simplified naming "ext": fileExtension, - "contentType": mimeType, + "mimeType": mimeType, "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -743,7 +786,7 @@ def extractWordContent(fileName: str, fileContent: bytes, mimeType: str) -> List "sequenceNr": 1, "name": "1_text", # Simplified naming "ext": "txt", - "contentType": "text/plain", + "mimeType": "text/plain", "data": extractedText, "base64Encoded": False, "metadata": { @@ -765,7 +808,7 @@ def extractWordContent(fileName: str, fileContent: bytes, mimeType: str) -> List "sequenceNr": 1, "name": "1_word", # Simplified naming "ext": fileExtension, - "contentType": mimeType, + "mimeType": mimeType, "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -804,7 +847,7 @@ def extractExcelContent(fileName: str, fileContent: bytes, mimeType: str) -> Lis "sequenceNr": 1, "name": "1_excel", # Simplified naming "ext": fileExtension, - "contentType": mimeType, + "mimeType": mimeType, "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -845,7 +888,7 @@ def extractExcelContent(fileName: str, fileContent: bytes, mimeType: str) -> Lis "sequenceNr": len(contents) + 1, "name": f"{len(contents) + 1}_csv_{sheetSafeName}", # Simplified naming with sheet label "ext": "csv", - "contentType": "text/csv", + "mimeType": "text/csv", "data": csvContent, "base64Encoded": False, "metadata": { @@ -867,7 +910,7 @@ def extractExcelContent(fileName: str, fileContent: bytes, mimeType: str) -> Lis "sequenceNr": 1, "name": "1_excel", # Simplified naming "ext": fileExtension, - "contentType": mimeType, + "mimeType": mimeType, "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -897,7 +940,7 @@ def extractPowerpointContent(fileName: str, fileContent: bytes, mimeType: str) - "sequenceNr": 1, "name": "1_powerpoint", # Simplified naming "ext": fileExtension, - "contentType": mimeType, + "mimeType": mimeType, "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { @@ -923,11 +966,165 @@ def extractBinaryContent(fileName: str, fileContent: bytes, mimeType: str) -> Li "sequenceNr": 1, "name": "1_binary", # Simplified naming "ext": fileExtension, - "contentType": mimeType, + "mimeType": mimeType, "data": base64.b64encode(fileContent).decode('utf-8'), "base64Encoded": True, "metadata": { "isText": False, "format": "binary" } - }] \ No newline at end of file + }] + +def processFile(self, fileContent: bytes, fileName: str, fileMetadata: Dict[str, Any] = None) -> List[Dict[str, Any]]: + """ + Process a file and return its contents as a list of documents. + + Args: + fileContent: Binary content of the file + fileName: Name of the file + fileMetadata: Optional metadata about the file + + Returns: + List of document dictionaries + """ + try: + # Get file extension and MIME type + fileExtension = os.path.splitext(fileName)[1].lower()[1:] + mimeType = fileMetadata.get("mimeType", self.mydom.getMimeType(fileName)) if fileMetadata else self.mydom.getMimeType(fileName) + + # Process based on file type + if mimeType.startswith("image/"): + return self._processImageFile(fileContent, fileName, fileExtension, mimeType, fileMetadata) + elif mimeType == "application/pdf": + return self._processPdfFile(fileContent, fileName, fileMetadata) + elif mimeType == "text/csv": + return self._processCsvFile(fileContent, fileName, fileMetadata) + elif mimeType == "text/plain": + return self._processTextFile(fileContent, fileName, fileMetadata) + else: + # Default binary file handling + return [{ + "name": fileName, + "ext": fileExtension, + "mimeType": mimeType, + "data": base64.b64encode(fileContent).decode('utf-8'), + "base64Encoded": True, + "metadata": { + "isText": False + } + }] + + except Exception as e: + logger.error(f"Error processing file {fileName}: {str(e)}") + raise FileProcessingError(f"Error processing file: {str(e)}") + + def _processImageFile(self, fileContent: bytes, fileName: str, fileExtension: str, mimeType: str, fileMetadata: Dict[str, Any] = None) -> List[Dict[str, Any]]: + """Process an image file.""" + try: + # Create image document + imageDoc = { + "name": fileName, + "ext": fileExtension, + "mimeType": mimeType, + "data": base64.b64encode(fileContent).decode('utf-8'), + "base64Encoded": True, + "metadata": { + "isText": False, + "isImage": True, + "format": fileExtension + } + } + + # Add image description if available + if fileMetadata and "description" in fileMetadata: + imageDoc["metadata"]["description"] = fileMetadata["description"] + + return [imageDoc] + + except Exception as e: + logger.error(f"Error processing image file {fileName}: {str(e)}") + raise FileProcessingError(f"Error processing image file: {str(e)}") + + def _processPdfFile(self, fileContent: bytes, fileName: str, fileMetadata: Dict[str, Any] = None) -> List[Dict[str, Any]]: + """Process a PDF file.""" + try: + # Create PDF document + pdfDoc = { + "name": fileName, + "ext": "pdf", + "mimeType": "application/pdf", + "data": base64.b64encode(fileContent).decode('utf-8'), + "base64Encoded": True, + "metadata": { + "isText": False, + "isPdf": True + } + } + + return [pdfDoc] + + except Exception as e: + logger.error(f"Error processing PDF file {fileName}: {str(e)}") + raise FileProcessingError(f"Error processing PDF file: {str(e)}") + + def _processCsvFile(self, fileContent: bytes, fileName: str, fileMetadata: Dict[str, Any] = None) -> List[Dict[str, Any]]: + """Process a CSV file.""" + try: + # Try to decode as text first + try: + csvContent = fileContent.decode('utf-8') + base64Encoded = False + except UnicodeDecodeError: + # If not valid UTF-8, encode as base64 + csvContent = base64.b64encode(fileContent).decode('utf-8') + base64Encoded = True + + # Create CSV document + csvDoc = { + "name": fileName, + "ext": "csv", + "mimeType": "text/csv", + "data": csvContent, + "base64Encoded": base64Encoded, + "metadata": { + "isText": True, + "isCsv": True, + "format": "csv" + } + } + + return [csvDoc] + + except Exception as e: + logger.error(f"Error processing CSV file {fileName}: {str(e)}") + raise FileProcessingError(f"Error processing CSV file: {str(e)}") + + def _processTextFile(self, fileContent: bytes, fileName: str, fileMetadata: Dict[str, Any] = None) -> List[Dict[str, Any]]: + """Process a text file.""" + try: + # Try to decode as text + try: + textContent = fileContent.decode('utf-8') + base64Encoded = False + except UnicodeDecodeError: + # If not valid UTF-8, encode as base64 + textContent = base64.b64encode(fileContent).decode('utf-8') + base64Encoded = True + + # Create text document + textDoc = { + "name": fileName, + "ext": "txt", + "mimeType": "text/plain", + "data": textContent, + "base64Encoded": base64Encoded, + "metadata": { + "isText": True + } + } + + return [textDoc] + + except Exception as e: + logger.error(f"Error processing text file {fileName}: {str(e)}") + raise FileProcessingError(f"Error processing text file: {str(e)}") \ No newline at end of file diff --git a/modules/lucydomInterface.py b/modules/lucydomInterface.py index a2106d6d..b09629d5 100644 --- a/modules/lucydomInterface.py +++ b/modules/lucydomInterface.py @@ -358,11 +358,14 @@ class LucyDOMInterface: return hashlib.sha256(fileContent).hexdigest() def checkForDuplicateFile(self, fileHash: str) -> Optional[Dict[str, Any]]: - """Checks if a file with the same hash already exists.""" - files = self.db.getRecordset("files", recordFilter={"fileHash": fileHash}) - filteredFiles = self._uam("files", files) - if filteredFiles: - return filteredFiles[0] + """Checks if a file with the same hash already exists for the current user and mandate.""" + files = self.db.getRecordset("files", recordFilter={ + "fileHash": fileHash, + "mandateId": self.mandateId, + "userId": self.userId + }) + if files: + return files[0] return None def getMimeType(self, filename: str) -> str: @@ -670,7 +673,7 @@ class LucyDOMInterface: fileHash = self.calculateFileHash(fileContent) logger.debug(f"Calculated file hash: {fileHash}") - # Check for duplicate + # Check for duplicate within same user/mandate existingFile = self.checkForDuplicateFile(fileHash) if existingFile: logger.info(f"Duplicate found for {fileName}: {existingFile['id']}") @@ -692,9 +695,6 @@ class LucyDOMInterface: # Save binary data logger.info(f"Saving file content to database for file: {fileName}") self.createFileData(dbFile["id"], fileContent) - - # Debug: Export file to static folder - self._exportFileToStatic(fileContent, dbFile["id"], fileName) logger.info(f"File upload process completed for: {fileName}") return dbFile @@ -731,12 +731,6 @@ class LucyDOMInterface: logger.error(f"Error downloading file {fileId}: {str(e)}") raise FileError(f"Error downloading file: {str(e)}") - def _exportFileToStatic(self, fileContent: bytes, fileId: int, fileName: str): - """Debug helper to export files to static folder.""" - debugFilename = f"{fileId}_{fileName}" - with open(f"./static/{debugFilename}", 'wb') as f: - f.write(fileContent) - # Workflow methods def getAllWorkflows(self) -> List[Dict[str, Any]]: diff --git a/modules/lucydomModel.py b/modules/lucydomModel.py index c14ab9f3..782d0b0e 100644 --- a/modules/lucydomModel.py +++ b/modules/lucydomModel.py @@ -110,7 +110,7 @@ class DocumentContent(BaseModel): sequenceNr: int = Field(1, description="Sequence number of the content in the source document") name: str = Field(description="Designation") ext: str = Field(description="Content extension for export: txt, csv, json, jpg, png") - contentType: str = Field(description="MIME type") + mimeType: str = Field(description="MIME type") summary: str = Field(description="Summary of the file content") data: str = Field(description="Actual content, text or base64 encoded based on base64Encoded flag") base64Encoded: bool = Field(description="Flag indicating whether the data is base64 encoded") @@ -122,6 +122,7 @@ class Document(BaseModel): name: str = Field(description="Name of the data object") ext: str = Field(description="Extension of the data object") fileId: int = Field(description="ID of the referenced file in the database") + mimeType: str = Field(description="MIME type") data: str = Field(description="Content of the data as text or base64 encoded based on base64Encoded flag") base64Encoded: bool = Field(description="Flag indicating whether the data is base64 encoded") contents: List[DocumentContent] = Field(description="Document contents") diff --git a/modules/workflowAgentsRegistry.py b/modules/workflowAgentsRegistry.py index 25d8d2ff..0847442b 100644 --- a/modules/workflowAgentsRegistry.py +++ b/modules/workflowAgentsRegistry.py @@ -85,51 +85,45 @@ class AgentBase: """Wrapper for the utility function""" return isTextMimeType(mimeType) - def formatAgentDocumentOutput(self, label: str, content: Any, contentType: str = None) -> Dict[str, Any]: + def formatAgentDocumentOutput(self, label: str, content: Any, mimeType: str = None) -> Dict[str, Any]: """ - Helper method to properly format a document output with base64Encoded flag and metadata. + Format agent output as a document. Args: - label: Name of the document + label: Label for the document content: Content of the document - contentType: Optional content type for the document - - Returns: - Properly formatted document dictionary + mimeType: Optional MIME type for the document """ - import base64 - - # Determine if content should be base64 encoded - should_base64_encode = self.determineBase64EncodingFlag(label, content) - - # Process content based on type and encoding flag - formatted_content = content - - if should_base64_encode: - if isinstance(content, bytes): - # Convert binary to base64 - formatted_content = base64.b64encode(content).decode('utf-8') - elif isinstance(content, str): - try: - # Check if it's already base64 encoded - base64.b64decode(content) - # If we get here, it appears to be valid base64 - formatted_content = content - except: - # Not valid base64, so encode it - formatted_content = base64.b64encode(content.encode('utf-8')).decode('utf-8') - - # Create document with metadata + # Create document structure doc = { - "label": label, - "content": formatted_content, - "base64Encoded": should_base64_encode, - "metadata": {} + "id": str(uuid.uuid4()), + "name": label, + "ext": "txt", # Default extension + "data": content, + "base64Encoded": False, + "metadata": { + "isText": True + } } - # Add content type if provided - if contentType: - doc["metadata"]["contentType"] = contentType + # Set MIME type if provided + if mimeType: + doc["mimeType"] = mimeType + # Update extension based on MIME type + if mimeType == "text/markdown": + doc["ext"] = "md" + elif mimeType == "text/html": + doc["ext"] = "html" + elif mimeType == "text/csv": + doc["ext"] = "csv" + elif mimeType == "application/json": + doc["ext"] = "json" + elif mimeType.startswith("image/"): + doc["ext"] = mimeType.split("/")[1] + doc["metadata"]["isText"] = False + elif mimeType == "application/pdf": + doc["ext"] = "pdf" + doc["metadata"]["isText"] = False return doc diff --git a/modules/workflowManager.py b/modules/workflowManager.py index 5d89d311..c4a6b520 100644 --- a/modules/workflowManager.py +++ b/modules/workflowManager.py @@ -10,7 +10,7 @@ import json import re import uuid import base64 -from datetime import datetime +from datetime import datetime, timedelta from typing import Dict, Any, List, Optional, Union, Tuple from modules.mimeUtils import isTextMimeType, determineContentEncoding @@ -382,6 +382,7 @@ Please analyze the request and create: 3. Do not define document inputs that don't exist or haven't been generated beforehand. 4. Create a logical sequence - earlier agents can create documents that are later used as inputs. 5. If the user has provided documents but hasn't clearly stated what they want, try to act according to the context. +6. ALL documents provided by the user (where fileSource is "user") MUST be included in the work plan, even if they don't have content summaries or if content extraction failed. Your answer must be strictly in the JSON_OUTPUT format, with no additions before or after the JSON object. @@ -415,6 +416,7 @@ JSON_OUTPUT = {{ ## RULES for inputDocuments: 1. The user request refers to documents where "fileSource" in available documents is "user". Those documents are in the focus for input 2. In case of redundant label in available documents, use document with highest sequenceNr if not specified differently +3. ALL documents provided by the user MUST be included in the work plan, even if they don't have content summaries or if content extraction failed ## STRICT RULES FOR document "label": 1. Every document label MUST include a proper file extension that matches the content type. @@ -789,8 +791,13 @@ filesDelivered = {self.parseJson2text(matchingDocuments)} "fileId": fileId, "name": os.path.splitext(fileNameExt)[0] if os.path.splitext(fileNameExt)[0] else "noname", "ext": os.path.splitext(fileNameExt)[1][1:] if os.path.splitext(fileNameExt)[1] else "bin", + "mimeType": mimeType, "data": encodedData, "base64Encoded": base64Encoded, + "metadata": { + "isText": isTextFormat, + "base64Encoded": base64Encoded # For backward compatibility + }, "contents": [] } @@ -799,7 +806,7 @@ filesDelivered = {self.parseJson2text(matchingDocuments)} # Add summaries to each content item for content in contents: - content["summary"] = await self.messageSummarizeContent(content) + content["summary"] = await self.getContentExtraction(content) # Ensure base64Encoded flag is set if "base64Encoded" not in content: @@ -861,92 +868,87 @@ filesDelivered = {self.parseJson2text(matchingDocuments)} return preparedInputs - - async def messageSummarizeContent(self, content: Dict[str, Any]) -> str: - return await self.getContentExtraction( - content, - "Create a very concise summary (1-2 sentences, maximum 200 characters) about this content." - ) - async def processDocumentForAgent(self, document: Dict[str, Any], docSpec: Dict[str, Any]) -> Dict[str, Any]: - """ - Processes a document for an agent based on the document specification. - Uses AI to extract relevant content from the document based on the specification. - - Args: - document: The document to process - docSpec: The document specification from the project manager + """ + Processes a document for an agent based on the document specification. + Uses AI to extract relevant content from the document based on the specification. - Returns: - Processed document with AI-extracted content - """ - processedDoc = document.copy() - partSpec = docSpec.get("contentPart", "") - - # Process each content item in the document - if "contents" in processedDoc: - processedContents = [] + Args: + document: The document to process + docSpec: The document specification from the project manager + + Returns: + Processed document with AI-extracted content + """ + processedDoc = document.copy() + partSpec = docSpec.get("contentPart", "") - for content in processedDoc["contents"]: - # Check if part required - if partSpec != "" and partSpec != content.get("name"): - continue + # Process each content item in the document + if "contents" in processedDoc: + processedContents = [] + + for content in processedDoc["contents"]: + # Check if part required + if partSpec != "" and partSpec != content.get("name"): + continue - # Get the prompt from the document specification - summary = docSpec.get("prompt", "Extract the relevant information from this document") + # Get the prompt from the document specification + summary = docSpec.get("prompt", "Extract the relevant information from this document") + + # Process content using the shared helper function + processedContent = content.copy() + processedContent["dataExtracted"] = await self.getContentExtraction(content, summary) + processedContent["metadata"]["aiProcessed"] = True + + processedContents.append(processedContent) - # Process content using the shared helper function - processedContent = content.copy() - processedContent["dataExtracted"] = await self.getContentExtraction(content, summary) - processedContent["metadata"]["aiProcessed"] = True - - processedContents.append(processedContent) + processedDoc["contents"] = processedContents - processedDoc["contents"] = processedContents - - return processedDoc + return processedDoc async def getContentExtraction(self, content: Dict[str, Any], prompt: str = None) -> str: """ - Helper function that extracts or summarizes content based on its type (text/image/binary). + Helper function that extracts or summarizes content based on its encoding. + For base64 encoded content, uses callAi4Image. For non-base64 content, uses callAi. Args: content: Content item to analyze - prompt: Optional custom prompt for extraction (default prompts used if not provided) + prompt: Custom prompt for extraction (default prompts used if not provided) Returns: Extracted or summarized content as text """ - # Extract relevant information - data = content.get("data", "") - contentType = content.get("contentType", "text/plain") - base64Encoded = content.get("base64Encoded", False) - - # Default prompts if none provided - if prompt is None: - text_prompt = "Create a very concise summary (1-2 sentences, maximum 200 characters) about this content." - image_prompt = "Create a very concise summary (1-2 sentences, maximum 200 characters) about this image." - else: - text_prompt = prompt - image_prompt = prompt - try: - # For image content, use the specialized image analysis - if base64Encoded: - return await self.mydom.callAi4Image(data, contentType, image_prompt) - - # For text data, use the regular AI processing - else: - return await self.mydom.callAi([ - {"role": "system", "content": "You are a content analyzer. Process the provided content as instructed."}, - {"role": "user", "content": f"{text_prompt}\n\n{data}"} - ]) + # Get content data and encoding status + data = content.get("data", "") + isBase64 = content.get("base64Encoded", False) + # Default prompts if none provided + if prompt is None: + textPrompt = "Create a very concise summary (1-2 sentences, maximum 200 characters) about this content." + imagePrompt = "Create a very concise summary (1-2 sentences, maximum 200 characters) about this image." + else: + textPrompt = prompt + imagePrompt = prompt + + # Handle base64 encoded content + if isBase64: + try: + # Pass base64 encoded data directly to callAi4Image + return await self.mydom.callAi4Image(data, content.get("mimeType", "application/octet-stream"), imagePrompt) + except Exception as e: + logger.error(f"Error processing base64 content: {str(e)}") + return f"Error processing content: {str(e)}" + else: + # For non-base64 content, use callAi + return await self.mydom.callAi([ + {"role": "system", "content": "You are a content analyzer. Extract relevant information from the provided content."}, + {"role": "user", "content": f"{textPrompt}\n\nContent:\n{data}"} + ], produceUserAnswer=True) + except Exception as e: logger.error(f"Error processing content: {str(e)}") - return f"Content of type {contentType} (processing failed)" - - + return f"Error processing content: {str(e)}" def messageAdd(self, workflow: Dict[str, Any], message: Dict[str, Any]) -> Dict[str, Any]: """ @@ -1086,56 +1088,69 @@ filesDelivered = {self.parseJson2text(matchingDocuments)} List of file IDs for the saved documents """ fileIds = [] + used_names = set() # Track used names to prevent duplicates # Extract documents from agent results documents = agentResults.get("documents", []) for doc in documents: try: - # Extract label (filename) and content - label = doc.get("label", "unnamed_file.txt") - content = doc.get("content", "") + # Extract document data according to LucyDOM model + name = doc.get("name", "") + ext = doc.get("ext", "") + data = doc.get("data", "") base64Encoded = doc.get("base64Encoded", False) - # Split label into name and extension - name, ext = os.path.splitext(label) - if ext.startswith('.'): - ext = ext[1:] # Remove leading dot - elif not ext: - # If no extension is provided, default to .txt for text content - ext = "txt" - label = f"{label}.{ext}" + # Skip if no name or data + if not name or not data: + logger.warning(f"Skipping document with missing name or data. Name: {name}, Has data: {bool(data)}") + continue + + # Ensure unique filename + base_name = name + counter = 1 + while f"{base_name}.{ext}" in used_names: + base_name = f"{name}_{counter}" + counter += 1 + used_names.add(f"{base_name}.{ext}") # Convert content to bytes based on base64Encoded flag - if isinstance(content, str): + if isinstance(data, str): if base64Encoded: # Decode base64 to bytes try: import base64 - fileContent = base64.b64decode(content) + fileContent = base64.b64decode(data) except Exception as e: logger.warning(f"Failed to decode base64 content: {str(e)}") - fileContent = content.encode('utf-8') + fileContent = data.encode('utf-8') base64Encoded = False else: # Convert text to bytes - fileContent = content.encode('utf-8') + fileContent = data.encode('utf-8') else: # Already bytes - fileContent = content + fileContent = data # Determine MIME type based on extension - mimeType = self.mydom.getMimeType(label) + mimeType = self.mydom.getMimeType(f"{base_name}.{ext}") - # Save file to database - fileMeta = self.mydom.saveUploadedFile(fileContent, label) + # Create file metadata + fileMeta = self.mydom.createFile( + name=base_name, + mimeType=mimeType, + size=len(fileContent) + ) if fileMeta and "id" in fileMeta: - fileId = fileMeta["id"] - fileIds.append(fileId) - logger.info(f"Saved document '{label}' with file ID: {fileId} (base64Encoded: {base64Encoded})") + # Save file content + if self.mydom.createFileData(fileMeta["id"], fileContent): + fileIds.append(fileMeta["id"]) + logger.info(f"Saved document '{base_name}.{ext}' with file ID: {fileMeta['id']} (base64Encoded: {base64Encoded})") + else: + logger.warning(f"Failed to save content for document '{base_name}.{ext}'") else: - logger.warning(f"Failed to save document '{label}'") + logger.warning(f"Failed to create file metadata for '{base_name}.{ext}'") except Exception as e: logger.error(f"Error saving document from agent results: {str(e)}") @@ -1174,11 +1189,19 @@ filesDelivered = {self.parseJson2text(matchingDocuments)} # Extract summaries from all contents contentSummaries = [] - for content in doc.get("contents", []): + if "contents" in doc and doc["contents"]: + for content in doc["contents"]: + contentSummaries.append({ + "contentPart": content.get("name", "noname"), + "metadata": content.get("metadata", ""), + "summary": content.get("summary", "No summary"), + }) + else: + # Add a default content summary if no contents exist contentSummaries.append({ - "contentPart": content.get("name", "noname"), - "metadata": content.get("metadata", ""), - "summary": content.get("summary", "No summary"), + "contentPart": "1_undefined", + "metadata": "", + "summary": "No content extracted", }) # Create document info @@ -1277,11 +1300,12 @@ filesDelivered = {self.parseJson2text(matchingDocuments)} # Singleton factory for the WorkflowManager _workflowManagers = {} +_workflowManagerLastAccess = {} # Track last access time for cleanup def getWorkflowManager(mandateId: int = 0, userId: int = 0) -> WorkflowManager: """ Returns a WorkflowManager for the specified context. - Reuses existing instances. + Reuses existing instances but implements cleanup for inactive instances. Args: mandateId: ID of the mandate @@ -1291,6 +1315,32 @@ def getWorkflowManager(mandateId: int = 0, userId: int = 0) -> WorkflowManager: WorkflowManager instance """ contextKey = f"{mandateId}_{userId}" + current_time = datetime.now() + + # Update last access time + _workflowManagerLastAccess[contextKey] = current_time + + # Cleanup old instances (older than 1 hour) + cleanup_threshold = current_time - timedelta(hours=1) + for key in list(_workflowManagers.keys()): + if _workflowManagerLastAccess.get(key, current_time) < cleanup_threshold: + del _workflowManagers[key] + del _workflowManagerLastAccess[key] + if contextKey not in _workflowManagers: _workflowManagers[contextKey] = WorkflowManager(mandateId, userId) - return _workflowManagers[contextKey] \ No newline at end of file + return _workflowManagers[contextKey] + +def cleanupWorkflowManager(mandateId: int, userId: int) -> None: + """ + Explicitly cleanup a WorkflowManager instance. + + Args: + mandateId: ID of the mandate + userId: ID of the user + """ + contextKey = f"{mandateId}_{userId}" + if contextKey in _workflowManagers: + del _workflowManagers[contextKey] + if contextKey in _workflowManagerLastAccess: + del _workflowManagerLastAccess[contextKey] \ No newline at end of file diff --git a/notes/changelog.txt b/notes/changelog.txt index 72a0aafe..1aeb06a9 100644 --- a/notes/changelog.txt +++ b/notes/changelog.txt @@ -1,28 +1,28 @@ ....................... TASKS + +Check data extraction of types! + +final message with 100% to give + + + ----------------------- OPEN PRIO1: -CHECK: If pictures not displayed to check utf-8 encoding in the base64 string!! general file writing and reading (example with svg) - -add connector to myoutlook +sharepoint connector with document search, content search, content extraction PRIO2: -todo an agent for "code writing and editing" connected to the codebase, working in loops over each document... - sharepoint connector with document search, content search, content extraction Split big files into content-parts Integrate NDA Text as modal form - Data governance agreement by login with checkbox -frontend to react - -frontend: no labels definition PRIO3: diff --git a/static/10_email_preview.html b/static/10_email_preview.html deleted file mode 100644 index c900e097..00000000 --- a/static/10_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Verschiebung des Meetings auf Freitag - - - -
- - - -
- - - \ No newline at end of file diff --git a/static/11_email_template.json b/static/11_email_template.json deleted file mode 100644 index bf14e27b..00000000 --- a/static/11_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "peter.muster@domain.com", - "subject": "Verschiebung des Meetings auf Freitag", - "plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob dieser neue Termin f\u00fcr Sie passt.\n\nVielen Dank f\u00fcr Ihr Verst\u00e4ndnis.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]", - "htmlBody": "

Sehr geehrter Herr Muster,

ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob dieser neue Termin f\u00fcr Sie passt.

Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.

Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]

" -} \ No newline at end of file diff --git a/static/12_email_preview.html b/static/12_email_preview.html deleted file mode 100644 index 87962b01..00000000 --- a/static/12_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Anfrage zur Terminverschiebung - - - -
- - - -
- - - \ No newline at end of file diff --git a/static/13_email_template.json b/static/13_email_template.json deleted file mode 100644 index ab8a946c..00000000 --- a/static/13_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "peter.muster@domain.com", - "subject": "Anfrage zur Terminverschiebung", - "plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um eine Verschiebung unseres Termins von 10 Uhr auf Freitag zu erbitten. Bitte lassen Sie mich wissen, ob dies f\u00fcr Sie m\u00f6glich ist.\n\nVielen Dank im Voraus f\u00fcr Ihre Flexibilit\u00e4t.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]", - "htmlBody": "

Sehr geehrter Herr Muster,

ich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um eine Verschiebung unseres Termins von 10 Uhr auf Freitag zu erbitten. Bitte lassen Sie mich wissen, ob dies f\u00fcr Sie m\u00f6glich ist.

Vielen Dank im Voraus f\u00fcr Ihre Flexibilit\u00e4t.

Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]

" -} \ No newline at end of file diff --git a/static/14_microsoft_authentication.html b/static/14_microsoft_authentication.html deleted file mode 100644 index b8a50d7f..00000000 --- a/static/14_microsoft_authentication.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - Microsoft Authentication Required - - - -
-

Microsoft Authentication Required

- -

To create email templates and drafts, you need to authenticate with your Microsoft account. Follow these steps:

- -
- 1 - Click the authentication link below -
- - Authenticate with Microsoft - -
- 2 - Sign in with your Microsoft account and grant the required permissions -
- -
- 3 - Return to this application and run the email agent again after completing authentication -
- -
-

Note: You only need to authenticate once. Your session will be remembered for future email operations.

-
-
- - - \ No newline at end of file diff --git a/static/15_microsoft_authentication.html b/static/15_microsoft_authentication.html deleted file mode 100644 index 521bae1c..00000000 --- a/static/15_microsoft_authentication.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Microsoft Authentication Required - - - -
-

Microsoft Authentication Required

- -

To create email templates and drafts, you need to authenticate with your Microsoft account.

- -

The application will now initiate the Microsoft authentication process. Please follow the instructions in the authentication window.

- -
-

Note: You only need to authenticate once. Your session will be remembered for future email operations.

-
-
- - - \ No newline at end of file diff --git a/static/16_email_preview.html b/static/16_email_preview.html deleted file mode 100644 index 95096bad..00000000 --- a/static/16_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Verschiebung des Meetings auf Freitag - - - -
- - - -
- - - \ No newline at end of file diff --git a/static/17_email_template.json b/static/17_email_template.json deleted file mode 100644 index 90e8f9f3..00000000 --- a/static/17_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "peter.muster@domain.com", - "subject": "Verschiebung des Meetings auf Freitag", - "plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting um 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob dieser Termin f\u00fcr Sie passt.\n\nVielen Dank f\u00fcr Ihr Verst\u00e4ndnis.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]", - "htmlBody": "

Sehr geehrter Herr Muster,

ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting um 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob dieser Termin f\u00fcr Sie passt.

Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.

Mit freundlichen Gr\u00fc\u00dfen,

[Ihr Name]

" -} \ No newline at end of file diff --git a/static/18_generated_code.py b/static/18_generated_code.py deleted file mode 100644 index b53f58c4..00000000 --- a/static/18_generated_code.py +++ /dev/null @@ -1,48 +0,0 @@ -inputFiles = [] # DO NOT CHANGE THIS LINE - -# REQUIREMENTS: - -import json -import csv -from io import StringIO - -def is_prime(n): - if n <= 1: - return False - if n <= 3: - return True - if n % 2 == 0 or n % 3 == 0: - return False - i = 5 - while i * i <= n: - if n % i == 0 or n % (i + 2) == 0: - return False - i += 6 - return True - -def generate_primes(limit): - primes = [] - num = 2 - while len(primes) < limit: - if is_prime(num): - primes.append(num) - num += 1 - return primes - -primes = generate_primes(1000) - -output = StringIO() -csv_writer = csv.writer(output) -for prime in primes: - csv_writer.writerow([prime]) - -result = { - "prime_numbers.csv": { - "content": output.getvalue(), - "base64Encoded": False, - "contentType": "text/csv" - } -} - -import json -print(json.dumps(result)) \ No newline at end of file diff --git a/static/19_execution_history.json b/static/19_execution_history.json deleted file mode 100644 index 8b61dc57..00000000 --- a/static/19_execution_history.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "attempt": 1, - "code": "inputFiles = [] # DO NOT CHANGE THIS LINE\n\n# REQUIREMENTS: \n\nimport json\nimport csv\nfrom io import StringIO\n\ndef is_prime(n):\n if n <= 1:\n return False\n if n <= 3:\n return True\n if n % 2 == 0 or n % 3 == 0:\n return False\n i = 5\n while i * i <= n:\n if n % i == 0 or n % (i + 2) == 0:\n return False\n i += 6\n return True\n\ndef generate_primes(limit):\n primes = []\n num = 2\n while len(primes) < limit:\n if is_prime(num):\n primes.append(num)\n num += 1\n return primes\n\nprimes = generate_primes(1000)\n\noutput = StringIO()\ncsv_writer = csv.writer(output)\nfor prime in primes:\n csv_writer.writerow([prime])\n\nresult = {\n \"prime_numbers.csv\": {\n \"content\": output.getvalue(),\n \"base64Encoded\": False,\n \"contentType\": \"text/csv\"\n }\n}\n\nimport json\nprint(json.dumps(result))", - "result": { - "success": true, - "output": "{\"prime_numbers.csv\": {\"content\": \"2\\r\\n3\\r\\n5\\r\\n7\\r\\n11\\r\\n13\\r\\n17\\r\\n19\\r\\n23\\r\\n29\\r\\n31\\r\\n37\\r\\n41\\r\\n43\\r\\n47\\r\\n53\\r\\n59\\r\\n61\\r\\n67\\r\\n71\\r\\n73\\r\\n79\\r\\n83\\r\\n89\\r\\n97\\r\\n101\\r\\n103\\r\\n107\\r\\n109\\r\\n113\\r\\n127\\r\\n131\\r\\n137\\r\\n139\\r\\n149\\r\\n151\\r\\n157\\r\\n163\\r\\n167\\r\\n173\\r\\n179\\r\\n181\\r\\n191\\r\\n193\\r\\n197\\r\\n199\\r\\n211\\r\\n223\\r\\n227\\r\\n229\\r\\n233\\r\\n239\\r\\n241\\r\\n251\\r\\n257\\r\\n263\\r\\n269\\r\\n271\\r\\n277\\r\\n281\\r\\n283\\r\\n293\\r\\n307\\r\\n311\\r\\n313\\r\\n317\\r\\n331\\r\\n337\\r\\n347\\r\\n349\\r\\n353\\r\\n359\\r\\n367\\r\\n373\\r\\n379\\r\\n383\\r\\n389\\r\\n397\\r\\n401\\r\\n409\\r\\n419\\r\\n421\\r\\n431\\r\\n433\\r\\n439\\r\\n443\\r\\n449\\r\\n457\\r\\n461\\r\\n463\\r\\n467\\r\\n479\\r\\n487\\r\\n491\\r\\n499\\r\\n503\\r\\n509\\r\\n521\\r\\n523\\r\\n541\\r\\n547\\r\\n557\\r\\n563\\r\\n569\\r\\n571\\r\\n577\\r\\n587\\r\\n593\\r\\n599\\r\\n601\\r\\n607\\r\\n613\\r\\n617\\r\\n619\\r\\n631\\r\\n641\\r\\n643\\r\\n647\\r\\n653\\r\\n659\\r\\n661\\r\\n673\\r\\n677\\r\\n683\\r\\n691\\r\\n701\\r\\n709\\r\\n719\\r\\n727\\r\\n733\\r\\n739\\r\\n743\\r\\n751\\r\\n757\\r\\n761\\r\\n769\\r\\n773\\r\\n787\\r\\n797\\r\\n809\\r\\n811\\r\\n821\\r\\n823\\r\\n827\\r\\n829\\r\\n839\\r\\n853\\r\\n857\\r\\n859\\r\\n863\\r\\n877\\r\\n881\\r\\n883\\r\\n887\\r\\n907\\r\\n911\\r\\n919\\r\\n929\\r\\n937\\r\\n941\\r\\n947\\r\\n953\\r\\n967\\r\\n971\\r\\n977\\r\\n983\\r\\n991\\r\\n997\\r\\n1009\\r\\n1013\\r\\n1019\\r\\n1021\\r\\n1031\\r\\n1033\\r\\n1039\\r\\n1049\\r\\n1051\\r\\n1061\\r\\n1063\\r\\n1069\\r\\n1087\\r\\n1091\\r\\n1093\\r\\n1097\\r\\n1103\\r\\n1109\\r\\n1117\\r\\n1123\\r\\n1129\\r\\n1151\\r\\n1153\\r\\n1163\\r\\n1171\\r\\n1181\\r\\n1187\\r\\n1193\\r\\n1201\\r\\n1213\\r\\n1217\\r\\n1223\\r\\n1229\\r\\n1231\\r\\n1237\\r\\n1249\\r\\n1259\\r\\n1277\\r\\n1279\\r\\n1283\\r\\n1289\\r\\n1291\\r\\n1297\\r\\n1301\\r\\n1303\\r\\n1307\\r\\n1319\\r\\n1321\\r\\n1327\\r\\n1361\\r\\n1367\\r\\n1373\\r\\n1381\\r\\n1399\\r\\n1409\\r\\n1423\\r\\n1427\\r\\n1429\\r\\n1433\\r\\n1439\\r\\n1447\\r\\n1451\\r\\n1453\\r\\n1459\\r\\n1471\\r\\n1481\\r\\n1483\\r\\n1487\\r\\n1489\\r\\n1493\\r\\n1499\\r\\n1511\\r\\n1523\\r\\n1531\\r\\n1543\\r\\n1549\\r\\n1553\\r\\n1559\\r\\n1567\\r\\n1571\\r\\n1579\\r\\n1583\\r\\n1597\\r\\n1601\\r\\n1607\\r\\n1609\\r\\n1613\\r\\n1619\\r\\n1621\\r\\n1627\\r\\n1637\\r\\n1657\\r\\n1663\\r\\n1667\\r\\n1669\\r\\n1693\\r\\n1697\\r\\n1699\\r\\n1709\\r\\n1721\\r\\n1723\\r\\n1733\\r\\n1741\\r\\n1747\\r\\n1753\\r\\n1759\\r\\n1777\\r\\n1783\\r\\n1787\\r\\n1789\\r\\n1801\\r\\n1811\\r\\n1823\\r\\n1831\\r\\n1847\\r\\n1861\\r\\n1867\\r\\n1871\\r\\n1873\\r\\n1877\\r\\n1879\\r\\n1889\\r\\n1901\\r\\n1907\\r\\n1913\\r\\n1931\\r\\n1933\\r\\n1949\\r\\n1951\\r\\n1973\\r\\n1979\\r\\n1987\\r\\n1993\\r\\n1997\\r\\n1999\\r\\n2003\\r\\n2011\\r\\n2017\\r\\n2027\\r\\n2029\\r\\n2039\\r\\n2053\\r\\n2063\\r\\n2069\\r\\n2081\\r\\n2083\\r\\n2087\\r\\n2089\\r\\n2099\\r\\n2111\\r\\n2113\\r\\n2129\\r\\n2131\\r\\n2137\\r\\n2141\\r\\n2143\\r\\n2153\\r\\n2161\\r\\n2179\\r\\n2203\\r\\n2207\\r\\n2213\\r\\n2221\\r\\n2237\\r\\n2239\\r\\n2243\\r\\n2251\\r\\n2267\\r\\n2269\\r\\n2273\\r\\n2281\\r\\n2287\\r\\n2293\\r\\n2297\\r\\n2309\\r\\n2311\\r\\n2333\\r\\n2339\\r\\n2341\\r\\n2347\\r\\n2351\\r\\n2357\\r\\n2371\\r\\n2377\\r\\n2381\\r\\n2383\\r\\n2389\\r\\n2393\\r\\n2399\\r\\n2411\\r\\n2417\\r\\n2423\\r\\n2437\\r\\n2441\\r\\n2447\\r\\n2459\\r\\n2467\\r\\n2473\\r\\n2477\\r\\n2503\\r\\n2521\\r\\n2531\\r\\n2539\\r\\n2543\\r\\n2549\\r\\n2551\\r\\n2557\\r\\n2579\\r\\n2591\\r\\n2593\\r\\n2609\\r\\n2617\\r\\n2621\\r\\n2633\\r\\n2647\\r\\n2657\\r\\n2659\\r\\n2663\\r\\n2671\\r\\n2677\\r\\n2683\\r\\n2687\\r\\n2689\\r\\n2693\\r\\n2699\\r\\n2707\\r\\n2711\\r\\n2713\\r\\n2719\\r\\n2729\\r\\n2731\\r\\n2741\\r\\n2749\\r\\n2753\\r\\n2767\\r\\n2777\\r\\n2789\\r\\n2791\\r\\n2797\\r\\n2801\\r\\n2803\\r\\n2819\\r\\n2833\\r\\n2837\\r\\n2843\\r\\n2851\\r\\n2857\\r\\n2861\\r\\n2879\\r\\n2887\\r\\n2897\\r\\n2903\\r\\n2909\\r\\n2917\\r\\n2927\\r\\n2939\\r\\n2953\\r\\n2957\\r\\n2963\\r\\n2969\\r\\n2971\\r\\n2999\\r\\n3001\\r\\n3011\\r\\n3019\\r\\n3023\\r\\n3037\\r\\n3041\\r\\n3049\\r\\n3061\\r\\n3067\\r\\n3079\\r\\n3083\\r\\n3089\\r\\n3109\\r\\n3119\\r\\n3121\\r\\n3137\\r\\n3163\\r\\n3167\\r\\n3169\\r\\n3181\\r\\n3187\\r\\n3191\\r\\n3203\\r\\n3209\\r\\n3217\\r\\n3221\\r\\n3229\\r\\n3251\\r\\n3253\\r\\n3257\\r\\n3259\\r\\n3271\\r\\n3299\\r\\n3301\\r\\n3307\\r\\n3313\\r\\n3319\\r\\n3323\\r\\n3329\\r\\n3331\\r\\n3343\\r\\n3347\\r\\n3359\\r\\n3361\\r\\n3371\\r\\n3373\\r\\n3389\\r\\n3391\\r\\n3407\\r\\n3413\\r\\n3433\\r\\n3449\\r\\n3457\\r\\n3461\\r\\n3463\\r\\n3467\\r\\n3469\\r\\n3491\\r\\n3499\\r\\n3511\\r\\n3517\\r\\n3527\\r\\n3529\\r\\n3533\\r\\n3539\\r\\n3541\\r\\n3547\\r\\n3557\\r\\n3559\\r\\n3571\\r\\n3581\\r\\n3583\\r\\n3593\\r\\n3607\\r\\n3613\\r\\n3617\\r\\n3623\\r\\n3631\\r\\n3637\\r\\n3643\\r\\n3659\\r\\n3671\\r\\n3673\\r\\n3677\\r\\n3691\\r\\n3697\\r\\n3701\\r\\n3709\\r\\n3719\\r\\n3727\\r\\n3733\\r\\n3739\\r\\n3761\\r\\n3767\\r\\n3769\\r\\n3779\\r\\n3793\\r\\n3797\\r\\n3803\\r\\n3821\\r\\n3823\\r\\n3833\\r\\n3847\\r\\n3851\\r\\n3853\\r\\n3863\\r\\n3877\\r\\n3881\\r\\n3889\\r\\n3907\\r\\n3911\\r\\n3917\\r\\n3919\\r\\n3923\\r\\n3929\\r\\n3931\\r\\n3943\\r\\n3947\\r\\n3967\\r\\n3989\\r\\n4001\\r\\n4003\\r\\n4007\\r\\n4013\\r\\n4019\\r\\n4021\\r\\n4027\\r\\n4049\\r\\n4051\\r\\n4057\\r\\n4073\\r\\n4079\\r\\n4091\\r\\n4093\\r\\n4099\\r\\n4111\\r\\n4127\\r\\n4129\\r\\n4133\\r\\n4139\\r\\n4153\\r\\n4157\\r\\n4159\\r\\n4177\\r\\n4201\\r\\n4211\\r\\n4217\\r\\n4219\\r\\n4229\\r\\n4231\\r\\n4241\\r\\n4243\\r\\n4253\\r\\n4259\\r\\n4261\\r\\n4271\\r\\n4273\\r\\n4283\\r\\n4289\\r\\n4297\\r\\n4327\\r\\n4337\\r\\n4339\\r\\n4349\\r\\n4357\\r\\n4363\\r\\n4373\\r\\n4391\\r\\n4397\\r\\n4409\\r\\n4421\\r\\n4423\\r\\n4441\\r\\n4447\\r\\n4451\\r\\n4457\\r\\n4463\\r\\n4481\\r\\n4483\\r\\n4493\\r\\n4507\\r\\n4513\\r\\n4517\\r\\n4519\\r\\n4523\\r\\n4547\\r\\n4549\\r\\n4561\\r\\n4567\\r\\n4583\\r\\n4591\\r\\n4597\\r\\n4603\\r\\n4621\\r\\n4637\\r\\n4639\\r\\n4643\\r\\n4649\\r\\n4651\\r\\n4657\\r\\n4663\\r\\n4673\\r\\n4679\\r\\n4691\\r\\n4703\\r\\n4721\\r\\n4723\\r\\n4729\\r\\n4733\\r\\n4751\\r\\n4759\\r\\n4783\\r\\n4787\\r\\n4789\\r\\n4793\\r\\n4799\\r\\n4801\\r\\n4813\\r\\n4817\\r\\n4831\\r\\n4861\\r\\n4871\\r\\n4877\\r\\n4889\\r\\n4903\\r\\n4909\\r\\n4919\\r\\n4931\\r\\n4933\\r\\n4937\\r\\n4943\\r\\n4951\\r\\n4957\\r\\n4967\\r\\n4969\\r\\n4973\\r\\n4987\\r\\n4993\\r\\n4999\\r\\n5003\\r\\n5009\\r\\n5011\\r\\n5021\\r\\n5023\\r\\n5039\\r\\n5051\\r\\n5059\\r\\n5077\\r\\n5081\\r\\n5087\\r\\n5099\\r\\n5101\\r\\n5107\\r\\n5113\\r\\n5119\\r\\n5147\\r\\n5153\\r\\n5167\\r\\n5171\\r\\n5179\\r\\n5189\\r\\n5197\\r\\n5209\\r\\n5227\\r\\n5231\\r\\n5233\\r\\n5237\\r\\n5261\\r\\n5273\\r\\n5279\\r\\n5281\\r\\n5297\\r\\n5303\\r\\n5309\\r\\n5323\\r\\n5333\\r\\n5347\\r\\n5351\\r\\n5381\\r\\n5387\\r\\n5393\\r\\n5399\\r\\n5407\\r\\n5413\\r\\n5417\\r\\n5419\\r\\n5431\\r\\n5437\\r\\n5441\\r\\n5443\\r\\n5449\\r\\n5471\\r\\n5477\\r\\n5479\\r\\n5483\\r\\n5501\\r\\n5503\\r\\n5507\\r\\n5519\\r\\n5521\\r\\n5527\\r\\n5531\\r\\n5557\\r\\n5563\\r\\n5569\\r\\n5573\\r\\n5581\\r\\n5591\\r\\n5623\\r\\n5639\\r\\n5641\\r\\n5647\\r\\n5651\\r\\n5653\\r\\n5657\\r\\n5659\\r\\n5669\\r\\n5683\\r\\n5689\\r\\n5693\\r\\n5701\\r\\n5711\\r\\n5717\\r\\n5737\\r\\n5741\\r\\n5743\\r\\n5749\\r\\n5779\\r\\n5783\\r\\n5791\\r\\n5801\\r\\n5807\\r\\n5813\\r\\n5821\\r\\n5827\\r\\n5839\\r\\n5843\\r\\n5849\\r\\n5851\\r\\n5857\\r\\n5861\\r\\n5867\\r\\n5869\\r\\n5879\\r\\n5881\\r\\n5897\\r\\n5903\\r\\n5923\\r\\n5927\\r\\n5939\\r\\n5953\\r\\n5981\\r\\n5987\\r\\n6007\\r\\n6011\\r\\n6029\\r\\n6037\\r\\n6043\\r\\n6047\\r\\n6053\\r\\n6067\\r\\n6073\\r\\n6079\\r\\n6089\\r\\n6091\\r\\n6101\\r\\n6113\\r\\n6121\\r\\n6131\\r\\n6133\\r\\n6143\\r\\n6151\\r\\n6163\\r\\n6173\\r\\n6197\\r\\n6199\\r\\n6203\\r\\n6211\\r\\n6217\\r\\n6221\\r\\n6229\\r\\n6247\\r\\n6257\\r\\n6263\\r\\n6269\\r\\n6271\\r\\n6277\\r\\n6287\\r\\n6299\\r\\n6301\\r\\n6311\\r\\n6317\\r\\n6323\\r\\n6329\\r\\n6337\\r\\n6343\\r\\n6353\\r\\n6359\\r\\n6361\\r\\n6367\\r\\n6373\\r\\n6379\\r\\n6389\\r\\n6397\\r\\n6421\\r\\n6427\\r\\n6449\\r\\n6451\\r\\n6469\\r\\n6473\\r\\n6481\\r\\n6491\\r\\n6521\\r\\n6529\\r\\n6547\\r\\n6551\\r\\n6553\\r\\n6563\\r\\n6569\\r\\n6571\\r\\n6577\\r\\n6581\\r\\n6599\\r\\n6607\\r\\n6619\\r\\n6637\\r\\n6653\\r\\n6659\\r\\n6661\\r\\n6673\\r\\n6679\\r\\n6689\\r\\n6691\\r\\n6701\\r\\n6703\\r\\n6709\\r\\n6719\\r\\n6733\\r\\n6737\\r\\n6761\\r\\n6763\\r\\n6779\\r\\n6781\\r\\n6791\\r\\n6793\\r\\n6803\\r\\n6823\\r\\n6827\\r\\n6829\\r\\n6833\\r\\n6841\\r\\n6857\\r\\n6863\\r\\n6869\\r\\n6871\\r\\n6883\\r\\n6899\\r\\n6907\\r\\n6911\\r\\n6917\\r\\n6947\\r\\n6949\\r\\n6959\\r\\n6961\\r\\n6967\\r\\n6971\\r\\n6977\\r\\n6983\\r\\n6991\\r\\n6997\\r\\n7001\\r\\n7013\\r\\n7019\\r\\n7027\\r\\n7039\\r\\n7043\\r\\n7057\\r\\n7069\\r\\n7079\\r\\n7103\\r\\n7109\\r\\n7121\\r\\n7127\\r\\n7129\\r\\n7151\\r\\n7159\\r\\n7177\\r\\n7187\\r\\n7193\\r\\n7207\\r\\n7211\\r\\n7213\\r\\n7219\\r\\n7229\\r\\n7237\\r\\n7243\\r\\n7247\\r\\n7253\\r\\n7283\\r\\n7297\\r\\n7307\\r\\n7309\\r\\n7321\\r\\n7331\\r\\n7333\\r\\n7349\\r\\n7351\\r\\n7369\\r\\n7393\\r\\n7411\\r\\n7417\\r\\n7433\\r\\n7451\\r\\n7457\\r\\n7459\\r\\n7477\\r\\n7481\\r\\n7487\\r\\n7489\\r\\n7499\\r\\n7507\\r\\n7517\\r\\n7523\\r\\n7529\\r\\n7537\\r\\n7541\\r\\n7547\\r\\n7549\\r\\n7559\\r\\n7561\\r\\n7573\\r\\n7577\\r\\n7583\\r\\n7589\\r\\n7591\\r\\n7603\\r\\n7607\\r\\n7621\\r\\n7639\\r\\n7643\\r\\n7649\\r\\n7669\\r\\n7673\\r\\n7681\\r\\n7687\\r\\n7691\\r\\n7699\\r\\n7703\\r\\n7717\\r\\n7723\\r\\n7727\\r\\n7741\\r\\n7753\\r\\n7757\\r\\n7759\\r\\n7789\\r\\n7793\\r\\n7817\\r\\n7823\\r\\n7829\\r\\n7841\\r\\n7853\\r\\n7867\\r\\n7873\\r\\n7877\\r\\n7879\\r\\n7883\\r\\n7901\\r\\n7907\\r\\n7919\\r\\n\", \"base64Encoded\": false, \"contentType\": \"text/csv\"}}\n", - "error": "", - "result": { - "prime_numbers.csv": { - "content": "2\r\n3\r\n5\r\n7\r\n11\r\n13\r\n17\r\n19\r\n23\r\n29\r\n31\r\n37\r\n41\r\n43\r\n47\r\n53\r\n59\r\n61\r\n67\r\n71\r\n73\r\n79\r\n83\r\n89\r\n97\r\n101\r\n103\r\n107\r\n109\r\n113\r\n127\r\n131\r\n137\r\n139\r\n149\r\n151\r\n157\r\n163\r\n167\r\n173\r\n179\r\n181\r\n191\r\n193\r\n197\r\n199\r\n211\r\n223\r\n227\r\n229\r\n233\r\n239\r\n241\r\n251\r\n257\r\n263\r\n269\r\n271\r\n277\r\n281\r\n283\r\n293\r\n307\r\n311\r\n313\r\n317\r\n331\r\n337\r\n347\r\n349\r\n353\r\n359\r\n367\r\n373\r\n379\r\n383\r\n389\r\n397\r\n401\r\n409\r\n419\r\n421\r\n431\r\n433\r\n439\r\n443\r\n449\r\n457\r\n461\r\n463\r\n467\r\n479\r\n487\r\n491\r\n499\r\n503\r\n509\r\n521\r\n523\r\n541\r\n547\r\n557\r\n563\r\n569\r\n571\r\n577\r\n587\r\n593\r\n599\r\n601\r\n607\r\n613\r\n617\r\n619\r\n631\r\n641\r\n643\r\n647\r\n653\r\n659\r\n661\r\n673\r\n677\r\n683\r\n691\r\n701\r\n709\r\n719\r\n727\r\n733\r\n739\r\n743\r\n751\r\n757\r\n761\r\n769\r\n773\r\n787\r\n797\r\n809\r\n811\r\n821\r\n823\r\n827\r\n829\r\n839\r\n853\r\n857\r\n859\r\n863\r\n877\r\n881\r\n883\r\n887\r\n907\r\n911\r\n919\r\n929\r\n937\r\n941\r\n947\r\n953\r\n967\r\n971\r\n977\r\n983\r\n991\r\n997\r\n1009\r\n1013\r\n1019\r\n1021\r\n1031\r\n1033\r\n1039\r\n1049\r\n1051\r\n1061\r\n1063\r\n1069\r\n1087\r\n1091\r\n1093\r\n1097\r\n1103\r\n1109\r\n1117\r\n1123\r\n1129\r\n1151\r\n1153\r\n1163\r\n1171\r\n1181\r\n1187\r\n1193\r\n1201\r\n1213\r\n1217\r\n1223\r\n1229\r\n1231\r\n1237\r\n1249\r\n1259\r\n1277\r\n1279\r\n1283\r\n1289\r\n1291\r\n1297\r\n1301\r\n1303\r\n1307\r\n1319\r\n1321\r\n1327\r\n1361\r\n1367\r\n1373\r\n1381\r\n1399\r\n1409\r\n1423\r\n1427\r\n1429\r\n1433\r\n1439\r\n1447\r\n1451\r\n1453\r\n1459\r\n1471\r\n1481\r\n1483\r\n1487\r\n1489\r\n1493\r\n1499\r\n1511\r\n1523\r\n1531\r\n1543\r\n1549\r\n1553\r\n1559\r\n1567\r\n1571\r\n1579\r\n1583\r\n1597\r\n1601\r\n1607\r\n1609\r\n1613\r\n1619\r\n1621\r\n1627\r\n1637\r\n1657\r\n1663\r\n1667\r\n1669\r\n1693\r\n1697\r\n1699\r\n1709\r\n1721\r\n1723\r\n1733\r\n1741\r\n1747\r\n1753\r\n1759\r\n1777\r\n1783\r\n1787\r\n1789\r\n1801\r\n1811\r\n1823\r\n1831\r\n1847\r\n1861\r\n1867\r\n1871\r\n1873\r\n1877\r\n1879\r\n1889\r\n1901\r\n1907\r\n1913\r\n1931\r\n1933\r\n1949\r\n1951\r\n1973\r\n1979\r\n1987\r\n1993\r\n1997\r\n1999\r\n2003\r\n2011\r\n2017\r\n2027\r\n2029\r\n2039\r\n2053\r\n2063\r\n2069\r\n2081\r\n2083\r\n2087\r\n2089\r\n2099\r\n2111\r\n2113\r\n2129\r\n2131\r\n2137\r\n2141\r\n2143\r\n2153\r\n2161\r\n2179\r\n2203\r\n2207\r\n2213\r\n2221\r\n2237\r\n2239\r\n2243\r\n2251\r\n2267\r\n2269\r\n2273\r\n2281\r\n2287\r\n2293\r\n2297\r\n2309\r\n2311\r\n2333\r\n2339\r\n2341\r\n2347\r\n2351\r\n2357\r\n2371\r\n2377\r\n2381\r\n2383\r\n2389\r\n2393\r\n2399\r\n2411\r\n2417\r\n2423\r\n2437\r\n2441\r\n2447\r\n2459\r\n2467\r\n2473\r\n2477\r\n2503\r\n2521\r\n2531\r\n2539\r\n2543\r\n2549\r\n2551\r\n2557\r\n2579\r\n2591\r\n2593\r\n2609\r\n2617\r\n2621\r\n2633\r\n2647\r\n2657\r\n2659\r\n2663\r\n2671\r\n2677\r\n2683\r\n2687\r\n2689\r\n2693\r\n2699\r\n2707\r\n2711\r\n2713\r\n2719\r\n2729\r\n2731\r\n2741\r\n2749\r\n2753\r\n2767\r\n2777\r\n2789\r\n2791\r\n2797\r\n2801\r\n2803\r\n2819\r\n2833\r\n2837\r\n2843\r\n2851\r\n2857\r\n2861\r\n2879\r\n2887\r\n2897\r\n2903\r\n2909\r\n2917\r\n2927\r\n2939\r\n2953\r\n2957\r\n2963\r\n2969\r\n2971\r\n2999\r\n3001\r\n3011\r\n3019\r\n3023\r\n3037\r\n3041\r\n3049\r\n3061\r\n3067\r\n3079\r\n3083\r\n3089\r\n3109\r\n3119\r\n3121\r\n3137\r\n3163\r\n3167\r\n3169\r\n3181\r\n3187\r\n3191\r\n3203\r\n3209\r\n3217\r\n3221\r\n3229\r\n3251\r\n3253\r\n3257\r\n3259\r\n3271\r\n3299\r\n3301\r\n3307\r\n3313\r\n3319\r\n3323\r\n3329\r\n3331\r\n3343\r\n3347\r\n3359\r\n3361\r\n3371\r\n3373\r\n3389\r\n3391\r\n3407\r\n3413\r\n3433\r\n3449\r\n3457\r\n3461\r\n3463\r\n3467\r\n3469\r\n3491\r\n3499\r\n3511\r\n3517\r\n3527\r\n3529\r\n3533\r\n3539\r\n3541\r\n3547\r\n3557\r\n3559\r\n3571\r\n3581\r\n3583\r\n3593\r\n3607\r\n3613\r\n3617\r\n3623\r\n3631\r\n3637\r\n3643\r\n3659\r\n3671\r\n3673\r\n3677\r\n3691\r\n3697\r\n3701\r\n3709\r\n3719\r\n3727\r\n3733\r\n3739\r\n3761\r\n3767\r\n3769\r\n3779\r\n3793\r\n3797\r\n3803\r\n3821\r\n3823\r\n3833\r\n3847\r\n3851\r\n3853\r\n3863\r\n3877\r\n3881\r\n3889\r\n3907\r\n3911\r\n3917\r\n3919\r\n3923\r\n3929\r\n3931\r\n3943\r\n3947\r\n3967\r\n3989\r\n4001\r\n4003\r\n4007\r\n4013\r\n4019\r\n4021\r\n4027\r\n4049\r\n4051\r\n4057\r\n4073\r\n4079\r\n4091\r\n4093\r\n4099\r\n4111\r\n4127\r\n4129\r\n4133\r\n4139\r\n4153\r\n4157\r\n4159\r\n4177\r\n4201\r\n4211\r\n4217\r\n4219\r\n4229\r\n4231\r\n4241\r\n4243\r\n4253\r\n4259\r\n4261\r\n4271\r\n4273\r\n4283\r\n4289\r\n4297\r\n4327\r\n4337\r\n4339\r\n4349\r\n4357\r\n4363\r\n4373\r\n4391\r\n4397\r\n4409\r\n4421\r\n4423\r\n4441\r\n4447\r\n4451\r\n4457\r\n4463\r\n4481\r\n4483\r\n4493\r\n4507\r\n4513\r\n4517\r\n4519\r\n4523\r\n4547\r\n4549\r\n4561\r\n4567\r\n4583\r\n4591\r\n4597\r\n4603\r\n4621\r\n4637\r\n4639\r\n4643\r\n4649\r\n4651\r\n4657\r\n4663\r\n4673\r\n4679\r\n4691\r\n4703\r\n4721\r\n4723\r\n4729\r\n4733\r\n4751\r\n4759\r\n4783\r\n4787\r\n4789\r\n4793\r\n4799\r\n4801\r\n4813\r\n4817\r\n4831\r\n4861\r\n4871\r\n4877\r\n4889\r\n4903\r\n4909\r\n4919\r\n4931\r\n4933\r\n4937\r\n4943\r\n4951\r\n4957\r\n4967\r\n4969\r\n4973\r\n4987\r\n4993\r\n4999\r\n5003\r\n5009\r\n5011\r\n5021\r\n5023\r\n5039\r\n5051\r\n5059\r\n5077\r\n5081\r\n5087\r\n5099\r\n5101\r\n5107\r\n5113\r\n5119\r\n5147\r\n5153\r\n5167\r\n5171\r\n5179\r\n5189\r\n5197\r\n5209\r\n5227\r\n5231\r\n5233\r\n5237\r\n5261\r\n5273\r\n5279\r\n5281\r\n5297\r\n5303\r\n5309\r\n5323\r\n5333\r\n5347\r\n5351\r\n5381\r\n5387\r\n5393\r\n5399\r\n5407\r\n5413\r\n5417\r\n5419\r\n5431\r\n5437\r\n5441\r\n5443\r\n5449\r\n5471\r\n5477\r\n5479\r\n5483\r\n5501\r\n5503\r\n5507\r\n5519\r\n5521\r\n5527\r\n5531\r\n5557\r\n5563\r\n5569\r\n5573\r\n5581\r\n5591\r\n5623\r\n5639\r\n5641\r\n5647\r\n5651\r\n5653\r\n5657\r\n5659\r\n5669\r\n5683\r\n5689\r\n5693\r\n5701\r\n5711\r\n5717\r\n5737\r\n5741\r\n5743\r\n5749\r\n5779\r\n5783\r\n5791\r\n5801\r\n5807\r\n5813\r\n5821\r\n5827\r\n5839\r\n5843\r\n5849\r\n5851\r\n5857\r\n5861\r\n5867\r\n5869\r\n5879\r\n5881\r\n5897\r\n5903\r\n5923\r\n5927\r\n5939\r\n5953\r\n5981\r\n5987\r\n6007\r\n6011\r\n6029\r\n6037\r\n6043\r\n6047\r\n6053\r\n6067\r\n6073\r\n6079\r\n6089\r\n6091\r\n6101\r\n6113\r\n6121\r\n6131\r\n6133\r\n6143\r\n6151\r\n6163\r\n6173\r\n6197\r\n6199\r\n6203\r\n6211\r\n6217\r\n6221\r\n6229\r\n6247\r\n6257\r\n6263\r\n6269\r\n6271\r\n6277\r\n6287\r\n6299\r\n6301\r\n6311\r\n6317\r\n6323\r\n6329\r\n6337\r\n6343\r\n6353\r\n6359\r\n6361\r\n6367\r\n6373\r\n6379\r\n6389\r\n6397\r\n6421\r\n6427\r\n6449\r\n6451\r\n6469\r\n6473\r\n6481\r\n6491\r\n6521\r\n6529\r\n6547\r\n6551\r\n6553\r\n6563\r\n6569\r\n6571\r\n6577\r\n6581\r\n6599\r\n6607\r\n6619\r\n6637\r\n6653\r\n6659\r\n6661\r\n6673\r\n6679\r\n6689\r\n6691\r\n6701\r\n6703\r\n6709\r\n6719\r\n6733\r\n6737\r\n6761\r\n6763\r\n6779\r\n6781\r\n6791\r\n6793\r\n6803\r\n6823\r\n6827\r\n6829\r\n6833\r\n6841\r\n6857\r\n6863\r\n6869\r\n6871\r\n6883\r\n6899\r\n6907\r\n6911\r\n6917\r\n6947\r\n6949\r\n6959\r\n6961\r\n6967\r\n6971\r\n6977\r\n6983\r\n6991\r\n6997\r\n7001\r\n7013\r\n7019\r\n7027\r\n7039\r\n7043\r\n7057\r\n7069\r\n7079\r\n7103\r\n7109\r\n7121\r\n7127\r\n7129\r\n7151\r\n7159\r\n7177\r\n7187\r\n7193\r\n7207\r\n7211\r\n7213\r\n7219\r\n7229\r\n7237\r\n7243\r\n7247\r\n7253\r\n7283\r\n7297\r\n7307\r\n7309\r\n7321\r\n7331\r\n7333\r\n7349\r\n7351\r\n7369\r\n7393\r\n7411\r\n7417\r\n7433\r\n7451\r\n7457\r\n7459\r\n7477\r\n7481\r\n7487\r\n7489\r\n7499\r\n7507\r\n7517\r\n7523\r\n7529\r\n7537\r\n7541\r\n7547\r\n7549\r\n7559\r\n7561\r\n7573\r\n7577\r\n7583\r\n7589\r\n7591\r\n7603\r\n7607\r\n7621\r\n7639\r\n7643\r\n7649\r\n7669\r\n7673\r\n7681\r\n7687\r\n7691\r\n7699\r\n7703\r\n7717\r\n7723\r\n7727\r\n7741\r\n7753\r\n7757\r\n7759\r\n7789\r\n7793\r\n7817\r\n7823\r\n7829\r\n7841\r\n7853\r\n7867\r\n7873\r\n7877\r\n7879\r\n7883\r\n7901\r\n7907\r\n7919\r\n", - "base64Encoded": false, - "contentType": "text/csv" - } - }, - "exitCode": 0 - } - } -] \ No newline at end of file diff --git a/static/1_LF-Details.png b/static/1_LF-Details.png deleted file mode 100644 index 3a2be57d619cb66e70f76081929e723e874ffcad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253009 zcmeFZcT`hb_bwb11rbC=R8T=sEFeXS^di{kk=_yMAVqo&C;|#9sB|eoDIpLLAs}7h zC`t)P??pb8H+bH2e1G@5|9#&WcZ|CSB7~i_*P3gVXFhYTJVL8nr=ww}L7`A| z@;9!ip-?ndC={g+^=^3OMaiQQ_>aO_?Yb-~n{<2(exbCytb7@T$_?AQX0i)@-{W{g z*BOP{SB3nesCK}a!5f0DHFR8bl$FFx9qf2a%p7i;^LW@f!rds8gtUjFiK(r*%gNj3 zme%%?XJ<+(&YrY3lRT>}q|B%6C}(bEeZ$MiT-{4W!_>>xRMhOOv=kkUgohZ6U}x@P za?-=@j=i&(ha~&fz+&(-@-i>Gl!TL+g_zovYrpS+-z3?sTwEN*czNC3-Fe&vcpRK8 zc`u5Jit_UD^YZg^!yVktp7t ^CfMr+yD`#oXD{$=cDy+QI%LGN8$A2UizKc2`$x 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 <%-6Eo*l=x^BB6uQj~m6$8uaci81tOBjq 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%0Adu}Gp`oLAvH7xLQ0eB~u=#w6_W9Rt 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 diff --git a/static/20_prime_numbers.csv b/static/20_prime_numbers.csv deleted file mode 100644 index d5c2a856..00000000 --- a/static/20_prime_numbers.csv +++ /dev/null @@ -1,1000 +0,0 @@ -2 -3 -5 -7 -11 -13 -17 -19 -23 -29 -31 -37 -41 -43 -47 -53 -59 -61 -67 -71 -73 -79 -83 -89 -97 -101 -103 -107 -109 -113 -127 -131 -137 -139 -149 -151 -157 -163 -167 -173 -179 -181 -191 -193 -197 -199 -211 -223 -227 -229 -233 -239 -241 -251 -257 -263 -269 -271 -277 -281 -283 -293 -307 -311 -313 -317 -331 -337 -347 -349 -353 -359 -367 -373 -379 -383 -389 -397 -401 -409 -419 -421 -431 -433 -439 -443 -449 -457 -461 -463 -467 -479 -487 -491 -499 -503 -509 -521 -523 -541 -547 -557 -563 -569 -571 -577 -587 -593 -599 -601 -607 -613 -617 -619 -631 -641 -643 -647 -653 -659 -661 -673 -677 -683 -691 -701 -709 -719 -727 -733 -739 -743 -751 -757 -761 -769 -773 -787 -797 -809 -811 -821 -823 -827 -829 -839 -853 -857 -859 -863 -877 -881 -883 -887 -907 -911 -919 -929 -937 -941 -947 -953 -967 -971 -977 -983 -991 -997 -1009 -1013 -1019 -1021 -1031 -1033 -1039 -1049 -1051 -1061 -1063 -1069 -1087 -1091 -1093 -1097 -1103 -1109 -1117 -1123 -1129 -1151 -1153 -1163 -1171 -1181 -1187 -1193 -1201 -1213 -1217 -1223 -1229 -1231 -1237 -1249 -1259 -1277 -1279 -1283 -1289 -1291 -1297 -1301 -1303 -1307 -1319 -1321 -1327 -1361 -1367 -1373 -1381 -1399 -1409 -1423 -1427 -1429 -1433 -1439 -1447 -1451 -1453 -1459 -1471 -1481 -1483 -1487 -1489 -1493 -1499 -1511 -1523 -1531 -1543 -1549 -1553 -1559 -1567 -1571 -1579 -1583 -1597 -1601 -1607 -1609 -1613 -1619 -1621 -1627 -1637 -1657 -1663 -1667 -1669 -1693 -1697 -1699 -1709 -1721 -1723 -1733 -1741 -1747 -1753 -1759 -1777 -1783 -1787 -1789 -1801 -1811 -1823 -1831 -1847 -1861 -1867 -1871 -1873 -1877 -1879 -1889 -1901 -1907 -1913 -1931 -1933 -1949 -1951 -1973 -1979 -1987 -1993 -1997 -1999 -2003 -2011 -2017 -2027 -2029 -2039 -2053 -2063 -2069 -2081 -2083 -2087 -2089 -2099 -2111 -2113 -2129 -2131 -2137 -2141 -2143 -2153 -2161 -2179 -2203 -2207 -2213 -2221 -2237 -2239 -2243 -2251 -2267 -2269 -2273 -2281 -2287 -2293 -2297 -2309 -2311 -2333 -2339 -2341 -2347 -2351 -2357 -2371 -2377 -2381 -2383 -2389 -2393 -2399 -2411 -2417 -2423 -2437 -2441 -2447 -2459 -2467 -2473 -2477 -2503 -2521 -2531 -2539 -2543 -2549 -2551 -2557 -2579 -2591 -2593 -2609 -2617 -2621 -2633 -2647 -2657 -2659 -2663 -2671 -2677 -2683 -2687 -2689 -2693 -2699 -2707 -2711 -2713 -2719 -2729 -2731 -2741 -2749 -2753 -2767 -2777 -2789 -2791 -2797 -2801 -2803 -2819 -2833 -2837 -2843 -2851 -2857 -2861 -2879 -2887 -2897 -2903 -2909 -2917 -2927 -2939 -2953 -2957 -2963 -2969 -2971 -2999 -3001 -3011 -3019 -3023 -3037 -3041 -3049 -3061 -3067 -3079 -3083 -3089 -3109 -3119 -3121 -3137 -3163 -3167 -3169 -3181 -3187 -3191 -3203 -3209 -3217 -3221 -3229 -3251 -3253 -3257 -3259 -3271 -3299 -3301 -3307 -3313 -3319 -3323 -3329 -3331 -3343 -3347 -3359 -3361 -3371 -3373 -3389 -3391 -3407 -3413 -3433 -3449 -3457 -3461 -3463 -3467 -3469 -3491 -3499 -3511 -3517 -3527 -3529 -3533 -3539 -3541 -3547 -3557 -3559 -3571 -3581 -3583 -3593 -3607 -3613 -3617 -3623 -3631 -3637 -3643 -3659 -3671 -3673 -3677 -3691 -3697 -3701 -3709 -3719 -3727 -3733 -3739 -3761 -3767 -3769 -3779 -3793 -3797 -3803 -3821 -3823 -3833 -3847 -3851 -3853 -3863 -3877 -3881 -3889 -3907 -3911 -3917 -3919 -3923 -3929 -3931 -3943 -3947 -3967 -3989 -4001 -4003 -4007 -4013 -4019 -4021 -4027 -4049 -4051 -4057 -4073 -4079 -4091 -4093 -4099 -4111 -4127 -4129 -4133 -4139 -4153 -4157 -4159 -4177 -4201 -4211 -4217 -4219 -4229 -4231 -4241 -4243 -4253 -4259 -4261 -4271 -4273 -4283 -4289 -4297 -4327 -4337 -4339 -4349 -4357 -4363 -4373 -4391 -4397 -4409 -4421 -4423 -4441 -4447 -4451 -4457 -4463 -4481 -4483 -4493 -4507 -4513 -4517 -4519 -4523 -4547 -4549 -4561 -4567 -4583 -4591 -4597 -4603 -4621 -4637 -4639 -4643 -4649 -4651 -4657 -4663 -4673 -4679 -4691 -4703 -4721 -4723 -4729 -4733 -4751 -4759 -4783 -4787 -4789 -4793 -4799 -4801 -4813 -4817 -4831 -4861 -4871 -4877 -4889 -4903 -4909 -4919 -4931 -4933 -4937 -4943 -4951 -4957 -4967 -4969 -4973 -4987 -4993 -4999 -5003 -5009 -5011 -5021 -5023 -5039 -5051 -5059 -5077 -5081 -5087 -5099 -5101 -5107 -5113 -5119 -5147 -5153 -5167 -5171 -5179 -5189 -5197 -5209 -5227 -5231 -5233 -5237 -5261 -5273 -5279 -5281 -5297 -5303 -5309 -5323 -5333 -5347 -5351 -5381 -5387 -5393 -5399 -5407 -5413 -5417 -5419 -5431 -5437 -5441 -5443 -5449 -5471 -5477 -5479 -5483 -5501 -5503 -5507 -5519 -5521 -5527 -5531 -5557 -5563 -5569 -5573 -5581 -5591 -5623 -5639 -5641 -5647 -5651 -5653 -5657 -5659 -5669 -5683 -5689 -5693 -5701 -5711 -5717 -5737 -5741 -5743 -5749 -5779 -5783 -5791 -5801 -5807 -5813 -5821 -5827 -5839 -5843 -5849 -5851 -5857 -5861 -5867 -5869 -5879 -5881 -5897 -5903 -5923 -5927 -5939 -5953 -5981 -5987 -6007 -6011 -6029 -6037 -6043 -6047 -6053 -6067 -6073 -6079 -6089 -6091 -6101 -6113 -6121 -6131 -6133 -6143 -6151 -6163 -6173 -6197 -6199 -6203 -6211 -6217 -6221 -6229 -6247 -6257 -6263 -6269 -6271 -6277 -6287 -6299 -6301 -6311 -6317 -6323 -6329 -6337 -6343 -6353 -6359 -6361 -6367 -6373 -6379 -6389 -6397 -6421 -6427 -6449 -6451 -6469 -6473 -6481 -6491 -6521 -6529 -6547 -6551 -6553 -6563 -6569 -6571 -6577 -6581 -6599 -6607 -6619 -6637 -6653 -6659 -6661 -6673 -6679 -6689 -6691 -6701 -6703 -6709 -6719 -6733 -6737 -6761 -6763 -6779 -6781 -6791 -6793 -6803 -6823 -6827 -6829 -6833 -6841 -6857 -6863 -6869 -6871 -6883 -6899 -6907 -6911 -6917 -6947 -6949 -6959 -6961 -6967 -6971 -6977 -6983 -6991 -6997 -7001 -7013 -7019 -7027 -7039 -7043 -7057 -7069 -7079 -7103 -7109 -7121 -7127 -7129 -7151 -7159 -7177 -7187 -7193 -7207 -7211 -7213 -7219 -7229 -7237 -7243 -7247 -7253 -7283 -7297 -7307 -7309 -7321 -7331 -7333 -7349 -7351 -7369 -7393 -7411 -7417 -7433 -7451 -7457 -7459 -7477 -7481 -7487 -7489 -7499 -7507 -7517 -7523 -7529 -7537 -7541 -7547 -7549 -7559 -7561 -7573 -7577 -7583 -7589 -7591 -7603 -7607 -7621 -7639 -7643 -7649 -7669 -7673 -7681 -7687 -7691 -7699 -7703 -7717 -7723 -7727 -7741 -7753 -7757 -7759 -7789 -7793 -7817 -7823 -7829 -7841 -7853 -7867 -7873 -7877 -7879 -7883 -7901 -7907 -7919 diff --git a/static/21_email_preview.html b/static/21_email_preview.html deleted file mode 100644 index b02167ca..00000000 --- a/static/21_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Prime Numbers CSV - - - - - - - \ No newline at end of file diff --git a/static/22_email_template.json b/static/22_email_template.json deleted file mode 100644 index ea8be43b..00000000 --- a/static/22_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "recipient@example.com", - "subject": "Prime Numbers CSV", - "plainBody": "Sehr geehrte Damen und Herren,\n\nanbei finden Sie die Datei 'prime_numbers.csv', die die Liste der Primzahlen enth\u00e4lt.\n\nMit freundlichen Gr\u00fc\u00dfen,\nIhr Team", - "htmlBody": "

Sehr geehrte Damen und Herren,

anbei finden Sie die Datei 'prime_numbers.csv', die die Liste der Primzahlen enth\u00e4lt.

Mit freundlichen Gr\u00fc\u00dfen,
Ihr Team

" -} \ No newline at end of file diff --git a/static/23_documentProcessor.py b/static/23_documentProcessor.py deleted file mode 100644 index d3b637e1..00000000 --- a/static/23_documentProcessor.py +++ /dev/null @@ -1,933 +0,0 @@ -""" -Module for extracting content from various file formats. -Provides specialized functions for processing text, PDF, Office documents, images, etc. -""" - -import logging -import os -import io -from typing import Dict, Any, List, Optional, Union, Tuple -import base64 - -# Configure logger -logger = logging.getLogger(__name__) - -# Optional imports - only loaded when needed -pdfExtractorLoaded = False -officeExtractorLoaded = False -imageProcessorLoaded = False - -def getDocumentContents(fileMetadata: Dict[str, Any], fileContent: bytes) -> List[Dict[str, Any]]: - """ - Main function for extracting content from a file based on its MIME type. - Delegates to specialized extraction functions. - - Args: - fileMetadata: File metadata (Name, MIME type, etc.) - fileContent: Binary data of the file - - Returns: - List of Document-Content objects with metadata and base64Encoded flag - """ - try: - mimeType = fileMetadata.get("mimeType", "application/octet-stream") - fileName = fileMetadata.get("name", "unknown") - - logger.info(f"Extracting content from file '{fileName}' (MIME type: {mimeType})") - - # Extract content based on MIME type - contents = [] - - # Text-based formats (excluding CSV which has its own handler) - if mimeType == "text/csv": - contents.extend(extractCsvContent(fileName, fileContent)) - - # Then handle other text-based formats - elif mimeType.startswith("text/") or mimeType in [ - "application/json", - "application/xml", - "application/javascript", - "application/x-python" - ]: - contents.extend(extractTextContent(fileName, fileContent, mimeType)) - - # SVG Files - elif mimeType == "image/svg+xml": - contents.extend(extractSvgContent(fileName, fileContent)) - - # Images - elif mimeType.startswith("image/"): - contents.extend(extractImageContent(fileName, fileContent, mimeType)) - - # PDF Documents - elif mimeType == "application/pdf": - contents.extend(extractPdfContent(fileName, fileContent)) - - # Word Documents - elif mimeType in [ - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "application/msword" - ]: - contents.extend(extractWordContent(fileName, fileContent, mimeType)) - - # Excel Documents - elif mimeType in [ - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "application/vnd.ms-excel" - ]: - contents.extend(extractExcelContent(fileName, fileContent, mimeType)) - - # PowerPoint Documents - elif mimeType in [ - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "application/vnd.ms-powerpoint" - ]: - contents.extend(extractPowerpointContent(fileName, fileContent, mimeType)) - - # Binary data as fallback for unknown formats - else: - contents.extend(extractBinaryContent(fileName, fileContent, mimeType)) - - # Fallback when no content could be extracted - if not contents: - logger.warning(f"No content extracted from file '{fileName}', using binary fallback") - - # Convert binary content to base64 - encoded_data = base64.b64encode(fileContent).decode('utf-8') - - contents.append({ - "sequenceNr": 1, - "name": '1_undefined', - "ext": os.path.splitext(fileName)[1][1:] if os.path.splitext(fileName)[1] else "bin", - "contentType": mimeType, - "data": encoded_data, - "base64Encoded": True, - "metadata": { - "isText": False - } - }) - - # Add generic attributes for all documents - for content in contents: - # Make sure all content items have the base64Encoded flag - if "base64Encoded" not in content: - if isinstance(content.get("data"), bytes): - # Convert bytes to base64 - content["data"] = base64.b64encode(content["data"]).decode('utf-8') - content["base64Encoded"] = True - else: - # Assume text content if not explicitly marked - content["base64Encoded"] = False - - # Maintain backward compatibility with old "base64Encoded" flag in metadata - if "metadata" not in content: - content["metadata"] = {} - - # Set base64Encoded in metadata for backward compatibility - content["metadata"]["base64Encoded"] = content["base64Encoded"] - - logger.info(f"Successfully extracted {len(contents)} content items from file '{fileName}'") - return contents - - except Exception as e: - logger.error(f"Error during content extraction: {str(e)}") - # Fallback on error - return original data - return [{ - "sequenceNr": 1, - "name": fileMetadata.get("name", "unknown"), - "ext": os.path.splitext(fileMetadata.get("name", ""))[1][1:] if os.path.splitext(fileMetadata.get("name", ""))[1] else "bin", - "contentType": fileMetadata.get("mimeType", "application/octet-stream"), - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "base64Encoded": True # For backward compatibility - } - }] - - -def _loadPdfExtractor(): - """Loads PDF extraction libraries when needed""" - global pdfExtractorLoaded - if not pdfExtractorLoaded: - try: - global PyPDF2, fitz - import PyPDF2 - import fitz # PyMuPDF for more extensive PDF processing - pdfExtractorLoaded = True - logger.info("PDF extraction libraries successfully loaded") - except ImportError as e: - logger.warning(f"PDF extraction libraries could not be loaded: {e}") - -def _loadOfficeExtractor(): - """Loads Office document extraction libraries when needed""" - global officeExtractorLoaded - if not officeExtractorLoaded: - try: - global docx, openpyxl - import docx # python-docx for Word documents - import openpyxl # for Excel files - officeExtractorLoaded = True - logger.info("Office extraction libraries successfully loaded") - except ImportError as e: - logger.warning(f"Office extraction libraries could not be loaded: {e}") - -def _loadImageProcessor(): - """Loads image processing libraries when needed""" - global imageProcessorLoaded - if not imageProcessorLoaded: - try: - global PIL, Image - from PIL import Image - imageProcessorLoaded = True - logger.info("Image processing libraries successfully loaded") - except ImportError as e: - logger.warning(f"Image processing libraries could not be loaded: {e}") - -def extractTextContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]: - """ - Extracts text from text files. - - Args: - fileName: Name of the file - fileContent: Binary data of the file - mimeType: MIME type of the file - - Returns: - List of Text-Content objects with base64Encoded = False - """ - try: - # Keep original file extension - fileExtension = os.path.splitext(fileName)[1][1:] if os.path.splitext(fileName)[1] else "txt" - - # Extract text content - textContent = fileContent.decode('utf-8') - return [{ - "sequenceNr": 1, - "name": "1_text", # Simplified naming - "ext": fileExtension, - "contentType": "text/plain", - "data": textContent, - "base64Encoded": False, - "metadata": { - "isText": True - } - }] - except UnicodeDecodeError: - logger.warning(f"Could not decode text from file '{fileName}' as UTF-8, trying alternative encodings") - try: - # Try alternative encodings - for encoding in ['latin-1', 'cp1252', 'iso-8859-1']: - try: - textContent = fileContent.decode(encoding) - logger.info(f"Text successfully decoded with encoding {encoding}") - return [{ - "sequenceNr": 1, - "name": "1_text", # Simplified naming - "ext": fileExtension, - "contentType": "text/plain", - "data": textContent, - "base64Encoded": False, - "metadata": { - "isText": True, - "encoding": encoding - } - }] - except UnicodeDecodeError: - continue - - # Fallback to binary data if no encoding works - logger.warning(f"Could not decode text, using binary data") - return [{ - "sequenceNr": 1, - "name": "1_binary", # Simplified naming - "ext": fileExtension, - "contentType": mimeType, - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False - } - }] - except Exception as e: - logger.error(f"Error in alternative text decoding: {str(e)}") - # Return binary data as fallback - return [{ - "sequenceNr": 1, - "name": "1_binary", # Simplified naming - "ext": fileExtension, - "contentType": mimeType, - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False - } - }] - -def extractCsvContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]]: - """ - Extracts content from CSV files. - - Args: - fileName: Name of the file - fileContent: Binary data of the file - - Returns: - List of CSV-Content objects with base64Encoded = False - """ - try: - # Extract text content - csvContent = fileContent.decode('utf-8') - return [{ - "sequenceNr": 1, - "name": "1_csv", # Simplified naming - "ext": "csv", - "contentType": "text/csv", - "data": csvContent, - "base64Encoded": False, - "metadata": { - "isText": True, - "format": "csv" - } - }] - except UnicodeDecodeError: - logger.warning(f"Could not decode CSV from file '{fileName}' as UTF-8, trying alternative encodings") - try: - # Try alternative encodings for CSV - for encoding in ['latin-1', 'cp1252', 'iso-8859-1']: - try: - csvContent = fileContent.decode(encoding) - logger.info(f"CSV successfully decoded with encoding {encoding}") - return [{ - "sequenceNr": 1, - "name": "1_csv", # Simplified naming - "ext": "csv", - "contentType": "text/csv", - "data": csvContent, - "base64Encoded": False, - "metadata": { - "isText": True, - "encoding": encoding, - "format": "csv" - } - }] - except UnicodeDecodeError: - continue - - # Fallback to binary data - return [{ - "sequenceNr": 1, - "name": "1_binary", # Simplified naming - "ext": "csv", - "contentType": "text/csv", - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False - } - }] - except Exception as e: - logger.error(f"Error in alternative CSV decoding: {str(e)}") - return [{ - "sequenceNr": 1, - "name": "1_binary", # Simplified naming - "ext": "csv", - "contentType": "text/csv", - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False - } - }] - -def extractSvgContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]]: - """ - Extracts content from SVG files. - - Args: - fileName: Name of the file - fileContent: Binary data of the file - - Returns: - List of SVG-Content objects with dual text/image metadata - """ - contents = [] - - try: - # Extract SVG as text content (XML) - svgText = fileContent.decode('utf-8') - - # Check if it's actually SVG by looking for the SVG tag - if " List[Dict[str, Any]]: - """ - Extracts content from image files and optionally generates metadata descriptions. - - Args: - fileName: Name of the file - fileContent: Binary data of the file - mimeType: MIME type of the file - - Returns: - List of Image-Content objects with base64Encoded = True - """ - - # Extract file extension from MIME type or filename - fileExtension = mimeType.split('/')[-1] - if fileExtension == "jpeg": - fileExtension = "jpg" - - # If possible, analyze image and extract metadata - imageMetadata = { - "isText": False, - "format": "image" - } - imageDescription = None - - try: - _loadImageProcessor() - if imageProcessorLoaded and fileContent and len(fileContent) > 0: - with io.BytesIO(fileContent) as imgStream: - try: - img = Image.open(imgStream) - # Check if the image was actually loaded - img.verify() - # To safely continue working, reload - imgStream.seek(0) - img = Image.open(imgStream) - imageMetadata.update({ - "format": img.format, - "mode": img.mode, - "width": img.width, - "height": img.height - }) - # Extract EXIF data if available - if hasattr(img, '_getexif') and callable(img._getexif): - exif = img._getexif() - if exif: - exifData = {} - for tagId, value in exif.items(): - exifData[f"tag_{tagId}"] = str(value) - imageMetadata["exif"] = exifData - - # Generate image description - imageDescription = f"Image ({img.width}x{img.height}, {img.format}, {img.mode})" - except Exception as innerE: - logger.warning(f"Error processing image: {str(innerE)}") - imageMetadata["error"] = str(innerE) - imageDescription = f"Image (unable to process: {str(innerE)})" - except Exception as e: - logger.warning(f"Could not extract image metadata: {str(e)}") - imageMetadata["error"] = str(e) - - # Convert binary image to base64 - encoded_data = base64.b64encode(fileContent).decode('utf-8') - - # Return image content - contents = [{ - "sequenceNr": 1, - "name": "1_image", # Simplified naming - "ext": fileExtension, - "contentType": mimeType, - "data": encoded_data, - "base64Encoded": True, - "metadata": imageMetadata - }] - - # If image description available, add as additional text content - if imageDescription: - contents.append({ - "sequenceNr": 2, - "name": "2_text_image_info", # Simplified naming with label - "ext": "txt", - "contentType": "text/plain", - "data": imageDescription, - "base64Encoded": False, - "metadata": { - "isText": True, - "imageDescription": True - } - }) - - return contents - -def extractPdfContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]]: - """ - Extracts text and images from PDF files. - - Args: - fileName: Name of the file - fileContent: Binary data of the file - - Returns: - List of PDF-Content objects (text and images) with appropriate base64Encoded flags - """ - contents = [] - extractedContentFound = False - - try: - # Load PDF extraction libraries - _loadPdfExtractor() - if not pdfExtractorLoaded: - logger.warning("PDF extraction not possible: Libraries not available") - # Add original file as binary content - contents.append({ - "sequenceNr": 1, - "name": "1_pdf", # Simplified naming - "ext": "pdf", - "contentType": "application/pdf", - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "format": "pdf" - } - }) - return contents - - # Extract text with PyPDF2 - extractedText = "" - pdfMetadata = {} - with io.BytesIO(fileContent) as pdfStream: - pdfReader = PyPDF2.PdfReader(pdfStream) - - # Extract metadata - pdfInfo = pdfReader.metadata or {} - for key, value in pdfInfo.items(): - if key.startswith('/'): - pdfMetadata[key[1:]] = value - else: - pdfMetadata[key] = value - - # Extract text from all pages - for pageNum in range(len(pdfReader.pages)): - page = pdfReader.pages[pageNum] - pageText = page.extract_text() - if pageText: - extractedText += f"--- Page {pageNum + 1} ---\n{pageText}\n\n" - - # If text was found, add as separate content - if extractedText.strip(): - extractedContentFound = True - contents.append({ - "sequenceNr": len(contents) + 1, - "name": f"{len(contents) + 1}_text", # Simplified naming - "ext": "txt", - "contentType": "text/plain", - "data": extractedText, - "base64Encoded": False, - "metadata": { - "isText": True, - "source": "pdf", - "pages": len(pdfReader.pages), - "pdfMetadata": pdfMetadata - } - }) - - # Extract images with PyMuPDF (fitz) - try: - with io.BytesIO(fileContent) as pdfStream: - doc = fitz.open(stream=pdfStream, filetype="pdf") - imageCount = 0 - - for pageNum in range(len(doc)): - page = doc[pageNum] - imageList = page.get_images(full=True) - - for imgIndex, imgInfo in enumerate(imageList): - try: - imageCount += 1 - xref = imgInfo[0] - baseImage = doc.extract_image(xref) - imageBytes = baseImage["image"] - imageExt = baseImage["ext"] - - # Add image as content - encode as base64 - extractedContentFound = True - contents.append({ - "sequenceNr": len(contents) + 1, - "name": f"{len(contents) + 1}_image_page{pageNum+1}_{imgIndex+1}", # Simplified naming with label - "ext": imageExt, - "contentType": f"image/{imageExt}", - "data": base64.b64encode(imageBytes).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "source": "pdf", - "page": pageNum + 1, - "index": imgIndex - } - }) - except Exception as imgE: - logger.warning(f"Error extracting image {imgIndex} on page {pageNum + 1}: {str(imgE)}") - - # Close document - doc.close() - - except Exception as imgExtractE: - logger.warning(f"Error extracting images from PDF: {str(imgExtractE)}") - - except Exception as e: - logger.error(f"Error in PDF extraction: {str(e)}") - - # If no content was extracted, add the original PDF - if not extractedContentFound: - contents.append({ - "sequenceNr": 1, - "name": "1_pdf", # Simplified naming - "ext": "pdf", - "contentType": "application/pdf", - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "format": "pdf" - } - }) - - return contents - -def extractWordContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]: - """ - Extracts text and images from Word documents. - - Args: - fileName: Name of the file - fileContent: Binary data of the file - mimeType: MIME type of the file - - Returns: - List of Word-Content objects (text and possibly images) with appropriate base64Encoded flags - """ - contents = [] - extractedContentFound = False - - # Determine file extension - fileExtension = "docx" if mimeType == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" else "doc" - - try: - # Load Office extraction libraries - _loadOfficeExtractor() - if not officeExtractorLoaded: - logger.warning("Word extraction not possible: Libraries not available") - # Add original file as binary content - contents.append({ - "sequenceNr": 1, - "name": "1_word", # Simplified naming - "ext": fileExtension, - "contentType": mimeType, - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "format": "word" - } - }) - return contents - - # Only supports DOCX (newer format) - if mimeType == "application/vnd.openxmlformats-officedocument.wordprocessingml.document": - with io.BytesIO(fileContent) as docxStream: - doc = docx.Document(docxStream) - - # Extract text - fullText = [] - for para in doc.paragraphs: - fullText.append(para.text) - - # Extract tables - for table in doc.tables: - for row in table.rows: - rowText = [] - for cell in row.cells: - rowText.append(cell.text) - fullText.append(" | ".join(rowText)) - - extractedText = "\n\n".join(fullText) - - # Add extracted text as content - if extractedText.strip(): - extractedContentFound = True - contents.append({ - "sequenceNr": 1, - "name": "1_text", # Simplified naming - "ext": "txt", - "contentType": "text/plain", - "data": extractedText, - "base64Encoded": False, - "metadata": { - "isText": True, - "source": "docx", - "paragraphCount": len(doc.paragraphs), - "tableCount": len(doc.tables) - } - }) - else: - logger.warning(f"Extraction from old Word format (DOC) not supported") - - except Exception as e: - logger.error(f"Error in Word extraction: {str(e)}") - - # If no content was extracted, add the original document - if not extractedContentFound: - contents.append({ - "sequenceNr": 1, - "name": "1_word", # Simplified naming - "ext": fileExtension, - "contentType": mimeType, - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "format": "word" - } - }) - - return contents - -def extractExcelContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]: - """ - Extracts table data from Excel files. - - Args: - fileName: Name of the file - fileContent: Binary data of the file - mimeType: MIME type of the file - - Returns: - List of Excel-Content objects with appropriate base64Encoded flags - """ - contents = [] - extractedContentFound = False - - # Determine file extension - fileExtension = "xlsx" if mimeType == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" else "xls" - - try: - # Load Office extraction libraries - _loadOfficeExtractor() - if not officeExtractorLoaded: - logger.warning("Excel extraction not possible: Libraries not available") - # Add original file as binary content - contents.append({ - "sequenceNr": 1, - "name": "1_excel", # Simplified naming - "ext": fileExtension, - "contentType": mimeType, - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "format": "excel" - } - }) - return contents - - # Only supports XLSX (newer format) - if mimeType == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": - with io.BytesIO(fileContent) as xlsxStream: - workbook = openpyxl.load_workbook(xlsxStream, data_only=True) - - # Extract each worksheet as separate CSV content - for sheetIndex, sheetName in enumerate(workbook.sheetnames): - sheet = workbook[sheetName] - - # Format data as CSV - csvRows = [] - for row in sheet.iter_rows(): - csvRow = [] - for cell in row: - value = cell.value - if value is None: - csvRow.append("") - else: - csvRow.append(str(value).replace('"', '""')) - csvRows.append(','.join(f'"{cell}"' for cell in csvRow)) - - csvContent = "\n".join(csvRows) - - # Add as CSV content - if csvContent.strip(): - extractedContentFound = True - sheetSafeName = sheetName.replace(" ", "_").replace("/", "_").replace("\\", "_") - contents.append({ - "sequenceNr": len(contents) + 1, - "name": f"{len(contents) + 1}_csv_{sheetSafeName}", # Simplified naming with sheet label - "ext": "csv", - "contentType": "text/csv", - "data": csvContent, - "base64Encoded": False, - "metadata": { - "isText": True, - "source": "xlsx", - "sheet": sheetName, - "format": "csv" - } - }) - else: - logger.warning(f"Extraction from old Excel format (XLS) not supported") - - except Exception as e: - logger.error(f"Error in Excel extraction: {str(e)}") - - # If no content was extracted, add the original document - if not extractedContentFound: - contents.append({ - "sequenceNr": 1, - "name": "1_excel", # Simplified naming - "ext": fileExtension, - "contentType": mimeType, - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "format": "excel" - } - }) - - return contents - -def extractPowerpointContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]: - """ - Extracts content from PowerPoint presentations. - - Args: - fileName: Name of the file - fileContent: Binary data of the file - mimeType: MIME type of the file - - Returns: - List of PowerPoint-Content objects with base64Encoded = True - """ - # For PowerPoint, we currently only return the original binary file - # A complete extraction would require more specialized libraries - fileExtension = "pptx" if mimeType == "application/vnd.openxmlformats-officedocument.presentationml.presentation" else "ppt" - return [{ - "sequenceNr": 1, - "name": "1_powerpoint", # Simplified naming - "ext": fileExtension, - "contentType": mimeType, - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "format": "powerpoint" - } - }] - -def extractBinaryContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]: - """ - Fallback for binary files where no specific extraction is possible. - - Args: - fileName: Name of the file - fileContent: Binary data of the file - mimeType: MIME type of the file - - Returns: - List with a binary Content object with base64Encoded = True - """ - fileExtension = os.path.splitext(fileName)[1][1:] if os.path.splitext(fileName)[1] else "bin" - return [{ - "sequenceNr": 1, - "name": "1_binary", # Simplified naming - "ext": fileExtension, - "contentType": mimeType, - "data": base64.b64encode(fileContent).decode('utf-8'), - "base64Encoded": True, - "metadata": { - "isText": False, - "format": "binary" - } - }] \ No newline at end of file diff --git a/static/24_defAttributes.py b/static/24_defAttributes.py deleted file mode 100644 index 731ecfd9..00000000 --- a/static/24_defAttributes.py +++ /dev/null @@ -1,123 +0,0 @@ -from pydantic import BaseModel, Field -from typing import List, Dict, Any, Optional - -# Define the model for attribute definitions -class AttributeDefinition(BaseModel): - name: str - label: str - type: str - required: bool = False - placeholder: Optional[str] = None - defaultValue: Optional[Any] = None - options: Optional[List[Dict[str, Any]]] = None - editable: bool = True - visible: bool = True - order: int = 0 - validation: Optional[Dict[str, Any]] = None - helpText: Optional[str] = None - -# Helper classes for type mapping -typeMappings = { - "int": "number", - "str": "string", - "float": "number", - "bool": "boolean", - "List[int]": "array", - "List[str]": "array", - "Dict[str, Any]": "object", - "Optional[str]": "string", - "Optional[int]": "number", - "Optional[Dict[str, Any]]": "object" -} - -# Special field types based on naming conventions -specialFieldTypes = { - "content": "textarea", - "description": "textarea", - "instructions": "textarea", - "password": "password", - "email": "email", - "workspaceId": "select", - "agentId": "select", - "type": "select" -} - -# Function to convert a Pydantic model into attribute definitions -def getModelAttributes(modelClass, userLanguage="de"): - """ - Converts a Pydantic model into a list of AttributeDefinition objects - """ - attributes = [] - - # Go through all fields in the model - for i, (fieldName, field) in enumerate(modelClass.__fields__.items()): - # Skip internal fields - if fieldName.startswith('_') or fieldName in ["label", "fieldLabels"]: - continue - - # Determine the field type - fieldType = typeMappings.get(str(field.type_), "string") - - # Check for special field types - if fieldName in specialFieldTypes: - fieldType = specialFieldTypes[fieldName] - - # Get the label (if available) - fieldLabel = fieldName.replace('_', ' ').capitalize() - if hasattr(modelClass, 'fieldLabels') and fieldName in modelClass.fieldLabels: - labelObj = modelClass.fieldLabels[fieldName] - fieldLabel = labelObj.getLabel(userLanguage) - - # Determine default values and required status - required = field.required - defaultValue = field.default if not field.required else None - - # Check for validation rules - validation = None - if field.validators: - validation = {"hasValidators": True} - - # Placeholder text - placeholder = f"Please enter {fieldLabel}" - - # Special options for Select fields - options = None - if fieldType == "select": - if fieldName == "type" and modelClass.__name__ == "Agent": - options = [ - {"value": "Analysis", "label": "Analysis"}, - {"value": "Transformation", "label": "Transformation"}, - {"value": "Generation", "label": "Generation"}, - {"value": "Classification", "label": "Classification"}, - {"value": "Custom", "label": "Custom"} - ] - - # Extract description from Field object - description = None - # Try to get description from various possible sources - if hasattr(field, 'field_info') and hasattr(field.field_info, 'description'): - description = field.field_info.description - elif hasattr(field, 'description'): - description = field.description - elif hasattr(field, 'schema') and hasattr(field.schema, 'description'): - description = field.schema.description - - # Create attribute definition - attrDef = AttributeDefinition( - name=fieldName, - label=fieldLabel, - type=fieldType, - required=required, - placeholder=placeholder, - defaultValue=defaultValue, - options=options, - editable=fieldName not in ["id", "mandateId", "userId", "createdAt", "uploadDate"], - visible=fieldName not in ["hashedPassword", "mandateId", "userId"], - order=i, - validation=validation, - helpText=description or "" # Set empty string as default value if no description found - ) - - attributes.append(attrDef) - - return attributes \ No newline at end of file diff --git a/static/25_email_preview.html b/static/25_email_preview.html deleted file mode 100644 index b9a1d176..00000000 --- a/static/25_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Attached: documentProcessor.py and defAttributes.py - - - - - - - \ No newline at end of file diff --git a/static/26_email_template.json b/static/26_email_template.json deleted file mode 100644 index bbc2aa46..00000000 --- a/static/26_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "recipient@example.com", - "subject": "Attached: documentProcessor.py and defAttributes.py", - "plainBody": "Sehr geehrte Damen und Herren,\n\nanbei finden Sie die angeforderten Dokumente 'documentProcessor.py' und 'defAttributes.py'. Bitte z\u00f6gern Sie nicht, sich bei Fragen oder weiteren Anliegen an uns zu wenden.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\nIhr Team", - "htmlBody": "

Sehr geehrte Damen und Herren,

anbei finden Sie die angeforderten Dokumente documentProcessor.py und defAttributes.py. Bitte z\u00f6gern Sie nicht, sich bei Fragen oder weiteren Anliegen an uns zu wenden.

Mit freundlichen Gr\u00fc\u00dfen,
Ihr Team

" -} \ No newline at end of file diff --git a/static/27_email_preview.html b/static/27_email_preview.html deleted file mode 100644 index b250da5e..00000000 --- a/static/27_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Angehängt: documentProcessor.py und defAttributes.py - - - - - - - \ No newline at end of file diff --git a/static/28_email_template.json b/static/28_email_template.json deleted file mode 100644 index e0355eea..00000000 --- a/static/28_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "team@example.com", - "subject": "Angeh\u00e4ngt: documentProcessor.py und defAttributes.py", - "plainBody": "Liebe Teammitglieder,\n\nim Anhang finden Sie die Dateien documentProcessor.py und defAttributes.py. Bitte \u00fcberpr\u00fcfen Sie diese und geben Sie mir Ihr Feedback.\n\nMit freundlichen Gr\u00fc\u00dfen,\n[Ihr Name]", - "htmlBody": "
E-Mail-Vorschau

Liebe Teammitglieder,

im Anhang finden Sie die Dateien documentProcessor.py und defAttributes.py. Bitte \u00fcberpr\u00fcfen Sie diese und geben Sie mir Ihr Feedback.

Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]

Dies ist eine Vorschau der E-Mail und kann in verschiedenen E-Mail-Clients unterschiedlich angezeigt werden.
" -} \ No newline at end of file diff --git a/static/29_email_preview.html b/static/29_email_preview.html deleted file mode 100644 index f4e97f9e..00000000 --- a/static/29_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Neuer Termin für unser Meeting - - - - - - - \ No newline at end of file diff --git a/static/2_LF-Details_Description.txt b/static/2_LF-Details_Description.txt deleted file mode 100644 index 74ea1ecd..00000000 --- a/static/2_LF-Details_Description.txt +++ /dev/null @@ -1,295 +0,0 @@ -Comprehensive Workflow and Team Roles in Product Development -============================================================ - -# Introduction to Comprehensive Workflow and Team Roles in Product Development - -## Purpose and Scope - -This guide, "Comprehensive Workflow and Team Roles in Product Development," is meticulously crafted to serve as an essential resource for professionals involved in the intricate process of product development. It aims to provide a detailed exploration of the workflows and team roles that are pivotal in transforming innovative ideas into successful products. By delving into the structured processes and collaborative dynamics that drive product development, this guide offers valuable insights into optimizing efficiency and effectiveness within technical teams. - -## Context and Background - -In today's fast-paced technological landscape, the development of a product from concept to market-ready status involves a complex interplay of various teams and tools. Each team, whether it be product management, engineering, quality assurance (QA), or operations, plays a critical role in ensuring that the product not only meets market demands but also adheres to high standards of quality and functionality. The integration of sophisticated tools for ticketing, roadmaps, and management dashboards further enhances the ability of these teams to coordinate and execute their tasks with precision. - -## Document Outline - -Readers of this guide will embark on a comprehensive journey through the product development lifecycle. The document is structured to provide: - -1. **An Overview of Product Development Workflow**: A detailed examination of the stages involved in product development, from initial concept through to deployment and maintenance. - -2. **Team Roles and Responsibilities**: Insight into the specific roles and responsibilities of the product, engineering, QA, and operations teams, highlighting how each contributes to the overall success of the product. - -3. **Tool Integration**: An analysis of the tools that facilitate seamless workflow management, including ticketing systems, roadmap planning tools, and management dashboards, and how they integrate into the daily operations of development teams. - -4. **Best Practices and Case Studies**: Practical examples and case studies that illustrate successful implementations of workflows and team collaborations in real-world scenarios. - -## Tone and Audience - -This guide is tailored for a technical audience, including product managers, engineers, QA specialists, and operations professionals. The tone is formal and professional, designed to engage readers who are seeking to deepen their understanding of product development processes and enhance their team's performance. By providing a structured and insightful examination of workflows and roles, this guide aims to empower technical teams to achieve greater synergy and success in their product development endeavors. - -Introduction ------------- - -# Introduction - -In the rapidly evolving landscape of product development, understanding the intricate workflow and the pivotal roles played by various teams is essential for achieving success. This guide, "Comprehensive Workflow and Team Roles in Product Development," aims to provide a detailed exploration of the processes and team dynamics that drive product innovation from conception to deployment. By delving into the workflow stages and the integration of essential tools, this guide serves as an invaluable resource for technical professionals seeking to optimize their product development strategies. - -## Overview of the Product Development Workflow - -The product development workflow is a structured sequence of stages that transforms initial ideas into market-ready products. This workflow is designed to ensure that each phase of development is meticulously planned and executed, minimizing risks and maximizing efficiency. The workflow typically begins with the **Input** stage, where ideas are sourced from customers, sales teams, and internal brainstorming sessions. These inputs serve as the foundation for the subsequent stages, guiding the product team in aligning development efforts with market needs and business objectives. - -The workflow progresses through several critical stages, each involving specific teams and processes: - -1. **Product Team:** - - **Discover:** - - **Collect:** The product team gathers a diverse array of ideas and inputs, ensuring a comprehensive understanding of potential opportunities. - - **Qualify:** Ideas are analyzed and matched against business goals, market trends, and feasibility, allowing the team to prioritize the most promising concepts. - -2. **Engineering Team:** - - **Design and Develop:** The engineering team translates qualified ideas into technical specifications and begins the development process, focusing on creating robust and scalable solutions. - -3. **Q&A Team:** - - **Test and Validate:** Quality assurance plays a crucial role in ensuring that the product meets the highest standards of quality and functionality. Rigorous testing and validation processes are employed to identify and rectify any issues before release. - -4. **Operations:** - - **Deploy and Monitor:** The operations team is responsible for deploying the product to the market and continuously monitoring its performance. This stage involves the integration of feedback loops to facilitate ongoing improvements and adaptations. - -## Importance of Team Roles and Tool Integration - -The success of the product development process hinges on the effective collaboration and coordination of various teams, each contributing their unique expertise and perspectives. The **Product Team** is tasked with strategic planning and market alignment, while the **Engineering Team** focuses on technical execution. The **Q&A Team** ensures quality assurance, and the **Operations Team** manages deployment and performance monitoring. - -In addition to clearly defined roles, the integration of specialized tools is crucial for streamlining workflows and enhancing productivity. Tools for **ticketing** facilitate efficient task management and communication across teams, ensuring that issues are promptly addressed and resolved. **Roadmaps** provide a visual representation of the product development timeline, helping teams stay aligned with project goals and deadlines. **Management dashboards** offer real-time insights into project progress and performance metrics, enabling informed decision-making and strategic adjustments. - -By leveraging these tools, teams can enhance collaboration, improve transparency, and maintain a cohesive approach to product development. This integration not only optimizes the workflow but also empowers teams to deliver high-quality products that meet customer expectations and drive business success. - -In conclusion, understanding the comprehensive workflow and the critical roles of each team in product development is essential for navigating the complexities of modern product innovation. This guide will delve deeper into each aspect, providing technical professionals with the knowledge and tools needed to excel in their roles and contribute to successful product outcomes. - -Teams Involved --------------- - -# Teams Involved - -In the complex landscape of product development, multiple teams collaborate to ensure the successful delivery of a product from conception to deployment. Each team plays a critical role in the workflow, contributing their expertise to different stages of the process. This section provides a detailed overview of the roles and responsibilities of the key teams involved: the Product Team, the Engineering Team, the QA Team, and the Operations Team. - -## Product Team - -The Product Team is at the forefront of the product development process, responsible for setting the vision and direction of the product. Their roles include: - -- **Discover:** - - **Collect:** The Product Team gathers ideas and inputs from various sources, including customers, sales teams, and internal stakeholders. This stage is crucial for understanding market needs and identifying potential opportunities. - - **Qualify:** Once ideas are collected, the team analyzes them to ensure alignment with business objectives and feasibility. This involves evaluating the potential impact and prioritizing ideas based on strategic goals. - -- **Define:** - - **Roadmap Creation:** The Product Team develops a product roadmap that outlines the strategic direction and key milestones. This roadmap serves as a guiding document for all teams involved in the development process. - - **Requirements Specification:** Detailed product requirements are documented, providing clear guidance for the Engineering Team. This includes user stories, acceptance criteria, and any necessary technical specifications. - -## Engineering Team - -The Engineering Team is responsible for transforming the product vision into a tangible, functional product. Their roles encompass: - -- **Design and Development:** - - **Architecture Design:** Engineers design the system architecture, ensuring scalability, reliability, and performance. This involves selecting appropriate technologies and frameworks. - - **Implementation:** The team writes code and develops features according to the specifications provided by the Product Team. They ensure that the product is built to meet the defined requirements. - -- **Integration:** - - **Tool Integration:** Engineers integrate various tools for ticketing, roadmaps, and management dashboards to streamline the development process and enhance collaboration across teams. - - **Continuous Integration/Continuous Deployment (CI/CD):** The team implements CI/CD pipelines to automate testing and deployment, ensuring rapid and reliable delivery of new features and updates. - -## QA Team - -The QA (Quality Assurance) Team plays a pivotal role in maintaining the quality and reliability of the product. Their responsibilities include: - -- **Testing:** - - **Test Planning:** The QA Team develops comprehensive test plans that cover all aspects of the product, including functionality, performance, and security. - - **Execution:** They conduct various types of testing, such as unit testing, integration testing, and user acceptance testing, to identify and resolve defects before the product reaches the end-users. - -- **Quality Control:** - - **Defect Management:** The team tracks and manages defects using ticketing systems, ensuring that issues are addressed promptly and effectively. - - **Continuous Improvement:** QA professionals analyze testing outcomes to identify areas for improvement, contributing to the enhancement of product quality over time. - -## Operations Team - -The Operations Team ensures that the product is deployed smoothly and operates efficiently in the production environment. Their roles include: - -- **Deployment:** - - **Release Management:** The Operations Team manages the release process, coordinating with other teams to ensure that deployments are executed without disruptions. - - **Environment Configuration:** They configure and maintain the production environment, ensuring that it meets the necessary requirements for optimal performance. - -- **Monitoring and Support:** - - **System Monitoring:** The team implements monitoring tools to track system performance and detect issues in real-time. This proactive approach helps in maintaining high availability and reliability. - - **Incident Response:** In the event of system failures or performance issues, the Operations Team is responsible for incident management and resolution, minimizing downtime and impact on users. - -Each team plays a vital role in the product development lifecycle, and their collaboration is essential for delivering high-quality products that meet customer needs and business objectives. By integrating tools and processes effectively, these teams ensure a seamless workflow from ideation to deployment. - -Workflow Stages ---------------- - -# Workflow Stages - -In the product development lifecycle, understanding the workflow stages is crucial for ensuring seamless collaboration among various teams and achieving successful product outcomes. This section provides a detailed overview of the workflow stages, focusing on the roles of the product, engineering, QA, and operations teams, and the integration of tools for ticketing, roadmaps, and management dashboards. - -## Input - -The initial stage of the workflow involves gathering inputs from various sources to fuel the product development process. These inputs are critical for identifying potential opportunities and challenges. - -- **Sources:** - - **Customers:** Feedback and suggestions from end-users provide valuable insights into product improvements and new features. - - **Sales:** Information from the sales team highlights market demands and competitive landscape, guiding product prioritization. - - **Internal Ideas:** Contributions from team members across the organization can lead to innovative solutions and enhancements. - -## Product Team Processes - -The product team plays a pivotal role in transforming raw inputs into actionable plans. This stage is divided into several key processes: - -- **Discover:** - - **Collect:** The product team gathers ideas and inputs from various sources, ensuring a comprehensive understanding of user needs and market trends. - - **Qualify:** Ideas are analyzed and matched against business objectives and feasibility to determine their potential impact and alignment with the company's vision. - -- **Define:** - - **Prioritize:** The team prioritizes ideas based on strategic importance, resource availability, and potential ROI. - - **Plan:** Detailed plans are developed, outlining the scope, objectives, and timelines for each initiative. - -- **Shape:** - - **Design:** The product team collaborates with designers to create wireframes and prototypes, ensuring the proposed solutions are user-friendly and effective. - - **Specification:** Detailed specifications are documented, providing clear guidance for the engineering team. - -## Engineering Team Processes - -Once the product team has defined and shaped the product, the engineering team takes over to assess and develop the technical aspects. - -- **Assessment:** - - **Feasibility Study:** Engineers evaluate the technical feasibility of the proposed solutions, identifying potential challenges and resource requirements. - - **Technical Planning:** A detailed technical plan is created, outlining the architecture, technologies, and tools to be used. - -- **Development:** - - **Implementation:** The engineering team begins coding and building the product, adhering to the specifications and timelines. - - **Integration:** New features and updates are integrated into the existing system, ensuring compatibility and performance. - -## QA Team Processes - -Quality assurance is a critical stage in the workflow, ensuring that the product meets the highest standards before deployment. - -- **Testing:** - - **Unit Testing:** Individual components are tested to ensure they function correctly in isolation. - - **Integration Testing:** The product is tested as a whole to verify that all components work together seamlessly. - -- **Validation:** - - **User Acceptance Testing (UAT):** The product is tested in real-world scenarios to validate its functionality and usability. - - **Bug Fixing:** Any issues identified during testing are addressed and resolved promptly. - -## Operations Team Processes - -The final stage involves deploying the product and monitoring its performance in the live environment. - -- **Deployment:** - - **Release Management:** The operations team manages the release process, ensuring a smooth transition from development to production. - - **Configuration:** The product is configured for optimal performance and security in the live environment. - -- **Monitoring:** - - **Performance Monitoring:** Continuous monitoring of the product's performance helps identify and address any issues proactively. - - **Feedback Loop:** Feedback from users and performance data are collected to inform future improvements and updates. - -In conclusion, each stage of the workflow is integral to the success of product development. By clearly defining roles and processes, and leveraging tools for ticketing, roadmaps, and management dashboards, teams can collaborate effectively to deliver high-quality products that meet user needs and business objectives. - -Tool Integration ----------------- - -Title: Tool Integration - -In the realm of product development, the integration of various tools is crucial to streamline processes, enhance collaboration, and ensure efficient workflow management. This section delves into the essential tools used in product development, focusing on ticketing systems, roadmap tools, and management dashboards. Each tool plays a pivotal role in facilitating communication and coordination among the product, engineering, QA, and operations teams. - -## Ticketing Systems - -Ticketing systems are the backbone of issue tracking and task management within product development. These systems enable teams to log, prioritize, and track the progress of tasks and issues throughout the development lifecycle. - -### Key Features: -- **Issue Tracking:** Allows teams to report bugs, feature requests, and other tasks, ensuring nothing falls through the cracks. -- **Prioritization:** Facilitates the organization of tasks based on urgency and importance, helping teams focus on high-impact work. -- **Collaboration:** Provides a platform for team members to discuss issues, share updates, and collaborate on solutions. -- **Integration:** Often integrates with other tools such as version control systems and CI/CD pipelines to provide a seamless workflow. - -### Examples: -- **Jira:** Widely used for its robust features and flexibility, Jira supports agile methodologies and offers extensive customization options. -- **Zendesk:** Known for its customer support capabilities, Zendesk also provides ticketing solutions that integrate customer feedback directly into the development process. - -## Roadmap Tools - -Roadmap tools are essential for strategic planning and communication of product vision and progress. They help align the product team’s efforts with business goals and provide a clear timeline for stakeholders. - -### Key Features: -- **Visualization:** Offers visual representations of product timelines, milestones, and dependencies, making it easier to communicate plans. -- **Collaboration:** Enables cross-functional teams to contribute to and update the roadmap, ensuring alignment across departments. -- **Flexibility:** Allows for adjustments as priorities shift, ensuring the roadmap remains relevant and actionable. - -### Examples: -- **Aha!:** A comprehensive tool that supports product strategy, planning, and roadmapping, with features for capturing ideas and aligning them with business objectives. -- **ProductPlan:** Known for its intuitive interface, ProductPlan allows teams to create and share roadmaps easily, facilitating stakeholder engagement. - -## Management Dashboards - -Management dashboards provide a high-level overview of project status, performance metrics, and key performance indicators (KPIs). They are crucial for decision-making and ensuring that projects stay on track. - -### Key Features: -- **Real-Time Data:** Offers up-to-date insights into project progress, resource allocation, and team performance. -- **Customization:** Allows managers to tailor dashboards to display the most relevant data for their specific needs. -- **Integration:** Connects with various data sources to provide a comprehensive view of the project landscape. - -### Examples: -- **Tableau:** A powerful analytics platform that enables the creation of interactive dashboards, providing deep insights into project data. -- **Power BI:** Microsoft's business analytics service that delivers robust data visualization and reporting capabilities, integrating seamlessly with other Microsoft tools. - -In conclusion, the integration of ticketing systems, roadmap tools, and management dashboards is vital for the efficient operation of product development teams. These tools not only enhance communication and collaboration but also provide the necessary infrastructure to manage complex workflows and align team efforts with strategic objectives. By leveraging these tools, teams can ensure a more organized, transparent, and effective product development process. - -Conclusion ----------- - -# Conclusion - -In this guide, we have explored the intricate workflow and team roles that are essential in the product development process. By understanding these components, teams can enhance their efficiency and effectiveness in bringing products to market. This conclusion will summarize the key aspects of the workflow and team roles, as well as highlight the benefits of integrating tools that facilitate seamless collaboration and management. - -## Summary of Workflow and Team Roles - -The product development process is a collaborative effort that involves multiple teams, each with distinct roles and responsibilities. The workflow begins with the **Product Team**, which is responsible for the discovery phase. This phase involves collecting inputs from various sources such as customers, sales, and internal ideas, and qualifying these inputs against business objectives and market needs. - -Once the product requirements are defined, the **Engineering Team** takes over to design and develop the product. This stage is critical as it transforms ideas into tangible solutions. The engineering team works closely with the product team to ensure that the technical specifications align with the product vision. - -The **Quality Assurance (QA) Team** plays a pivotal role in maintaining the integrity of the product. Through rigorous testing and validation, the QA team ensures that the product meets the required standards and functions as intended. Their feedback is crucial for identifying and rectifying defects before the product reaches the market. - -Finally, the **Operations Team** is responsible for deploying and maintaining the product. They ensure that the product is delivered efficiently and that any operational issues are promptly addressed. This team also monitors the product's performance and gathers data to inform future development cycles. - -## Benefits of Integrated Tools - -The integration of tools for ticketing, roadmaps, and management dashboards significantly enhances the product development process. These tools provide a centralized platform for tracking progress, managing tasks, and facilitating communication across teams. - -- **Ticketing Systems**: These systems streamline issue tracking and resolution, allowing teams to prioritize tasks and allocate resources effectively. By maintaining a clear record of issues and resolutions, teams can improve their response times and reduce downtime. - -- **Roadmaps**: Product roadmaps offer a strategic overview of the product's development trajectory. They help teams align their efforts with long-term goals and ensure that all stakeholders are informed of the product's progress and future direction. - -- **Management Dashboards**: Dashboards provide real-time insights into the development process, enabling managers to make informed decisions. They offer visibility into key performance indicators (KPIs) and facilitate the identification of bottlenecks or areas for improvement. - -In conclusion, a well-defined workflow and clear team roles are fundamental to successful product development. By leveraging integrated tools, teams can enhance their collaboration, streamline processes, and ultimately deliver high-quality products that meet market demands. As organizations continue to evolve, embracing these practices will be crucial for maintaining a competitive edge in the ever-changing landscape of product development. - -CONCLUSION ----------- - -Conclusion - -In this guide, "Comprehensive Workflow and Team Roles in Product Development," we have explored the intricate processes and collaborative efforts that drive successful product development. By dissecting the workflow, we have highlighted the critical roles played by various teams, including product management, engineering, quality assurance (QA), and operations. Each team's responsibilities are pivotal in ensuring that the product development lifecycle is efficient, effective, and aligned with organizational goals. - -Key Points Summary: -- **Product Development Workflow**: We detailed the sequential and iterative processes involved in product development, emphasizing the importance of clear communication and structured phases from ideation to deployment. -- **Team Roles**: The guide outlined the specific roles and responsibilities of the product, engineering, QA, and operations teams. Each team contributes uniquely to the development process, ensuring that products are not only built to specifications but also meet quality standards and operational requirements. -- **Tool Integration**: We discussed the integration of various tools that facilitate seamless workflow management. Tools for ticketing, roadmaps, and management dashboards play a crucial role in tracking progress, managing tasks, and ensuring transparency across teams. - -Closure and Recommendations: -Understanding the workflow and team roles in product development is essential for any organization aiming to enhance its product delivery capabilities. By implementing structured processes and fostering collaboration across teams, organizations can improve efficiency, reduce time-to-market, and increase product quality. It is recommended that teams continuously evaluate and refine their workflows and tool integrations to adapt to evolving project needs and technological advancements. - -Next Steps: -- Encourage cross-functional training to enhance team collaboration and understanding of each other's roles. -- Regularly review and update tool integrations to ensure they align with current project requirements and industry standards. -- Foster a culture of continuous improvement by soliciting feedback from all team members and stakeholders. - -Significance: -This guide serves as a foundational resource for technical teams and project managers involved in product development. By providing a comprehensive overview of workflows and team roles, it equips readers with the knowledge to optimize their development processes and achieve successful product outcomes. As the landscape of product development continues to evolve, staying informed and adaptable will be key to maintaining a competitive edge. - -Thank you for engaging with this guide. We hope it serves as a valuable asset in your product development endeavors. diff --git a/static/30_email_template.json b/static/30_email_template.json deleted file mode 100644 index b4745416..00000000 --- a/static/30_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "peter.muster@domain.com", - "subject": "Neuer Termin f\u00fcr unser Meeting", - "plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob dieser neue Termin f\u00fcr Sie passt.\n\nVielen Dank f\u00fcr Ihr Verst\u00e4ndnis.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]", - "htmlBody": "

Sehr geehrter Herr Muster,

ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob dieser neue Termin f\u00fcr Sie passt.

Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.

Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]

" -} \ No newline at end of file diff --git a/static/31_email_preview.html b/static/31_email_preview.html deleted file mode 100644 index 2e367670..00000000 --- a/static/31_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Verschiebung des Meetings auf Freitag - - - - - - - \ No newline at end of file diff --git a/static/32_email_template.json b/static/32_email_template.json deleted file mode 100644 index 852b45ec..00000000 --- a/static/32_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "peter.muster@domain.com", - "subject": "Verschiebung des Meetings auf Freitag", - "plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob Ihnen dieser neue Termin passt.\n\nVielen Dank f\u00fcr Ihr Verst\u00e4ndnis.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]", - "htmlBody": "

Sehr geehrter Herr Muster,

ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob Ihnen dieser neue Termin passt.

Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.

Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]

" -} \ No newline at end of file diff --git a/static/33_email_preview.html b/static/33_email_preview.html deleted file mode 100644 index 17c0c832..00000000 --- a/static/33_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Erneuter Versand: Verschiebung des Meetings auf Freitag - - - - - - - \ No newline at end of file diff --git a/static/34_email_template.json b/static/34_email_template.json deleted file mode 100644 index 5b95930f..00000000 --- a/static/34_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "patrick@motsch.ch", - "subject": "Erneuter Versand: Verschiebung des Meetings auf Freitag", - "plainBody": "Sehr geehrter Herr Motsch,\n\nich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob Ihnen dieser neue Termin passt.\n\nVielen Dank f\u00fcr Ihr Verst\u00e4ndnis.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]", - "htmlBody": "

Sehr geehrter Herr Motsch,

ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unser geplantes Meeting von 10 Uhr auf Freitag zu verschieben. Bitte lassen Sie mich wissen, ob Ihnen dieser neue Termin passt.

Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.

Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]

" -} \ No newline at end of file diff --git a/static/35_email_preview.html b/static/35_email_preview.html deleted file mode 100644 index 8173b7b5..00000000 --- a/static/35_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Python Scripts Attached for Your Review - - - - - - - \ No newline at end of file diff --git a/static/36_email_template.json b/static/36_email_template.json deleted file mode 100644 index cf80603f..00000000 --- a/static/36_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "example@domain.com", - "subject": "Python Scripts Attached for Your Review", - "plainBody": "Sehr geehrter Empf\u00e4nger,\n\nim Anhang finden Sie die beiden Python-Skripte, die Sie angefordert haben. Bitte z\u00f6gern Sie nicht, sich bei Fragen oder weiteren Anliegen an mich zu wenden.\n\nMit freundlichen Gr\u00fc\u00dfen,\nIhr Name", - "htmlBody": "

Sehr geehrter Empf\u00e4nger,

im Anhang finden Sie die beiden Python-Skripte, die Sie angefordert haben. Bitte z\u00f6gern Sie nicht, sich bei Fragen oder weiteren Anliegen an mich zu wenden.

Mit freundlichen Gr\u00fc\u00dfen,
Ihr Name

" -} \ No newline at end of file diff --git a/static/37_gatewayInterface.py b/static/37_gatewayInterface.py deleted file mode 100644 index 94359949..00000000 --- a/static/37_gatewayInterface.py +++ /dev/null @@ -1,522 +0,0 @@ -""" -Interface to the Gateway system. -Manages users and mandates for authentication. -""" - -import os -import logging -from typing import Dict, Any, List, Optional, Union -import importlib -from passlib.context import CryptContext - -from connectors.connectorDbJson import DatabaseConnector -from modules.configuration import APP_CONFIG - -logger = logging.getLogger(__name__) - -# Password-Hashing -pwdContext = CryptContext(schemes=["argon2"], deprecated="auto") - - -class GatewayInterface: - """ - Interface to the Gateway system. - Manages users and mandates. - """ - - def __init__(self, mandateId: int = None, userId: int = None): - """Initializes the Gateway Interface with optional mandate and user context.""" - # Context can be empty during initialization - self.mandateId = mandateId - self.userId = userId - - # Import data model module - try: - self.modelModule = importlib.import_module("modules.gatewayModel") - logger.info("gatewayModel successfully imported") - except ImportError as e: - logger.error(f"Error importing gatewayModel: {e}") - raise - - # Initialize database - self._initializeDatabase() - - # Load user information - self.currentUser = self._getCurrentUserInfo() - - # Initialize standard records if needed - self._initRecords() - - def _getCurrentUserInfo(self) -> Dict[str, Any]: - """Gets information about the current user including privileges.""" - # For initialization, set default values - userInfo = { - "id": self.userId, - "mandateId": self.mandateId, - "privilege": "user", # Default privilege level - "language": "en" - } - - # Try to load actual user info if IDs are provided - if self.userId: - userRecords = self.db.getRecordset("users", recordFilter={"id": self.userId}) - if userRecords: - user = userRecords[0] - userInfo["privilege"] = user.get("privilege", "user") - userInfo["language"] = user.get("language", "en") - - return userInfo - - def _initializeDatabase(self): - """Initializes the database connection.""" - self.db = DatabaseConnector( - dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"), - dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"), - dbUser=APP_CONFIG.get("DB_SYSTEM_USER"), - dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"), - mandateId=self.mandateId if self.mandateId else 0, - userId=self.userId if self.userId else 0 - ) - - def _initRecords(self): - """Initializes standard records in the database if they don't exist.""" - self._initRootMandate() - self._initAdminUser() - - def _initRootMandate(self): - """Creates the Root mandate if it doesn't exist.""" - existingMandateId = self.getInitialId("mandates") - mandates = self.db.getRecordset("mandates") - if existingMandateId is None or not mandates: - logger.info("Creating Root mandate") - rootMandate = { - "name": "Root", - "language": "de" - } - createdMandate = self.db.recordCreate("mandates", rootMandate) - logger.info(f"Root mandate created with ID {createdMandate['id']}") - - # Update mandate context - self.mandateId = createdMandate['id'] - - def _initAdminUser(self): - """Creates the Admin user if it doesn't exist.""" - existingUserId = self.getInitialId("users") - users = self.db.getRecordset("users") - if existingUserId is None or not users: - logger.info("Creating Admin user") - adminUser = { - "mandateId": self.mandateId, - "username": "admin", - "email": "admin@example.com", - "fullName": "Administrator", - "disabled": False, - "language": "de", - "privilege": "sysadmin", - "hashedPassword": self._getPasswordHash("The 1st Poweron Admin") # Use a secure password in production! - } - createdUser = self.db.recordCreate("users", adminUser) - logger.info(f"Admin user created with ID {createdUser['id']}") - - # Update user context - self.userId = createdUser['id'] - - def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """ - Unified user access management function that filters data based on user privileges. - - Args: - table: Name of the table - recordset: Recordset to filter based on access rules - - Returns: - Filtered recordset based on user privilege level - """ - userPrivilege = self.currentUser.get("privilege", "user") - - # Apply filtering based on privilege - if userPrivilege == "sysadmin": - return recordset # System admins see all records - elif userPrivilege == "admin": - # Admins see records in their mandate - return [r for r in recordset if r.get("mandateId") == self.mandateId] - else: # Regular users - # Users only see records they own within their mandate - return [r for r in recordset - if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId] - - def _canModify(self, table: str, recordId: Optional[int] = None) -> bool: - """ - Checks if the current user can modify (create/update/delete) records in a table. - - Args: - table: Name of the table - recordId: Optional record ID for specific record check - - Returns: - Boolean indicating permission - """ - userPrivilege = self.currentUser.get("privilege", "user") - - # System admins can modify anything - if userPrivilege == "sysadmin": - return True - - # Check specific record permissions - if recordId is not None: - # Get the record to check ownership - records = self.db.getRecordset(table, recordFilter={"id": recordId}) - if not records: - return False - - record = records[0] - - # Admins can modify anything in their mandate - if userPrivilege == "admin" and record.get("mandateId") == self.mandateId: - # Exception: Can't modify Root mandate unless you are a sysadmin - if table == "mandates" and recordId == 1 and userPrivilege != "sysadmin": - return False - return True - - # Users can only modify their own records - if (record.get("mandateId") == self.mandateId and - record.get("userId") == self.userId): - return True - - return False - else: - # For general table modify permission (e.g., create) - # Admins can create anything in their mandate - if userPrivilege == "admin": - return True - - # Regular users can create most entities - if table == "mandates": - return False # Regular users can't create mandates - return True - - def getInitialId(self, table: str) -> Optional[int]: - """Returns the initial ID for a table.""" - return self.db.getInitialId(table) - - def _getPasswordHash(self, password: str) -> str: - """Creates a hash for a password.""" - return pwdContext.hash(password) - - def _verifyPassword(self, plainPassword: str, hashedPassword: str) -> bool: - """Checks if the password matches the hash.""" - return pwdContext.verify(plainPassword, hashedPassword) - - def _getCurrentTimestamp(self) -> str: - """Returns the current timestamp in ISO format.""" - from datetime import datetime - return datetime.now().isoformat() - - # Mandate methods - - def getAllMandates(self) -> List[Dict[str, Any]]: - """Returns mandates based on user access level.""" - allMandates = self.db.getRecordset("mandates") - return self._uam("mandates", allMandates) - - def getMandate(self, mandateId: int) -> Optional[Dict[str, Any]]: - """Returns a mandate by ID if user has access.""" - mandates = self.db.getRecordset("mandates", recordFilter={"id": mandateId}) - if not mandates: - return None - - filteredMandates = self._uam("mandates", mandates) - return filteredMandates[0] if filteredMandates else None - - def createMandate(self, name: str, language: str = "de") -> Dict[str, Any]: - """Creates a new mandate if user has permission.""" - if not self._canModify("mandates"): - raise PermissionError("No permission to create mandates") - - mandateData = { - "name": name, - "language": language - } - - return self.db.recordCreate("mandates", mandateData) - - def updateMandate(self, mandateId: int, mandateData: Dict[str, Any]) -> Dict[str, Any]: - """Updates a mandate if user has access.""" - # Check if the mandate exists and user has access - mandate = self.getMandate(mandateId) - if not mandate: - raise ValueError(f"Mandate with ID {mandateId} not found") - - if not self._canModify("mandates", mandateId): - raise PermissionError(f"No permission to update mandate {mandateId}") - - # Update the mandate - return self.db.recordModify("mandates", mandateId, mandateData) - - def deleteMandate(self, mandateId: int) -> bool: - """ - Deletes a mandate and all associated users and data if user has permission. - """ - # Check if the mandate exists and user has access - mandate = self.getMandate(mandateId) - if not mandate: - return False - - if not self._canModify("mandates", mandateId): - raise PermissionError(f"No permission to delete mandate {mandateId}") - - # Check if it's the initial mandate - initialMandateId = self.getInitialId("mandates") - if initialMandateId is not None and mandateId == initialMandateId: - logger.warning(f"Attempt to delete the Root mandate was prevented") - return False - - # Find all users of the mandate - users = self.getUsersByMandate(mandateId) - - # Delete all users of the mandate and their associated data - for user in users: - self.deleteUser(user["id"]) - - # Delete the mandate - success = self.db.recordDelete("mandates", mandateId) - - if success: - logger.info(f"Mandate with ID {mandateId} was successfully deleted") - else: - logger.error(f"Error deleting mandate with ID {mandateId}") - - return success - - # User methods - - def getAllUsers(self) -> List[Dict[str, Any]]: - """Returns users based on user access level.""" - allUsers = self.db.getRecordset("users") - filteredUsers = self._uam("users", allUsers) - - # Remove password hashes - for user in filteredUsers: - if "hashedPassword" in user: - del user["hashedPassword"] - - return filteredUsers - - def getUsersByMandate(self, mandateId: int) -> List[Dict[str, Any]]: - """Returns users for a specific mandate if user has access.""" - # First check if user has access to the mandate - mandate = self.getMandate(mandateId) - if not mandate: - return [] - - # Get users for this mandate - users = self.db.getRecordset("users", recordFilter={"mandateId": mandateId}) - filteredUsers = self._uam("users", users) - - # Remove password hashes - for user in filteredUsers: - if "hashedPassword" in user: - del user["hashedPassword"] - - return filteredUsers - - def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]: - """Returns a user by username.""" - users = self.db.getRecordset("users") - for user in users: - if user.get("username") == username: - return user - return None - - def getUser(self, userId: int) -> Optional[Dict[str, Any]]: - """Returns a user by ID if user has access.""" - users = self.db.getRecordset("users", recordFilter={"id": userId}) - if not users: - return None - - filteredUsers = self._uam("users", users) - if not filteredUsers: - return None - - user = filteredUsers[0] - - # Remove password hash - if "hashedPassword" in user: - userCopy = user.copy() - del userCopy["hashedPassword"] - return userCopy - - return user - - def createUser(self, username: str, password: str, email: str = None, - fullName: str = None, language: str = "de", mandateId: int = None, - disabled: bool = False, privilege: str = "user") -> Dict[str, Any]: - """Creates a new user if current user has permission.""" - # Check if the username already exists - existingUser = self.getUserByUsername(username) - if existingUser: - raise ValueError(f"User '{username}' already exists") - - # Use the provided mandateId or the current context - userMandateId = mandateId if mandateId is not None else self.mandateId - - # Check if user has access to the mandate - if userMandateId != self.mandateId and self.currentUser.get("privilege") != "sysadmin": - raise PermissionError(f"No permission to create users in mandate {userMandateId}") - - if not self._canModify("users"): - raise PermissionError("No permission to create users") - - # Check privilege escalation - if (privilege == "sysadmin" or - (privilege == "admin" and self.currentUser.get("privilege") == "user")): - raise PermissionError(f"Cannot create user with higher privilege: {privilege}") - - userData = { - "mandateId": userMandateId, - "username": username, - "email": email, - "fullName": fullName, - "disabled": disabled, - "language": language, - "privilege": privilege, - "hashedPassword": self._getPasswordHash(password) - } - - createdUser = self.db.recordCreate("users", userData) - - # Remove password hash from the response - if "hashedPassword" in createdUser: - del createdUser["hashedPassword"] - - return createdUser - - def authenticateUser(self, username: str, password: str) -> Optional[Dict[str, Any]]: - """Authenticates a user by username and password.""" - # Instead of using UAM filtering, directly get user from database - users = self.db.getRecordset("users") - user = next((u for u in users if u.get("username") == username), None) - - if not user: - return None - - if not self._verifyPassword(password, user.get("hashedPassword", "")): - return None - - # Check if the user is disabled - if user.get("disabled", False): - return None - - # Create a copy without password hash - authenticatedUser = {**user} - if "hashedPassword" in authenticatedUser: - del authenticatedUser["hashedPassword"] - - return authenticatedUser - - - def updateUser(self, userId: int, userData: Dict[str, Any]) -> Dict[str, Any]: - """Updates a user if current user has permission.""" - # Check if the user exists and current user has access - user = self.getUser(userId) - if not user: - # Try to get the raw user record for admin access check - users = self.db.getRecordset("users", recordFilter={"id": userId}) - if not users: - raise ValueError(f"User with ID {userId} not found") - - # Check if current user is admin/sysadmin - if not self._canModify("users", userId): - raise PermissionError(f"No permission to update user {userId}") - - user = users[0] - - # Check privilege escalation - if "privilege" in userData: - currentPrivilege = self.currentUser.get("privilege") - targetPrivilege = userData["privilege"] - - if (targetPrivilege == "sysadmin" and currentPrivilege != "sysadmin") or ( - targetPrivilege == "admin" and currentPrivilege == "user"): - raise PermissionError(f"Cannot escalate privilege to {targetPrivilege}") - - # If the password is being changed, hash it - if "password" in userData: - userData["hashedPassword"] = self._getPasswordHash(userData["password"]) - del userData["password"] - - # Update the user - updatedUser = self.db.recordModify("users", userId, userData) - - # Remove password hash from the response - if "hashedPassword" in updatedUser: - del updatedUser["hashedPassword"] - - return updatedUser - - def disableUser(self, userId: int) -> Dict[str, Any]: - """Disables a user if current user has permission.""" - return self.updateUser(userId, {"disabled": True}) - - def enableUser(self, userId: int) -> Dict[str, Any]: - """Enables a user if current user has permission.""" - return self.updateUser(userId, {"disabled": False}) - - def _deleteUserReferencedData(self, userId: int) -> None: - """Deletes all data associated with a user.""" - # Delete user attributes - try: - attributes = self.db.getRecordset("attributes", recordFilter={"userId": userId}) - for attribute in attributes: - self.db.recordDelete("attributes", attribute["id"]) - except Exception as e: - logger.error(f"Error deleting attributes for user {userId}: {e}") - - logger.info(f"All referenced data for user {userId} has been deleted") - - def deleteUser(self, userId: int) -> bool: - """Deletes a user and all associated data if current user has permission.""" - # Check if the user exists - users = self.db.getRecordset("users", recordFilter={"id": userId}) - if not users: - return False - - # Check if current user has permission - if not self._canModify("users", userId): - raise PermissionError(f"No permission to delete user {userId}") - - # Check if it's the initial user - initialUserId = self.getInitialId("users") - if initialUserId is not None and userId == initialUserId: - logger.warning("Attempt to delete the Root Admin was prevented") - return False - - # Delete all data associated with the user - self._deleteUserReferencedData(userId) - - # Delete the user - success = self.db.recordDelete("users", userId) - - if success: - logger.info(f"User with ID {userId} was successfully deleted") - else: - logger.error(f"Error deleting user with ID {userId}") - - return success - - -# Singleton factory for GatewayInterface instances per context -_gatewayInterfaces = {} - -def getGatewayInterface(mandateId: int = None, userId: int = None) -> GatewayInterface: - """ - Returns a GatewayInterface instance for the specified context. - Reuses existing instances. - """ - contextKey = f"{mandateId}_{userId}" - if contextKey not in _gatewayInterfaces: - _gatewayInterfaces[contextKey] = GatewayInterface(mandateId, userId) - return _gatewayInterfaces[contextKey] - -# Initialize an instance -getGatewayInterface() \ No newline at end of file diff --git a/static/38_email_preview.html b/static/38_email_preview.html deleted file mode 100644 index 1a3aba57..00000000 --- a/static/38_email_preview.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - Email Preview: Review of Attached Python Modules for Attribute and Gateway Management - - - - - - - \ No newline at end of file diff --git a/static/39_email_template.json b/static/39_email_template.json deleted file mode 100644 index a8f6ab89..00000000 --- a/static/39_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "devteam@example.com", - "subject": "Review of Attached Python Modules for Attribute and Gateway Management", - "plainBody": "Hello Development Team,\n\nPlease find attached the Python modules 'defAttributes.py' and 'gatewayInterface.py'. The 'defAttributes.py' file defines a Pydantic model for attribute definitions and includes a function to convert it into a list of AttributeDefinition objects. The 'gatewayInterface.py' file serves as an interface to the Gateway system, managing users and mandates.\n\nKindly review the attached files and provide your feedback.\n\nBest regards,\n[Your Name]", - "htmlBody": "\n\n

Hello Development Team,

\n

Please find attached the Python modules 'defAttributes.py' and 'gatewayInterface.py'. The 'defAttributes.py' file defines a Pydantic model for attribute definitions and includes a function to convert it into a list of AttributeDefinition objects. The 'gatewayInterface.py' file serves as an interface to the Gateway system, managing users and mandates.

\n

Kindly review the attached files and provide your feedback.

\n

Best regards,
[Your Name]

\n\n" -} \ No newline at end of file diff --git a/static/3_generated_code.py b/static/3_generated_code.py deleted file mode 100644 index 3b2db9ae..00000000 --- a/static/3_generated_code.py +++ /dev/null @@ -1,38 +0,0 @@ -inputFiles = [] # DO NOT CHANGE THIS LINE - -def is_prime(n): - if n <= 1: - return False - if n <= 3: - return True - if n % 2 == 0 or n % 3 == 0: - return False - i = 5 - while i * i <= n: - if n % i == 0 or n % (i + 2) == 0: - return False - i += 6 - return True - -def generate_primes(limit): - primes = [] - num = 2 - while len(primes) < limit: - if is_prime(num): - primes.append(num) - num += 1 - return primes - -primes = generate_primes(1000) -prime_numbers_content = "\n".join(map(str, primes)) - -result = { - "prime_numbers.txt": { - "content": prime_numbers_content, - "base64Encoded": False, - "contentType": "text/plain" - } -} - -import json -print(json.dumps(result)) \ No newline at end of file diff --git a/static/40_email_preview.html b/static/40_email_preview.html deleted file mode 100644 index 67062963..00000000 --- a/static/40_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Terminverschiebung: Neuer Termin am Freitag - - - - - - - \ No newline at end of file diff --git a/static/41_email_template.json b/static/41_email_template.json deleted file mode 100644 index 842f4c72..00000000 --- a/static/41_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "peter.muster@domain.com", - "subject": "Terminverschiebung: Neuer Termin am Freitag", - "plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unseren geplanten Termin um 10 Uhr zu verschieben. K\u00f6nnten wir den Termin stattdessen auf Freitag verlegen?\n\nBitte lassen Sie mich wissen, ob dieser neue Termin f\u00fcr Sie passt.\n\nVielen Dank f\u00fcr Ihr Verst\u00e4ndnis.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]", - "htmlBody": "

Sehr geehrter Herr Muster,

ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unseren geplanten Termin um 10 Uhr zu verschieben. K\u00f6nnten wir den Termin stattdessen auf Freitag verlegen?

Bitte lassen Sie mich wissen, ob dieser neue Termin f\u00fcr Sie passt.

Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.

Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]

" -} \ No newline at end of file diff --git a/static/42_gatewayInterface.py b/static/42_gatewayInterface.py deleted file mode 100644 index ea5aa542..00000000 --- a/static/42_gatewayInterface.py +++ /dev/null @@ -1,526 +0,0 @@ -""" -Interface to the Gateway system. -Manages users and mandates for authentication. -""" - -import os -import logging -from typing import Dict, Any, List, Optional, Union -import importlib -from passlib.context import CryptContext - -from connectors.connectorDbJson import DatabaseConnector -from modules.configuration import APP_CONFIG - -logger = logging.getLogger(__name__) - -# Password-Hashing -pwdContext = CryptContext(schemes=["argon2"], deprecated="auto") - - -class GatewayInterface: - """ - Interface to the Gateway system. - Manages users and mandates. - """ - - def __init__(self, mandateId: int = None, userId: int = None): - """Initializes the Gateway Interface with optional mandate and user context.""" - # Context can be empty during initialization - self.mandateId = mandateId - self.userId = userId - - # Import data model module - try: - self.modelModule = importlib.import_module("modules.gatewayModel") - logger.info("gatewayModel successfully imported") - except ImportError as e: - logger.error(f"Error importing gatewayModel: {e}") - raise - - # Initialize database - self._initializeDatabase() - - # Load user information - self.currentUser = self._getCurrentUserInfo() - - # Initialize standard records if needed - self._initRecords() - - def _getCurrentUserInfo(self) -> Dict[str, Any]: - """Gets information about the current user including privileges.""" - # For initialization, set default values - userInfo = { - "id": self.userId, - "mandateId": self.mandateId, - "privilege": "user", # Default privilege level - "language": "en" - } - - # Try to load actual user info if IDs are provided - if self.userId: - userRecords = self.db.getRecordset("users", recordFilter={"id": self.userId}) - if userRecords: - user = userRecords[0] - userInfo["privilege"] = user.get("privilege", "user") - userInfo["language"] = user.get("language", "en") - - return userInfo - - def _initializeDatabase(self): - """Initializes the database connection.""" - self.db = DatabaseConnector( - dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"), - dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"), - dbUser=APP_CONFIG.get("DB_SYSTEM_USER"), - dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"), - mandateId=self.mandateId if self.mandateId else 0, - userId=self.userId if self.userId else 0 - ) - - def _initRecords(self): - """Initializes standard records in the database if they don't exist.""" - self._initRootMandate() - self._initAdminUser() - - def _initRootMandate(self): - """Creates the Root mandate if it doesn't exist.""" - existingMandateId = self.getInitialId("mandates") - mandates = self.db.getRecordset("mandates") - if existingMandateId is None or not mandates: - logger.info("Creating Root mandate") - rootMandate = { - "name": "Root", - "language": "de" - } - createdMandate = self.db.recordCreate("mandates", rootMandate) - logger.info(f"Root mandate created with ID {createdMandate['id']}") - - # Update mandate context - self.mandateId = createdMandate['id'] - - def _initAdminUser(self): - """Creates the Admin user if it doesn't exist.""" - existingUserId = self.getInitialId("users") - users = self.db.getRecordset("users") - if existingUserId is None or not users: - logger.info("Creating Admin user") - adminUser = { - "mandateId": self.mandateId, - "username": "admin", - "email": "admin@example.com", - "fullName": "Administrator", - "disabled": False, - "language": "de", - "privilege": "sysadmin", - "hashedPassword": self._getPasswordHash("The 1st Poweron Admin") # Use a secure password in production! - } - createdUser = self.db.recordCreate("users", adminUser) - logger.info(f"Admin user created with ID {createdUser['id']}") - - # Update user context - self.userId = createdUser['id'] - - def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """ - Unified user access management function that filters data based on user privileges. - - Args: - table: Name of the table - recordset: Recordset to filter based on access rules - - Returns: - Filtered recordset based on user privilege level - """ - userPrivilege = self.currentUser.get("privilege", "user") - - # Apply filtering based on privilege - if userPrivilege == "sysadmin": - return recordset # System admins see all records - elif userPrivilege == "admin": - # Admins see records in their mandate - return [r for r in recordset if r.get("mandateId") == self.mandateId] - else: # Regular users - # Users only see records they own within their mandate - return [r for r in recordset - if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId] - - def _canModify(self, table: str, recordId: Optional[int] = None) -> bool: - """ - Checks if the current user can modify (create/update/delete) records in a table. - - Args: - table: Name of the table - recordId: Optional record ID for specific record check - - Returns: - Boolean indicating permission - """ - userPrivilege = self.currentUser.get("privilege", "user") - - # System admins can modify anything - if userPrivilege == "sysadmin": - return True - - # Check specific record permissions - if recordId is not None: - # Get the record to check ownership - records = self.db.getRecordset(table, recordFilter={"id": recordId}) - if not records: - return False - - record = records[0] - - # Admins can modify anything in their mandate - if userPrivilege == "admin" and record.get("mandateId") == self.mandateId: - # Exception: Can't modify Root mandate unless you are a sysadmin - if table == "mandates" and recordId == 1 and userPrivilege != "sysadmin": - return False - return True - - # Users can only modify their own records - if (record.get("mandateId") == self.mandateId and - record.get("userId") == self.userId): - return True - - return False - else: - # For general table modify permission (e.g., create) - # Admins can create anything in their mandate - if userPrivilege == "admin": - return True - - # Regular users can create most entities - if table == "mandates": - return False # Regular users can't create mandates - return True - - def getInitialId(self, table: str) -> Optional[int]: - """Returns the initial ID for a table.""" - return self.db.getInitialId(table) - - def _getPasswordHash(self, password: str) -> str: - """Creates a hash for a password.""" - return pwdContext.hash(password) - - def _verifyPassword(self, plainPassword: str, hashedPassword: str) -> bool: - """Checks if the password matches the hash.""" - return pwdContext.verify(plainPassword, hashedPassword) - - def _getCurrentTimestamp(self) -> str: - """Returns the current timestamp in ISO format.""" - from datetime import datetime - return datetime.now().isoformat() - - # Mandate methods - - def getAllMandates(self) -> List[Dict[str, Any]]: - """Returns mandates based on user access level.""" - allMandates = self.db.getRecordset("mandates") - return self._uam("mandates", allMandates) - - def getMandate(self, mandateId: int) -> Optional[Dict[str, Any]]: - """Returns a mandate by ID if user has access.""" - mandates = self.db.getRecordset("mandates", recordFilter={"id": mandateId}) - if not mandates: - return None - - filteredMandates = self._uam("mandates", mandates) - return filteredMandates[0] if filteredMandates else None - - def createMandate(self, name: str, language: str = "de") -> Dict[str, Any]: - """Creates a new mandate if user has permission.""" - if not self._canModify("mandates"): - raise PermissionError("No permission to create mandates") - - mandateData = { - "name": name, - "language": language - } - - return self.db.recordCreate("mandates", mandateData) - - def updateMandate(self, mandateId: int, mandateData: Dict[str, Any]) -> Dict[str, Any]: - """Updates a mandate if user has access.""" - # Check if the mandate exists and user has access - mandate = self.getMandate(mandateId) - if not mandate: - raise ValueError(f"Mandate with ID {mandateId} not found") - - if not self._canModify("mandates", mandateId): - raise PermissionError(f"No permission to update mandate {mandateId}") - - # Update the mandate - return self.db.recordModify("mandates", mandateId, mandateData) - - def deleteMandate(self, mandateId: int) -> bool: - """ - Deletes a mandate and all associated users and data if user has permission. - """ - # Check if the mandate exists and user has access - mandate = self.getMandate(mandateId) - if not mandate: - return False - - if not self._canModify("mandates", mandateId): - raise PermissionError(f"No permission to delete mandate {mandateId}") - - # Check if it's the initial mandate - initialMandateId = self.getInitialId("mandates") - if initialMandateId is not None and mandateId == initialMandateId: - logger.warning(f"Attempt to delete the Root mandate was prevented") - return False - - # Find all users of the mandate - users = self.getUsersByMandate(mandateId) - - # Delete all users of the mandate and their associated data - for user in users: - self.deleteUser(user["id"]) - - # Delete the mandate - success = self.db.recordDelete("mandates", mandateId) - - if success: - logger.info(f"Mandate with ID {mandateId} was successfully deleted") - else: - logger.error(f"Error deleting mandate with ID {mandateId}") - - return success - - # User methods - - def getAllUsers(self) -> List[Dict[str, Any]]: - """Returns users based on user access level.""" - allUsers = self.db.getRecordset("users") - filteredUsers = self._uam("users", allUsers) - - # Remove password hashes - for user in filteredUsers: - if "hashedPassword" in user: - del user["hashedPassword"] - - return filteredUsers - - def getUsersByMandate(self, mandateId: int) -> List[Dict[str, Any]]: - """Returns users for a specific mandate if user has access.""" - # First check if user has access to the mandate - mandate = self.getMandate(mandateId) - if not mandate: - return [] - - # Get users for this mandate - users = self.db.getRecordset("users", recordFilter={"mandateId": mandateId}) - filteredUsers = self._uam("users", users) - - # Remove password hashes - for user in filteredUsers: - if "hashedPassword" in user: - del user["hashedPassword"] - - return filteredUsers - - def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]: - """Returns a user by username.""" - users = self.db.getRecordset("users") - for user in users: - if user.get("username") == username: - return user - return None - - def getUser(self, userId: int) -> Optional[Dict[str, Any]]: - """Returns a user by ID if user has access.""" - users = self.db.getRecordset("users", recordFilter={"id": userId}) - if not users: - return None - - filteredUsers = self._uam("users", users) - if not filteredUsers: - return None - - user = filteredUsers[0] - - # Remove password hash - if "hashedPassword" in user: - userCopy = user.copy() - del userCopy["hashedPassword"] - return userCopy - - return user - - def createUser(self, username: str, password: str, email: str = None, - fullName: str = None, language: str = "de", mandateId: int = None, - disabled: bool = False, privilege: str = "user") -> Dict[str, Any]: - """Creates a new user if current user has permission.""" - # Check if the username already exists - existingUser = self.getUserByUsername(username) - if existingUser: - raise ValueError(f"User '{username}' already exists") - - # Use the provided mandateId or the current context - userMandateId = mandateId if mandateId is not None else self.mandateId - - # Check if user has access to the mandate - if userMandateId != self.mandateId and self.currentUser.get("privilege") != "sysadmin": - raise PermissionError(f"No permission to create users in mandate {userMandateId}") - - if not self._canModify("users"): - raise PermissionError("No permission to create users") - - # Check privilege escalation - if (privilege == "sysadmin" or - (privilege == "admin" and self.currentUser.get("privilege") == "user")): - raise PermissionError(f"Cannot create user with higher privilege: {privilege}") - - userData = { - "mandateId": userMandateId, - "username": username, - "email": email, - "fullName": fullName, - "disabled": disabled, - "language": language, - "privilege": privilege, - "hashedPassword": self._getPasswordHash(password) - } - - createdUser = self.db.recordCreate("users", userData) - - # Remove password hash from the response - if "hashedPassword" in createdUser: - del createdUser["hashedPassword"] - - return createdUser - - def authenticateUser(self, username: str, password: str) -> Optional[Dict[str, Any]]: - """Authenticates a user by username and password.""" - # Clear the users table from cache and reload it - if "users" in self.db._tablesCache: - del self.db._tablesCache["users"] - - # Get fresh user data - users = self.db.getRecordset("users") - user = next((u for u in users if u.get("username") == username), None) - - if not user: - return None - - if not self._verifyPassword(password, user.get("hashedPassword", "")): - return None - - # Check if the user is disabled - if user.get("disabled", False): - return None - - # Create a copy without password hash - authenticatedUser = {**user} - if "hashedPassword" in authenticatedUser: - del authenticatedUser["hashedPassword"] - - return authenticatedUser - - - def updateUser(self, userId: int, userData: Dict[str, Any]) -> Dict[str, Any]: - """Updates a user if current user has permission.""" - # Check if the user exists and current user has access - user = self.getUser(userId) - if not user: - # Try to get the raw user record for admin access check - users = self.db.getRecordset("users", recordFilter={"id": userId}) - if not users: - raise ValueError(f"User with ID {userId} not found") - - # Check if current user is admin/sysadmin - if not self._canModify("users", userId): - raise PermissionError(f"No permission to update user {userId}") - - user = users[0] - - # Check privilege escalation - if "privilege" in userData: - currentPrivilege = self.currentUser.get("privilege") - targetPrivilege = userData["privilege"] - - if (targetPrivilege == "sysadmin" and currentPrivilege != "sysadmin") or ( - targetPrivilege == "admin" and currentPrivilege == "user"): - raise PermissionError(f"Cannot escalate privilege to {targetPrivilege}") - - # If the password is being changed, hash it - if "password" in userData: - userData["hashedPassword"] = self._getPasswordHash(userData["password"]) - del userData["password"] - - # Update the user - updatedUser = self.db.recordModify("users", userId, userData) - - # Remove password hash from the response - if "hashedPassword" in updatedUser: - del updatedUser["hashedPassword"] - - return updatedUser - - def disableUser(self, userId: int) -> Dict[str, Any]: - """Disables a user if current user has permission.""" - return self.updateUser(userId, {"disabled": True}) - - def enableUser(self, userId: int) -> Dict[str, Any]: - """Enables a user if current user has permission.""" - return self.updateUser(userId, {"disabled": False}) - - def _deleteUserReferencedData(self, userId: int) -> None: - """Deletes all data associated with a user.""" - # Delete user attributes - try: - attributes = self.db.getRecordset("attributes", recordFilter={"userId": userId}) - for attribute in attributes: - self.db.recordDelete("attributes", attribute["id"]) - except Exception as e: - logger.error(f"Error deleting attributes for user {userId}: {e}") - - logger.info(f"All referenced data for user {userId} has been deleted") - - def deleteUser(self, userId: int) -> bool: - """Deletes a user and all associated data if current user has permission.""" - # Check if the user exists - users = self.db.getRecordset("users", recordFilter={"id": userId}) - if not users: - return False - - # Check if current user has permission - if not self._canModify("users", userId): - raise PermissionError(f"No permission to delete user {userId}") - - # Check if it's the initial user - initialUserId = self.getInitialId("users") - if initialUserId is not None and userId == initialUserId: - logger.warning("Attempt to delete the Root Admin was prevented") - return False - - # Delete all data associated with the user - self._deleteUserReferencedData(userId) - - # Delete the user - success = self.db.recordDelete("users", userId) - - if success: - logger.info(f"User with ID {userId} was successfully deleted") - else: - logger.error(f"Error deleting user with ID {userId}") - - return success - - -# Singleton factory for GatewayInterface instances per context -_gatewayInterfaces = {} - -def getGatewayInterface(mandateId: int = None, userId: int = None) -> GatewayInterface: - """ - Returns a GatewayInterface instance for the specified context. - Reuses existing instances. - """ - contextKey = f"{mandateId}_{userId}" - if contextKey not in _gatewayInterfaces: - _gatewayInterfaces[contextKey] = GatewayInterface(mandateId, userId) - return _gatewayInterfaces[contextKey] - -# Initialize an instance -getGatewayInterface() \ No newline at end of file diff --git a/static/43_defAttributes.py b/static/43_defAttributes.py deleted file mode 100644 index 731ecfd9..00000000 --- a/static/43_defAttributes.py +++ /dev/null @@ -1,123 +0,0 @@ -from pydantic import BaseModel, Field -from typing import List, Dict, Any, Optional - -# Define the model for attribute definitions -class AttributeDefinition(BaseModel): - name: str - label: str - type: str - required: bool = False - placeholder: Optional[str] = None - defaultValue: Optional[Any] = None - options: Optional[List[Dict[str, Any]]] = None - editable: bool = True - visible: bool = True - order: int = 0 - validation: Optional[Dict[str, Any]] = None - helpText: Optional[str] = None - -# Helper classes for type mapping -typeMappings = { - "int": "number", - "str": "string", - "float": "number", - "bool": "boolean", - "List[int]": "array", - "List[str]": "array", - "Dict[str, Any]": "object", - "Optional[str]": "string", - "Optional[int]": "number", - "Optional[Dict[str, Any]]": "object" -} - -# Special field types based on naming conventions -specialFieldTypes = { - "content": "textarea", - "description": "textarea", - "instructions": "textarea", - "password": "password", - "email": "email", - "workspaceId": "select", - "agentId": "select", - "type": "select" -} - -# Function to convert a Pydantic model into attribute definitions -def getModelAttributes(modelClass, userLanguage="de"): - """ - Converts a Pydantic model into a list of AttributeDefinition objects - """ - attributes = [] - - # Go through all fields in the model - for i, (fieldName, field) in enumerate(modelClass.__fields__.items()): - # Skip internal fields - if fieldName.startswith('_') or fieldName in ["label", "fieldLabels"]: - continue - - # Determine the field type - fieldType = typeMappings.get(str(field.type_), "string") - - # Check for special field types - if fieldName in specialFieldTypes: - fieldType = specialFieldTypes[fieldName] - - # Get the label (if available) - fieldLabel = fieldName.replace('_', ' ').capitalize() - if hasattr(modelClass, 'fieldLabels') and fieldName in modelClass.fieldLabels: - labelObj = modelClass.fieldLabels[fieldName] - fieldLabel = labelObj.getLabel(userLanguage) - - # Determine default values and required status - required = field.required - defaultValue = field.default if not field.required else None - - # Check for validation rules - validation = None - if field.validators: - validation = {"hasValidators": True} - - # Placeholder text - placeholder = f"Please enter {fieldLabel}" - - # Special options for Select fields - options = None - if fieldType == "select": - if fieldName == "type" and modelClass.__name__ == "Agent": - options = [ - {"value": "Analysis", "label": "Analysis"}, - {"value": "Transformation", "label": "Transformation"}, - {"value": "Generation", "label": "Generation"}, - {"value": "Classification", "label": "Classification"}, - {"value": "Custom", "label": "Custom"} - ] - - # Extract description from Field object - description = None - # Try to get description from various possible sources - if hasattr(field, 'field_info') and hasattr(field.field_info, 'description'): - description = field.field_info.description - elif hasattr(field, 'description'): - description = field.description - elif hasattr(field, 'schema') and hasattr(field.schema, 'description'): - description = field.schema.description - - # Create attribute definition - attrDef = AttributeDefinition( - name=fieldName, - label=fieldLabel, - type=fieldType, - required=required, - placeholder=placeholder, - defaultValue=defaultValue, - options=options, - editable=fieldName not in ["id", "mandateId", "userId", "createdAt", "uploadDate"], - visible=fieldName not in ["hashedPassword", "mandateId", "userId"], - order=i, - validation=validation, - helpText=description or "" # Set empty string as default value if no description found - ) - - attributes.append(attrDef) - - return attributes \ No newline at end of file diff --git a/static/44_email_preview.html b/static/44_email_preview.html deleted file mode 100644 index 053e9b7c..00000000 --- a/static/44_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Anfrage zur Terminverschiebung und Dokumentenanhang - - - - - - - \ No newline at end of file diff --git a/static/45_email_template.json b/static/45_email_template.json deleted file mode 100644 index b0a9f641..00000000 --- a/static/45_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "peter.muster@domain.com", - "subject": "Anfrage zur Terminverschiebung und Dokumentenanhang", - "plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es m\u00f6glich w\u00e4re, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die Dokumente 'gatewayInterface.py' und 'defAttributes.py'.\n\nIch freue mich auf Ihre R\u00fcckmeldung.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]", - "htmlBody": "

Sehr geehrter Herr Muster,

ich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es m\u00f6glich w\u00e4re, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die Dokumente 'gatewayInterface.py' und 'defAttributes.py'.

Ich freue mich auf Ihre R\u00fcckmeldung.

Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]

" -} \ No newline at end of file diff --git a/static/46_email_preview.html b/static/46_email_preview.html deleted file mode 100644 index 6e7a2e58..00000000 --- a/static/46_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Wichtige Dokumente: gatewayInterface.py und defAttributes.py - - - - - - - \ No newline at end of file diff --git a/static/47_email_template.json b/static/47_email_template.json deleted file mode 100644 index b61972e1..00000000 --- a/static/47_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "recipient@example.com", - "subject": "Wichtige Dokumente: gatewayInterface.py und defAttributes.py", - "plainBody": "Sehr geehrte Damen und Herren,\n\nanbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte \u00fcberpr\u00fcfen Sie die Anh\u00e4nge und lassen Sie uns wissen, falls Sie weitere Informationen ben\u00f6tigen.\n\nMit freundlichen Gr\u00fc\u00dfen,\nIhr Team", - "htmlBody": "

Sehr geehrte Damen und Herren,

anbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte \u00fcberpr\u00fcfen Sie die Anh\u00e4nge und lassen Sie uns wissen, falls Sie weitere Informationen ben\u00f6tigen.

Mit freundlichen Gr\u00fc\u00dfen,
Ihr Team

" -} \ No newline at end of file diff --git a/static/48_email_preview.html b/static/48_email_preview.html deleted file mode 100644 index 9b11e454..00000000 --- a/static/48_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Resending Requested Documents - - - - - - - \ No newline at end of file diff --git a/static/49_email_template.json b/static/49_email_template.json deleted file mode 100644 index c34f0e44..00000000 --- a/static/49_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "recipient@example.com", - "subject": "Resending Requested Documents", - "plainBody": "Sehr geehrte/r Empf\u00e4nger/in,\n\nanbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte lassen Sie mich wissen, falls Sie weitere Informationen ben\u00f6tigen.\n\nMit freundlichen Gr\u00fc\u00dfen,\nIhr Team", - "htmlBody": "

Sehr geehrte/r Empf\u00e4nger/in,

anbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte lassen Sie mich wissen, falls Sie weitere Informationen ben\u00f6tigen.

Mit freundlichen Gr\u00fc\u00dfen,
Ihr Team

" -} \ No newline at end of file diff --git a/static/4_execution_history.json b/static/4_execution_history.json deleted file mode 100644 index 7ee8aa63..00000000 --- a/static/4_execution_history.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "attempt": 1, - "code": "inputFiles = [] # DO NOT CHANGE THIS LINE\n\ndef is_prime(n):\n if n <= 1:\n return False\n if n <= 3:\n return True\n if n % 2 == 0 or n % 3 == 0:\n return False\n i = 5\n while i * i <= n:\n if n % i == 0 or n % (i + 2) == 0:\n return False\n i += 6\n return True\n\ndef generate_primes(limit):\n primes = []\n num = 2\n while len(primes) < limit:\n if is_prime(num):\n primes.append(num)\n num += 1\n return primes\n\nprimes = generate_primes(1000)\nprime_numbers_content = \"\\n\".join(map(str, primes))\n\nresult = {\n \"prime_numbers.txt\": {\n \"content\": prime_numbers_content,\n \"base64Encoded\": False,\n \"contentType\": \"text/plain\"\n }\n}\n\nimport json\nprint(json.dumps(result))", - "result": { - "success": true, - "output": "{\"prime_numbers.txt\": {\"content\": \"2\\n3\\n5\\n7\\n11\\n13\\n17\\n19\\n23\\n29\\n31\\n37\\n41\\n43\\n47\\n53\\n59\\n61\\n67\\n71\\n73\\n79\\n83\\n89\\n97\\n101\\n103\\n107\\n109\\n113\\n127\\n131\\n137\\n139\\n149\\n151\\n157\\n163\\n167\\n173\\n179\\n181\\n191\\n193\\n197\\n199\\n211\\n223\\n227\\n229\\n233\\n239\\n241\\n251\\n257\\n263\\n269\\n271\\n277\\n281\\n283\\n293\\n307\\n311\\n313\\n317\\n331\\n337\\n347\\n349\\n353\\n359\\n367\\n373\\n379\\n383\\n389\\n397\\n401\\n409\\n419\\n421\\n431\\n433\\n439\\n443\\n449\\n457\\n461\\n463\\n467\\n479\\n487\\n491\\n499\\n503\\n509\\n521\\n523\\n541\\n547\\n557\\n563\\n569\\n571\\n577\\n587\\n593\\n599\\n601\\n607\\n613\\n617\\n619\\n631\\n641\\n643\\n647\\n653\\n659\\n661\\n673\\n677\\n683\\n691\\n701\\n709\\n719\\n727\\n733\\n739\\n743\\n751\\n757\\n761\\n769\\n773\\n787\\n797\\n809\\n811\\n821\\n823\\n827\\n829\\n839\\n853\\n857\\n859\\n863\\n877\\n881\\n883\\n887\\n907\\n911\\n919\\n929\\n937\\n941\\n947\\n953\\n967\\n971\\n977\\n983\\n991\\n997\\n1009\\n1013\\n1019\\n1021\\n1031\\n1033\\n1039\\n1049\\n1051\\n1061\\n1063\\n1069\\n1087\\n1091\\n1093\\n1097\\n1103\\n1109\\n1117\\n1123\\n1129\\n1151\\n1153\\n1163\\n1171\\n1181\\n1187\\n1193\\n1201\\n1213\\n1217\\n1223\\n1229\\n1231\\n1237\\n1249\\n1259\\n1277\\n1279\\n1283\\n1289\\n1291\\n1297\\n1301\\n1303\\n1307\\n1319\\n1321\\n1327\\n1361\\n1367\\n1373\\n1381\\n1399\\n1409\\n1423\\n1427\\n1429\\n1433\\n1439\\n1447\\n1451\\n1453\\n1459\\n1471\\n1481\\n1483\\n1487\\n1489\\n1493\\n1499\\n1511\\n1523\\n1531\\n1543\\n1549\\n1553\\n1559\\n1567\\n1571\\n1579\\n1583\\n1597\\n1601\\n1607\\n1609\\n1613\\n1619\\n1621\\n1627\\n1637\\n1657\\n1663\\n1667\\n1669\\n1693\\n1697\\n1699\\n1709\\n1721\\n1723\\n1733\\n1741\\n1747\\n1753\\n1759\\n1777\\n1783\\n1787\\n1789\\n1801\\n1811\\n1823\\n1831\\n1847\\n1861\\n1867\\n1871\\n1873\\n1877\\n1879\\n1889\\n1901\\n1907\\n1913\\n1931\\n1933\\n1949\\n1951\\n1973\\n1979\\n1987\\n1993\\n1997\\n1999\\n2003\\n2011\\n2017\\n2027\\n2029\\n2039\\n2053\\n2063\\n2069\\n2081\\n2083\\n2087\\n2089\\n2099\\n2111\\n2113\\n2129\\n2131\\n2137\\n2141\\n2143\\n2153\\n2161\\n2179\\n2203\\n2207\\n2213\\n2221\\n2237\\n2239\\n2243\\n2251\\n2267\\n2269\\n2273\\n2281\\n2287\\n2293\\n2297\\n2309\\n2311\\n2333\\n2339\\n2341\\n2347\\n2351\\n2357\\n2371\\n2377\\n2381\\n2383\\n2389\\n2393\\n2399\\n2411\\n2417\\n2423\\n2437\\n2441\\n2447\\n2459\\n2467\\n2473\\n2477\\n2503\\n2521\\n2531\\n2539\\n2543\\n2549\\n2551\\n2557\\n2579\\n2591\\n2593\\n2609\\n2617\\n2621\\n2633\\n2647\\n2657\\n2659\\n2663\\n2671\\n2677\\n2683\\n2687\\n2689\\n2693\\n2699\\n2707\\n2711\\n2713\\n2719\\n2729\\n2731\\n2741\\n2749\\n2753\\n2767\\n2777\\n2789\\n2791\\n2797\\n2801\\n2803\\n2819\\n2833\\n2837\\n2843\\n2851\\n2857\\n2861\\n2879\\n2887\\n2897\\n2903\\n2909\\n2917\\n2927\\n2939\\n2953\\n2957\\n2963\\n2969\\n2971\\n2999\\n3001\\n3011\\n3019\\n3023\\n3037\\n3041\\n3049\\n3061\\n3067\\n3079\\n3083\\n3089\\n3109\\n3119\\n3121\\n3137\\n3163\\n3167\\n3169\\n3181\\n3187\\n3191\\n3203\\n3209\\n3217\\n3221\\n3229\\n3251\\n3253\\n3257\\n3259\\n3271\\n3299\\n3301\\n3307\\n3313\\n3319\\n3323\\n3329\\n3331\\n3343\\n3347\\n3359\\n3361\\n3371\\n3373\\n3389\\n3391\\n3407\\n3413\\n3433\\n3449\\n3457\\n3461\\n3463\\n3467\\n3469\\n3491\\n3499\\n3511\\n3517\\n3527\\n3529\\n3533\\n3539\\n3541\\n3547\\n3557\\n3559\\n3571\\n3581\\n3583\\n3593\\n3607\\n3613\\n3617\\n3623\\n3631\\n3637\\n3643\\n3659\\n3671\\n3673\\n3677\\n3691\\n3697\\n3701\\n3709\\n3719\\n3727\\n3733\\n3739\\n3761\\n3767\\n3769\\n3779\\n3793\\n3797\\n3803\\n3821\\n3823\\n3833\\n3847\\n3851\\n3853\\n3863\\n3877\\n3881\\n3889\\n3907\\n3911\\n3917\\n3919\\n3923\\n3929\\n3931\\n3943\\n3947\\n3967\\n3989\\n4001\\n4003\\n4007\\n4013\\n4019\\n4021\\n4027\\n4049\\n4051\\n4057\\n4073\\n4079\\n4091\\n4093\\n4099\\n4111\\n4127\\n4129\\n4133\\n4139\\n4153\\n4157\\n4159\\n4177\\n4201\\n4211\\n4217\\n4219\\n4229\\n4231\\n4241\\n4243\\n4253\\n4259\\n4261\\n4271\\n4273\\n4283\\n4289\\n4297\\n4327\\n4337\\n4339\\n4349\\n4357\\n4363\\n4373\\n4391\\n4397\\n4409\\n4421\\n4423\\n4441\\n4447\\n4451\\n4457\\n4463\\n4481\\n4483\\n4493\\n4507\\n4513\\n4517\\n4519\\n4523\\n4547\\n4549\\n4561\\n4567\\n4583\\n4591\\n4597\\n4603\\n4621\\n4637\\n4639\\n4643\\n4649\\n4651\\n4657\\n4663\\n4673\\n4679\\n4691\\n4703\\n4721\\n4723\\n4729\\n4733\\n4751\\n4759\\n4783\\n4787\\n4789\\n4793\\n4799\\n4801\\n4813\\n4817\\n4831\\n4861\\n4871\\n4877\\n4889\\n4903\\n4909\\n4919\\n4931\\n4933\\n4937\\n4943\\n4951\\n4957\\n4967\\n4969\\n4973\\n4987\\n4993\\n4999\\n5003\\n5009\\n5011\\n5021\\n5023\\n5039\\n5051\\n5059\\n5077\\n5081\\n5087\\n5099\\n5101\\n5107\\n5113\\n5119\\n5147\\n5153\\n5167\\n5171\\n5179\\n5189\\n5197\\n5209\\n5227\\n5231\\n5233\\n5237\\n5261\\n5273\\n5279\\n5281\\n5297\\n5303\\n5309\\n5323\\n5333\\n5347\\n5351\\n5381\\n5387\\n5393\\n5399\\n5407\\n5413\\n5417\\n5419\\n5431\\n5437\\n5441\\n5443\\n5449\\n5471\\n5477\\n5479\\n5483\\n5501\\n5503\\n5507\\n5519\\n5521\\n5527\\n5531\\n5557\\n5563\\n5569\\n5573\\n5581\\n5591\\n5623\\n5639\\n5641\\n5647\\n5651\\n5653\\n5657\\n5659\\n5669\\n5683\\n5689\\n5693\\n5701\\n5711\\n5717\\n5737\\n5741\\n5743\\n5749\\n5779\\n5783\\n5791\\n5801\\n5807\\n5813\\n5821\\n5827\\n5839\\n5843\\n5849\\n5851\\n5857\\n5861\\n5867\\n5869\\n5879\\n5881\\n5897\\n5903\\n5923\\n5927\\n5939\\n5953\\n5981\\n5987\\n6007\\n6011\\n6029\\n6037\\n6043\\n6047\\n6053\\n6067\\n6073\\n6079\\n6089\\n6091\\n6101\\n6113\\n6121\\n6131\\n6133\\n6143\\n6151\\n6163\\n6173\\n6197\\n6199\\n6203\\n6211\\n6217\\n6221\\n6229\\n6247\\n6257\\n6263\\n6269\\n6271\\n6277\\n6287\\n6299\\n6301\\n6311\\n6317\\n6323\\n6329\\n6337\\n6343\\n6353\\n6359\\n6361\\n6367\\n6373\\n6379\\n6389\\n6397\\n6421\\n6427\\n6449\\n6451\\n6469\\n6473\\n6481\\n6491\\n6521\\n6529\\n6547\\n6551\\n6553\\n6563\\n6569\\n6571\\n6577\\n6581\\n6599\\n6607\\n6619\\n6637\\n6653\\n6659\\n6661\\n6673\\n6679\\n6689\\n6691\\n6701\\n6703\\n6709\\n6719\\n6733\\n6737\\n6761\\n6763\\n6779\\n6781\\n6791\\n6793\\n6803\\n6823\\n6827\\n6829\\n6833\\n6841\\n6857\\n6863\\n6869\\n6871\\n6883\\n6899\\n6907\\n6911\\n6917\\n6947\\n6949\\n6959\\n6961\\n6967\\n6971\\n6977\\n6983\\n6991\\n6997\\n7001\\n7013\\n7019\\n7027\\n7039\\n7043\\n7057\\n7069\\n7079\\n7103\\n7109\\n7121\\n7127\\n7129\\n7151\\n7159\\n7177\\n7187\\n7193\\n7207\\n7211\\n7213\\n7219\\n7229\\n7237\\n7243\\n7247\\n7253\\n7283\\n7297\\n7307\\n7309\\n7321\\n7331\\n7333\\n7349\\n7351\\n7369\\n7393\\n7411\\n7417\\n7433\\n7451\\n7457\\n7459\\n7477\\n7481\\n7487\\n7489\\n7499\\n7507\\n7517\\n7523\\n7529\\n7537\\n7541\\n7547\\n7549\\n7559\\n7561\\n7573\\n7577\\n7583\\n7589\\n7591\\n7603\\n7607\\n7621\\n7639\\n7643\\n7649\\n7669\\n7673\\n7681\\n7687\\n7691\\n7699\\n7703\\n7717\\n7723\\n7727\\n7741\\n7753\\n7757\\n7759\\n7789\\n7793\\n7817\\n7823\\n7829\\n7841\\n7853\\n7867\\n7873\\n7877\\n7879\\n7883\\n7901\\n7907\\n7919\", \"base64Encoded\": false, \"contentType\": \"text/plain\"}}\n", - "error": "", - "result": { - "prime_numbers.txt": { - "content": "2\n3\n5\n7\n11\n13\n17\n19\n23\n29\n31\n37\n41\n43\n47\n53\n59\n61\n67\n71\n73\n79\n83\n89\n97\n101\n103\n107\n109\n113\n127\n131\n137\n139\n149\n151\n157\n163\n167\n173\n179\n181\n191\n193\n197\n199\n211\n223\n227\n229\n233\n239\n241\n251\n257\n263\n269\n271\n277\n281\n283\n293\n307\n311\n313\n317\n331\n337\n347\n349\n353\n359\n367\n373\n379\n383\n389\n397\n401\n409\n419\n421\n431\n433\n439\n443\n449\n457\n461\n463\n467\n479\n487\n491\n499\n503\n509\n521\n523\n541\n547\n557\n563\n569\n571\n577\n587\n593\n599\n601\n607\n613\n617\n619\n631\n641\n643\n647\n653\n659\n661\n673\n677\n683\n691\n701\n709\n719\n727\n733\n739\n743\n751\n757\n761\n769\n773\n787\n797\n809\n811\n821\n823\n827\n829\n839\n853\n857\n859\n863\n877\n881\n883\n887\n907\n911\n919\n929\n937\n941\n947\n953\n967\n971\n977\n983\n991\n997\n1009\n1013\n1019\n1021\n1031\n1033\n1039\n1049\n1051\n1061\n1063\n1069\n1087\n1091\n1093\n1097\n1103\n1109\n1117\n1123\n1129\n1151\n1153\n1163\n1171\n1181\n1187\n1193\n1201\n1213\n1217\n1223\n1229\n1231\n1237\n1249\n1259\n1277\n1279\n1283\n1289\n1291\n1297\n1301\n1303\n1307\n1319\n1321\n1327\n1361\n1367\n1373\n1381\n1399\n1409\n1423\n1427\n1429\n1433\n1439\n1447\n1451\n1453\n1459\n1471\n1481\n1483\n1487\n1489\n1493\n1499\n1511\n1523\n1531\n1543\n1549\n1553\n1559\n1567\n1571\n1579\n1583\n1597\n1601\n1607\n1609\n1613\n1619\n1621\n1627\n1637\n1657\n1663\n1667\n1669\n1693\n1697\n1699\n1709\n1721\n1723\n1733\n1741\n1747\n1753\n1759\n1777\n1783\n1787\n1789\n1801\n1811\n1823\n1831\n1847\n1861\n1867\n1871\n1873\n1877\n1879\n1889\n1901\n1907\n1913\n1931\n1933\n1949\n1951\n1973\n1979\n1987\n1993\n1997\n1999\n2003\n2011\n2017\n2027\n2029\n2039\n2053\n2063\n2069\n2081\n2083\n2087\n2089\n2099\n2111\n2113\n2129\n2131\n2137\n2141\n2143\n2153\n2161\n2179\n2203\n2207\n2213\n2221\n2237\n2239\n2243\n2251\n2267\n2269\n2273\n2281\n2287\n2293\n2297\n2309\n2311\n2333\n2339\n2341\n2347\n2351\n2357\n2371\n2377\n2381\n2383\n2389\n2393\n2399\n2411\n2417\n2423\n2437\n2441\n2447\n2459\n2467\n2473\n2477\n2503\n2521\n2531\n2539\n2543\n2549\n2551\n2557\n2579\n2591\n2593\n2609\n2617\n2621\n2633\n2647\n2657\n2659\n2663\n2671\n2677\n2683\n2687\n2689\n2693\n2699\n2707\n2711\n2713\n2719\n2729\n2731\n2741\n2749\n2753\n2767\n2777\n2789\n2791\n2797\n2801\n2803\n2819\n2833\n2837\n2843\n2851\n2857\n2861\n2879\n2887\n2897\n2903\n2909\n2917\n2927\n2939\n2953\n2957\n2963\n2969\n2971\n2999\n3001\n3011\n3019\n3023\n3037\n3041\n3049\n3061\n3067\n3079\n3083\n3089\n3109\n3119\n3121\n3137\n3163\n3167\n3169\n3181\n3187\n3191\n3203\n3209\n3217\n3221\n3229\n3251\n3253\n3257\n3259\n3271\n3299\n3301\n3307\n3313\n3319\n3323\n3329\n3331\n3343\n3347\n3359\n3361\n3371\n3373\n3389\n3391\n3407\n3413\n3433\n3449\n3457\n3461\n3463\n3467\n3469\n3491\n3499\n3511\n3517\n3527\n3529\n3533\n3539\n3541\n3547\n3557\n3559\n3571\n3581\n3583\n3593\n3607\n3613\n3617\n3623\n3631\n3637\n3643\n3659\n3671\n3673\n3677\n3691\n3697\n3701\n3709\n3719\n3727\n3733\n3739\n3761\n3767\n3769\n3779\n3793\n3797\n3803\n3821\n3823\n3833\n3847\n3851\n3853\n3863\n3877\n3881\n3889\n3907\n3911\n3917\n3919\n3923\n3929\n3931\n3943\n3947\n3967\n3989\n4001\n4003\n4007\n4013\n4019\n4021\n4027\n4049\n4051\n4057\n4073\n4079\n4091\n4093\n4099\n4111\n4127\n4129\n4133\n4139\n4153\n4157\n4159\n4177\n4201\n4211\n4217\n4219\n4229\n4231\n4241\n4243\n4253\n4259\n4261\n4271\n4273\n4283\n4289\n4297\n4327\n4337\n4339\n4349\n4357\n4363\n4373\n4391\n4397\n4409\n4421\n4423\n4441\n4447\n4451\n4457\n4463\n4481\n4483\n4493\n4507\n4513\n4517\n4519\n4523\n4547\n4549\n4561\n4567\n4583\n4591\n4597\n4603\n4621\n4637\n4639\n4643\n4649\n4651\n4657\n4663\n4673\n4679\n4691\n4703\n4721\n4723\n4729\n4733\n4751\n4759\n4783\n4787\n4789\n4793\n4799\n4801\n4813\n4817\n4831\n4861\n4871\n4877\n4889\n4903\n4909\n4919\n4931\n4933\n4937\n4943\n4951\n4957\n4967\n4969\n4973\n4987\n4993\n4999\n5003\n5009\n5011\n5021\n5023\n5039\n5051\n5059\n5077\n5081\n5087\n5099\n5101\n5107\n5113\n5119\n5147\n5153\n5167\n5171\n5179\n5189\n5197\n5209\n5227\n5231\n5233\n5237\n5261\n5273\n5279\n5281\n5297\n5303\n5309\n5323\n5333\n5347\n5351\n5381\n5387\n5393\n5399\n5407\n5413\n5417\n5419\n5431\n5437\n5441\n5443\n5449\n5471\n5477\n5479\n5483\n5501\n5503\n5507\n5519\n5521\n5527\n5531\n5557\n5563\n5569\n5573\n5581\n5591\n5623\n5639\n5641\n5647\n5651\n5653\n5657\n5659\n5669\n5683\n5689\n5693\n5701\n5711\n5717\n5737\n5741\n5743\n5749\n5779\n5783\n5791\n5801\n5807\n5813\n5821\n5827\n5839\n5843\n5849\n5851\n5857\n5861\n5867\n5869\n5879\n5881\n5897\n5903\n5923\n5927\n5939\n5953\n5981\n5987\n6007\n6011\n6029\n6037\n6043\n6047\n6053\n6067\n6073\n6079\n6089\n6091\n6101\n6113\n6121\n6131\n6133\n6143\n6151\n6163\n6173\n6197\n6199\n6203\n6211\n6217\n6221\n6229\n6247\n6257\n6263\n6269\n6271\n6277\n6287\n6299\n6301\n6311\n6317\n6323\n6329\n6337\n6343\n6353\n6359\n6361\n6367\n6373\n6379\n6389\n6397\n6421\n6427\n6449\n6451\n6469\n6473\n6481\n6491\n6521\n6529\n6547\n6551\n6553\n6563\n6569\n6571\n6577\n6581\n6599\n6607\n6619\n6637\n6653\n6659\n6661\n6673\n6679\n6689\n6691\n6701\n6703\n6709\n6719\n6733\n6737\n6761\n6763\n6779\n6781\n6791\n6793\n6803\n6823\n6827\n6829\n6833\n6841\n6857\n6863\n6869\n6871\n6883\n6899\n6907\n6911\n6917\n6947\n6949\n6959\n6961\n6967\n6971\n6977\n6983\n6991\n6997\n7001\n7013\n7019\n7027\n7039\n7043\n7057\n7069\n7079\n7103\n7109\n7121\n7127\n7129\n7151\n7159\n7177\n7187\n7193\n7207\n7211\n7213\n7219\n7229\n7237\n7243\n7247\n7253\n7283\n7297\n7307\n7309\n7321\n7331\n7333\n7349\n7351\n7369\n7393\n7411\n7417\n7433\n7451\n7457\n7459\n7477\n7481\n7487\n7489\n7499\n7507\n7517\n7523\n7529\n7537\n7541\n7547\n7549\n7559\n7561\n7573\n7577\n7583\n7589\n7591\n7603\n7607\n7621\n7639\n7643\n7649\n7669\n7673\n7681\n7687\n7691\n7699\n7703\n7717\n7723\n7727\n7741\n7753\n7757\n7759\n7789\n7793\n7817\n7823\n7829\n7841\n7853\n7867\n7873\n7877\n7879\n7883\n7901\n7907\n7919", - "base64Encoded": false, - "contentType": "text/plain" - } - }, - "exitCode": 0 - } - } -] \ No newline at end of file diff --git a/static/50_LF-Nutshell.png b/static/50_LF-Nutshell.png deleted file mode 100644 index 5ec8cd9402f034bca1753b0c347d2c93a67760da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52108 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 V%NfG5ZtC5-@1Ff$CA*HN{s!nsTTcK0 diff --git a/static/51_PowerOn NDA 2025.pdf b/static/51_PowerOn NDA 2025.pdf deleted file mode 100644 index 1af40e6421812482e11cb9676788365ae855c27d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39612 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�f5}ug7O3CVzWHnf;Wj_yEE%qz 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�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 diff --git a/static/52_email_preview.html b/static/52_email_preview.html deleted file mode 100644 index cd1c4ae6..00000000 --- a/static/52_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Anfrage zur Terminverschiebung und Dokumente im Anhang - - - - - - - \ No newline at end of file diff --git a/static/53_email_template.json b/static/53_email_template.json deleted file mode 100644 index d47ed786..00000000 --- a/static/53_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "peter.muster@domain.com", - "subject": "Anfrage zur Terminverschiebung und Dokumente im Anhang", - "plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es m\u00f6glich w\u00e4re, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die relevanten Dokumente.\n\nVielen Dank f\u00fcr Ihre Flexibilit\u00e4t und Unterst\u00fctzung.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]", - "htmlBody": "

Sehr geehrter Herr Muster,

ich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es m\u00f6glich w\u00e4re, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die relevanten Dokumente.

Vielen Dank f\u00fcr Ihre Flexibilit\u00e4t und Unterst\u00fctzung.

Mit freundlichen Gr\u00fc\u00dfen,
[Ihr Name]

" -} \ No newline at end of file diff --git a/static/5_prime_numbers.txt b/static/5_prime_numbers.txt deleted file mode 100644 index 4dbadc38..00000000 --- a/static/5_prime_numbers.txt +++ /dev/null @@ -1,1000 +0,0 @@ -2 -3 -5 -7 -11 -13 -17 -19 -23 -29 -31 -37 -41 -43 -47 -53 -59 -61 -67 -71 -73 -79 -83 -89 -97 -101 -103 -107 -109 -113 -127 -131 -137 -139 -149 -151 -157 -163 -167 -173 -179 -181 -191 -193 -197 -199 -211 -223 -227 -229 -233 -239 -241 -251 -257 -263 -269 -271 -277 -281 -283 -293 -307 -311 -313 -317 -331 -337 -347 -349 -353 -359 -367 -373 -379 -383 -389 -397 -401 -409 -419 -421 -431 -433 -439 -443 -449 -457 -461 -463 -467 -479 -487 -491 -499 -503 -509 -521 -523 -541 -547 -557 -563 -569 -571 -577 -587 -593 -599 -601 -607 -613 -617 -619 -631 -641 -643 -647 -653 -659 -661 -673 -677 -683 -691 -701 -709 -719 -727 -733 -739 -743 -751 -757 -761 -769 -773 -787 -797 -809 -811 -821 -823 -827 -829 -839 -853 -857 -859 -863 -877 -881 -883 -887 -907 -911 -919 -929 -937 -941 -947 -953 -967 -971 -977 -983 -991 -997 -1009 -1013 -1019 -1021 -1031 -1033 -1039 -1049 -1051 -1061 -1063 -1069 -1087 -1091 -1093 -1097 -1103 -1109 -1117 -1123 -1129 -1151 -1153 -1163 -1171 -1181 -1187 -1193 -1201 -1213 -1217 -1223 -1229 -1231 -1237 -1249 -1259 -1277 -1279 -1283 -1289 -1291 -1297 -1301 -1303 -1307 -1319 -1321 -1327 -1361 -1367 -1373 -1381 -1399 -1409 -1423 -1427 -1429 -1433 -1439 -1447 -1451 -1453 -1459 -1471 -1481 -1483 -1487 -1489 -1493 -1499 -1511 -1523 -1531 -1543 -1549 -1553 -1559 -1567 -1571 -1579 -1583 -1597 -1601 -1607 -1609 -1613 -1619 -1621 -1627 -1637 -1657 -1663 -1667 -1669 -1693 -1697 -1699 -1709 -1721 -1723 -1733 -1741 -1747 -1753 -1759 -1777 -1783 -1787 -1789 -1801 -1811 -1823 -1831 -1847 -1861 -1867 -1871 -1873 -1877 -1879 -1889 -1901 -1907 -1913 -1931 -1933 -1949 -1951 -1973 -1979 -1987 -1993 -1997 -1999 -2003 -2011 -2017 -2027 -2029 -2039 -2053 -2063 -2069 -2081 -2083 -2087 -2089 -2099 -2111 -2113 -2129 -2131 -2137 -2141 -2143 -2153 -2161 -2179 -2203 -2207 -2213 -2221 -2237 -2239 -2243 -2251 -2267 -2269 -2273 -2281 -2287 -2293 -2297 -2309 -2311 -2333 -2339 -2341 -2347 -2351 -2357 -2371 -2377 -2381 -2383 -2389 -2393 -2399 -2411 -2417 -2423 -2437 -2441 -2447 -2459 -2467 -2473 -2477 -2503 -2521 -2531 -2539 -2543 -2549 -2551 -2557 -2579 -2591 -2593 -2609 -2617 -2621 -2633 -2647 -2657 -2659 -2663 -2671 -2677 -2683 -2687 -2689 -2693 -2699 -2707 -2711 -2713 -2719 -2729 -2731 -2741 -2749 -2753 -2767 -2777 -2789 -2791 -2797 -2801 -2803 -2819 -2833 -2837 -2843 -2851 -2857 -2861 -2879 -2887 -2897 -2903 -2909 -2917 -2927 -2939 -2953 -2957 -2963 -2969 -2971 -2999 -3001 -3011 -3019 -3023 -3037 -3041 -3049 -3061 -3067 -3079 -3083 -3089 -3109 -3119 -3121 -3137 -3163 -3167 -3169 -3181 -3187 -3191 -3203 -3209 -3217 -3221 -3229 -3251 -3253 -3257 -3259 -3271 -3299 -3301 -3307 -3313 -3319 -3323 -3329 -3331 -3343 -3347 -3359 -3361 -3371 -3373 -3389 -3391 -3407 -3413 -3433 -3449 -3457 -3461 -3463 -3467 -3469 -3491 -3499 -3511 -3517 -3527 -3529 -3533 -3539 -3541 -3547 -3557 -3559 -3571 -3581 -3583 -3593 -3607 -3613 -3617 -3623 -3631 -3637 -3643 -3659 -3671 -3673 -3677 -3691 -3697 -3701 -3709 -3719 -3727 -3733 -3739 -3761 -3767 -3769 -3779 -3793 -3797 -3803 -3821 -3823 -3833 -3847 -3851 -3853 -3863 -3877 -3881 -3889 -3907 -3911 -3917 -3919 -3923 -3929 -3931 -3943 -3947 -3967 -3989 -4001 -4003 -4007 -4013 -4019 -4021 -4027 -4049 -4051 -4057 -4073 -4079 -4091 -4093 -4099 -4111 -4127 -4129 -4133 -4139 -4153 -4157 -4159 -4177 -4201 -4211 -4217 -4219 -4229 -4231 -4241 -4243 -4253 -4259 -4261 -4271 -4273 -4283 -4289 -4297 -4327 -4337 -4339 -4349 -4357 -4363 -4373 -4391 -4397 -4409 -4421 -4423 -4441 -4447 -4451 -4457 -4463 -4481 -4483 -4493 -4507 -4513 -4517 -4519 -4523 -4547 -4549 -4561 -4567 -4583 -4591 -4597 -4603 -4621 -4637 -4639 -4643 -4649 -4651 -4657 -4663 -4673 -4679 -4691 -4703 -4721 -4723 -4729 -4733 -4751 -4759 -4783 -4787 -4789 -4793 -4799 -4801 -4813 -4817 -4831 -4861 -4871 -4877 -4889 -4903 -4909 -4919 -4931 -4933 -4937 -4943 -4951 -4957 -4967 -4969 -4973 -4987 -4993 -4999 -5003 -5009 -5011 -5021 -5023 -5039 -5051 -5059 -5077 -5081 -5087 -5099 -5101 -5107 -5113 -5119 -5147 -5153 -5167 -5171 -5179 -5189 -5197 -5209 -5227 -5231 -5233 -5237 -5261 -5273 -5279 -5281 -5297 -5303 -5309 -5323 -5333 -5347 -5351 -5381 -5387 -5393 -5399 -5407 -5413 -5417 -5419 -5431 -5437 -5441 -5443 -5449 -5471 -5477 -5479 -5483 -5501 -5503 -5507 -5519 -5521 -5527 -5531 -5557 -5563 -5569 -5573 -5581 -5591 -5623 -5639 -5641 -5647 -5651 -5653 -5657 -5659 -5669 -5683 -5689 -5693 -5701 -5711 -5717 -5737 -5741 -5743 -5749 -5779 -5783 -5791 -5801 -5807 -5813 -5821 -5827 -5839 -5843 -5849 -5851 -5857 -5861 -5867 -5869 -5879 -5881 -5897 -5903 -5923 -5927 -5939 -5953 -5981 -5987 -6007 -6011 -6029 -6037 -6043 -6047 -6053 -6067 -6073 -6079 -6089 -6091 -6101 -6113 -6121 -6131 -6133 -6143 -6151 -6163 -6173 -6197 -6199 -6203 -6211 -6217 -6221 -6229 -6247 -6257 -6263 -6269 -6271 -6277 -6287 -6299 -6301 -6311 -6317 -6323 -6329 -6337 -6343 -6353 -6359 -6361 -6367 -6373 -6379 -6389 -6397 -6421 -6427 -6449 -6451 -6469 -6473 -6481 -6491 -6521 -6529 -6547 -6551 -6553 -6563 -6569 -6571 -6577 -6581 -6599 -6607 -6619 -6637 -6653 -6659 -6661 -6673 -6679 -6689 -6691 -6701 -6703 -6709 -6719 -6733 -6737 -6761 -6763 -6779 -6781 -6791 -6793 -6803 -6823 -6827 -6829 -6833 -6841 -6857 -6863 -6869 -6871 -6883 -6899 -6907 -6911 -6917 -6947 -6949 -6959 -6961 -6967 -6971 -6977 -6983 -6991 -6997 -7001 -7013 -7019 -7027 -7039 -7043 -7057 -7069 -7079 -7103 -7109 -7121 -7127 -7129 -7151 -7159 -7177 -7187 -7193 -7207 -7211 -7213 -7219 -7229 -7237 -7243 -7247 -7253 -7283 -7297 -7307 -7309 -7321 -7331 -7333 -7349 -7351 -7369 -7393 -7411 -7417 -7433 -7451 -7457 -7459 -7477 -7481 -7487 -7489 -7499 -7507 -7517 -7523 -7529 -7537 -7541 -7547 -7549 -7559 -7561 -7573 -7577 -7583 -7589 -7591 -7603 -7607 -7621 -7639 -7643 -7649 -7669 -7673 -7681 -7687 -7691 -7699 -7703 -7717 -7723 -7727 -7741 -7753 -7757 -7759 -7789 -7793 -7817 -7823 -7829 -7841 -7853 -7867 -7873 -7877 -7879 -7883 -7901 -7907 -7919 \ No newline at end of file diff --git a/static/6_email_preview.html b/static/6_email_preview.html deleted file mode 100644 index 2f609af2..00000000 --- a/static/6_email_preview.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - Email Preview: Verspätete Ankunft morgen - - - - - - - \ No newline at end of file diff --git a/static/7_email_template.json b/static/7_email_template.json deleted file mode 100644 index 9d50aa0d..00000000 --- a/static/7_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "i.dittrich@valueon.ch", - "subject": "Versp\u00e4tete Ankunft morgen", - "plainBody": "Hallo Ida,\n\nich wollte dich nur kurz informieren, dass ich morgen etwas sp\u00e4ter ankommen werde. Ich hoffe, das ist in Ordnung.\n\nBis dann!\n\nViele Gr\u00fc\u00dfe", - "htmlBody": "

Hallo Ida,

ich wollte dich nur kurz informieren, dass ich morgen etwas sp\u00e4ter ankommen werde. Ich hoffe, das ist in Ordnung.

Bis dann!

Viele Gr\u00fc\u00dfe

" -} \ No newline at end of file diff --git a/static/8_email_preview.html b/static/8_email_preview.html deleted file mode 100644 index dd5c9ff8..00000000 --- a/static/8_email_preview.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - Email Preview: Verspätete Ankunft morgen - - - - - - - \ No newline at end of file diff --git a/static/9_email_template.json b/static/9_email_template.json deleted file mode 100644 index 704bb247..00000000 --- a/static/9_email_template.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recipient": "i.dittrich@valueon.ch", - "subject": "Versp\u00e4tete Ankunft morgen", - "plainBody": "Hallo Ida,\n\nich wollte dich nur kurz informieren, dass ich morgen etwas sp\u00e4ter ankommen werde. Ich hoffe, das ist in Ordnung.\n\nBis dann!\n\nViele Gr\u00fc\u00dfe", - "htmlBody": "\n\n\nEmail Preview: Versp\u00e4tete Ankunft morgen\n\n\n\n
\n
\n

Email Template Preview

\n
\n
\n

To: i.dittrich@valueon.ch

\n

Subject: Versp\u00e4tete Ankunft morgen

\n
\n

Hallo Ida,

\n

ich wollte dich nur kurz informieren, dass ich morgen etwas sp\u00e4ter ankommen werde. Ich hoffe, das ist in Ordnung.

\n

Bis dann!

\n

Viele Gr\u00fc\u00dfe

\n
\n
\n
\n

Dies ist eine Vorschau des E-Mail-Templates.

\n
\n
\n\n" -} \ No newline at end of file diff --git a/token_storage/7d08aab9-a170-4975-8898-bc7e0a95488e.json b/token_storage/7d08aab9-a170-4975-8898-bc7e0a95488e.json deleted file mode 100644 index 723a30f3..00000000 --- a/token_storage/7d08aab9-a170-4975-8898-bc7e0a95488e.json +++ /dev/null @@ -1 +0,0 @@ -{"token_type": "Bearer", "scope": "Mail.ReadWrite openid profile User.Read email", "expires_in": 5258, "ext_expires_in": 5258, "access_token": "eyJ0eXAiOiJKV1QiLCJub25jZSI6Il9nWEtUM1JCWWt0Nzk1VThwU0tKbThSQ3BvRkxGcHFpZ3dJb1AzekhGN0kiLCJhbGciOiJSUzI1NiIsIng1dCI6IkNOdjBPSTNSd3FsSEZFVm5hb01Bc2hDSDJYRSIsImtpZCI6IkNOdjBPSTNSd3FsSEZFVm5hb01Bc2hDSDJYRSJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC82YTUxYWFlYi0yNDY3LTQxODYtOTUwNC0yYTA1YWVkYzU5MWYvIiwiaWF0IjoxNzQ2NjEyOTMwLCJuYmYiOjE3NDY2MTI5MzAsImV4cCI6MTc0NjYxODQ4OSwiYWNjdCI6MCwiYWNyIjoiMSIsImFjcnMiOlsicDEiXSwiYWlvIjoiQVpRQWEvOFpBQUFBcUFIWWsrL1ZjVG5lbXJZSkZhOEZvaHhPSVZRa1hPM2FqNzRJd1F5amlxWE9KNC9HOXVFREdhLzZGS3Bjdm9aeEJYMDVXeTFFYzIyQklVTDVUWUt1MW5OR2dTUGtZU3cvOFRpSzNJbklJVlVTMlgyS25jbnFJbGhGZytWV2FEdWdicktWMTNNNXF6Q2o5MmVvU3lGejdMNXpHcXNUK3hGcS9GMEpFT2F2MG9CU0ZQT2xpbmZpRUpDVzJpMEtZbWN2IiwiYW1yIjpbInB3ZCIsIm1mYSJdLCJhcHBfZGlzcGxheW5hbWUiOiJQTSBUZXN0IC0gRW1haWwgRHJhZnQiLCJhcHBpZCI6ImM3ZTcxMTJkLTYxZGMtNGYzYS04Y2QzLTA4Y2M0Y2Q3NTA0YyIsImFwcGlkYWNyIjoiMSIsImZhbWlseV9uYW1lIjoiTW90c2NoIiwiZ2l2ZW5fbmFtZSI6IlBhdHJpY2siLCJpZHR5cCI6InVzZXIiLCJpcGFkZHIiOiIxMDkuMTY0LjI1NC4yNDkiLCJuYW1lIjoiUGF0cmljayBNb3RzY2giLCJvaWQiOiI3ZDA4YWFiOS1hMTcwLTQ5NzUtODg5OC1iYzdlMGE5NTQ4OGUiLCJwbGF0ZiI6IjMiLCJwdWlkIjoiMTAwMzdGRkU4Q0RENkE4MiIsInJoIjoiMS5BUXNBNjZwUmFtY2toa0dWQkNvRnJ0eFpId01BQUFBQUFBQUF3QUFBQUFBQUFBQ0VBREFMQUEuIiwic2NwIjoiTWFpbC5SZWFkV3JpdGUgb3BlbmlkIHByb2ZpbGUgVXNlci5SZWFkIGVtYWlsIiwic2lkIjoiMDAyMDg4MzktZjE5NS1jYTJiLTk1ODYtMTdhN2RlMzk1NTFmIiwic2lnbmluX3N0YXRlIjpbImttc2kiXSwic3ViIjoiSWcwaXAzeGFkYkxpdUt6YkZnd1ZoTklNX0R6RzB3cHhpRUZiMkpZdWNuNCIsInRlbmFudF9yZWdpb25fc2NvcGUiOiJFVSIsInRpZCI6IjZhNTFhYWViLTI0NjctNDE4Ni05NTA0LTJhMDVhZWRjNTkxZiIsInVuaXF1ZV9uYW1lIjoicC5tb3RzY2hAdmFsdWVvbi5jaCIsInVwbiI6InAubW90c2NoQHZhbHVlb24uY2giLCJ1dGkiOiJxNldYODRvSzZrZTR0NUhRUk9LRUFBIiwidmVyIjoiMS4wIiwid2lkcyI6WyIxNThjMDQ3YS1jOTA3LTQ1NTYtYjdlZi00NDY1NTFhNmI1ZjciLCI5Yjg5NWQ5Mi0yY2QzLTQ0YzctOWQwMi1hNmFjMmQ1ZWE1YzMiLCJjZjFjMzhlNS0zNjIxLTQwMDQtYTdjYi04Nzk2MjRkY2VkN2MiLCI5ZjA2MjA0ZC03M2MxLTRkNGMtODgwYS02ZWRiOTA2MDZmZDgiLCI4OTJjNTg0Mi1hOWE2LTQ2M2EtODA0MS03MmFhMDhjYTNjZjYiLCJiNzlmYmY0ZC0zZWY5LTQ2ODktODE0My03NmIxOTRlODU1MDkiXSwieG1zX2Z0ZCI6ImFaS1didjU0QW92WXVvelQxUlpfY2lOczRoLWhueE1hV2M3SUtXLVA2Q1lCWm5KaGJtTmxZeTFrYzIxeiIsInhtc19pZHJlbCI6IjEgMTgiLCJ4bXNfc3QiOnsic3ViIjoiUjJ2RDBHMW1tYVlSQzdKWVdjSVNaVzJLRFBnTkJqQkxGbDZlTEFCX1BVTSJ9LCJ4bXNfdGNkdCI6MTQxODIxNDUwMSwieG1zX3RkYnIiOiJFVSJ9.CrvSaMBnkX0orusNqR90PHxu2TTGWTh11EGpxyyrQPsAw8JWVHiCjxrdLPHzXi1OuBia-hCnWcm-VSVnfbPDfnI_TQmslrZVXXjxOO5zOeFUnLpoiSRbN1X81r9e6lFTl6Y7G6in21g4XdH0UbP7WMq8-3xkKVY_a1bZXrtRFBY3BzOAMOOSz2UchYTG_2w2kDuSoILzh2QXX4DMIxhaFiYDz7XBeftYjse6mJU_yPJBu_2mvw4XGhpzmhT88CNIflUyE9SzDslb099etouO66J7_6TOe4YHZ_JYG52BRK6M4ZNfnM7glrgN9tVDMnxvrT-0hvMDGNK4rZao-x2ILw", "refresh_token": "1.AQsA66pRamckhkGVBCoFrtxZHy0R58fcYTpPjNMIzEzXUEyEADALAA.AgABAwEAAABVrSpeuWamRam2jAF1XRQEAwDs_wUA9P_Q_lUJCXV8u5CZh3ubTScjIL6w0fIKC_Hgr2rqK7vyFSgxIyQ8U0jA5a_nZzRV2ZDIC5RQWt9a9NJM0LOyL55xOr3K0JE3V2u4DOFih2mF7G3o_HWU530BtJO9oBSQ9Wa4b9MOs1K30t7xHGHgt3pld_l7HTzvO62xFPscwGq4s5RvkDH-uEgbDOI0zIB_Ormo_CJN1zR9C8XCnQFAwqU_5MzEIXzVwSPN9DGU4gSrYLxKfbmU8WSluHdiCgUeNkM0D9C645zofB7FV4w8lndTiKI7ne-ZF1CPnv9lsOZiQv6OPZXPRFELxahAfMpvqWDgc9aCZUMs4pdtat_TCY593okrXCbG7y5zqXkbi6WZZgnsgqC-KcETMvQK4onL98Bndyp_Xq6-GIFEoKBwrz7fnlJ4b-14WHbEAlYV2ldcnD4EX3m5at4BjevoVUuvyz4N2d5yIaozdU2Y6Hb2wRbIWvqGAc-w_6lsix98mDhuwQQ3H0klD4czL1UcORDwoy_HhRLJcHG1iuoRBjqGjrilWvDPBORMSkRzwOv5E_uONm7co6s8Tv1zdCN1sLsiREBOeGSHNk09mEg6xddA_AOL3t45g1h0xn7vL_GNtyxq3_pW5mr-7GVtrUAd5wqfI8fE44GxSTfBv4emTuygCT1E-pYid1IvRdEgUoZp3WOeUFSd80CMyfCu6hPgoByzOwbafgPEUSZaJREoyCHp_6Zs51QerlwOZ05KHLL71WAIC6u08cyRAENC3Or9XW1YrwThbVzH65sPanDeieb7G32LfO1qYlac3mQWvxWeg0rdlKLVAc7zb7JamIzJFh1SyQ255BEGFtXbaL9R8LZ4cw-okeOTDTQgMQznBWeygwhF6I7k0y6kRw", "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkNOdjBPSTNSd3FsSEZFVm5hb01Bc2hDSDJYRSJ9.eyJhdWQiOiJjN2U3MTEyZC02MWRjLTRmM2EtOGNkMy0wOGNjNGNkNzUwNGMiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmE1MWFhZWItMjQ2Ny00MTg2LTk1MDQtMmEwNWFlZGM1OTFmL3YyLjAiLCJpYXQiOjE3NDY2MTI5MzAsIm5iZiI6MTc0NjYxMjkzMCwiZXhwIjoxNzQ2NjE2ODMwLCJhaW8iOiJBYVFBVy84WkFBQUFoazc3ZEgvVENNeGdZVTdxMGFnaUhXSzkxY3d2alpTaWU1SURkLzB5dTF2eEI1b2hBeTRrbDR6RnRwL3FlQmRLenBNcFBzUXVCajcySFl3QVZaN1lLeXpXUFkwSWJjQ0dxNTZsS25kdDFwd3RQTHJRY2xqQnhqTGZWUVhNdXRSS1JLN1FKNzNSUjJoUGxGVWJKQ1gvU3lmWTRnZ2tKaXplVVRoQVVHOHplRUFkTG1IYjRxL09rMzVuelFTVWY1aktSQ3d4Sk85cm1nci9uY2N2SVZ2RTR3PT0iLCJuYW1lIjoiUGF0cmljayBNb3RzY2giLCJvaWQiOiI3ZDA4YWFiOS1hMTcwLTQ5NzUtODg5OC1iYzdlMGE5NTQ4OGUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJwLm1vdHNjaEB2YWx1ZW9uLmNoIiwicmgiOiIxLkFRc0E2NnBSYW1ja2hrR1ZCQ29GcnR4Wkh5MFI1OGZjWVRwUGpOTUl6RXpYVUV5RUFEQUxBQS4iLCJzaWQiOiIwMDIwODgzOS1mMTk1LWNhMmItOTU4Ni0xN2E3ZGUzOTU1MWYiLCJzdWIiOiJSMnZEMEcxbW1hWVJDN0pZV2NJU1pXMktEUGdOQmpCTEZsNmVMQUJfUFVNIiwidGlkIjoiNmE1MWFhZWItMjQ2Ny00MTg2LTk1MDQtMmEwNWFlZGM1OTFmIiwidXRpIjoicTZXWDg0b0s2a2U0dDVIUVJPS0VBQSIsInZlciI6IjIuMCJ9.Nudb1JfecVFeW_kcRfbVZsfvoCOXm6c_qdNmXsL1zC1CyWU9PnLFwNtS4Cu1c8cX4NaFB2LnImmyu9vmB_zWEoYDjv_XS-2jZp0fOumzqPpWgQ3dfCicD58aa9mD46vT439YVAN8rpEC2hkK-bZw3JWauf25U-L8zgF6g39EWXIr_Y7QHEVA20hf4ND-TBJlfJ_JZXE-TqxRYDOybayQcGKF0kSJ4Kmuu6_HTKrjRemSqLnh0Q-nRoAUReskgFdx3KEuWlAjTQGaMI_wMIv_8Z5taSECM8MQNal66503Ifpk4lvqTtZM_6yQusv7q49Glg7FVk37nIx82yWKfiIqVw", "client_info": "eyJ1aWQiOiI3ZDA4YWFiOS1hMTcwLTQ5NzUtODg5OC1iYzdlMGE5NTQ4OGUiLCJ1dGlkIjoiNmE1MWFhZWItMjQ2Ny00MTg2LTk1MDQtMmEwNWFlZGM1OTFmIn0", "id_token_claims": {"aud": "c7e7112d-61dc-4f3a-8cd3-08cc4cd7504c", "iss": "https://login.microsoftonline.com/6a51aaeb-2467-4186-9504-2a05aedc591f/v2.0", "iat": 1746612930, "nbf": 1746612930, "exp": 1746616830, "aio": "AaQAW/8ZAAAAhk77dH/TCMxgYU7q0agiHWK91cwvjZSie5IDd/0yu1vxB5ohAy4kl4zFtp/qeBdKzpMpPsQuBj72HYwAVZ7YKyzWPY0IbcCGq56lKndt1pwtPLrQcljBxjLfVQXMutRKRK7QJ73RR2hPlFUbJCX/SyfY4ggkJizeUThAUG8zeEAdLmHb4q/Ok35nzQSUf5jKRCwxJO9rmgr/nccvIVvE4w==", "name": "Patrick Motsch", "oid": "7d08aab9-a170-4975-8898-bc7e0a95488e", "preferred_username": "p.motsch@valueon.ch", "rh": "1.AQsA66pRamckhkGVBCoFrtxZHy0R58fcYTpPjNMIzEzXUEyEADALAA.", "sid": "00208839-f195-ca2b-9586-17a7de39551f", "sub": "R2vD0G1mmaYRC7JYWcISZW2KDPgNBjBLFl6eLAB_PUM", "tid": "6a51aaeb-2467-4186-9504-2a05aedc591f", "uti": "q6WX84oK6ke4t5HQROKEAA", "ver": "2.0"}, "user_info": {"name": "Patrick Motsch", "email": "p.motsch@valueon.ch", "id": "7d08aab9-a170-4975-8898-bc7e0a95488e"}} \ No newline at end of file diff --git a/tool_testBackendSingle.py b/tool_testBackendSingle.py deleted file mode 100644 index 371c6951..00000000 --- a/tool_testBackendSingle.py +++ /dev/null @@ -1,432 +0,0 @@ -#!/usr/bin/env python3 -""" -Simplified Test Runner for Workflow State Machine - -This script provides a clean and simple test runner for the workflow state machine -tests that properly handles async test methods. - -Usage: - python tool_testBackendSingle.py [test_name] - -Examples: - python tool_testBackendSingle.py # Run all tests - python tool_testBackendSingle.py test_state_1 # Run tests starting with test_state_1 -""" - -import os -import sys -import asyncio -import time -import traceback -import importlib -import inspect -from unittest.mock import patch, MagicMock, AsyncMock - -# Try to import colorama, install if not available -try: - from colorama import init, Fore, Back, Style - init() # Initialize colorama -except ImportError: - print("Installing required package: colorama") - import subprocess - subprocess.call([sys.executable, "-m", "pip", "install", "colorama"]) - from colorama import init, Fore, Back, Style - init() # Initialize colorama - - -class AsyncTestRunner: - """Simple test runner that supports async test methods""" - - def __init__(self): - """Initialize the test runner""" - self.success_count = 0 - self.failure_count = 0 - self.results = [] - self.total_time = 0 - - def print_header(self, test_case_name): - """Print a header for the test suite""" - print("\n" + "=" * 80) - print(f"{Fore.CYAN}{Style.BRIGHT}{test_case_name}{Style.RESET_ALL}") - print("=" * 80) - - def print_result(self, test_name, success, duration, error=None): - """Print a test result with appropriate formatting""" - clean_name = test_name.replace('test_', '').replace('_', ' ').title() - - if success: - status = f"{Fore.GREEN}[PASS]{Style.RESET_ALL}" - self.success_count += 1 - else: - status = f"{Fore.RED}[FAIL]{Style.RESET_ALL}" - self.failure_count += 1 - - # Print result line - print(f"{status} {clean_name} - {duration:.2f}s") - - # Print error if any - if error: - print(f" {Fore.RED}→ {error}{Style.RESET_ALL}") - if isinstance(error, Exception): - traceback.print_exception(type(error), error, error.__traceback__) - - # Store result - self.results.append({ - 'name': clean_name, - 'success': success, - 'duration': duration, - 'error': error - }) - - def print_summary(self): - """Print a summary of test results""" - print("\n" + "=" * 80) - print(f"{Fore.CYAN}{Style.BRIGHT}TEST SUMMARY{Style.RESET_ALL}") - print("-" * 80) - - # Print timing - print(f"Total execution time: {self.total_time:.2f}s") - - # Print counts - total = self.success_count + self.failure_count - print(f"Tests: {total}, Passed: {Fore.GREEN}{self.success_count}{Style.RESET_ALL}, Failed: {Fore.RED}{self.failure_count}{Style.RESET_ALL}") - - # Print overall status - if self.failure_count == 0: - print(f"\n{Fore.GREEN}{Style.BRIGHT}✓ ALL TESTS PASSED{Style.RESET_ALL}") - else: - print(f"\n{Fore.RED}{Style.BRIGHT}✗ TESTS FAILED{Style.RESET_ALL}") - - # Print failures - print(f"\n{Fore.RED}Failed tests:{Style.RESET_ALL}") - for result in self.results: - if not result['success']: - print(f" - {result['name']}") - - print("=" * 80) - - async def run_test(self, test_instance, test_method): - """Run a single test method (sync or async)""" - # Prepare test - test_name = test_method.__name__ - clean_name = test_name.replace('test_', '').replace('_', ' ').title() - - # Print start - print(f"\n{Fore.BLUE}[RUNNING]{Style.RESET_ALL} {clean_name}...") - - # Run setUp - if hasattr(test_instance, 'setUp'): - await self.run_method_with_instance(test_instance, test_instance.setUp) - - # Time the test execution - start_time = time.time() - success = True - error = None - - try: - # Run the test - ensure bound method gets called with instance - if hasattr(test_method, '__self__') and test_method.__self__ is None: - # This is an unbound method, bind it to the instance - bound_method = getattr(test_instance, test_method.__name__) - await self.run_method_with_instance(test_instance, bound_method) - else: - # This is already a bound method - await self.run_method_with_instance(test_instance, test_method) - except Exception as e: - success = False - error = e - - # Calculate duration - duration = time.time() - start_time - - # Run tearDown - if hasattr(test_instance, 'tearDown'): - await self.run_method_with_instance(test_instance, test_instance.tearDown) - - # Record and print result - self.print_result(test_name, success, duration, error) - - return success - - - async def run_method_with_instance(self, instance, method): - """Run a method ensuring it has the correct instance""" - method_name = method.__name__ - bound_method = getattr(instance, method_name) - - if asyncio.iscoroutinefunction(bound_method): - return await bound_method() - else: - return bound_method() - - async def run_method(self, method): - """Run a method that might be async or regular""" - # Check if this is an unbound method that needs self - if hasattr(method, '__self__') and method.__self__ is None: - # This suggests it's an unbound method that needs an instance - raise TypeError(f"Method {method.__name__} appears to be unbound and needs 'self'") - - if asyncio.iscoroutinefunction(method): - return await method() - else: - return method() - - - def _reset_mocks(self): - """Reset all mocks for a fresh test""" - # Only reset if the objects have reset_mock method - if hasattr(self.mydom_mock, 'reset_mock'): - self.mydom_mock.reset_mock() - else: - # Recreate the mock objects - self._setup_mocks() - - if hasattr(self.registry_mock, 'reset_mock'): - self.registry_mock.reset_mock() - - if hasattr(self.getDocumentContents_mock, 'reset_mock'): - self.getDocumentContents_mock.reset_mock() - - - - async def run_test_case(self, test_case_class, filter_pattern=None): - """Run all test methods in a test case class""" - # Initialize timing - start_time = time.time() - - # Print header - self.print_header(test_case_class.__name__) - - # Get all test methods - test_methods = sorted([ - getattr(test_case_class, name) for name in dir(test_case_class) - if name.startswith('test_') and callable(getattr(test_case_class, name)) - ], key=lambda x: x.__name__) - - # Filter tests if pattern provided - if filter_pattern: - test_methods = [ - method for method in test_methods - if filter_pattern in method.__name__ - ] - - if not test_methods: - print(f"{Fore.YELLOW}No tests found{Style.RESET_ALL}") - return - - print(f"Running {len(test_methods)} tests...\n") - - # Run each test - for test_method in test_methods: - # Create a fresh instance for each test - test_instance = test_case_class() - await self.run_test(test_instance, test_method) - - # Record total time - self.total_time = time.time() - start_time - - # Print summary - self.print_summary() - - return self.failure_count == 0 - - -def setup_module_paths(): - """Set up module paths to make imports work""" - # Add current directory and parent directory to path - current_dir = os.path.dirname(os.path.abspath(__file__)) - parent_dir = os.path.dirname(current_dir) - - if current_dir not in sys.path: - sys.path.insert(0, current_dir) - if parent_dir not in sys.path: - sys.path.insert(0, parent_dir) - - # Also add any module directories that might exist - modules_dir = os.path.join(parent_dir, 'modules') - if os.path.exists(modules_dir) and modules_dir not in sys.path: - sys.path.insert(0, modules_dir) - - gateway_dir = os.path.join(parent_dir, 'gateway') - if os.path.exists(gateway_dir) and gateway_dir not in sys.path: - sys.path.insert(0, gateway_dir) - - print(f"{Fore.CYAN}Python path set to:{Style.RESET_ALL}") - for path in sys.path[:5]: # Print first 5 paths - print(f" - {path}") - - -def find_test_files(): - """Find test files in the current directory""" - # Look for test files in priority order - test_files = [] - - # First priority: test_workflow_state_machine.py - if os.path.exists('./test_workflow_state_machine.py'): - test_files.append('test_workflow_state_machine.py') - - # Second priority: any tool_test*.py files - tool_test_files = [f for f in os.listdir('.') if f.startswith('tool_test') and f.endswith('.py') and f != 'tool_testBackendSingle.py'] - test_files.extend(tool_test_files) - - # Last priority: any test_*.py files - other_test_files = [f for f in os.listdir('.') if f.startswith('test_') and f.endswith('.py') and f not in test_files] - test_files.extend(other_test_files) - - return test_files - - -async def run_tests(test_file=None, test_filter=None): - """Run all tests""" - # Set up paths - setup_module_paths() - - # Find test files if not specified - if not test_file: - test_files = find_test_files() - if not test_files: - print(f"{Fore.RED}No test files found{Style.RESET_ALL}") - return False - test_file = test_files[0] - print(f"{Fore.YELLOW}Found test files: {', '.join(test_files)}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}Using: {test_file}{Style.RESET_ALL}") - - # Remove .py extension for import - module_name = test_file[:-3] if test_file.endswith('.py') else test_file - - try: - # First try a normal import - print(f"{Fore.YELLOW}Attempting to import module: {module_name}{Style.RESET_ALL}") - test_module = importlib.import_module(module_name) - print(f"{Fore.GREEN}Successfully imported test module: {module_name}{Style.RESET_ALL}") - except ImportError as e: - print(f"{Fore.RED}Error importing module {module_name}: {e}{Style.RESET_ALL}") - - # Try different import approaches - try: - # Try to load as a relative module - print(f"{Fore.YELLOW}Trying relative import...{Style.RESET_ALL}") - test_module = importlib.import_module('.' + module_name, package=__package__) - print(f"{Fore.GREEN}Imported test module via relative import: {module_name}{Style.RESET_ALL}") - except ImportError as e: - print(f"{Fore.RED}Relative import failed: {e}{Style.RESET_ALL}") - - # Fall back to exec (not recommended but sometimes necessary) - print(f"{Fore.YELLOW}Attempting to load using exec: {test_file}{Style.RESET_ALL}") - try: - with open(test_file, 'r') as f: - module_content = f.read() - # Create a new module namespace - module_namespace = {} - # Execute the module code in the namespace - exec(module_content, module_namespace) - - # Create a mock module - class MockModule: - pass - - test_module = MockModule() - - # Copy the relevant attributes to the mock module - for key, value in module_namespace.items(): - setattr(test_module, key, value) - - print(f"{Fore.GREEN}Loaded test module using exec: {test_file}{Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.RED}Failed to load module using exec: {e}{Style.RESET_ALL}") - traceback.print_exc() - return False - - # Find test case class - test_case_class = None - print(f"{Fore.YELLOW}Looking for test case class in module...{Style.RESET_ALL}") - for item_name in dir(test_module): - item = getattr(test_module, item_name) - if inspect.isclass(item) and (item_name.startswith('Test') or 'Test' in item_name): - print(f"{Fore.GREEN}Found test case class: {item_name}{Style.RESET_ALL}") - test_case_class = item - break - - if not test_case_class: - print(f"{Fore.RED}No test case class found in {test_file}{Style.RESET_ALL}") - return False - - # Try to check for required imports - try: - print(f"{Fore.YELLOW}Checking for agent registry...{Style.RESET_ALL}") - try: - # First try direct import - from modules.workflowAgentsRegistry import getAgentRegistry - print(f"{Fore.GREEN}Successfully imported getAgentRegistry{Style.RESET_ALL}") - except ImportError: - try: - # Try alternate path - from modules.workflowAgentsRegistry import getAgentRegistry - print(f"{Fore.GREEN}Successfully imported getAgentRegistry from modules{Style.RESET_ALL}") - except ImportError: - print(f"{Fore.YELLOW}Agent registry import not found - may cause issues{Style.RESET_ALL}") - except Exception as e: - print(f"{Fore.YELLOW}Error checking agent registry: {e}{Style.RESET_ALL}") - - # Run the tests - print(f"{Fore.CYAN}Starting test execution{Style.RESET_ALL}") - runner = AsyncTestRunner() - return await runner.run_test_case(test_case_class, test_filter) - - - -if __name__ == "__main__": - # Get test filter from command line - test_file = None - test_filter = None - - if len(sys.argv) > 1: - # Check if first arg is a file - if os.path.exists(sys.argv[1]) or sys.argv[1].endswith('.py'): - test_file = sys.argv[1] - if len(sys.argv) > 2: - test_filter = sys.argv[2] - else: - test_filter = sys.argv[1] - - # Run tests - asyncio.run(run_tests(test_file, test_filter)) - - -class MockDomInterface: - def __init__(self, *args, **kwargs): - self.getWorkflow = MagicMock(return_value=None) - self.loadWorkflowState = MagicMock(return_value=None) - self.createWorkflow = MagicMock() - self.updateWorkflow = MagicMock() - self.createWorkflowLog = MagicMock() - self.createWorkflowMessage = MagicMock() - self.getFile = MagicMock() - self.getFileData = MagicMock() - self.saveUploadedFile = MagicMock() - self.userLanguage = "en" - self.callAi = AsyncMock() - self.setUserLanguage = MagicMock() - - def reset_mock(self): - """Reset all mocks in this interface""" - for attr_name in dir(self): - attr = getattr(self, attr_name) - if hasattr(attr, 'reset_mock'): - attr.reset_mock() - - -class MockAgentRegistry: - def __init__(self): - self.getAgent = MagicMock() - self.getAgentInfos = MagicMock(return_value=[ - {"name": "test_agent", "description": "Test agent", "capabilities": ["text_processing"]} - ]) - self.setMydom = MagicMock() - - def reset_mock(self): - """Reset all mocks in this registry""" - for attr_name in dir(self): - attr = getattr(self, attr_name) - if hasattr(attr, 'reset_mock'): - attr.reset_mock() \ No newline at end of file diff --git a/tool_testData.py b/tool_testData.py deleted file mode 100644 index 9b5228a0..00000000 --- a/tool_testData.py +++ /dev/null @@ -1,1064 +0,0 @@ -""" -Test Module for Workflow State Machine - -This script tests each state of the workflow state machine implementation -from initialization to completion, including error scenarios. - -Enhanced with colorized output, progress indicators, and detailed result reporting. -""" - -import os -import sys -import uuid -import json -import base64 -import asyncio -import unittest -from unittest.mock import patch, MagicMock, AsyncMock -from datetime import datetime, timedelta -from typing import Dict, List, Any -import time -import traceback - -# Try to import colorama, install if not available -try: - from colorama import init, Fore, Back, Style - init() # Initialize colorama -except ImportError: - print("Installing required package: colorama") - import subprocess - subprocess.call([sys.executable, "-m", "pip", "install", "colorama"]) - from colorama import init, Fore, Back, Style - init() # Initialize colorama - -# Add parent directory to path for imports if needed -current_dir = os.path.dirname(os.path.abspath(__file__)) -parent_dir = os.path.dirname(current_dir) -if parent_dir not in sys.path: - sys.path.insert(0, parent_dir) - -# Mock modules for testing environment -class MockDomInterface: - def __init__(self, *args, **kwargs): - self.getWorkflow = MagicMock(return_value=None) - self.loadWorkflowState = MagicMock(return_value=None) - self.createWorkflow = MagicMock() - self.updateWorkflow = MagicMock() - self.createWorkflowLog = MagicMock() - self.createWorkflowMessage = MagicMock() - self.getFile = MagicMock() - self.getFileData = MagicMock() - self.saveUploadedFile = MagicMock() - self.userLanguage = "en" - self.callAi = AsyncMock() - self.setUserLanguage = MagicMock() - - def reset_mock(self): - """Reset all mocks in this interface""" - for attr_name in dir(self): - attr = getattr(self, attr_name) - if hasattr(attr, 'reset_mock'): - attr.reset_mock() - -class MockAgentRegistry: - def __init__(self): - self.getAgent = MagicMock() - self.getAgentInfos = MagicMock(return_value=[ - {"name": "test_agent", "description": "Test agent", "capabilities": ["text_processing"]} - ]) - self.setMydom = MagicMock() - -# Patching the imports - this allows tests to run even without the actual modules -sys.modules['modules.lucydomInterface'] = MagicMock() -sys.modules['modules.lucydomInterface'].getLucydomInterface = MagicMock(return_value=MockDomInterface()) -sys.modules['gateway.modules.workflowAgentsRegistry'] = MagicMock() -sys.modules['gateway.modules.workflowAgentsRegistry'].getAgentRegistry = MagicMock(return_value=MockAgentRegistry()) -sys.modules['modules.documentProcessor'] = MagicMock() -sys.modules['modules.documentProcessor'].getDocumentContents = MagicMock() - -# Import the module under test -try: - from modules.workflowManager import WorkflowManager, getWorkflowManager -except ImportError: - try: - from modules.workflowManager import WorkflowManager, getWorkflowManager - except ImportError: - try: - from gateway.modules.workflowManager import WorkflowManager, getWorkflowManager - except ImportError: - # If all imports fail, create a mock class for testing - print(f"{Fore.YELLOW}Could not import WorkflowManager, using mock implementation{Style.RESET_ALL}") - class WorkflowManager: - def __init__(self, mandateId, userId): - self.mandateId = mandateId - self.userId = userId - self.mydom = MockDomInterface(mandateId, userId) - self.agentRegistry = MockAgentRegistry() - - async def workflowStart(self, userInput, workflowId=None): - workflow = self.workflowInit(workflowId) - return workflow - - def workflowInit(self, workflowId=None): - return { - "id": workflowId or str(uuid.uuid4()), - "mandateId": self.mandateId, - "userId": self.userId, - "messages": [], - "messageIds": [], - "logs": [], - "currentRound": 1, - "status": "running" - } - - async def workflowStop(self, workflowId): - return {"id": workflowId, "status": "stopped"} - - async def workflowProcess(self, userInput, workflow): - return workflow - - def logAdd(self, workflow, message, level="info", progress=None): - return "log_id" - - def parseJsonResponse(self, responseText): - return {} - - def getFilename(self, document): - return document.get("name", "") + "." + document.get("ext", "") - - def getWorkflowManager(mandateId=0, userId=0): - return WorkflowManager(mandateId, userId) - - -class TestWorkflowStateMachine: - """Test case for workflow state machine implementation""" - - def setUp(self): - """Set up test environment""" - self.mandateId = 1 - self.userId = 1 - - # Create mocks - self._setup_mocks() - - # Create manager with mocked dependencies - self.manager = self._create_workflow_manager() - - # Store test start time for duration calculation - self.test_start_time = time.time() - - def _setup_mocks(self): - """Set up mock objects for testing""" - # Mock LucyDOM interface - self.mydom_mock = MockDomInterface() - self.mydom_mock.callAi = AsyncMock() - self.mydom_mock.loadWorkflowState.return_value = None # Default to no existing workflow - self.mydom_mock.getWorkflow.return_value = None # Default to no existing workflow - self.mydom_mock.userLanguage = "en" # Default language - - # Mock AgentRegistry - self.registry_mock = MockAgentRegistry() - test_agent = MagicMock() - test_agent.processTask = AsyncMock() - test_agent.mydom = self.mydom_mock - self.registry_mock.getAgent.return_value = test_agent - self.registry_mock.getAgentInfos.return_value = [ - {"name": "test_agent", "description": "Test agent for workflow", "capabilities": ["text_processing"]} - ] - - # Mock getDocumentContents - self.getDocumentContents_mock = MagicMock() - self.getDocumentContents_mock.return_value = [ - { - "name": "content_1", - "sequenceNr": 1, - "contentType": "text/plain", - "data": "Test content", - "metadata": {"isText": True, "base64Encoded": False} - } - ] - - # Setup default AI responses - self.ai_responses = { - "project_manager": json.dumps({ - "objFinalDocuments": ["result.txt"], - "objWorkplan": [ - { - "agent": "test_agent", - "prompt": "Process the input documents", - "outputDocuments": [ - { - "label": "result.txt", - "prompt": "Generate a text document" - } - ], - "inputDocuments": [] - } - ], - "objUserResponse": "I will process your request", - "userLanguage": "en" - }), - "summary": "This is a summary", - "content_summary": "Content summary", - "agent_extract": "Extracted data", - "final_response": "Here are your results" - } - - # Configure AI service mock to return different responses based on context - async def ai_side_effect(messages, produceUserAnswer=False, temperature=None): - content = "" - if len(messages) > 0 and "content" in messages[-1]: - content = messages[-1]["content"] - - if "analyze the request and create" in content.lower(): - return self.ai_responses["project_manager"] - elif "summarize this" in content.lower(): - return self.ai_responses["content_summary"] - elif "review the promised" in content.lower() or "final" in content.lower(): - return self.ai_responses["final_response"] - elif "extracting information" in content.lower(): - return self.ai_responses["agent_extract"] - else: - return self.ai_responses["summary"] - - self.mydom_mock.callAi.side_effect = ai_side_effect - - # Mock agent response - async def process_task_side_effect(task): - return { - "feedback": "Task completed successfully", - "documents": [ - { - "label": "result.txt", - "content": "Generated content" - } - ] - } - - test_agent.processTask.side_effect = process_task_side_effect - - def _create_workflow_manager(self): - """Create a workflow manager instance with mocked dependencies""" - # Create the manager - manager = WorkflowManager(self.mandateId, self.userId) - - # Inject mocks - manager.mydom = self.mydom_mock - manager.agentRegistry = self.registry_mock - - # Patch getDocumentContents if possible - try: - import modules.documentProcessor - modules.documentProcessor.getDocumentContents = self.getDocumentContents_mock - except: - pass - - # Set up error-free mock for saveUploadedFile - self.mydom_mock.saveUploadedFile.return_value = {"id": 1, "name": "test.txt"} - - return manager - - def _create_test_workflow(self): - """Create a test workflow object""" - workflow_id = str(uuid.uuid4()) - current_time = datetime.now().isoformat() - - return { - "id": workflow_id, - "mandateId": self.mandateId, - "userId": self.userId, - "name": f"Test Workflow {workflow_id[:8]}", - "startedAt": current_time, - "messages": [], - "messageIds": [], - "logs": [], - "dataStats": {}, - "currentRound": 1, - "status": "running", - "lastActivity": current_time - } - - def _create_test_message(self, workflow, content="Test message", role="user"): - """Create a test message for a workflow""" - message_id = f"msg_{str(uuid.uuid4())}" - return { - "id": message_id, - "workflowId": workflow["id"], - "role": role, - "agentName": "" if role == "user" else "test_agent", - "content": content, - "documents": [], - "timestamp": datetime.now().isoformat(), - "sequenceNo": len(workflow.get("messages", [])) + 1, - "status": "first" if role == "user" else "step" - } - - def _create_test_document(self, name="test.txt", content="Test content"): - """Create a test document object""" - doc_id = f"doc_{str(uuid.uuid4())}" - return { - "id": doc_id, - "fileId": 1, - "name": os.path.splitext(name)[0], - "ext": os.path.splitext(name)[1][1:] if os.path.splitext(name)[1] else "txt", - "data": base64.b64encode(content.encode('utf-8')).decode('utf-8'), - "contents": [ - { - "sequenceNr": 1, - "name": "content_1", - "ext": "txt", - "contentType": "text/plain", - "data": content, - "dataExtracted": "Extracted content", - "metadata": { - "isText": True, - "base64Encoded": False, - "aiProcessed": False - }, - "summary": "Content summary" - } - ] - } - - def _assert_workflow_state(self, workflow, expected_status, expected_round=1, - expected_message_count=None, expected_log_count=None): - """Assert the state of a workflow with detailed diagnostic output""" - # Print current workflow state for debugging - print(f"\n{Fore.CYAN}Workflow State Verification:{Style.RESET_ALL}") - print(f" Status: {Fore.YELLOW}{workflow.get('status', 'N/A')}{Style.RESET_ALL} (Expected: {Fore.GREEN}{expected_status}{Style.RESET_ALL})") - print(f" Round: {Fore.YELLOW}{workflow.get('currentRound', 'N/A')}{Style.RESET_ALL} (Expected: {Fore.GREEN}{expected_round}{Style.RESET_ALL})") - - if expected_message_count is not None: - current_count = len(workflow.get("messages", [])) - print(f" Messages: {Fore.YELLOW}{current_count}{Style.RESET_ALL} (Expected: {Fore.GREEN}{expected_message_count}{Style.RESET_ALL})") - - if expected_log_count is not None: - current_count = len(workflow.get("logs", [])) - print(f" Logs: {Fore.YELLOW}{current_count}{Style.RESET_ALL} (Expected: {Fore.GREEN}{expected_log_count}{Style.RESET_ALL})") - - # Perform the actual assertions - assert workflow["status"] == expected_status, f"Workflow status should be {expected_status}" - assert workflow["currentRound"] == expected_round, f"Workflow round should be {expected_round}" - - if expected_message_count is not None: - assert len(workflow.get("messages", [])) == expected_message_count, f"Workflow should have {expected_message_count} messages" - - if expected_log_count is not None: - assert len(workflow.get("logs", [])) == expected_log_count, f"Workflow should have {expected_log_count} logs" - - def _reset_mocks(self): - """Reset all mocks for a fresh test""" - self.mydom_mock.reset_mock() - self.registry_mock.reset_mock() - self.getDocumentContents_mock.reset_mock() - - # ------------------------------------------------------------------------ - # TESTS FOR EACH STATE - # ------------------------------------------------------------------------ - - def _print_test_info(self, state_num, state_name, description=None): - """Print formatted information about the current test state""" - print(f"\n{Fore.CYAN}{Style.BRIGHT}STATE {state_num}: {state_name}{Style.RESET_ALL}") - if description: - print(f"{Fore.WHITE}{description}{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{'-' * 60}{Style.RESET_ALL}") - - async def test_state_1_workflow_initialization_new(self): - """Test State 1: Workflow Initialization (new workflow)""" - self._print_test_info(1, "Workflow Initialization", - "Creating a new workflow from scratch") - - # Ensure no existing workflow - self.mydom_mock.getWorkflow.return_value = None - self.mydom_mock.loadWorkflowState.return_value = None - - print(f"{Fore.YELLOW}➤ Initializing new workflow...{Style.RESET_ALL}") - # Initialize a new workflow - workflow = self.manager.workflowInit() - - # Print workflow details - print(f"{Fore.GREEN}✓ Workflow created:{Style.RESET_ALL}") - print(f" ID: {workflow.get('id', 'N/A')}") - print(f" Mandate: {workflow.get('mandateId', 'N/A')}") - print(f" User: {workflow.get('userId', 'N/A')}") - print(f" Round: {workflow.get('currentRound', 'N/A')}") - print(f" Status: {workflow.get('status', 'N/A')}") - - # Assert workflow state - print(f"{Fore.YELLOW}➤ Validating workflow state...{Style.RESET_ALL}") - assert workflow is not None, "Workflow should not be None" - assert workflow["mandateId"] == self.mandateId, f"Workflow mandate ID should be {self.mandateId}" - assert workflow["userId"] == self.userId, f"Workflow user ID should be {self.userId}" - assert workflow["currentRound"] == 1, "Workflow round should be 1" - assert workflow["status"] == "running", "Workflow status should be 'running'" - - # Verify interactions - print(f"{Fore.YELLOW}➤ Verifying database interactions...{Style.RESET_ALL}") - self.mydom_mock.createWorkflow.assert_called_once() - self.mydom_mock.getWorkflow.assert_called_once() - print(f"{Fore.GREEN}✓ Database correctly updated{Style.RESET_ALL}") - - async def test_state_1_workflow_initialization_existing(self): - """Test State 1: Workflow Initialization (existing workflow)""" - self._print_test_info(1, "Workflow Initialization (Existing)", - "Loading and incremented round for an existing workflow") - - # Create a test workflow - existing_workflow = self._create_test_workflow() - existing_workflow["currentRound"] = 3 # Already ran 3 rounds - - # Configure mock to return our test workflow - self.mydom_mock.getWorkflow.return_value = existing_workflow - - print(f"{Fore.YELLOW}➤ Initializing with existing workflow ID: {existing_workflow['id'][:8]}...{Style.RESET_ALL}") - # Initialize with existing workflowId - workflow = self.manager.workflowInit(existing_workflow["id"]) - - # Assert workflow state - print(f"{Fore.GREEN}✓ Workflow loaded:{Style.RESET_ALL}") - print(f" ID: {workflow.get('id', 'N/A')}") - print(f" Previous round: {existing_workflow['currentRound']}") - print(f" New round: {workflow.get('currentRound', 'N/A')}") - print(f" Status: {workflow.get('status', 'N/A')}") - - assert workflow is not None, "Workflow should not be None" - assert workflow["id"] == existing_workflow["id"], "Workflow ID should match existing ID" - assert workflow["currentRound"] == 4, "Workflow round should be incremented to 4" - assert workflow["status"] == "running", "Workflow status should be 'running'" - - # Verify interactions - print(f"{Fore.YELLOW}➤ Verifying database interactions...{Style.RESET_ALL}") - self.mydom_mock.updateWorkflow.assert_called_once() - print(f"{Fore.GREEN}✓ Database correctly updated with incremented round{Style.RESET_ALL}") - - async def test_state_2_workflow_exception(self): - """Test State 2: Workflow Exception""" - self._print_test_info(2, "Workflow Exception", - "Testing error handling in the workflow state machine") - - # Create a test workflow - print(f"{Fore.YELLOW}➤ Creating test workflow...{Style.RESET_ALL}") - workflow = self._create_test_workflow() - print(f"{Fore.GREEN}✓ Created workflow with ID: {workflow['id']}{Style.RESET_ALL}") - - # Simulate an exception in workflow processing - print(f"{Fore.YELLOW}➤ Setting up exception scenario...{Style.RESET_ALL}") - def raise_exception(*args, **kwargs): - print(f"{Fore.RED} [INJECTED ERROR] Raising test exception{Style.RESET_ALL}") - raise ValueError("Test exception") - - # Apply mocks for the exception scenario - self.mydom_mock.createWorkflowMessage = MagicMock(side_effect=raise_exception) - - # Create user input - user_input = {"prompt": "Test prompt", "listFileId": []} - print(f"{Fore.GREEN}✓ Exception scenario prepared{Style.RESET_ALL}") - - # Run workflow and expect exception to be caught - print(f"{Fore.YELLOW}➤ Running workflow with exception...{Style.RESET_ALL}") - try: - result = await self.manager.workflowProcess(user_input, workflow) - - # Print workflow state after exception - print(f"{Fore.GREEN}✓ Exception handled correctly{Style.RESET_ALL}") - print(f" Workflow status: {Fore.YELLOW}{result['status']}{Style.RESET_ALL}") - - # Print log entries - logs = result.get("logs", []) - if logs: - print(f" Error logs:") - for log in logs: - if "failed" in log.get("message", "").lower(): - print(f" {Fore.RED}→ {log.get('message')}{Style.RESET_ALL}") - - # Verify workflow is marked as failed - print(f"{Fore.YELLOW}➤ Validating workflow state after exception...{Style.RESET_ALL}") - assert result["status"] == "failed", "Workflow status should be 'failed'" - - # Verify log creation - self.mydom_mock.createWorkflowLog.assert_called() - - # Verify database update - print(f"{Fore.YELLOW}➤ Verifying database was updated correctly...{Style.RESET_ALL}") - self.mydom_mock.updateWorkflow.assert_called() - print(f"{Fore.GREEN}✓ Database correctly updated with failure status{Style.RESET_ALL}") - - except Exception as e: - assert False, f"Exception should be caught in workflowProcess, but was raised: {e}" - - async def test_state_3_user_message_processing(self): - """Test State 3: User Message Processing""" - self._print_test_info(3, "User Message Processing", - "Processing user input into a workflow message with documents") - - # Create a test workflow - workflow = self._create_test_workflow() - - # Create test user input - user_input = {"prompt": "Please analyze this document", "listFileId": [1]} - - # Configure file processing mock - test_document = self._create_test_document() - self.mydom_mock.getFile.return_value = {"name": "test.txt", "mandateId": self.mandateId} - self.mydom_mock.getFileData.return_value = b"Test content" - - print(f"{Fore.YELLOW}➤ Processing user message...{Style.RESET_ALL}") - # Process user message - message = await self.manager.chatMessageToWorkflow("user", "", user_input, workflow) - - # Print message details - print(f"{Fore.GREEN}✓ Message processed:{Style.RESET_ALL}") - print(f" Role: {message.get('role', 'N/A')}") - print(f" Content: {message.get('content', 'N/A')}") - print(f" Status: {message.get('status', 'N/A')}") - - # Assert message processing - assert message["role"] == "user", "Message role should be 'user'" - assert message["content"] == "Please analyze this document", "Message content should match input" - - # Verify document processing - print(f"{Fore.YELLOW}➤ Verifying document processing...{Style.RESET_ALL}") - self.mydom_mock.getFile.assert_called() - self.mydom_mock.getFileData.assert_called() - - # Verify the message was added to the workflow - print(f"{Fore.YELLOW}➤ Verifying message added to workflow...{Style.RESET_ALL}") - assert message in workflow["messages"], "Message should be added to workflow messages" - assert message["id"] in workflow["messageIds"], "Message ID should be added to workflow messageIds" - print(f"{Fore.GREEN}✓ Message successfully added to workflow{Style.RESET_ALL}") - - async def test_state_4_project_manager_analysis(self): - """Test State 4: Project Manager Analysis""" - self._print_test_info(4, "Project Manager Analysis", - "Analyzing user request and planning the workflow") - - # Create a test workflow - workflow = self._create_test_workflow() - - # Create user message - user_message = self._create_test_message(workflow, "Please create a report") - workflow["messages"].append(user_message) - - print(f"{Fore.YELLOW}➤ Running project manager analysis...{Style.RESET_ALL}") - # Run project manager analysis - project_manager_response = await self.manager.projectManagerAnalysis(user_message, workflow) - - # Print response details - print(f"{Fore.GREEN}✓ Project manager analysis completed:{Style.RESET_ALL}") - print(f" Final docs: {project_manager_response.get('objFinalDocuments', [])}") - print(f" Work steps: {len(project_manager_response.get('objWorkplan', []))}") - print(f" User lang: {project_manager_response.get('userLanguage', 'N/A')}") - - # Assert project manager output - assert "objFinalDocuments" in project_manager_response, "Response should contain objFinalDocuments" - assert "objWorkplan" in project_manager_response, "Response should contain objWorkplan" - assert "objUserResponse" in project_manager_response, "Response should contain objUserResponse" - assert "userLanguage" in project_manager_response, "Response should contain userLanguage" - - # Verify AI call - print(f"{Fore.YELLOW}➤ Verifying AI service call...{Style.RESET_ALL}") - self.mydom_mock.callAi.assert_called_once() - print(f"{Fore.GREEN}✓ AI service correctly called for analysis{Style.RESET_ALL}") - - async def test_state_5_agent_execution(self): - """Test State 5: Agent Execution""" - self._print_test_info(5, "Agent Execution", - "Processing a task with an agent and storing results") - - # Create a test workflow with user message - workflow = self._create_test_workflow() - user_message = self._create_test_message(workflow, "Please create a report") - workflow["messages"].append(user_message) - - # Create test task for the agent - task = { - "agent": "test_agent", - "prompt": "Generate a test report", - "outputDocuments": [ - { - "label": "report.txt", - "prompt": "Create a detailed report" - } - ], - "inputDocuments": [] - } - - print(f"{Fore.YELLOW}➤ Executing agent task...{Style.RESET_ALL}") - # Execute the agent - results = await self.manager.agentProcessing(task, workflow) - - # Print results - print(f"{Fore.GREEN}✓ Agent task executed:{Style.RESET_ALL}") - print(f" Results: {len(results)} documents") - - # Assert agent was called correctly - test_agent = self.registry_mock.getAgent.return_value - test_agent.processTask.assert_called_once() - - # Verify agent message was added to workflow - print(f"{Fore.YELLOW}➤ Verifying workflow state after agent execution...{Style.RESET_ALL}") - assert len(workflow["messages"]) == 2, "Workflow should have 2 messages (user + agent)" - assert workflow["messages"][1]["role"] == "assistant", "Second message role should be 'assistant'" - assert workflow["messages"][1]["agentName"] == "test_agent", "Second message agentName should be 'test_agent'" - print(f"{Fore.GREEN}✓ Agent message correctly added to workflow{Style.RESET_ALL}") - - async def test_state_6_final_response_generation(self): - """Test State 6: Final Response Generation""" - self._print_test_info(6, "Final Response Generation", - "Creating the final user-facing response message") - - # Create a test workflow - workflow = self._create_test_workflow() - - # Set up test data - obj_user_response = "I will process your request" - obj_final_documents = ["report.txt"] - - # Create a test document result - doc_result = self._create_test_document("report.txt", "Report content") - obj_results = [doc_result] - - print(f"{Fore.YELLOW}➤ Generating final message...{Style.RESET_ALL}") - # Generate final message - final_message = await self.manager.generateFinalMessage(obj_user_response, obj_final_documents, obj_results) - - # Print message details - print(f"{Fore.GREEN}✓ Final message generated:{Style.RESET_ALL}") - print(f" Role: {final_message.get('role', 'N/A')}") - print(f" Agent: {final_message.get('agentName', 'N/A')}") - print(f" Content: {final_message.get('content', 'N/A')[:50]}...") - - # Assert final message - assert final_message["role"] == "assistant", "Final message role should be 'assistant'" - assert final_message["agentName"] == "project_manager", "Final message agentName should be 'project_manager'" - assert final_message["content"] is not None, "Final message content should not be None" - - # Verify AI call for final response - print(f"{Fore.YELLOW}➤ Verifying AI service call...{Style.RESET_ALL}") - self.mydom_mock.callAi.assert_called_once() - print(f"{Fore.GREEN}✓ AI service correctly called for final response{Style.RESET_ALL}") - - async def test_state_7_workflow_completion(self): - """Test State 7: Workflow Completion""" - self._print_test_info(7, "Workflow Completion", - "Finalizing workflow and setting status to completed") - - # Create a test workflow - workflow = self._create_test_workflow() - - print(f"{Fore.YELLOW}➤ Finishing workflow...{Style.RESET_ALL}") - # Finalize the workflow - result = self.manager.workflowFinish(workflow) - - # Print result details - print(f"{Fore.GREEN}✓ Workflow finished:{Style.RESET_ALL}") - print(f" Status: {result.get('status', 'N/A')}") - print(f" Last activity: {result.get('lastActivity', 'N/A')[:19]}") - - # Assert workflow state - assert result["status"] == "completed", "Workflow status should be 'completed'" - - # Verify database update - print(f"{Fore.YELLOW}➤ Verifying database update...{Style.RESET_ALL}") - self.mydom_mock.updateWorkflow.assert_called_once() - print(f"{Fore.GREEN}✓ Database correctly updated with completion status{Style.RESET_ALL}") - - async def test_state_8_workflow_stopped(self): - """Test State 8: Workflow Stopped""" - self._print_test_info(8, "Workflow Stopped", - "Testing the workflow stop function") - - # Create a test workflow - workflow = self._create_test_workflow() - - # Configure mock - self.mydom_mock.loadWorkflowState.return_value = workflow - - print(f"{Fore.YELLOW}➤ Stopping workflow...{Style.RESET_ALL}") - # Stop the workflow - result = await self.manager.workflowStop(workflow["id"]) - - # Print result details - print(f"{Fore.GREEN}✓ Workflow stopped:{Style.RESET_ALL}") - print(f" Status: {result.get('status', 'N/A')}") - - # Assert workflow state - assert result["status"] == "stopped", "Workflow status should be 'stopped'" - - # Verify database update - print(f"{Fore.YELLOW}➤ Verifying database update...{Style.RESET_ALL}") - self.mydom_mock.updateWorkflow.assert_called_once() - print(f"{Fore.GREEN}✓ Database correctly updated with stopped status{Style.RESET_ALL}") - - async def test_state_9_workflow_failed(self): - """Test State 9: Workflow Failed""" - self._print_test_info(9, "Workflow Failed", - "Testing agent failure handling") - - # Create a test workflow - workflow = self._create_test_workflow() - - # Introduce a failing agent - failing_agent = MagicMock() - async def fail_task(*args, **kwargs): - raise ValueError("Agent failure") - failing_agent.processTask = AsyncMock(side_effect=fail_task) - failing_agent.mydom = self.mydom_mock - self.registry_mock.getAgent.return_value = failing_agent - - # Create test task for the agent - task = { - "agent": "failing_agent", - "prompt": "This will fail", - "outputDocuments": [{"label": "fail.txt", "prompt": ""}], - "inputDocuments": [] - } - - print(f"{Fore.YELLOW}➤ Executing failing agent task...{Style.RESET_ALL}") - # Execute the agent and expect it to handle the failure - results = await self.manager.agentProcessing(task, workflow) - - # Print results - print(f"{Fore.GREEN}✓ Agent failure handled correctly:{Style.RESET_ALL}") - print(f" Results: {results}") - - # Assert empty results due to failure - assert results == [], "Results should be an empty list due to failure" - - # Verify error log was created - print(f"{Fore.YELLOW}➤ Verifying error logging...{Style.RESET_ALL}") - self.mydom_mock.createWorkflowLog.assert_called() - print(f"{Fore.GREEN}✓ Error correctly logged{Style.RESET_ALL}") - - async def test_state_10_workflow_resumption(self): - """Test State 10: Workflow Resumption""" - self._print_test_info(10, "Workflow Resumption", - "Continuing a previously completed workflow") - - # Create a test workflow that was previously completed - existing_workflow = self._create_test_workflow() - existing_workflow["status"] = "completed" - existing_workflow["currentRound"] = 2 - - # Add some previous messages - existing_workflow["messages"].append(self._create_test_message(existing_workflow, "Previous request")) - existing_workflow["messageIds"].append(existing_workflow["messages"][0]["id"]) - - # Configure mock to return our test workflow - self.mydom_mock.getWorkflow.return_value = existing_workflow - - print(f"{Fore.YELLOW}➤ Resuming workflow...{Style.RESET_ALL}") - # Resume the workflow with a new message - workflow = self.manager.workflowInit(existing_workflow["id"]) - - # Print workflow details - print(f"{Fore.GREEN}✓ Workflow resumed:{Style.RESET_ALL}") - print(f" ID: {workflow.get('id', 'N/A')}") - print(f" Status: {workflow.get('status', 'N/A')}") - print(f" Round: {workflow.get('currentRound', 'N/A')}") - - # Assert workflow state - assert workflow["id"] == existing_workflow["id"], "Workflow ID should match existing ID" - assert workflow["status"] == "running", "Workflow status should be 'running'" - assert workflow["currentRound"] == 3, "Workflow round should be incremented to 3" - - # Verify database update - print(f"{Fore.YELLOW}➤ Verifying database update...{Style.RESET_ALL}") - self.mydom_mock.updateWorkflow.assert_called_once() - print(f"{Fore.GREEN}✓ Database correctly updated with new round{Style.RESET_ALL}") - - async def test_state_11_workflow_deletion(self): - """Test State 11: Workflow Deletion (Not directly implemented in workflow manager but through API)""" - self._print_test_info(11, "Workflow Deletion", - "Testing deletion through mydom interface") - - # Create a test workflow - workflow_id = str(uuid.uuid4()) - - # Configure mock - self.mydom_mock.getWorkflow.return_value = {"id": workflow_id, "userId": self.userId} - self.mydom_mock.deleteWorkflow.return_value = True - - print(f"{Fore.YELLOW}➤ Deleting workflow...{Style.RESET_ALL}") - # Delete the workflow through mydom - result = self.mydom_mock.deleteWorkflow(workflow_id) - - # Print result - print(f"{Fore.GREEN}✓ Workflow deletion result: {result}{Style.RESET_ALL}") - - # Assert result - assert result is True, "deleteWorkflow should return True" - - # Verify deletion call - print(f"{Fore.YELLOW}➤ Verifying deletion call...{Style.RESET_ALL}") - self.mydom_mock.deleteWorkflow.assert_called_once_with(workflow_id) - print(f"{Fore.GREEN}✓ Workflow correctly deleted{Style.RESET_ALL}") - - # ------------------------------------------------------------------------ - # INTEGRATION TESTS - # ------------------------------------------------------------------------ - - async def test_full_workflow_cycle(self): - """Test a complete workflow cycle from start to finish""" - print(f"\n{Fore.CYAN}{Style.BRIGHT}INTEGRATION TEST: Full Workflow Cycle{Style.RESET_ALL}") - print(f"{Fore.WHITE}This test simulates a complete workflow from start to finish{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{'-' * 60}{Style.RESET_ALL}") - - # Configure mocks for a successful flow - print(f"{Fore.YELLOW}➤ Setting up test environment...{Style.RESET_ALL}") - self._reset_mocks() - - # Create user input with a document - user_input = { - "prompt": "Please analyze the attached document and create a report", - "listFileId": [1] - } - print(f"{Fore.GREEN}✓ Created user input with document request{Style.RESET_ALL}") - print(f" Prompt: {user_input['prompt']}") - print(f" Files: {user_input['listFileId']}") - - # Configure file mock responses - print(f"{Fore.YELLOW}➤ Configuring mock files...{Style.RESET_ALL}") - self.mydom_mock.getFile.return_value = {"name": "source.txt", "mandateId": self.mandateId} - self.mydom_mock.getFileData.return_value = b"Source content for analysis" - self.mydom_mock.saveUploadedFile.return_value = {"id": 2, "name": "result.txt"} - print(f"{Fore.GREEN}✓ Mock files configured{Style.RESET_ALL}") - - # Start a new workflow - print(f"\n{Fore.YELLOW}➤ Starting workflow...{Style.RESET_ALL}") - workflow = await self.manager.workflowStart(user_input) - print(f"{Fore.GREEN}✓ Workflow started with ID: {workflow['id']}{Style.RESET_ALL}") - - # Wait for async processing to complete - print(f"{Fore.YELLOW}➤ Waiting for async processing...{Style.RESET_ALL}") - - # Create a progress spinner - spinner = "|/-\\" - for i in range(10): # Show spinner for 1 second - print(f"\r Processing {spinner[i % len(spinner)]}", end="") - await asyncio.sleep(0.1) - print("\r Processing complete! ") - - # Visualize the workflow state transitions - print(f"\n{Fore.CYAN}Workflow State Transitions:{Style.RESET_ALL}") - states = [ - {"state": "Initialization", "status": "running", "round": 1}, - {"state": "User Message Processing", "status": "running", "round": 1}, - {"state": "Project Manager Analysis", "status": "running", "round": 1}, - {"state": "Agent Execution", "status": "running", "round": 1}, - {"state": "Final Response Generation", "status": "running", "round": 1}, - {"state": "Workflow Completion", "status": "completed", "round": 1} - ] - - for i, state in enumerate(states): - status_color = Fore.GREEN if state["status"] == "completed" else Fore.YELLOW - print(f" {i+1}. {state['state']}: {status_color}{state['status']}{Style.RESET_ALL}") - - # Verify start state - print(f"\n{Fore.YELLOW}➤ Verifying workflow state...{Style.RESET_ALL}") - print(f" Initial status: {Fore.YELLOW}{workflow['status']}{Style.RESET_ALL}") - print(f" Current round: {workflow['currentRound']}") - - assert workflow["status"] == "running", "Workflow status should be 'running'" - assert workflow["id"] is not None, "Workflow ID should not be None" - assert workflow["currentRound"] == 1, "Workflow round should be 1" - - # Verify workflow was initialized correctly - print(f"{Fore.YELLOW}➤ Verifying database interactions...{Style.RESET_ALL}") - self.mydom_mock.createWorkflow.assert_called_once() - print(f"{Fore.GREEN}✓ Workflow correctly initialized in database{Style.RESET_ALL}") - - async def test_workflow_with_exception(self): - """Test workflow handling an exception during processing""" - print(f"\n{Fore.CYAN}{Style.BRIGHT}INTEGRATION TEST: Workflow Exception Handling{Style.RESET_ALL}") - print(f"{Fore.WHITE}This test simulates a workflow with an exception during processing{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{'-' * 60}{Style.RESET_ALL}") - - # Configure mocks for an exception scenario - print(f"{Fore.YELLOW}➤ Setting up exception scenario...{Style.RESET_ALL}") - self._reset_mocks() - - # Force an exception in getFile - def raise_exception(*args, **kwargs): - raise ValueError("Test exception in getFile") - - self.mydom_mock.getFile.side_effect = raise_exception - - # Create user input with a document - user_input = { - "prompt": "This will cause an exception", - "listFileId": [1] - } - - # Create workflow - workflow = self._create_test_workflow() - - print(f"{Fore.YELLOW}➤ Processing workflow with exception...{Style.RESET_ALL}") - # Process with exception - result = await self.manager.workflowProcess(user_input, workflow) - - # Print workflow state - print(f"{Fore.GREEN}✓ Exception handled:{Style.RESET_ALL}") - print(f" Status: {result.get('status', 'N/A')}") - - # Verify failure state - assert result["status"] == "failed", "Workflow status should be 'failed'" - - # Verify error log - print(f"{Fore.YELLOW}➤ Verifying error logging...{Style.RESET_ALL}") - self.mydom_mock.createWorkflowLog.assert_called() - print(f"{Fore.GREEN}✓ Error correctly logged{Style.RESET_ALL}") - - # Verify database update - print(f"{Fore.YELLOW}➤ Verifying database update...{Style.RESET_ALL}") - self.mydom_mock.updateWorkflow.assert_called() - print(f"{Fore.GREEN}✓ Database correctly updated with failure status{Style.RESET_ALL}") - - async def test_workflow_stop_during_processing(self): - """Test stopping a workflow during processing""" - print(f"\n{Fore.CYAN}{Style.BRIGHT}INTEGRATION TEST: Workflow Stop During Processing{Style.RESET_ALL}") - print(f"{Fore.WHITE}This test simulates stopping a workflow during processing{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{'-' * 60}{Style.RESET_ALL}") - - # Create a test workflow that is running - workflow = self._create_test_workflow() - - # Configure mock - self.mydom_mock.loadWorkflowState.return_value = workflow - - print(f"{Fore.YELLOW}➤ Stopping workflow during processing...{Style.RESET_ALL}") - # Stop the workflow - stopped_workflow = await self.manager.workflowStop(workflow["id"]) - - # Print workflow state - print(f"{Fore.GREEN}✓ Workflow stopped:{Style.RESET_ALL}") - print(f" Status: {stopped_workflow.get('status', 'N/A')}") - - # Verify stopped state - assert stopped_workflow["status"] == "stopped", "Workflow status should be 'stopped'" - - # Verify database update - print(f"{Fore.YELLOW}➤ Verifying database update...{Style.RESET_ALL}") - self.mydom_mock.updateWorkflow.assert_called_once() - print(f"{Fore.GREEN}✓ Database correctly updated with stopped status{Style.RESET_ALL}") - - # ------------------------------------------------------------------------ - # UTILITY FUNCTION TESTS - # ------------------------------------------------------------------------ - - def test_parse_json_response(self): - """Test JSON response parsing""" - print(f"\n{Fore.CYAN}{Style.BRIGHT}UTILITY TEST: JSON Response Parsing{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{'-' * 60}{Style.RESET_ALL}") - - # Test with clean JSON - print(f"{Fore.YELLOW}➤ Testing with clean JSON...{Style.RESET_ALL}") - clean_json = '{"key": "value", "number": 123}' - result = self.manager.parseJsonResponse(clean_json) - - print(f"{Fore.GREEN}✓ Clean JSON result:{Style.RESET_ALL}") - print(f" key: {result.get('key', 'N/A')}") - print(f" number: {result.get('number', 'N/A')}") - - assert result["key"] == "value", "Clean JSON parsing should extract key value" - assert result["number"] == 123, "Clean JSON parsing should extract number value" - - # Test with JSON embedded in text - print(f"\n{Fore.YELLOW}➤ Testing with JSON embedded in text...{Style.RESET_ALL}") - text_with_json = 'Some text before {"key": "value"} and after' - result = self.manager.parseJsonResponse(text_with_json) - - print(f"{Fore.GREEN}✓ Embedded JSON result:{Style.RESET_ALL}") - print(f" key: {result.get('key', 'N/A')}") - - assert result["key"] == "value", "Embedded JSON parsing should extract key value" - - # Test with invalid JSON - print(f"\n{Fore.YELLOW}➤ Testing with invalid JSON...{Style.RESET_ALL}") - invalid_json = 'Not a JSON at all' - result = self.manager.parseJsonResponse(invalid_json) - - print(f"{Fore.GREEN}✓ Invalid JSON fallback result:{Style.RESET_ALL}") - print(f" objWorkplan: {result.get('objWorkplan', 'N/A')}") - - # Should return a fallback structure - assert "objFinalDocuments" in result, "Invalid JSON should return fallback with objFinalDocuments" - assert "objWorkplan" in result, "Invalid JSON should return fallback with objWorkplan" - assert "objUserResponse" in result, "Invalid JSON should return fallback with objUserResponse" - print(f"{Fore.GREEN}✓ All JSON parsing scenarios handled correctly{Style.RESET_ALL}") - - def test_get_filename(self): - """Test filename extraction from document""" - print(f"\n{Fore.CYAN}{Style.BRIGHT}UTILITY TEST: Filename Extraction{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{'-' * 60}{Style.RESET_ALL}") - - # Test with extension - print(f"{Fore.YELLOW}➤ Testing with extension...{Style.RESET_ALL}") - document = {"name": "test", "ext": "txt"} - filename = self.manager.getFilename(document) - - print(f"{Fore.GREEN}✓ Filename with extension result:{Style.RESET_ALL}") - print(f" Result: {filename}") - - assert filename == "test.txt", "Filename should be 'test.txt'" - - # Test with no extension - print(f"\n{Fore.YELLOW}➤ Testing with no extension...{Style.RESET_ALL}") - document = {"name": "test", "ext": ""} - filename = self.manager.getFilename(document) - - print(f"{Fore.GREEN}✓ Filename without extension result:{Style.RESET_ALL}") - print(f" Result: {filename}") - - assert filename == "test", "Filename should be 'test'" - print(f"{Fore.GREEN}✓ All filename extraction scenarios handled correctly{Style.RESET_ALL}") - - def test_get_available_documents(self): - """Test getting available documents from workflow""" - print(f"\n{Fore.CYAN}{Style.BRIGHT}UTILITY TEST: Get Available Documents{Style.RESET_ALL}") - print(f"{Fore.YELLOW}{'-' * 60}{Style.RESET_ALL}") - - # Create a test workflow with messages containing documents - workflow = self._create_test_workflow() - - # Add user message with document - print(f"{Fore.YELLOW}➤ Creating user message with document...{Style.RESET_ALL}") - user_message = self._create_test_message(workflow, "Message with document") - user_message["documents"] = [self._create_test_document("user_doc.txt")] - workflow["messages"].append(user_message) - - # Add assistant message with document - print(f"{Fore.YELLOW}➤ Creating assistant message with document...{Style.RESET_ALL}") - assistant_message = self._create_test_message(workflow, "Response with document", "assistant") - assistant_message["documents"] = [self._create_test_document("assistant_doc.txt")] - workflow["messages"].append(assistant_message) - - print(f"{Fore.YELLOW}➤ Getting available documents...{Style.RESET_ALL}") - # Get available documents - available_docs = self.manager.getAvailableDocuments(workflow, user_message) - - # Print results - print(f"{Fore.GREEN}✓ Available documents result:{Style.RESET_ALL}") - print(f" Count: {len(available_docs)}") - for i, doc in enumerate(available_docs): - print(f" {i+1}. {doc.get('label', 'N/A')} ({doc.get('fileSource', 'N/A')})") - - # Verify result - assert len(available_docs) == 2, "Available documents should have 2 entries" - # Should be sorted newest first - assert available_docs[0]["label"] == "assistant_doc.txt", "First document should be assistant_doc.txt" - assert available_docs[1]["label"] == "user_doc.txt", "Second document should be user_doc.txt" - # User's document should be marked as from user - assert available_docs[1]["fileSource"] == "user", "User document should have fileSource='user'" - print(f"{Fore.GREEN}✓ Available documents correctly identified and sorted{Style.RESET_ALL}") - - -# Simple test runner -if __name__ == "__main__": - # Import the runner and execute tests - try: - sys.path.append(os.path.dirname(os.path.abspath(__file__))) - from tool_testBackendSingle import run_tests - asyncio.run(run_tests()) - except ImportError: - print(f"{Fore.YELLOW}Please use tool_testBackendSingle.py to run the tests properly{Style.RESET_ALL}") \ No newline at end of file diff --git a/tool_testUser.py b/tool_testUser.py deleted file mode 100644 index 7f328ca8..00000000 --- a/tool_testUser.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python3 -""" -Direct Interface Workflow Test Script - -This script bypasses the API layer and works directly with the interface classes -to simulate a user uploading two files and then sending a chat request with these files. - -It follows the state machine as defined in the backend documentation. -""" - -import os -import sys -import json -import asyncio -import uuid -from datetime import datetime - -# Adjust import paths -current_dir = os.path.dirname(os.path.abspath(__file__)) -parent_dir = os.path.dirname(current_dir) -if parent_dir not in sys.path: - sys.path.insert(0, parent_dir) - -# Try to import the required modules -try: - from modules.workflowManager import getWorkflowManager - from modules.lucydomInterface import getLucydomInterface -except ImportError: - print("Error: Required modules not found. Attempting alternative imports...") - try: - from gateway.modules.workflowManager import getWorkflowManager - from gateway.modules.lucydomInterface import getLucydomInterface - except ImportError: - print("Error: Could not import required modules. Make sure the script is run from the correct directory.") - sys.exit(1) - -# Constants -MANDATE_ID = 1 -USER_ID = 1 -#USER_PROMPT = "Please analyze these sales figures and the chart to identify key trends and opportunities." -#USER_PROMPT = "Please make me a svg file with forecast for Apr-Jun." -USER_PROMPT = "Please make me a jpg file with forecast for Apr-Jun." - -# Sample files to upload -SAMPLE_SVG = """ - - Sales Q1 Bar Chart - - - - - - - - Sales ($) - - - Month - - - - Jan - $150K - - - - Feb - $165K - - - - Mar - $180K - - -""" - -SAMPLE_DATA = """ -# Sales Data - Q1 2023 - -Month,Revenue,Growth,Units Sold -January,150000,5.2%,1250 -February,165000,10.0%,1380 -March,180000,9.1%,1490 - -## Regional Breakdown -- North: 35% of total sales -- South: 25% of total sales -- East: 20% of total sales -- West: 20% of total sales - -## Top Products -1. Product A: 40% of revenue -2. Product B: 30% of revenue -3. Product C: 20% of revenue -4. Others: 10% of revenue -""" - -async def create_test_files(mydom): - """Create two test files and return their IDs""" - print("\n--- Uploading Test Files (State 0: File Upload) ---") - - # Create SVG chart file - print("Uploading SVG chart file...") - chart_meta = mydom.saveUploadedFile(SAMPLE_SVG.encode('utf-8'), "q1_sales_chart.svg") - chart_id = chart_meta['id'] - print(f"Created SVG chart file with ID: {chart_id}") - - # Create data text file - print("Uploading markdown data file...") - data_meta = mydom.saveUploadedFile(SAMPLE_DATA.encode('utf-8'), "q1_sales_data.md") - data_id = data_meta['id'] - print(f"Created markdown data file with ID: {data_id}") - - return chart_id, data_id - -async def monitor_workflow(mydom, workflow_id, timeout=300, interval=2): - """Monitor the workflow until it completes or times out""" - print("\n--- Monitoring Workflow ---") - start_time = datetime.now() - elapsed = 0 - - while elapsed < timeout: - # Get current workflow state - workflow = mydom.loadWorkflowState(workflow_id) - if not workflow: - print("Error: Workflow not found") - return None - - status = workflow.get("status", "unknown") - - # Show progress - logs = workflow.get("logs", []) - latest_log = logs[-1] if logs else None - - if latest_log: - progress = latest_log.get("progress", 0) - message = latest_log.get("message", "No message") - print(f"Status: {status} | Progress: {progress}% | {message}") - - # Check if workflow is done - if status in ["completed", "failed", "stopped"]: - if status == "completed": - print("\nWorkflow completed successfully!") - elif status == "failed": - print("\nWorkflow failed!") - else: - print("\nWorkflow was stopped!") - return workflow - - # Wait before checking again - await asyncio.sleep(interval) - elapsed = (datetime.now() - start_time).total_seconds() - - print(f"Monitoring timed out after {timeout} seconds") - return mydom.loadWorkflowState(workflow_id) - -async def run_test(): - """Main test function that follows the state machine workflow""" - print("\n=== Direct Interface Workflow Test ===\n") - - # Initialize the interfaces - print("Initializing system...") - mydom = getLucydomInterface(MANDATE_ID, USER_ID) - manager = getWorkflowManager(MANDATE_ID, USER_ID) - - # Upload test files (State 0: File Upload) - chart_id, data_id = await create_test_files(mydom) - - # Prepare the user input - user_input = { - "prompt": USER_PROMPT, - "listFileId": [chart_id, data_id] - } - - # Start workflow (State 1: Workflow Initialization) - print(f"\n--- Starting Workflow (State 1: Workflow Initialization) ---") - print(f"Sending user prompt: '{USER_PROMPT}'") - print(f"With files: SVG chart (ID: {chart_id}) and sales data (ID: {data_id})") - - # Start the workflow with the user input - workflow = await manager.workflowStart(user_input) - workflow_id = workflow["id"] - - print(f"Workflow initiated with ID: {workflow_id}") - print(f"Initial status: {workflow['status']}") - - # Monitor the workflow progress - # This will monitor states 2-7 of the state machine - await monitor_workflow(mydom, workflow_id, timeout=120) - - # Get final workflow state - final_workflow = mydom.loadWorkflowState(workflow_id) - - # Print the results - print("\n--- Final Workflow Results ---") - if final_workflow: - # Print status information - print(f"Workflow Status: {final_workflow.get('status', 'unknown')}") - print(f"Current Round: {final_workflow.get('currentRound', 0)}") - - # Print messages - print("\n=== Messages ===") - for msg in final_workflow.get("messages", []): - role = msg.get("role", "unknown") - agent = msg.get("agentName", "") - - # Get a preview of the content - content = msg.get("content", "") - if len(content) > 100: - content_preview = content[:100] + "..." - else: - content_preview = content - - # Format based on role - if role == "assistant" and agent: - print(f"\n[{role} - {agent}]: {content_preview}") - else: - print(f"\n[{role}]: {content_preview}") - - # Print document info - docs = msg.get("documents", []) - if docs: - print(f" Documents ({len(docs)}):") - for doc in docs: - name = doc.get("name", "unnamed") - ext = doc.get("ext", "") - file_id = doc.get("fileId", "unknown") - print(f" - {name}.{ext} (ID: {file_id})") - - # Print the final log - logs = final_workflow.get("logs", []) - if logs: - final_log = logs[-1] - print(f"\nFinal Log: {final_log.get('message', 'No message')}") - else: - print("Error: Could not retrieve final workflow state") - - print("\n=== Test Complete ===") - return workflow_id - -if __name__ == "__main__": - workflow_id = asyncio.run(run_test()) - print(f"Completed workflow ID: {workflow_id}") \ No newline at end of file