fixed imports
This commit is contained in:
parent
f02ebead7c
commit
bb9630d6c4
161 changed files with 1318 additions and 1165 deletions
|
|
@ -6,8 +6,8 @@ import re
|
|||
import time
|
||||
import base64
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from modules.features.aichat.datamodelFeatureAiChat import PromptPlaceholder, ChatDocument
|
||||
from modules.features.aichat.serviceExtraction.mainServiceExtraction import ExtractionService
|
||||
from modules.aichat.datamodelFeatureAiChat import PromptPlaceholder, ChatDocument
|
||||
from modules.aichat.serviceExtraction.mainServiceExtraction import ExtractionService
|
||||
from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum
|
||||
from modules.datamodels.datamodelExtraction import ContentPart, DocumentIntent
|
||||
from modules.datamodels.datamodelWorkflow import AiResponse, AiResponseMetadata, DocumentData
|
||||
|
|
@ -329,7 +329,7 @@ Respond with ONLY a JSON object in this exact format:
|
|||
parentOperationId: Optional[str]
|
||||
) -> AiResponse:
|
||||
"""Handle IMAGE_GENERATE operation type using image generation path."""
|
||||
from modules.features.aichat.serviceGeneration.paths.imagePath import ImageGenerationPath
|
||||
from modules.aichat.serviceGeneration.paths.imagePath import ImageGenerationPath
|
||||
|
||||
imagePath = ImageGenerationPath(self.services)
|
||||
|
||||
|
|
@ -514,7 +514,7 @@ Respond with ONLY a JSON object in this exact format:
|
|||
)
|
||||
|
||||
try:
|
||||
from modules.features.aichat.serviceGeneration.mainServiceGeneration import GenerationService
|
||||
from modules.aichat.serviceGeneration.mainServiceGeneration import GenerationService
|
||||
|
||||
generationService = GenerationService(self.services)
|
||||
|
||||
|
|
@ -829,7 +829,7 @@ Respond with ONLY a JSON object in this exact format:
|
|||
parentOperationId: Optional[str]
|
||||
) -> AiResponse:
|
||||
"""Handle code generation using code generation path."""
|
||||
from modules.features.aichat.serviceGeneration.paths.codePath import CodeGenerationPath
|
||||
from modules.aichat.serviceGeneration.paths.codePath import CodeGenerationPath
|
||||
|
||||
codePath = CodeGenerationPath(self.services)
|
||||
return await codePath.generateCode(
|
||||
|
|
@ -852,7 +852,7 @@ Respond with ONLY a JSON object in this exact format:
|
|||
parentOperationId: Optional[str]
|
||||
) -> AiResponse:
|
||||
"""Handle document generation using document generation path."""
|
||||
from modules.features.aichat.serviceGeneration.paths.documentPath import DocumentGenerationPath
|
||||
from modules.aichat.serviceGeneration.paths.documentPath import DocumentGenerationPath
|
||||
|
||||
# Set compression options for document generation
|
||||
options.compressPrompt = False
|
||||
|
|
@ -14,7 +14,7 @@ import logging
|
|||
import base64
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.datamodels.datamodelExtraction import ContentPart, DocumentIntent
|
||||
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ import json
|
|||
import logging
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.datamodels.datamodelExtraction import DocumentIntent
|
||||
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
||||
|
||||
|
|
@ -2531,7 +2531,7 @@ CRITICAL:
|
|||
List of accepted section content types (e.g., ["table", "code_block"])
|
||||
"""
|
||||
try:
|
||||
from modules.features.aichat.serviceGeneration.renderers.registry import getRenderer
|
||||
from modules.aichat.serviceGeneration.renderers.registry import getRenderer
|
||||
|
||||
# Get renderer for this format
|
||||
renderer = getRenderer(outputFormat, self.services)
|
||||
|
|
@ -231,7 +231,7 @@ CRITICAL:
|
|||
raise ValueError("Structure has no documents - cannot generate without documents")
|
||||
|
||||
# Import renderer registry for format validation (existing infrastructure)
|
||||
from modules.features.aichat.serviceGeneration.renderers.registry import getRenderer
|
||||
from modules.aichat.serviceGeneration.renderers.registry import getRenderer
|
||||
|
||||
# Validate and fix each document
|
||||
for doc in documents:
|
||||
|
|
@ -11,10 +11,10 @@ import json
|
|||
from .subRegistry import ExtractorRegistry, ChunkerRegistry
|
||||
from .subPipeline import runExtraction
|
||||
from modules.datamodels.datamodelExtraction import ContentExtracted, ContentPart, MergeStrategy, ExtractionOptions, PartResult, DocumentIntent
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.datamodels.datamodelAi import AiCallResponse, AiCallRequest, AiCallOptions, OperationTypeEnum, AiModelCall
|
||||
from modules.features.aichat.aicore.aicoreModelRegistry import modelRegistry
|
||||
from modules.features.aichat.aicore.aicoreModelSelector import modelSelector
|
||||
from modules.aichat.aicore.aicoreModelRegistry import modelRegistry
|
||||
from modules.aichat.aicore.aicoreModelSelector import modelSelector
|
||||
from modules.shared.jsonUtils import stripCodeFences
|
||||
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, Operati
|
|||
# Type hint for renderer parameter
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from modules.features.aichat.serviceGeneration.renderers.documentRendererBaseTemplate import BaseRenderer
|
||||
from modules.aichat.serviceGeneration.renderers.documentRendererBaseTemplate import BaseRenderer
|
||||
_RendererLike = BaseRenderer
|
||||
else:
|
||||
_RendererLike = Any
|
||||
|
|
@ -71,7 +71,7 @@ class ExtractorRegistry:
|
|||
module_name = file_path.stem
|
||||
try:
|
||||
# Import the module
|
||||
module = importlib.import_module(f".{module_name}", package="modules.features.aichat.serviceExtraction.extractors")
|
||||
module = importlib.import_module(f".{module_name}", package="modules.aichat.serviceExtraction.extractors")
|
||||
|
||||
# Find all extractor classes in the module
|
||||
for attr_name in dir(module):
|
||||
|
|
@ -6,8 +6,8 @@ import base64
|
|||
import traceback
|
||||
from typing import Any, Dict, List, Optional, Callable
|
||||
from modules.datamodels.datamodelDocument import RenderedDocument
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.features.aichat.serviceGeneration.subDocumentUtility import (
|
||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.aichat.serviceGeneration.subDocumentUtility import (
|
||||
getFileExtension,
|
||||
getMimeTypeFromExtension,
|
||||
detectMimeTypeFromContent,
|
||||
|
|
@ -414,7 +414,7 @@ class GenerationService:
|
|||
continue
|
||||
|
||||
# Check output style classification (code/document/image/etc.) from renderer
|
||||
from modules.features.aichat.serviceGeneration.renderers.registry import getOutputStyle
|
||||
from modules.aichat.serviceGeneration.renderers.registry import getOutputStyle
|
||||
outputStyle = getOutputStyle(docFormat)
|
||||
if outputStyle:
|
||||
logger.debug(f"Document {doc.get('id', docIndex)} format '{docFormat}' classified as '{outputStyle}' style")
|
||||
|
|
@ -471,8 +471,8 @@ class GenerationService:
|
|||
Complete document structure with populated elements ready for rendering
|
||||
"""
|
||||
try:
|
||||
from modules.features.aichat.serviceGeneration.subStructureGenerator import StructureGenerator
|
||||
from modules.features.aichat.serviceGeneration.subContentGenerator import ContentGenerator
|
||||
from modules.aichat.serviceGeneration.subStructureGenerator import StructureGenerator
|
||||
from modules.aichat.serviceGeneration.subContentGenerator import ContentGenerator
|
||||
|
||||
# Phase 1: Generate structure skeleton
|
||||
if progressCallback:
|
||||
|
|
@ -537,7 +537,7 @@ class GenerationService:
|
|||
aiService=None
|
||||
) -> str:
|
||||
"""Get adaptive extraction prompt."""
|
||||
from modules.features.aichat.serviceExtraction.subPromptBuilderExtraction import buildExtractionPrompt
|
||||
from modules.aichat.serviceExtraction.subPromptBuilderExtraction import buildExtractionPrompt
|
||||
return await buildExtractionPrompt(
|
||||
outputFormat=outputFormat,
|
||||
userPrompt=userPrompt,
|
||||
|
|
@ -920,7 +920,7 @@ CRITICAL:
|
|||
|
||||
def _getCodeRenderer(self, fileType: str):
|
||||
"""Get code renderer for file type."""
|
||||
from modules.features.aichat.serviceGeneration.renderers.registry import getRenderer
|
||||
from modules.aichat.serviceGeneration.renderers.registry import getRenderer
|
||||
|
||||
# Map file types to renderer formats
|
||||
formatMap = {
|
||||
|
|
@ -12,7 +12,7 @@ import base64
|
|||
import re
|
||||
import traceback
|
||||
from typing import Dict, Any, Optional, List, Callable
|
||||
from modules.features.aichat.serviceGeneration.subContentIntegrator import ContentIntegrator
|
||||
from modules.aichat.serviceGeneration.subContentIntegrator import ContentIntegrator
|
||||
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
from typing import Optional, Any, Union, List, Dict, Callable, Awaitable
|
||||
from pydantic import BaseModel, Field
|
||||
from modules.datamodels.datamodelChat import ActionResult
|
||||
from modules.aichat.datamodelFeatureAiChat import ActionResult
|
||||
from modules.shared.frontendTypes import FrontendType
|
||||
from modules.shared.attributeUtils import registerModelLabels
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ import logging
|
|||
import json
|
||||
|
||||
# Import interfaces and models
|
||||
from modules.features.aichat.interfaceFeatureAiChat import getInterface as getChatInterface
|
||||
from modules.aichat.interfaceFeatureAiChat import getInterface as getChatInterface
|
||||
from modules.auth import getCurrentUser, limiter
|
||||
from modules.features.aichat.datamodelFeatureAiChat import AutomationDefinition, ChatWorkflow
|
||||
from modules.aichat.datamodelFeatureAiChat import AutomationDefinition, ChatWorkflow
|
||||
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResponse, PaginationMetadata, normalize_pagination_dict
|
||||
from modules.shared.attributeUtils import getModelAttributeDefinitions
|
||||
from modules.workflows.automation import executeAutomation
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ import asyncio
|
|||
import re
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum, ChatLog, ChatDocument
|
||||
from modules.aichat.datamodelFeatureAiChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum, ChatLog, ChatDocument
|
||||
from modules.datamodels.datamodelUam import User
|
||||
from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationTypeEnum, ProcessingModeEnum
|
||||
from modules.datamodels.datamodelDocref import DocumentReferenceList, DocumentItemReference
|
||||
|
|
@ -439,7 +439,7 @@ async def _emit_log_and_event(
|
|||
# Emit event directly for streaming (using correct signature)
|
||||
if created_log and event_manager:
|
||||
try:
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatLog
|
||||
from modules.aichat.datamodelFeatureAiChat import ChatLog
|
||||
# Convert to dict if it's a Pydantic model
|
||||
if hasattr(created_log, "model_dump"):
|
||||
log_dict = created_log.model_dump()
|
||||
|
|
|
|||
176
modules/features/neutralizer/interfaceFeatureNeutralizer.py
Normal file
176
modules/features/neutralizer/interfaceFeatureNeutralizer.py
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
"""
|
||||
Database interface for the Neutralizer feature.
|
||||
Handles CRUD operations for neutralization configuration and attributes.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
from modules.features.neutralizer.datamodelFeatureNeutralizer import (
|
||||
DataNeutraliserConfig,
|
||||
DataNeutralizerAttributes,
|
||||
)
|
||||
from modules.interfaces.interfaceRbac import getRecordsetWithRBAC
|
||||
from modules.shared.timeUtils import getUtcTimestamp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InterfaceFeatureNeutralizer:
|
||||
"""Database interface for Neutralizer feature operations"""
|
||||
|
||||
def __init__(self, db, currentUser, mandateId: str, userId: str):
|
||||
"""
|
||||
Initialize the interface with database connection and user context.
|
||||
|
||||
Args:
|
||||
db: Database connection instance
|
||||
currentUser: Current user object for RBAC
|
||||
mandateId: Current mandate ID
|
||||
userId: Current user ID
|
||||
"""
|
||||
self.db = db
|
||||
self.currentUser = currentUser
|
||||
self.mandateId = mandateId
|
||||
self.userId = userId
|
||||
|
||||
def getNeutralizationConfig(self) -> Optional[DataNeutraliserConfig]:
|
||||
"""Get the data neutralization configuration for the current user's mandate"""
|
||||
try:
|
||||
# Use RBAC filtering
|
||||
filteredConfigs = getRecordsetWithRBAC(
|
||||
self.db,
|
||||
DataNeutraliserConfig,
|
||||
self.currentUser,
|
||||
recordFilter={"mandateId": self.mandateId}
|
||||
)
|
||||
|
||||
if not filteredConfigs:
|
||||
return None
|
||||
|
||||
# Filter out database-specific fields
|
||||
configDict = filteredConfigs[0]
|
||||
cleanedConfig = {k: v for k, v in configDict.items() if not k.startswith("_")}
|
||||
return DataNeutraliserConfig(**cleanedConfig)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting neutralization config: {str(e)}")
|
||||
return None
|
||||
|
||||
def createOrUpdateNeutralizationConfig(
|
||||
self, configData: Dict[str, Any]
|
||||
) -> DataNeutraliserConfig:
|
||||
"""Create or update the data neutralization configuration"""
|
||||
try:
|
||||
# Check if config already exists
|
||||
existingConfig = self.getNeutralizationConfig()
|
||||
|
||||
if existingConfig:
|
||||
# Update existing config
|
||||
updateData = existingConfig.model_dump()
|
||||
updateData.update(configData)
|
||||
updateData["updatedAt"] = getUtcTimestamp()
|
||||
|
||||
updatedConfig = DataNeutraliserConfig(**updateData)
|
||||
self.db.recordModify(
|
||||
DataNeutraliserConfig, existingConfig.id, updatedConfig
|
||||
)
|
||||
|
||||
return updatedConfig
|
||||
else:
|
||||
# Create new config
|
||||
configData["mandateId"] = self.mandateId
|
||||
configData["userId"] = self.userId
|
||||
|
||||
newConfig = DataNeutraliserConfig(**configData)
|
||||
createdRecord = self.db.recordCreate(DataNeutraliserConfig, newConfig)
|
||||
|
||||
return DataNeutraliserConfig(**createdRecord)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating/updating neutralization config: {str(e)}")
|
||||
raise ValueError(f"Failed to create/update neutralization config: {str(e)}")
|
||||
|
||||
def getNeutralizationAttributes(
|
||||
self, fileId: Optional[str] = None
|
||||
) -> List[DataNeutralizerAttributes]:
|
||||
"""Get neutralization attributes, optionally filtered by file ID"""
|
||||
try:
|
||||
filterDict = {"mandateId": self.mandateId}
|
||||
if fileId:
|
||||
filterDict["fileId"] = fileId
|
||||
|
||||
# Use RBAC filtering
|
||||
filteredAttributes = getRecordsetWithRBAC(
|
||||
self.db,
|
||||
DataNeutralizerAttributes,
|
||||
self.currentUser,
|
||||
recordFilter=filterDict
|
||||
)
|
||||
|
||||
# Filter out database-specific fields
|
||||
cleanedAttributes = []
|
||||
for attr in filteredAttributes:
|
||||
cleanedAttr = {k: v for k, v in attr.items() if not k.startswith("_")}
|
||||
cleanedAttributes.append(cleanedAttr)
|
||||
|
||||
return [
|
||||
DataNeutralizerAttributes(**attr)
|
||||
for attr in cleanedAttributes
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting neutralization attributes: {str(e)}")
|
||||
return []
|
||||
|
||||
def deleteNeutralizationAttributes(self, fileId: str) -> bool:
|
||||
"""Delete all neutralization attributes for a specific file"""
|
||||
try:
|
||||
attributes = self.db.getRecordset(
|
||||
DataNeutralizerAttributes,
|
||||
recordFilter={"mandateId": self.mandateId, "fileId": fileId},
|
||||
)
|
||||
|
||||
for attribute in attributes:
|
||||
self.db.recordDelete(DataNeutralizerAttributes, attribute["id"])
|
||||
|
||||
logger.info(
|
||||
f"Deleted {len(attributes)} neutralization attributes for file {fileId}"
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting neutralization attributes: {str(e)}")
|
||||
return False
|
||||
|
||||
def getAttributeById(self, attributeId: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get a single neutralization attribute by ID"""
|
||||
try:
|
||||
attributes = self.db.getRecordset(
|
||||
DataNeutralizerAttributes,
|
||||
recordFilter={"mandateId": self.mandateId, "id": attributeId}
|
||||
)
|
||||
if attributes:
|
||||
return attributes[0]
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting attribute by ID: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
def getInterface(db, currentUser, mandateId: str, userId: str) -> InterfaceFeatureNeutralizer:
|
||||
"""
|
||||
Factory function to create a Neutralizer interface instance.
|
||||
|
||||
Args:
|
||||
db: Database connection
|
||||
currentUser: Current user for RBAC
|
||||
mandateId: Current mandate ID
|
||||
userId: Current user ID
|
||||
|
||||
Returns:
|
||||
InterfaceFeatureNeutralizer instance
|
||||
"""
|
||||
return InterfaceFeatureNeutralizer(db, currentUser, mandateId, userId)
|
||||
|
|
@ -14,6 +14,7 @@ import json
|
|||
from typing import Dict, List, Any, Optional
|
||||
|
||||
from modules.features.neutralizer.datamodelFeatureNeutralizer import DataNeutraliserConfig, DataNeutralizerAttributes
|
||||
from modules.features.neutralizer.interfaceFeatureNeutralizer import InterfaceFeatureNeutralizer
|
||||
|
||||
# Import all necessary classes and functions for neutralization
|
||||
from .subProcessCommon import CommonUtils, NeutralizationResult, NeutralizationAttribute
|
||||
|
|
@ -35,9 +36,19 @@ class NeutralizationService:
|
|||
NamesToParse: List of names to parse and replace (case-insensitive)
|
||||
"""
|
||||
self.services = serviceCenter
|
||||
self.interfaceDbApp = serviceCenter.interfaceDbApp
|
||||
self.interfaceDbComponent = serviceCenter.interfaceDbComponent
|
||||
|
||||
# Create feature-specific interface for neutralizer DB operations
|
||||
self.interfaceNeutralizer: InterfaceFeatureNeutralizer = None
|
||||
if serviceCenter and serviceCenter.interfaceDbApp:
|
||||
dbApp = serviceCenter.interfaceDbApp
|
||||
self.interfaceNeutralizer = InterfaceFeatureNeutralizer(
|
||||
db=dbApp.db,
|
||||
currentUser=dbApp.currentUser,
|
||||
mandateId=dbApp.mandateId,
|
||||
userId=dbApp.userId
|
||||
)
|
||||
|
||||
# Initialize anonymization processors
|
||||
self.NamesToParse = NamesToParse or []
|
||||
self.textProcessor = TextProcessor(NamesToParse)
|
||||
|
|
@ -47,15 +58,15 @@ class NeutralizationService:
|
|||
|
||||
def getConfig(self) -> Optional[DataNeutraliserConfig]:
|
||||
"""Get the neutralization configuration for the current user's mandate"""
|
||||
if not self.interfaceDbApp:
|
||||
if not self.interfaceNeutralizer:
|
||||
return None
|
||||
return self.interfaceDbApp.getNeutralizationConfig()
|
||||
return self.interfaceNeutralizer.getNeutralizationConfig()
|
||||
|
||||
def saveConfig(self, config_data: Dict[str, Any]) -> DataNeutraliserConfig:
|
||||
def saveConfig(self, configData: Dict[str, Any]) -> DataNeutraliserConfig:
|
||||
"""Save or update the neutralization configuration"""
|
||||
if not self.interfaceDbApp:
|
||||
if not self.interfaceNeutralizer:
|
||||
raise ValueError("User context required for saving configuration")
|
||||
return self.interfaceDbApp.createOrUpdateNeutralizationConfig(config_data)
|
||||
return self.interfaceNeutralizer.createOrUpdateNeutralizationConfig(configData)
|
||||
|
||||
# Public API: process text or file
|
||||
|
||||
|
|
@ -125,44 +136,37 @@ class NeutralizationService:
|
|||
return result
|
||||
|
||||
def resolveText(self, text: str) -> str:
|
||||
if not self.interfaceDbApp:
|
||||
if not self.interfaceNeutralizer:
|
||||
return text
|
||||
try:
|
||||
placeholder_pattern = r'\[([a-z]+)\.([a-f0-9-]{36})\]'
|
||||
matches = re.findall(placeholder_pattern, text)
|
||||
resolved_text = text
|
||||
for placeholder_type, uid in matches:
|
||||
attributes = self.interfaceDbApp.db.getRecordset(
|
||||
DataNeutralizerAttributes,
|
||||
recordFilter={
|
||||
"mandateId": self.interfaceDbApp.mandateId,
|
||||
"id": uid
|
||||
}
|
||||
)
|
||||
if attributes:
|
||||
attribute = attributes[0]
|
||||
placeholder = f"[{placeholder_type}.{uid}]"
|
||||
resolved_text = resolved_text.replace(placeholder, attribute["originalText"])
|
||||
return resolved_text
|
||||
placeholderPattern = r'\[([a-z]+)\.([a-f0-9-]{36})\]'
|
||||
matches = re.findall(placeholderPattern, text)
|
||||
resolvedText = text
|
||||
for placeholderType, uid in matches:
|
||||
attribute = self.interfaceNeutralizer.getAttributeById(uid)
|
||||
if attribute:
|
||||
placeholder = f"[{placeholderType}.{uid}]"
|
||||
resolvedText = resolvedText.replace(placeholder, attribute["originalText"])
|
||||
return resolvedText
|
||||
except Exception:
|
||||
return text
|
||||
|
||||
def getAttributes(self) -> List[DataNeutralizerAttributes]:
|
||||
"""Get all neutralization attributes for the current user's mandate"""
|
||||
if not self.interfaceDbApp:
|
||||
if not self.interfaceNeutralizer:
|
||||
return []
|
||||
try:
|
||||
# Use the interface method which properly converts dicts to objects
|
||||
return self.interfaceDbApp.getNeutralizationAttributes()
|
||||
return self.interfaceNeutralizer.getNeutralizationAttributes()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting neutralization attributes: {str(e)}")
|
||||
return []
|
||||
|
||||
def deleteNeutralizationAttributes(self, fileId: str) -> bool:
|
||||
"""Delete neutralization attributes for a specific file"""
|
||||
if not self.interfaceDbApp:
|
||||
if not self.interfaceNeutralizer:
|
||||
return False
|
||||
return self.interfaceDbApp.deleteNeutralizationAttributes(fileId)
|
||||
return self.interfaceNeutralizer.deleteNeutralizationAttributes(fileId)
|
||||
|
||||
def _reloadNamesFromConfig(self) -> None:
|
||||
"""Reload names from config and update processors"""
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import time
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from modules.features.aichat.aicore.aicoreModelRegistry import modelRegistry
|
||||
from modules.features.aichat.aicore.aicoreModelSelector import modelSelector
|
||||
from modules.aichat.aicore.aicoreModelRegistry import modelRegistry
|
||||
from modules.aichat.aicore.aicoreModelSelector import modelSelector
|
||||
from modules.datamodels.datamodelAi import (
|
||||
AiModel,
|
||||
AiCallOptions,
|
||||
|
|
|
|||
|
|
@ -36,10 +36,6 @@ from modules.datamodels.datamodelRbac import (
|
|||
)
|
||||
from modules.datamodels.datamodelUam import AccessLevel
|
||||
from modules.datamodels.datamodelSecurity import Token, AuthEvent, TokenStatus
|
||||
from modules.features.neutralizer.datamodelFeatureNeutralizer import (
|
||||
DataNeutraliserConfig,
|
||||
DataNeutralizerAttributes,
|
||||
)
|
||||
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResult
|
||||
from modules.datamodels.datamodelMembership import (
|
||||
UserMandate,
|
||||
|
|
@ -2040,115 +2036,6 @@ class AppObjects:
|
|||
logger.error(f"Error during logout: {str(e)}")
|
||||
raise
|
||||
|
||||
# Neutralization methods
|
||||
|
||||
def getNeutralizationConfig(self) -> Optional[DataNeutraliserConfig]:
|
||||
"""Get the data neutralization configuration for the current user's mandate"""
|
||||
try:
|
||||
# Use RBAC filtering
|
||||
filtered_configs = getRecordsetWithRBAC(self.db,
|
||||
DataNeutraliserConfig,
|
||||
self.currentUser,
|
||||
recordFilter={"mandateId": self.mandateId}
|
||||
)
|
||||
|
||||
if not filtered_configs:
|
||||
return None
|
||||
|
||||
# Filter out database-specific fields
|
||||
configDict = filtered_configs[0]
|
||||
cleanedConfig = {k: v for k, v in configDict.items() if not k.startswith("_")}
|
||||
return DataNeutraliserConfig(**cleanedConfig)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting neutralization config: {str(e)}")
|
||||
return None
|
||||
|
||||
def createOrUpdateNeutralizationConfig(
|
||||
self, config_data: Dict[str, Any]
|
||||
) -> DataNeutraliserConfig:
|
||||
"""Create or update the data neutralization configuration"""
|
||||
try:
|
||||
# Check if config already exists
|
||||
existing_config = self.getNeutralizationConfig()
|
||||
|
||||
if existing_config:
|
||||
# Update existing config
|
||||
update_data = existing_config.model_dump()
|
||||
update_data.update(config_data)
|
||||
update_data["updatedAt"] = getUtcTimestamp()
|
||||
|
||||
updated_config = DataNeutraliserConfig(**update_data)
|
||||
self.db.recordModify(
|
||||
DataNeutraliserConfig, existing_config.id, updated_config
|
||||
)
|
||||
|
||||
return updated_config
|
||||
else:
|
||||
# Create new config
|
||||
config_data["mandateId"] = self.mandateId
|
||||
config_data["userId"] = self.userId
|
||||
|
||||
new_config = DataNeutraliserConfig(**config_data)
|
||||
created_record = self.db.recordCreate(DataNeutraliserConfig, new_config)
|
||||
|
||||
return DataNeutraliserConfig(**created_record)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating/updating neutralization config: {str(e)}")
|
||||
raise ValueError(f"Failed to create/update neutralization config: {str(e)}")
|
||||
|
||||
def getNeutralizationAttributes(
|
||||
self, file_id: Optional[str] = None
|
||||
) -> List[DataNeutralizerAttributes]:
|
||||
"""Get neutralization attributes, optionally filtered by file ID"""
|
||||
try:
|
||||
filter_dict = {"mandateId": self.mandateId}
|
||||
if file_id:
|
||||
filter_dict["fileId"] = file_id
|
||||
|
||||
# Use RBAC filtering
|
||||
filtered_attributes = getRecordsetWithRBAC(self.db,
|
||||
DataNeutralizerAttributes,
|
||||
self.currentUser,
|
||||
recordFilter=filter_dict
|
||||
)
|
||||
|
||||
# Filter out database-specific fields
|
||||
cleaned_attributes = []
|
||||
for attr in filtered_attributes:
|
||||
cleanedAttr = {k: v for k, v in attr.items() if not k.startswith("_")}
|
||||
cleaned_attributes.append(cleanedAttr)
|
||||
|
||||
return [
|
||||
DataNeutralizerAttributes(**attr)
|
||||
for attr in cleaned_attributes
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting neutralization attributes: {str(e)}")
|
||||
return []
|
||||
|
||||
def deleteNeutralizationAttributes(self, file_id: str) -> bool:
|
||||
"""Delete all neutralization attributes for a specific file"""
|
||||
try:
|
||||
attributes = self.db.getRecordset(
|
||||
DataNeutralizerAttributes,
|
||||
recordFilter={"mandateId": self.mandateId, "fileId": file_id},
|
||||
)
|
||||
|
||||
for attribute in attributes:
|
||||
self.db.recordDelete(DataNeutralizerAttributes, attribute["id"])
|
||||
|
||||
logger.info(
|
||||
f"Deleted {len(attributes)} neutralization attributes for file {file_id}"
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting neutralization attributes: {str(e)}")
|
||||
return False
|
||||
|
||||
# RBAC CRUD Methods
|
||||
|
||||
def createAccessRule(self, accessRule: AccessRule) -> AccessRule:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from fastapi import status
|
|||
import logging
|
||||
|
||||
# Import interfaces and models from feature containers
|
||||
import modules.features.aichat.interfaceFeatureAiChat as interfaceDbChat
|
||||
import modules.aichat.interfaceFeatureAiChat as interfaceDbChat
|
||||
from modules.auth import limiter, getRequestContext, requireSysAdmin, RequestContext
|
||||
from modules.datamodels.datamodelUam import User
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ async def sync_all_automation_events(
|
|||
This will register/remove events based on active flags.
|
||||
"""
|
||||
try:
|
||||
from modules.features.aichat.interfaceFeatureAiChat import getInterface as getChatInterface
|
||||
from modules.aichat.interfaceFeatureAiChat import getInterface as getChatInterface
|
||||
from modules.interfaces.interfaceDbApp import getRootInterface
|
||||
from modules.workflows.automation import syncAutomationEvents
|
||||
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query, Respon
|
|||
from modules.auth import limiter, getCurrentUser
|
||||
|
||||
# Import interfaces from feature containers
|
||||
import modules.features.aichat.interfaceFeatureAiChat as interfaceDbChat
|
||||
from modules.features.aichat.interfaceFeatureAiChat import getInterface
|
||||
import modules.aichat.interfaceFeatureAiChat as interfaceDbChat
|
||||
from modules.aichat.interfaceFeatureAiChat import getInterface
|
||||
from modules.interfaces.interfaceRbac import getRecordsetWithRBAC
|
||||
|
||||
# Import models from feature containers
|
||||
from modules.features.aichat.datamodelFeatureAiChat import (
|
||||
from modules.aichat.datamodelFeatureAiChat import (
|
||||
ChatWorkflow,
|
||||
ChatMessage,
|
||||
ChatLog,
|
||||
|
|
|
|||
54
modules/security/passwordUtils.py
Normal file
54
modules/security/passwordUtils.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
"""
|
||||
Password utility functions for secure password handling.
|
||||
Uses Argon2 for password hashing.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from passlib.context import CryptContext
|
||||
|
||||
# Password hashing context using Argon2
|
||||
_pwdContext = CryptContext(schemes=["argon2"], deprecated="auto")
|
||||
|
||||
|
||||
def hashPassword(password: str) -> str:
|
||||
"""
|
||||
Hash a password using Argon2.
|
||||
|
||||
Args:
|
||||
password: Plain text password to hash
|
||||
|
||||
Returns:
|
||||
Hashed password string
|
||||
"""
|
||||
return _pwdContext.hash(password)
|
||||
|
||||
|
||||
def verifyPassword(plainPassword: str, hashedPassword: str) -> bool:
|
||||
"""
|
||||
Verify a plain password against a hashed password.
|
||||
|
||||
Args:
|
||||
plainPassword: Plain text password to verify
|
||||
hashedPassword: Hashed password to compare against
|
||||
|
||||
Returns:
|
||||
True if password matches, False otherwise
|
||||
"""
|
||||
return _pwdContext.verify(plainPassword, hashedPassword)
|
||||
|
||||
|
||||
def getPasswordHash(password: Optional[str]) -> Optional[str]:
|
||||
"""
|
||||
Hash a password, returning None if password is None.
|
||||
|
||||
Args:
|
||||
password: Plain text password or None
|
||||
|
||||
Returns:
|
||||
Hashed password or None if input was None
|
||||
"""
|
||||
if password is None:
|
||||
return None
|
||||
return _pwdContext.hash(password)
|
||||
|
|
@ -19,7 +19,7 @@ import logging
|
|||
from modules.datamodels.datamodelUam import User
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatWorkflow
|
||||
from modules.aichat.datamodelFeatureAiChat import ChatWorkflow
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import logging
|
||||
from typing import Dict, Any, List, Optional
|
||||
from modules.datamodels.datamodelUam import User, UserConnection
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatDocument, ChatMessage, ChatStat, ChatLog
|
||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument, ChatMessage, ChatStat, ChatLog
|
||||
from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum
|
||||
from modules.shared.progressLogger import ProgressLogger
|
||||
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ class UtilsService:
|
|||
Mirrors storeDebugMessageAndDocuments() in modules.interfaces.interfaceDbChat.
|
||||
"""
|
||||
try:
|
||||
from modules.features.aichat.interfaceFeatureAiChat import storeDebugMessageAndDocuments as _storeDebugMessageAndDocuments
|
||||
from modules.aichat.interfaceFeatureAiChat import storeDebugMessageAndDocuments as _storeDebugMessageAndDocuments
|
||||
_storeDebugMessageAndDocuments(message, currentUser)
|
||||
except Exception:
|
||||
# Silent fail to never break main flow
|
||||
|
|
|
|||
19
modules/shared/__init__.py
Normal file
19
modules/shared/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
"""
|
||||
Shared utilities module.
|
||||
Contains common utilities used across the gateway application.
|
||||
"""
|
||||
|
||||
from . import jsonUtils
|
||||
from . import timeUtils
|
||||
from . import attributeUtils
|
||||
from . import frontendTypes
|
||||
from . import configuration
|
||||
from . import eventManagement
|
||||
from . import auditLogger
|
||||
from . import debugLogger
|
||||
from . import progressLogger
|
||||
from . import callbackRegistry
|
||||
from . import jsonContinuation
|
||||
from . import dbMultiTenantOptimizations
|
||||
|
|
@ -12,7 +12,7 @@ import logging
|
|||
import json
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum, AutomationDefinition
|
||||
from modules.aichat.datamodelFeatureAiChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum, AutomationDefinition
|
||||
from modules.datamodels.datamodelUam import User
|
||||
from modules.shared.timeUtils import getUtcTimestamp
|
||||
from modules.shared.eventManagement import eventManager
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ActionResult
|
||||
from modules.aichat.datamodelFeatureAiChat import ActionResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import logging
|
||||
import time
|
||||
from typing import Dict, Any, Optional, List
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ActionResult, ActionDocument
|
||||
from modules.aichat.datamodelFeatureAiChat import ActionResult, ActionDocument
|
||||
from modules.datamodels.datamodelExtraction import ContentPart
|
||||
from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum
|
||||
from modules.datamodels.datamodelWorkflow import AiResponse, DocumentData
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import logging
|
||||
import time
|
||||
from typing import Dict, Any, Optional, List
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ActionResult, ActionDocument
|
||||
from modules.aichat.datamodelFeatureAiChat import ActionResult, ActionDocument
|
||||
from modules.datamodels.datamodelExtraction import ContentPart
|
||||
from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum
|
||||
from modules.datamodels.datamodelWorkflow import AiResponse, DocumentData
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import logging
|
|||
import time
|
||||
import json
|
||||
from typing import Dict, Any, List, Optional
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ActionResult, ActionDocument
|
||||
from modules.aichat.datamodelFeatureAiChat import ActionResult, ActionDocument
|
||||
from modules.datamodels.datamodelAi import AiCallOptions
|
||||
from modules.datamodels.datamodelExtraction import ContentPart
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ActionResult
|
||||
from modules.aichat.datamodelFeatureAiChat import ActionResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue