isolate features
This commit is contained in:
parent
04ba89a0e8
commit
362080791a
195 changed files with 4966 additions and 1461 deletions
68
app.py
68
app.py
|
|
@ -19,8 +19,9 @@ from datetime import datetime
|
|||
|
||||
from modules.shared.configuration import APP_CONFIG
|
||||
from modules.shared.eventManagement import eventManager
|
||||
from modules.features import featuresLifecycle as featuresLifecycle
|
||||
from modules.workflows.automation import subAutomationSchedule
|
||||
from modules.interfaces.interfaceDbApp import getRootInterface
|
||||
from modules.features.featureRegistry import loadFeatureMainModules
|
||||
|
||||
class DailyRotatingFileHandler(RotatingFileHandler):
|
||||
"""
|
||||
|
|
@ -282,18 +283,27 @@ instanceLabel = APP_CONFIG.get("APP_ENV_LABEL")
|
|||
async def lifespan(app: FastAPI):
|
||||
logger.info("Application is starting up")
|
||||
|
||||
# Initialize AI connectors once at startup to avoid per-request discovery
|
||||
from modules.aicore.aicoreModelRegistry import modelRegistry
|
||||
modelRegistry.ensureConnectorsRegistered()
|
||||
|
||||
# Get event user for feature lifecycle (system-level user for background operations)
|
||||
rootInterface = getRootInterface()
|
||||
eventUser = rootInterface.getUserByUsername("event")
|
||||
if not eventUser:
|
||||
logger.error("Could not get event user - some features may not start properly")
|
||||
|
||||
# --- Init Feature Containers (Plug&Play) ---
|
||||
try:
|
||||
mainModules = loadFeatureMainModules()
|
||||
for featureName, module in mainModules.items():
|
||||
if hasattr(module, "onStart"):
|
||||
try:
|
||||
await module.onStart(eventUser)
|
||||
logger.info(f"Feature '{featureName}' started")
|
||||
except Exception as e:
|
||||
logger.error(f"Feature '{featureName}' failed to start: {e}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not initialize feature containers: {e}")
|
||||
|
||||
# --- Init Managers ---
|
||||
await featuresLifecycle.start(eventUser)
|
||||
await subAutomationSchedule.start(eventUser) # Automation scheduler
|
||||
eventManager.start()
|
||||
|
||||
# Register audit log cleanup scheduler
|
||||
|
|
@ -304,7 +314,21 @@ async def lifespan(app: FastAPI):
|
|||
|
||||
# --- Stop Managers ---
|
||||
eventManager.stop()
|
||||
await featuresLifecycle.stop(eventUser)
|
||||
await subAutomationSchedule.stop(eventUser) # Automation scheduler
|
||||
|
||||
# --- Stop Feature Containers (Plug&Play) ---
|
||||
try:
|
||||
mainModules = loadFeatureMainModules()
|
||||
for featureName, module in mainModules.items():
|
||||
if hasattr(module, "onStop"):
|
||||
try:
|
||||
await module.onStop(eventUser)
|
||||
logger.info(f"Feature '{featureName}' stopped")
|
||||
except Exception as e:
|
||||
logger.error(f"Feature '{featureName}' failed to stop: {e}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not shutdown feature containers: {e}")
|
||||
|
||||
logger.info("Application has been shut down")
|
||||
|
||||
|
||||
|
|
@ -412,9 +436,6 @@ app.include_router(userRouter)
|
|||
from modules.routes.routeDataFiles import router as fileRouter
|
||||
app.include_router(fileRouter)
|
||||
|
||||
from modules.routes.routeFeatureNeutralization import router as neutralizationRouter
|
||||
app.include_router(neutralizationRouter)
|
||||
|
||||
from modules.routes.routeDataPrompts import router as promptRouter
|
||||
app.include_router(promptRouter)
|
||||
|
||||
|
|
@ -424,12 +445,6 @@ app.include_router(connectionsRouter)
|
|||
from modules.routes.routeDataWorkflows import router as dataWorkflowsRouter
|
||||
app.include_router(dataWorkflowsRouter)
|
||||
|
||||
from modules.routes.routeFeatureChatDynamic import router as chatPlaygroundRouter
|
||||
app.include_router(chatPlaygroundRouter)
|
||||
|
||||
from modules.routes.routeFeatureRealEstate import router as realEstateRouter
|
||||
app.include_router(realEstateRouter)
|
||||
|
||||
from modules.routes.routeSecurityLocal import router as localRouter
|
||||
app.include_router(localRouter)
|
||||
|
||||
|
|
@ -448,27 +463,15 @@ app.include_router(adminSecurityRouter)
|
|||
from modules.routes.routeSharepoint import router as sharepointRouter
|
||||
app.include_router(sharepointRouter)
|
||||
|
||||
from modules.routes.routeDataAutomation import router as automationRouter
|
||||
app.include_router(automationRouter)
|
||||
|
||||
from modules.routes.routeAdminAutomationEvents import router as adminAutomationEventsRouter
|
||||
app.include_router(adminAutomationEventsRouter)
|
||||
|
||||
from modules.routes.routeAdminRbacRules import router as rbacAdminRulesRouter
|
||||
app.include_router(rbacAdminRulesRouter)
|
||||
|
||||
from modules.routes.routeOptions import router as optionsRouter
|
||||
app.include_router(optionsRouter)
|
||||
|
||||
from modules.routes.routeMessaging import router as messagingRouter
|
||||
app.include_router(messagingRouter)
|
||||
|
||||
from modules.routes.routeFeatureChatbot import router as chatbotRouter
|
||||
app.include_router(chatbotRouter)
|
||||
|
||||
from modules.routes.routeFeatureTrustee import router as trusteeRouter
|
||||
app.include_router(trusteeRouter)
|
||||
|
||||
# Phase 8: New Feature Routes
|
||||
from modules.routes.routeAdminFeatures import router as featuresAdminRouter
|
||||
app.include_router(featuresAdminRouter)
|
||||
|
|
@ -481,3 +484,12 @@ app.include_router(rbacAdminExportRouter)
|
|||
|
||||
from modules.routes.routeGdpr import router as gdprRouter
|
||||
app.include_router(gdprRouter)
|
||||
|
||||
# ============================================================================
|
||||
# PLUG&PLAY FEATURE ROUTERS
|
||||
# Dynamically load routers from feature containers in modules/features/
|
||||
# ============================================================================
|
||||
from modules.features.featureRegistry import loadFeatureRouters
|
||||
|
||||
featureLoadResults = loadFeatureRouters(app)
|
||||
logger.info(f"Feature router load results: {featureLoadResults}")
|
||||
|
|
|
|||
|
|
@ -12,12 +12,6 @@ from modules.shared.jsonUtils import extractJsonString, tryParseJson, repairBrok
|
|||
# Import DocumentReferenceList at runtime (needed for ActionDefinition)
|
||||
from modules.datamodels.datamodelDocref import DocumentReferenceList
|
||||
|
||||
# Forward references for circular imports (use string annotations)
|
||||
if TYPE_CHECKING:
|
||||
from modules.datamodels.datamodelChat import ChatDocument, ActionResult
|
||||
from modules.datamodels.datamodelExtraction import ExtractionOptions
|
||||
|
||||
|
||||
class ActionDefinition(BaseModel):
|
||||
"""Action definition with selection and parameters from planning phase"""
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import importlib
|
|||
import os
|
||||
from typing import Dict, List, Optional, Any
|
||||
from modules.datamodels.datamodelAi import AiModel
|
||||
from modules.aicore.aicoreBase import BaseConnectorAi
|
||||
from .aicoreBase import BaseConnectorAi
|
||||
from modules.datamodels.datamodelUam import User
|
||||
from modules.security.rbacHelpers import checkResourceAccess
|
||||
from modules.security.rbac import RbacClass
|
||||
|
|
@ -6,7 +6,7 @@ import os
|
|||
from typing import Dict, Any, List
|
||||
from fastapi import HTTPException
|
||||
from modules.shared.configuration import APP_CONFIG
|
||||
from modules.aicore.aicoreBase import BaseConnectorAi
|
||||
from .aicoreBase import BaseConnectorAi
|
||||
from modules.datamodels.datamodelAi import AiModel, PriorityEnum, ProcessingModeEnum, OperationTypeEnum, AiModelCall, AiModelResponse, createOperationTypeRatings
|
||||
|
||||
# Configure logger
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
# All rights reserved.
|
||||
import logging
|
||||
from typing import List
|
||||
from modules.aicore.aicoreBase import BaseConnectorAi
|
||||
from .aicoreBase import BaseConnectorAi
|
||||
from modules.datamodels.datamodelAi import AiModel, PriorityEnum, ProcessingModeEnum, OperationTypeEnum, AiModelCall, AiModelResponse, createOperationTypeRatings
|
||||
|
||||
# Configure logger
|
||||
|
|
@ -5,7 +5,7 @@ import httpx
|
|||
from typing import List
|
||||
from fastapi import HTTPException
|
||||
from modules.shared.configuration import APP_CONFIG
|
||||
from modules.aicore.aicoreBase import BaseConnectorAi
|
||||
from .aicoreBase import BaseConnectorAi
|
||||
from modules.datamodels.datamodelAi import AiModel, PriorityEnum, ProcessingModeEnum, OperationTypeEnum, AiModelCall, AiModelResponse, createOperationTypeRatings
|
||||
|
||||
# Configure logger
|
||||
|
|
@ -5,7 +5,7 @@ import httpx
|
|||
from typing import List
|
||||
from fastapi import HTTPException
|
||||
from modules.shared.configuration import APP_CONFIG
|
||||
from modules.aicore.aicoreBase import BaseConnectorAi
|
||||
from .aicoreBase import BaseConnectorAi
|
||||
from modules.datamodels.datamodelAi import AiModel, PriorityEnum, ProcessingModeEnum, OperationTypeEnum, AiModelCall, AiModelResponse, createOperationTypeRatings, AiCallPromptWebSearch, AiCallPromptWebCrawl
|
||||
from modules.datamodels.datamodelTools import CountryCodes
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ from dataclasses import dataclass
|
|||
from typing import Optional, List, Dict
|
||||
from tavily import AsyncTavilyClient
|
||||
from modules.shared.configuration import APP_CONFIG
|
||||
from modules.aicore.aicoreBase import BaseConnectorAi
|
||||
from .aicoreBase import BaseConnectorAi
|
||||
from modules.datamodels.datamodelAi import AiModel, PriorityEnum, ProcessingModeEnum, OperationTypeEnum, AiModelCall, AiModelResponse, createOperationTypeRatings, AiCallPromptWebSearch, AiCallPromptWebCrawl
|
||||
from modules.datamodels.datamodelTools import CountryCodes
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ from modules.security.rbac import RbacClass
|
|||
from modules.datamodels.datamodelRbac import AccessRuleContext
|
||||
from modules.datamodels.datamodelUam import AccessLevel
|
||||
|
||||
from modules.datamodels.datamodelChat import (
|
||||
from .datamodelFeatureAiChat import (
|
||||
ChatDocument,
|
||||
ChatStat,
|
||||
ChatLog,
|
||||
|
|
@ -1095,26 +1095,6 @@ class ChatObjects:
|
|||
actionName=createdMessage.get("actionName")
|
||||
)
|
||||
|
||||
# Emit message event for streaming (if event manager is available)
|
||||
try:
|
||||
from modules.features.chatbot.eventManager import get_event_manager
|
||||
event_manager = get_event_manager()
|
||||
message_timestamp = parseTimestamp(chat_message.publishedAt, default=getUtcTimestamp())
|
||||
# Emit message event in exact chatData format: {type, createdAt, item}
|
||||
asyncio.create_task(event_manager.emit_event(
|
||||
context_id=workflowId,
|
||||
event_type="chatdata",
|
||||
data={
|
||||
"type": "message",
|
||||
"createdAt": message_timestamp,
|
||||
"item": chat_message.dict()
|
||||
},
|
||||
event_category="chat"
|
||||
))
|
||||
except Exception as e:
|
||||
# Event manager not available or error - continue without emitting
|
||||
logger.debug(f"Could not emit message event: {e}")
|
||||
|
||||
# Debug: Store message and documents for debugging - only if debug enabled
|
||||
storeDebugMessageAndDocuments(chat_message, self.currentUser)
|
||||
|
||||
|
|
@ -1481,29 +1461,6 @@ class ChatObjects:
|
|||
# Create log in normalized table
|
||||
createdLog = self.db.recordCreate(ChatLog, log_model)
|
||||
|
||||
# Emit log event for streaming (only for chatbot workflows)
|
||||
# Only emit events for chatbot workflows, not for automation or dynamic workflows
|
||||
if workflow.workflowMode == WorkflowModeEnum.WORKFLOW_CHATBOT:
|
||||
try:
|
||||
from modules.features.chatbot.eventManager import get_event_manager
|
||||
event_manager = get_event_manager()
|
||||
log_timestamp = parseTimestamp(createdLog.get("timestamp"), default=getUtcTimestamp())
|
||||
# Emit log event in exact chatData format: {type, createdAt, item}
|
||||
asyncio.create_task(event_manager.emit_event(
|
||||
workflowId,
|
||||
"chatdata",
|
||||
"New log",
|
||||
"log",
|
||||
{
|
||||
"type": "log",
|
||||
"createdAt": log_timestamp,
|
||||
"item": ChatLog(**createdLog).model_dump()
|
||||
}
|
||||
))
|
||||
except Exception as e:
|
||||
# Event manager not available or error - continue without emitting
|
||||
logger.debug(f"Could not emit log event: {e}")
|
||||
|
||||
# Return validated ChatLog instance
|
||||
return ChatLog(**createdLog)
|
||||
|
||||
|
|
@ -1888,14 +1845,6 @@ class ChatObjects:
|
|||
if not self.checkRbacPermission(AutomationDefinition, "delete", automationId):
|
||||
raise PermissionError(f"No permission to delete automation {automationId}")
|
||||
|
||||
# Remove event if exists
|
||||
if existing.get("eventId"):
|
||||
from modules.shared.eventManagement import eventManager
|
||||
try:
|
||||
eventManager.remove(existing["eventId"])
|
||||
except Exception as e:
|
||||
logger.warning(f"Error removing event {existing['eventId']}: {str(e)}")
|
||||
|
||||
# Delete automation from database
|
||||
self.db.recordDelete(AutomationDefinition, automationId)
|
||||
|
||||
166
modules/features/aichat/mainAiChat.py
Normal file
166
modules/features/aichat/mainAiChat.py
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
"""
|
||||
AIChat Feature Container - Main Module.
|
||||
Handles feature initialization and RBAC catalog registration.
|
||||
|
||||
AIChat is the dynamic chat workflow feature that handles:
|
||||
- AI-powered document processing
|
||||
- Dynamic workflow execution
|
||||
- Automation definitions
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Feature metadata
|
||||
FEATURE_CODE = "chatworkflow"
|
||||
FEATURE_LABEL = {"en": "Chat Workflow", "de": "Chat-Workflow", "fr": "Workflow de Chat"}
|
||||
FEATURE_ICON = "mdi-message-cog"
|
||||
|
||||
# UI Objects for RBAC catalog
|
||||
UI_OBJECTS = [
|
||||
{
|
||||
"objectKey": "ui.feature.aichat.workflows",
|
||||
"label": {"en": "Workflows", "de": "Workflows", "fr": "Workflows"},
|
||||
"meta": {"area": "workflows"}
|
||||
},
|
||||
{
|
||||
"objectKey": "ui.feature.aichat.automations",
|
||||
"label": {"en": "Automations", "de": "Automatisierungen", "fr": "Automatisations"},
|
||||
"meta": {"area": "automations"}
|
||||
},
|
||||
{
|
||||
"objectKey": "ui.feature.aichat.logs",
|
||||
"label": {"en": "Logs", "de": "Logs", "fr": "Journaux"},
|
||||
"meta": {"area": "logs"}
|
||||
},
|
||||
]
|
||||
|
||||
# Resource Objects for RBAC catalog
|
||||
RESOURCE_OBJECTS = [
|
||||
{
|
||||
"objectKey": "resource.feature.aichat.workflow.start",
|
||||
"label": {"en": "Start Workflow", "de": "Workflow starten", "fr": "Démarrer workflow"},
|
||||
"meta": {"endpoint": "/api/chat/playground/start", "method": "POST"}
|
||||
},
|
||||
{
|
||||
"objectKey": "resource.feature.aichat.workflow.stop",
|
||||
"label": {"en": "Stop Workflow", "de": "Workflow stoppen", "fr": "Arrêter workflow"},
|
||||
"meta": {"endpoint": "/api/chat/playground/stop/{workflowId}", "method": "POST"}
|
||||
},
|
||||
{
|
||||
"objectKey": "resource.feature.aichat.workflow.delete",
|
||||
"label": {"en": "Delete Workflow", "de": "Workflow löschen", "fr": "Supprimer workflow"},
|
||||
"meta": {"endpoint": "/api/chat/playground/workflow/{workflowId}", "method": "DELETE"}
|
||||
},
|
||||
]
|
||||
|
||||
# Template roles for this feature
|
||||
TEMPLATE_ROLES = [
|
||||
{
|
||||
"roleLabel": "workflow-admin",
|
||||
"description": {
|
||||
"en": "Workflow Administrator - Full access to workflow configuration and execution",
|
||||
"de": "Workflow-Administrator - Vollzugriff auf Workflow-Konfiguration und Ausführung",
|
||||
"fr": "Administrateur workflow - Accès complet à la configuration et exécution"
|
||||
}
|
||||
},
|
||||
{
|
||||
"roleLabel": "workflow-editor",
|
||||
"description": {
|
||||
"en": "Workflow Editor - Create and modify workflows",
|
||||
"de": "Workflow-Editor - Workflows erstellen und bearbeiten",
|
||||
"fr": "Éditeur workflow - Créer et modifier les workflows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"roleLabel": "workflow-viewer",
|
||||
"description": {
|
||||
"en": "Workflow Viewer - View workflows and execution results",
|
||||
"de": "Workflow-Betrachter - Workflows und Ausführungsergebnisse einsehen",
|
||||
"fr": "Visualiseur workflow - Consulter les workflows et résultats"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def getFeatureDefinition() -> Dict[str, Any]:
|
||||
"""Return the feature definition for registration."""
|
||||
return {
|
||||
"code": FEATURE_CODE,
|
||||
"label": FEATURE_LABEL,
|
||||
"icon": FEATURE_ICON
|
||||
}
|
||||
|
||||
|
||||
def getUiObjects() -> List[Dict[str, Any]]:
|
||||
"""Return UI objects for RBAC catalog registration."""
|
||||
return UI_OBJECTS
|
||||
|
||||
|
||||
def getResourceObjects() -> List[Dict[str, Any]]:
|
||||
"""Return resource objects for RBAC catalog registration."""
|
||||
return RESOURCE_OBJECTS
|
||||
|
||||
|
||||
def getTemplateRoles() -> List[Dict[str, Any]]:
|
||||
"""Return template roles for this feature."""
|
||||
return TEMPLATE_ROLES
|
||||
|
||||
|
||||
def registerFeature(catalogService) -> bool:
|
||||
"""
|
||||
Register this feature's RBAC objects in the catalog.
|
||||
|
||||
Args:
|
||||
catalogService: The RBAC catalog service instance
|
||||
|
||||
Returns:
|
||||
True if registration was successful
|
||||
"""
|
||||
try:
|
||||
# Register UI objects
|
||||
for uiObj in UI_OBJECTS:
|
||||
catalogService.registerUiObject(
|
||||
featureCode=FEATURE_CODE,
|
||||
objectKey=uiObj["objectKey"],
|
||||
label=uiObj["label"],
|
||||
meta=uiObj.get("meta")
|
||||
)
|
||||
|
||||
# Register Resource objects
|
||||
for resObj in RESOURCE_OBJECTS:
|
||||
catalogService.registerResourceObject(
|
||||
featureCode=FEATURE_CODE,
|
||||
objectKey=resObj["objectKey"],
|
||||
label=resObj["label"],
|
||||
meta=resObj.get("meta")
|
||||
)
|
||||
|
||||
logger.info(f"Feature '{FEATURE_CODE}' registered {len(UI_OBJECTS)} UI objects and {len(RESOURCE_OBJECTS)} resource objects")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to register feature '{FEATURE_CODE}': {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def onStart(eventUser) -> None:
|
||||
"""
|
||||
Called when the feature container starts.
|
||||
Initializes AI connectors for model registry.
|
||||
"""
|
||||
try:
|
||||
from .aicore.aicoreModelRegistry import modelRegistry
|
||||
modelRegistry.ensureConnectorsRegistered()
|
||||
logger.info(f"Feature '{FEATURE_CODE}' started - AI connectors initialized")
|
||||
except Exception as e:
|
||||
logger.error(f"Feature '{FEATURE_CODE}' failed to initialize AI connectors: {e}")
|
||||
|
||||
|
||||
async def onStop(eventUser) -> None:
|
||||
"""Called when the feature container stops."""
|
||||
logger.info(f"Feature '{FEATURE_CODE}' stopped")
|
||||
|
|
@ -13,13 +13,13 @@ from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query, Reques
|
|||
from modules.auth import limiter, getRequestContext, RequestContext
|
||||
|
||||
# Import interfaces
|
||||
import modules.interfaces.interfaceDbChat as interfaceDbChat
|
||||
from . import interfaceFeatureAiChat as interfaceDbChat
|
||||
|
||||
# Import models
|
||||
from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum
|
||||
from .datamodelFeatureAiChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum
|
||||
|
||||
# Import workflow control functions
|
||||
from modules.features.workflow import chatStart, chatStop
|
||||
from modules.workflows.automation import chatStart, chatStop
|
||||
|
||||
# Configure logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -6,8 +6,8 @@ import re
|
|||
import time
|
||||
import base64
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from modules.datamodels.datamodelChat import PromptPlaceholder, ChatDocument
|
||||
from modules.services.serviceExtraction.mainServiceExtraction import ExtractionService
|
||||
from modules.features.aichat.datamodelFeatureAiChat import PromptPlaceholder, ChatDocument
|
||||
from modules.features.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
|
||||
|
|
@ -16,7 +16,7 @@ from modules.interfaces.interfaceAiObjects import AiObjects
|
|||
from modules.shared.jsonUtils import (
|
||||
parseJsonWithModel
|
||||
)
|
||||
from modules.services.serviceAi.subJsonResponseHandling import JsonResponseHandler
|
||||
from .subJsonResponseHandling import JsonResponseHandler
|
||||
from modules.datamodels.datamodelAi import JsonAccumulationState
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -49,12 +49,12 @@ class AiService:
|
|||
self.extractionService = ExtractionService(self.services)
|
||||
|
||||
# Initialize new submodules
|
||||
from modules.services.serviceAi.subResponseParsing import ResponseParser
|
||||
from modules.services.serviceAi.subDocumentIntents import DocumentIntentAnalyzer
|
||||
from modules.services.serviceAi.subContentExtraction import ContentExtractor
|
||||
from modules.services.serviceAi.subStructureGeneration import StructureGenerator
|
||||
from modules.services.serviceAi.subStructureFilling import StructureFiller
|
||||
from modules.services.serviceAi.subAiCallLooping import AiCallLooper
|
||||
from .subResponseParsing import ResponseParser
|
||||
from .subDocumentIntents import DocumentIntentAnalyzer
|
||||
from .subContentExtraction import ContentExtractor
|
||||
from .subStructureGeneration import StructureGenerator
|
||||
from .subStructureFilling import StructureFiller
|
||||
from .subAiCallLooping import AiCallLooper
|
||||
|
||||
if not hasattr(self, 'responseParser'):
|
||||
logger.info("Initializing ResponseParser...")
|
||||
|
|
@ -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.services.serviceGeneration.paths.imagePath import ImageGenerationPath
|
||||
from modules.features.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.services.serviceGeneration.mainServiceGeneration import GenerationService
|
||||
from modules.features.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.services.serviceGeneration.paths.codePath import CodeGenerationPath
|
||||
from modules.features.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.services.serviceGeneration.paths.documentPath import DocumentGenerationPath
|
||||
from modules.features.aichat.serviceGeneration.paths.documentPath import DocumentGenerationPath
|
||||
|
||||
# Set compression options for document generation
|
||||
options.compressPrompt = False
|
||||
|
|
@ -53,8 +53,8 @@ from modules.datamodels.datamodelAi import (
|
|||
AiCallRequest, AiCallOptions
|
||||
)
|
||||
from modules.datamodels.datamodelExtraction import ContentPart
|
||||
from modules.services.serviceAi.subJsonResponseHandling import JsonResponseHandler
|
||||
from modules.services.serviceAi.subLoopingUseCases import LoopingUseCaseRegistry
|
||||
from .subJsonResponseHandling import JsonResponseHandler
|
||||
from .subLoopingUseCases import LoopingUseCaseRegistry
|
||||
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
||||
from modules.shared.jsonContinuation import getContexts
|
||||
from modules.shared.jsonUtils import buildContinuationContext, extractJsonString, tryParseJson
|
||||
|
|
@ -14,7 +14,7 @@ import logging
|
|||
import base64
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from modules.datamodels.datamodelChat import ChatDocument
|
||||
from modules.features.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.datamodels.datamodelChat import ChatDocument
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.datamodels.datamodelExtraction import DocumentIntent
|
||||
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
||||
|
||||
|
|
@ -1346,7 +1346,7 @@ class JsonResponseHandler:
|
|||
|
||||
# Use new modular merger
|
||||
try:
|
||||
from modules.services.serviceAi.subJsonMerger import ModularJsonMerger
|
||||
from .subJsonMerger import ModularJsonMerger
|
||||
result, hasOverlap = ModularJsonMerger.merge(accumulated, newFragment)
|
||||
# IMPORTANT: ModularJsonMerger returns unclosed JSON if overlap found (with incomplete element at end)
|
||||
# If no overlap, returns closed JSON (iterations should stop)
|
||||
|
|
@ -15,7 +15,7 @@ import logging
|
|||
from typing import Dict, Any, List, Optional, Tuple
|
||||
|
||||
from modules.shared.jsonUtils import extractJsonString, repairBrokenJson, extractSectionsFromDocument
|
||||
from modules.services.serviceAi.subJsonResponseHandling import JsonResponseHandler
|
||||
from .subJsonResponseHandling import JsonResponseHandler
|
||||
from modules.datamodels.datamodelAi import JsonAccumulationState
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -2531,7 +2531,7 @@ CRITICAL:
|
|||
List of accepted section content types (e.g., ["table", "code_block"])
|
||||
"""
|
||||
try:
|
||||
from modules.services.serviceGeneration.renderers.registry import getRenderer
|
||||
from modules.features.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.services.serviceGeneration.renderers.registry import getRenderer
|
||||
from modules.features.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.datamodels.datamodelChat import ChatDocument
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.datamodels.datamodelAi import AiCallResponse, AiCallRequest, AiCallOptions, OperationTypeEnum, AiModelCall
|
||||
from modules.aicore.aicoreModelRegistry import modelRegistry
|
||||
from modules.aicore.aicoreModelSelector import modelSelector
|
||||
from modules.features.aichat.aicore.aicoreModelRegistry import modelRegistry
|
||||
from modules.features.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.services.serviceGeneration.renderers.documentRendererBaseTemplate import BaseRenderer
|
||||
from modules.features.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.services.serviceExtraction.extractors")
|
||||
module = importlib.import_module(f".{module_name}", package="modules.features.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.datamodels.datamodelChat import ChatDocument
|
||||
from modules.services.serviceGeneration.subDocumentUtility import (
|
||||
from modules.features.aichat.datamodelFeatureAiChat import ChatDocument
|
||||
from modules.features.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.services.serviceGeneration.renderers.registry import getOutputStyle
|
||||
from modules.features.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.services.serviceGeneration.subStructureGenerator import StructureGenerator
|
||||
from modules.services.serviceGeneration.subContentGenerator import ContentGenerator
|
||||
from modules.features.aichat.serviceGeneration.subStructureGenerator import StructureGenerator
|
||||
from modules.features.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.services.serviceExtraction.subPromptBuilderExtraction import buildExtractionPrompt
|
||||
from modules.features.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.services.serviceGeneration.renderers.registry import getRenderer
|
||||
from modules.features.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.services.serviceGeneration.subContentIntegrator import ContentIntegrator
|
||||
from modules.features.aichat.serviceGeneration.subContentIntegrator import ContentIntegrator
|
||||
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
148
modules/features/automation/mainAutomation.py
Normal file
148
modules/features/automation/mainAutomation.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
"""
|
||||
Automation Feature Container - Main Module.
|
||||
Handles feature initialization and RBAC catalog registration.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Feature metadata
|
||||
FEATURE_CODE = "automation"
|
||||
FEATURE_LABEL = {"en": "Automation", "de": "Automatisierung", "fr": "Automatisation"}
|
||||
FEATURE_ICON = "mdi-cog-clockwise"
|
||||
|
||||
# UI Objects for RBAC catalog
|
||||
UI_OBJECTS = [
|
||||
{
|
||||
"objectKey": "ui.feature.automation.definitions",
|
||||
"label": {"en": "Automation Definitions", "de": "Automatisierungs-Definitionen", "fr": "Définitions d'automatisation"},
|
||||
"meta": {"area": "definitions"}
|
||||
},
|
||||
{
|
||||
"objectKey": "ui.feature.automation.templates",
|
||||
"label": {"en": "Templates", "de": "Vorlagen", "fr": "Modèles"},
|
||||
"meta": {"area": "templates"}
|
||||
},
|
||||
{
|
||||
"objectKey": "ui.feature.automation.logs",
|
||||
"label": {"en": "Execution Logs", "de": "Ausführungsprotokolle", "fr": "Journaux d'exécution"},
|
||||
"meta": {"area": "logs"}
|
||||
},
|
||||
]
|
||||
|
||||
# Resource Objects for RBAC catalog
|
||||
RESOURCE_OBJECTS = [
|
||||
{
|
||||
"objectKey": "resource.feature.automation.create",
|
||||
"label": {"en": "Create Automation", "de": "Automatisierung erstellen", "fr": "Créer automatisation"},
|
||||
"meta": {"endpoint": "/api/automations", "method": "POST"}
|
||||
},
|
||||
{
|
||||
"objectKey": "resource.feature.automation.update",
|
||||
"label": {"en": "Update Automation", "de": "Automatisierung aktualisieren", "fr": "Modifier automatisation"},
|
||||
"meta": {"endpoint": "/api/automations/{automationId}", "method": "PUT"}
|
||||
},
|
||||
{
|
||||
"objectKey": "resource.feature.automation.delete",
|
||||
"label": {"en": "Delete Automation", "de": "Automatisierung löschen", "fr": "Supprimer automatisation"},
|
||||
"meta": {"endpoint": "/api/automations/{automationId}", "method": "DELETE"}
|
||||
},
|
||||
{
|
||||
"objectKey": "resource.feature.automation.execute",
|
||||
"label": {"en": "Execute Automation", "de": "Automatisierung ausführen", "fr": "Exécuter automatisation"},
|
||||
"meta": {"endpoint": "/api/automations/{automationId}/execute", "method": "POST"}
|
||||
},
|
||||
]
|
||||
|
||||
# Template roles for this feature
|
||||
TEMPLATE_ROLES = [
|
||||
{
|
||||
"roleLabel": "automation-admin",
|
||||
"description": {
|
||||
"en": "Automation Administrator - Full access to automation configuration and execution",
|
||||
"de": "Automatisierungs-Administrator - Vollzugriff auf Automatisierungs-Konfiguration und Ausführung",
|
||||
"fr": "Administrateur automatisation - Accès complet à la configuration et exécution"
|
||||
}
|
||||
},
|
||||
{
|
||||
"roleLabel": "automation-editor",
|
||||
"description": {
|
||||
"en": "Automation Editor - Create and modify automations",
|
||||
"de": "Automatisierungs-Editor - Automatisierungen erstellen und bearbeiten",
|
||||
"fr": "Éditeur automatisation - Créer et modifier les automatisations"
|
||||
}
|
||||
},
|
||||
{
|
||||
"roleLabel": "automation-viewer",
|
||||
"description": {
|
||||
"en": "Automation Viewer - View automations and execution results",
|
||||
"de": "Automatisierungs-Betrachter - Automatisierungen und Ausführungsergebnisse einsehen",
|
||||
"fr": "Visualiseur automatisation - Consulter les automatisations et résultats"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def getFeatureDefinition() -> Dict[str, Any]:
|
||||
"""Return the feature definition for registration."""
|
||||
return {
|
||||
"code": FEATURE_CODE,
|
||||
"label": FEATURE_LABEL,
|
||||
"icon": FEATURE_ICON
|
||||
}
|
||||
|
||||
|
||||
def getUiObjects() -> List[Dict[str, Any]]:
|
||||
"""Return UI objects for RBAC catalog registration."""
|
||||
return UI_OBJECTS
|
||||
|
||||
|
||||
def getResourceObjects() -> List[Dict[str, Any]]:
|
||||
"""Return resource objects for RBAC catalog registration."""
|
||||
return RESOURCE_OBJECTS
|
||||
|
||||
|
||||
def getTemplateRoles() -> List[Dict[str, Any]]:
|
||||
"""Return template roles for this feature."""
|
||||
return TEMPLATE_ROLES
|
||||
|
||||
|
||||
def registerFeature(catalogService) -> bool:
|
||||
"""
|
||||
Register this feature's RBAC objects in the catalog.
|
||||
|
||||
Args:
|
||||
catalogService: The RBAC catalog service instance
|
||||
|
||||
Returns:
|
||||
True if registration was successful
|
||||
"""
|
||||
try:
|
||||
# Register UI objects
|
||||
for uiObj in UI_OBJECTS:
|
||||
catalogService.registerUiObject(
|
||||
featureCode=FEATURE_CODE,
|
||||
objectKey=uiObj["objectKey"],
|
||||
label=uiObj["label"],
|
||||
meta=uiObj.get("meta")
|
||||
)
|
||||
|
||||
# Register Resource objects
|
||||
for resObj in RESOURCE_OBJECTS:
|
||||
catalogService.registerResourceObject(
|
||||
featureCode=FEATURE_CODE,
|
||||
objectKey=resObj["objectKey"],
|
||||
label=resObj["label"],
|
||||
meta=resObj.get("meta")
|
||||
)
|
||||
|
||||
logger.info(f"Feature '{FEATURE_CODE}' registered {len(UI_OBJECTS)} UI objects and {len(RESOURCE_OBJECTS)} resource objects")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to register feature '{FEATURE_CODE}': {e}")
|
||||
return False
|
||||
|
|
@ -13,13 +13,13 @@ import logging
|
|||
import json
|
||||
|
||||
# Import interfaces and models
|
||||
from modules.interfaces.interfaceDbChat import getInterface as getChatInterface
|
||||
from modules.features.aichat.interfaceFeatureAiChat import getInterface as getChatInterface
|
||||
from modules.auth import getCurrentUser, limiter
|
||||
from modules.datamodels.datamodelChat import AutomationDefinition, ChatWorkflow
|
||||
from modules.features.aichat.datamodelFeatureAiChat import AutomationDefinition, ChatWorkflow
|
||||
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResponse, PaginationMetadata, normalize_pagination_dict
|
||||
from modules.shared.attributeUtils import getModelAttributeDefinitions
|
||||
from modules.features.workflow import executeAutomation
|
||||
from modules.features.workflow.subAutomationTemplates import getAutomationTemplates
|
||||
from modules.workflows.automation import executeAutomation
|
||||
from .subAutomationTemplates import getAutomationTemplates
|
||||
|
||||
# Configure logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -16,7 +16,7 @@ from modules.security.rbac import RbacClass
|
|||
from modules.datamodels.datamodelRbac import AccessRuleContext
|
||||
from modules.datamodels.datamodelUam import AccessLevel
|
||||
|
||||
from modules.datamodels.datamodelChat import (
|
||||
from .datamodelFeatureChatbot import (
|
||||
ChatDocument,
|
||||
ChatStat,
|
||||
ChatLog,
|
||||
|
|
@ -4,16 +4,120 @@
|
|||
Simple chatbot feature - basic implementation.
|
||||
User input is processed by AI to create list of needed queries.
|
||||
Those queries get streamed back.
|
||||
|
||||
This module also handles feature initialization and RBAC catalog registration.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
# Feature metadata for RBAC catalog
|
||||
FEATURE_CODE = "chatbot"
|
||||
FEATURE_LABEL = {"en": "Chatbot", "de": "Chatbot", "fr": "Chatbot"}
|
||||
FEATURE_ICON = "mdi-robot"
|
||||
|
||||
# UI Objects for RBAC catalog
|
||||
UI_OBJECTS = [
|
||||
{
|
||||
"objectKey": "ui.feature.chatbot.conversations",
|
||||
"label": {"en": "Conversations", "de": "Konversationen", "fr": "Conversations"},
|
||||
"meta": {"area": "conversations"}
|
||||
},
|
||||
{
|
||||
"objectKey": "ui.feature.chatbot.settings",
|
||||
"label": {"en": "Settings", "de": "Einstellungen", "fr": "Paramètres"},
|
||||
"meta": {"area": "settings"}
|
||||
},
|
||||
]
|
||||
|
||||
# Resource Objects for RBAC catalog
|
||||
RESOURCE_OBJECTS = [
|
||||
{
|
||||
"objectKey": "resource.feature.chatbot.start",
|
||||
"label": {"en": "Start Chatbot", "de": "Chatbot starten", "fr": "Démarrer chatbot"},
|
||||
"meta": {"endpoint": "/api/chatbot/start/stream", "method": "POST"}
|
||||
},
|
||||
{
|
||||
"objectKey": "resource.feature.chatbot.stop",
|
||||
"label": {"en": "Stop Chatbot", "de": "Chatbot stoppen", "fr": "Arrêter chatbot"},
|
||||
"meta": {"endpoint": "/api/chatbot/stop/{workflowId}", "method": "POST"}
|
||||
},
|
||||
]
|
||||
|
||||
# Template roles for this feature
|
||||
TEMPLATE_ROLES = [
|
||||
{
|
||||
"roleLabel": "chatbot-admin",
|
||||
"description": {
|
||||
"en": "Chatbot Administrator - Full access to chatbot settings and all conversations",
|
||||
"de": "Chatbot-Administrator - Vollzugriff auf Chatbot-Einstellungen und alle Konversationen",
|
||||
"fr": "Administrateur chatbot - Accès complet aux paramètres et conversations"
|
||||
}
|
||||
},
|
||||
{
|
||||
"roleLabel": "chatbot-user",
|
||||
"description": {
|
||||
"en": "Chatbot User - Use chatbot and view own conversations",
|
||||
"de": "Chatbot-Benutzer - Chatbot nutzen und eigene Konversationen einsehen",
|
||||
"fr": "Utilisateur chatbot - Utiliser le chatbot et consulter ses conversations"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def getFeatureDefinition():
|
||||
"""Return the feature definition for registration."""
|
||||
return {
|
||||
"code": FEATURE_CODE,
|
||||
"label": FEATURE_LABEL,
|
||||
"icon": FEATURE_ICON
|
||||
}
|
||||
|
||||
|
||||
def getUiObjects():
|
||||
"""Return UI objects for RBAC catalog registration."""
|
||||
return UI_OBJECTS
|
||||
|
||||
|
||||
def getResourceObjects():
|
||||
"""Return resource objects for RBAC catalog registration."""
|
||||
return RESOURCE_OBJECTS
|
||||
|
||||
|
||||
def getTemplateRoles():
|
||||
"""Return template roles for this feature."""
|
||||
return TEMPLATE_ROLES
|
||||
|
||||
|
||||
def registerFeature(catalogService) -> bool:
|
||||
"""Register this feature's RBAC objects in the catalog."""
|
||||
try:
|
||||
for uiObj in UI_OBJECTS:
|
||||
catalogService.registerUiObject(
|
||||
featureCode=FEATURE_CODE,
|
||||
objectKey=uiObj["objectKey"],
|
||||
label=uiObj["label"],
|
||||
meta=uiObj.get("meta")
|
||||
)
|
||||
|
||||
for resObj in RESOURCE_OBJECTS:
|
||||
catalogService.registerResourceObject(
|
||||
featureCode=FEATURE_CODE,
|
||||
objectKey=resObj["objectKey"],
|
||||
label=resObj["label"],
|
||||
meta=resObj.get("meta")
|
||||
)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.getLogger(__name__).error(f"Failed to register feature '{FEATURE_CODE}': {e}")
|
||||
return False
|
||||
import json
|
||||
import uuid
|
||||
import asyncio
|
||||
import re
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum, ChatLog, ChatDocument
|
||||
from modules.features.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
|
||||
|
|
@ -335,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.datamodels.datamodelChat import ChatLog
|
||||
from modules.features.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()
|
||||
|
|
|
|||
|
|
@ -18,19 +18,19 @@ from modules.shared.timeUtils import parseTimestamp
|
|||
from modules.auth import limiter, getRequestContext, RequestContext
|
||||
|
||||
# Import interfaces
|
||||
import modules.interfaces.interfaceDbChat as interfaceDbChat
|
||||
from . import interfaceFeatureChatbot as interfaceDbChat
|
||||
from modules.interfaces.interfaceRbac import getRecordsetWithRBAC
|
||||
|
||||
# Import models
|
||||
from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum
|
||||
from .datamodelFeatureChatbot import ChatWorkflow, UserInputRequest, WorkflowModeEnum
|
||||
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResponse
|
||||
|
||||
# Import chatbot feature
|
||||
from modules.features.chatbot import chatProcess
|
||||
from modules.features.chatbot.eventManager import get_event_manager
|
||||
from . import chatProcess
|
||||
from .eventManager import get_event_manager
|
||||
|
||||
# Import workflow control functions
|
||||
from modules.features.workflow import chatStop
|
||||
from modules.workflows.automation import chatStop
|
||||
|
||||
# Configure logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
"""
|
||||
Dynamic Options API feature module.
|
||||
Provides dynamic options for frontend select/multiselect fields.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Any, Optional
|
||||
from modules.datamodels.datamodelUam import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Standard role definitions (fallback if database is not available)
|
||||
STANDARD_ROLES = [
|
||||
{"value": "sysadmin", "label": {"en": "System Administrator", "fr": "Administrateur système"}},
|
||||
{"value": "admin", "label": {"en": "Administrator", "fr": "Administrateur"}},
|
||||
{"value": "user", "label": {"en": "User", "fr": "Utilisateur"}},
|
||||
{"value": "viewer", "label": {"en": "Viewer", "fr": "Visualiseur"}},
|
||||
]
|
||||
|
||||
# Authentication authority options
|
||||
AUTH_AUTHORITY_OPTIONS = [
|
||||
{"value": "local", "label": {"en": "Local", "fr": "Local"}},
|
||||
{"value": "google", "label": {"en": "Google", "fr": "Google"}},
|
||||
{"value": "msft", "label": {"en": "Microsoft", "fr": "Microsoft"}},
|
||||
]
|
||||
|
||||
# Connection status options
|
||||
# Note: Matches ConnectionStatus enum values (active, expired, revoked, pending)
|
||||
# Plus "error" for error states (not in enum but used in UI)
|
||||
CONNECTION_STATUS_OPTIONS = [
|
||||
{"value": "active", "label": {"en": "Active", "fr": "Actif"}},
|
||||
{"value": "expired", "label": {"en": "Expired", "fr": "Expiré"}},
|
||||
{"value": "revoked", "label": {"en": "Revoked", "fr": "Révoqué"}},
|
||||
{"value": "pending", "label": {"en": "Pending", "fr": "En attente"}},
|
||||
{"value": "error", "label": {"en": "Error", "fr": "Erreur"}},
|
||||
]
|
||||
|
||||
|
||||
def getOptions(optionsName: str, services, currentUser: Optional[User] = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get options for a given options name.
|
||||
|
||||
Args:
|
||||
optionsName: Name of the options set to retrieve (e.g., "user.role", "user.connection")
|
||||
services: Services instance for data access
|
||||
currentUser: Optional current user for context-aware options
|
||||
|
||||
Returns:
|
||||
List of option dictionaries with "value" and "label" keys
|
||||
|
||||
Raises:
|
||||
ValueError: If optionsName is not recognized
|
||||
"""
|
||||
logger.debug(f"getOptions called with optionsName='{optionsName}' (repr: {repr(optionsName)})")
|
||||
optionsNameLower = optionsName.lower()
|
||||
logger.debug(f"optionsNameLower='{optionsNameLower}'")
|
||||
|
||||
if optionsNameLower == "user.role":
|
||||
# Fetch roles from database
|
||||
if currentUser:
|
||||
try:
|
||||
roles = services.interfaceDbApp.getAllRoles()
|
||||
|
||||
# Convert Role objects to options format
|
||||
options = []
|
||||
for role in roles:
|
||||
# Use English description as label, fallback to roleLabel
|
||||
# Handle TextMultilingual object
|
||||
if hasattr(role.description, 'get_text'):
|
||||
# TextMultilingual object
|
||||
label = role.description.get_text('en')
|
||||
elif isinstance(role.description, dict):
|
||||
# Dict format (backward compatibility)
|
||||
label = role.description.get("en", role.roleLabel)
|
||||
else:
|
||||
# Fallback to roleLabel
|
||||
label = role.roleLabel
|
||||
|
||||
options.append({
|
||||
"value": role.roleLabel,
|
||||
"label": label
|
||||
})
|
||||
|
||||
# If no roles in database, return standard roles as fallback
|
||||
if options:
|
||||
return options
|
||||
except Exception as e:
|
||||
logger.warning(f"Error fetching roles from database, using fallback: {e}")
|
||||
|
||||
# Fallback to standard roles if database fetch fails or no user context
|
||||
return STANDARD_ROLES
|
||||
|
||||
elif optionsNameLower == "auth.authority":
|
||||
return AUTH_AUTHORITY_OPTIONS
|
||||
|
||||
elif optionsNameLower == "connection.status":
|
||||
return CONNECTION_STATUS_OPTIONS
|
||||
|
||||
elif optionsNameLower == "user.connection":
|
||||
# Dynamic options: Get user connections from database
|
||||
if not currentUser:
|
||||
return []
|
||||
|
||||
try:
|
||||
connections = services.interfaceDbApp.getUserConnections(currentUser.id)
|
||||
|
||||
return [
|
||||
{
|
||||
"value": conn.id,
|
||||
"label": {
|
||||
"en": f"{conn.authority.value} - {conn.externalUsername or conn.externalId}",
|
||||
"fr": f"{conn.authority.value} - {conn.externalUsername or conn.externalId}"
|
||||
}
|
||||
}
|
||||
for conn in connections
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching user connections for options: {e}")
|
||||
return []
|
||||
|
||||
elif optionsNameLower in ("user", "users"):
|
||||
# Dynamic options: Get all users for the current mandate
|
||||
if not currentUser:
|
||||
return []
|
||||
|
||||
try:
|
||||
users = services.interfaceDbApp.getUsersByMandate(services.mandateId)
|
||||
|
||||
# Handle both list and PaginatedResult
|
||||
if hasattr(users, 'items'):
|
||||
userList = users.items
|
||||
else:
|
||||
userList = users
|
||||
|
||||
return [
|
||||
{
|
||||
"value": user.id,
|
||||
"label": user.fullName or user.username or user.email or user.id
|
||||
}
|
||||
for user in userList
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching users for options: {e}")
|
||||
return []
|
||||
|
||||
elif optionsNameLower in ("trusteeorganisation", "trustee.organisation"):
|
||||
# Dynamic options: Get all trustee organisations
|
||||
if not currentUser:
|
||||
return []
|
||||
|
||||
try:
|
||||
result = services.interfaceDbTrustee.getAllOrganisations()
|
||||
|
||||
# Handle PaginatedResult
|
||||
items = result.items if hasattr(result, 'items') else result
|
||||
|
||||
return [
|
||||
{
|
||||
"value": org.get("id") if isinstance(org, dict) else org.id,
|
||||
"label": org.get("label") if isinstance(org, dict) else org.label
|
||||
}
|
||||
for org in items
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching trustee organisations for options: {e}")
|
||||
return []
|
||||
|
||||
elif optionsNameLower in ("trusteerole", "trustee.role"):
|
||||
# Dynamic options: Get all trustee roles
|
||||
if not currentUser:
|
||||
return []
|
||||
|
||||
try:
|
||||
result = services.interfaceDbTrustee.getAllRoles()
|
||||
|
||||
# Handle PaginatedResult
|
||||
items = result.items if hasattr(result, 'items') else result
|
||||
|
||||
return [
|
||||
{
|
||||
"value": role.get("id") if isinstance(role, dict) else role.id,
|
||||
# TrusteeRole uses 'desc' field, not 'label'
|
||||
"label": role.get("desc", role.get("id")) if isinstance(role, dict) else getattr(role, "desc", role.id)
|
||||
}
|
||||
for role in items
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching trustee roles for options: {e}")
|
||||
return []
|
||||
|
||||
elif optionsNameLower in ("trusteecontract", "trustee.contract"):
|
||||
# Dynamic options: Get all trustee contracts
|
||||
if not currentUser:
|
||||
return []
|
||||
|
||||
try:
|
||||
result = services.interfaceDbTrustee.getAllContracts()
|
||||
|
||||
# Handle PaginatedResult
|
||||
items = result.items if hasattr(result, 'items') else result
|
||||
|
||||
return [
|
||||
{
|
||||
"value": contract.get("id") if isinstance(contract, dict) else contract.id,
|
||||
"label": contract.get("label") if isinstance(contract, dict) else (contract.get("name") if isinstance(contract, dict) else getattr(contract, "label", getattr(contract, "name", contract.id)))
|
||||
}
|
||||
for contract in items
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching trustee contracts for options: {e}")
|
||||
return []
|
||||
|
||||
else:
|
||||
logger.error(f"Unknown options name: '{optionsName}' (lower: '{optionsNameLower}')")
|
||||
raise ValueError(f"Unknown options name: {optionsName}")
|
||||
|
||||
|
||||
def getAvailableOptionsNames() -> List[str]:
|
||||
"""
|
||||
Get list of all available options names.
|
||||
|
||||
Returns:
|
||||
List of available options names
|
||||
"""
|
||||
return [
|
||||
"user.role",
|
||||
"auth.authority",
|
||||
"connection.status",
|
||||
"user.connection",
|
||||
"User",
|
||||
"TrusteeOrganisation",
|
||||
"TrusteeRole",
|
||||
"TrusteeContract",
|
||||
]
|
||||
|
||||
117
modules/features/featureRegistry.py
Normal file
117
modules/features/featureRegistry.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
"""
|
||||
Feature Registry for Plug&Play Feature Container Loading.
|
||||
Dynamically discovers and loads feature containers from the features directory.
|
||||
"""
|
||||
|
||||
import os
|
||||
import glob
|
||||
import importlib
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
from fastapi import FastAPI
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Path to the features directory
|
||||
FEATURES_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def discoverFeatureContainers() -> List[str]:
|
||||
"""
|
||||
Discover all feature container directories by filename pattern.
|
||||
A valid feature container has a routeFeature*.py file.
|
||||
"""
|
||||
containers = []
|
||||
pattern = os.path.join(FEATURES_DIR, "*", "routeFeature*.py")
|
||||
|
||||
for filepath in glob.glob(pattern):
|
||||
featureDir = os.path.basename(os.path.dirname(filepath))
|
||||
if featureDir not in containers and not featureDir.startswith("_"):
|
||||
containers.append(featureDir)
|
||||
|
||||
return sorted(containers)
|
||||
|
||||
|
||||
def loadFeatureRouters(app: FastAPI) -> Dict[str, Any]:
|
||||
"""
|
||||
Dynamically load and register routers from all discovered feature containers.
|
||||
"""
|
||||
results = {}
|
||||
pattern = os.path.join(FEATURES_DIR, "*", "routeFeature*.py")
|
||||
|
||||
for filepath in glob.glob(pattern):
|
||||
featureDir = os.path.basename(os.path.dirname(filepath))
|
||||
routerFile = os.path.basename(filepath)[:-3] # Remove .py
|
||||
|
||||
if featureDir.startswith("_"):
|
||||
continue
|
||||
|
||||
try:
|
||||
modulePath = f"modules.features.{featureDir}.{routerFile}"
|
||||
module = importlib.import_module(modulePath)
|
||||
|
||||
if hasattr(module, "router"):
|
||||
app.include_router(module.router)
|
||||
logger.info(f"Loaded router: {featureDir}")
|
||||
results[featureDir] = {"status": "loaded", "module": modulePath}
|
||||
else:
|
||||
logger.warning(f"No 'router' in {modulePath}")
|
||||
results[featureDir] = {"status": "no_router_object"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load router from {featureDir}: {e}")
|
||||
results[featureDir] = {"status": "error", "error": str(e)}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def loadFeatureMainModules() -> Dict[str, Any]:
|
||||
"""
|
||||
Dynamically load main modules from all discovered feature containers.
|
||||
"""
|
||||
mainModules = {}
|
||||
pattern = os.path.join(FEATURES_DIR, "*", "main*.py")
|
||||
|
||||
for filepath in glob.glob(pattern):
|
||||
filename = os.path.basename(filepath)
|
||||
if filename == "__init__.py":
|
||||
continue
|
||||
|
||||
featureDir = os.path.basename(os.path.dirname(filepath))
|
||||
if featureDir.startswith("_"):
|
||||
continue
|
||||
|
||||
mainFile = filename[:-3] # Remove .py
|
||||
|
||||
try:
|
||||
modulePath = f"modules.features.{featureDir}.{mainFile}"
|
||||
module = importlib.import_module(modulePath)
|
||||
mainModules[featureDir] = module
|
||||
logger.debug(f"Loaded main module: {featureDir}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load main module from {featureDir}: {e}")
|
||||
|
||||
return mainModules
|
||||
|
||||
|
||||
def registerAllFeaturesInCatalog(catalogService) -> Dict[str, bool]:
|
||||
"""
|
||||
Register all features' RBAC objects in the catalog.
|
||||
"""
|
||||
mainModules = loadFeatureMainModules()
|
||||
results = {}
|
||||
|
||||
for featureName, module in mainModules.items():
|
||||
if hasattr(module, "registerFeature"):
|
||||
try:
|
||||
success = module.registerFeature(catalogService)
|
||||
results[featureName] = success
|
||||
if success:
|
||||
logger.info(f"Registered RBAC objects: {featureName}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error registering {featureName}: {e}")
|
||||
results[featureName] = False
|
||||
|
||||
return results
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
import logging
|
||||
from modules.services import getInterface as getServices
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def start(eventUser) -> None:
|
||||
""" Start feature triggers and background managers
|
||||
|
||||
Args:
|
||||
eventUser: System-level event user for background operations (provided by app.py)
|
||||
"""
|
||||
|
||||
# Feature Workflow (Automation)
|
||||
if eventUser:
|
||||
try:
|
||||
from modules.features.workflow import syncAutomationEvents
|
||||
from modules.shared.callbackRegistry import callbackRegistry
|
||||
|
||||
# Get services for event user (provides access to interfaces)
|
||||
services = getServices(eventUser, None)
|
||||
|
||||
# Register callback for automation changes
|
||||
async def onAutomationChanged(chatInterface):
|
||||
"""Callback triggered when automations are created/updated/deleted."""
|
||||
# Get services for event user to pass to syncAutomationEvents
|
||||
eventServices = getServices(eventUser, None)
|
||||
await syncAutomationEvents(eventServices, eventUser)
|
||||
|
||||
callbackRegistry.register('automation.changed', onAutomationChanged)
|
||||
logger.info("Workflow: Registered change callback")
|
||||
|
||||
# Initial sync on startup - use services
|
||||
await syncAutomationEvents(services, eventUser)
|
||||
logger.info("Workflow: Events synced on startup")
|
||||
except Exception as e:
|
||||
logger.error(f"Workflow: Error setting up events on startup: {str(e)}")
|
||||
# Don't fail startup if automation sync fails
|
||||
|
||||
|
||||
# Feature ...
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
async def stop(eventUser) -> None:
|
||||
""" Stop feature triggers and background managers
|
||||
|
||||
Args:
|
||||
eventUser: System-level event user (provided by app.py)
|
||||
"""
|
||||
|
||||
# Feature Workflow (Automation)
|
||||
# Callbacks will remain registered (acceptable for shutdown)
|
||||
logger.info("Workflow: Callbacks remain registered (will be cleaned up on shutdown)")
|
||||
|
||||
|
||||
# Feature ...
|
||||
|
||||
return True
|
||||
|
|
@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional
|
|||
from urllib.parse import urlparse, unquote
|
||||
|
||||
from modules.datamodels.datamodelUam import User
|
||||
from modules.datamodels.datamodelNeutralizer import DataNeutralizerAttributes, DataNeutraliserConfig
|
||||
from .datamodelFeatureNeutralizer import DataNeutralizerAttributes, DataNeutraliserConfig
|
||||
from modules.services import getInterface as getServices
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
125
modules/features/neutralizer/mainNeutralizer.py
Normal file
125
modules/features/neutralizer/mainNeutralizer.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
"""
|
||||
Neutralizer Feature Container - Main Module.
|
||||
Handles feature initialization and RBAC catalog registration.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Feature metadata
|
||||
FEATURE_CODE = "neutralization"
|
||||
FEATURE_LABEL = {"en": "Neutralization", "de": "Neutralisierung", "fr": "Neutralisation"}
|
||||
FEATURE_ICON = "mdi-shield-check"
|
||||
|
||||
# UI Objects for RBAC catalog
|
||||
UI_OBJECTS = [
|
||||
{
|
||||
"objectKey": "ui.feature.neutralizer.playground",
|
||||
"label": {"en": "Playground", "de": "Spielwiese", "fr": "Bac à sable"},
|
||||
"meta": {"area": "playground"}
|
||||
},
|
||||
{
|
||||
"objectKey": "ui.feature.neutralizer.config",
|
||||
"label": {"en": "Configuration", "de": "Konfiguration", "fr": "Configuration"},
|
||||
"meta": {"area": "config"}
|
||||
},
|
||||
{
|
||||
"objectKey": "ui.feature.neutralizer.attributes",
|
||||
"label": {"en": "Attributes", "de": "Attribute", "fr": "Attributs"},
|
||||
"meta": {"area": "attributes"}
|
||||
},
|
||||
]
|
||||
|
||||
# Resource Objects for RBAC catalog
|
||||
RESOURCE_OBJECTS = [
|
||||
{
|
||||
"objectKey": "resource.feature.neutralizer.process.text",
|
||||
"label": {"en": "Process Text", "de": "Text verarbeiten", "fr": "Traiter texte"},
|
||||
"meta": {"endpoint": "/api/neutralization/process/text", "method": "POST"}
|
||||
},
|
||||
{
|
||||
"objectKey": "resource.feature.neutralizer.process.files",
|
||||
"label": {"en": "Process Files", "de": "Dateien verarbeiten", "fr": "Traiter fichiers"},
|
||||
"meta": {"endpoint": "/api/neutralization/process/files", "method": "POST"}
|
||||
},
|
||||
{
|
||||
"objectKey": "resource.feature.neutralizer.config.update",
|
||||
"label": {"en": "Update Config", "de": "Konfiguration aktualisieren", "fr": "Mettre à jour config"},
|
||||
"meta": {"endpoint": "/api/neutralization/config", "method": "PUT"}
|
||||
},
|
||||
]
|
||||
|
||||
# Template roles for this feature
|
||||
TEMPLATE_ROLES = [
|
||||
{
|
||||
"roleLabel": "neutralization-admin",
|
||||
"description": {
|
||||
"en": "Neutralization Administrator - Full access to neutralization settings and data",
|
||||
"de": "Neutralisierungs-Administrator - Vollzugriff auf Neutralisierungs-Einstellungen und Daten",
|
||||
"fr": "Administrateur neutralisation - Accès complet aux paramètres et données"
|
||||
}
|
||||
},
|
||||
{
|
||||
"roleLabel": "neutralization-analyst",
|
||||
"description": {
|
||||
"en": "Neutralization Analyst - Analyze and process neutralization data",
|
||||
"de": "Neutralisierungs-Analyst - Neutralisierungsdaten analysieren und verarbeiten",
|
||||
"fr": "Analyste neutralisation - Analyser et traiter les données de neutralisation"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def getFeatureDefinition() -> Dict[str, Any]:
|
||||
"""Return the feature definition for registration."""
|
||||
return {
|
||||
"code": FEATURE_CODE,
|
||||
"label": FEATURE_LABEL,
|
||||
"icon": FEATURE_ICON
|
||||
}
|
||||
|
||||
|
||||
def getUiObjects() -> List[Dict[str, Any]]:
|
||||
"""Return UI objects for RBAC catalog registration."""
|
||||
return UI_OBJECTS
|
||||
|
||||
|
||||
def getResourceObjects() -> List[Dict[str, Any]]:
|
||||
"""Return resource objects for RBAC catalog registration."""
|
||||
return RESOURCE_OBJECTS
|
||||
|
||||
|
||||
def getTemplateRoles() -> List[Dict[str, Any]]:
|
||||
"""Return template roles for this feature."""
|
||||
return TEMPLATE_ROLES
|
||||
|
||||
|
||||
def registerFeature(catalogService) -> bool:
|
||||
"""Register this feature's RBAC objects in the catalog."""
|
||||
try:
|
||||
for uiObj in UI_OBJECTS:
|
||||
catalogService.registerUiObject(
|
||||
featureCode=FEATURE_CODE,
|
||||
objectKey=uiObj["objectKey"],
|
||||
label=uiObj["label"],
|
||||
meta=uiObj.get("meta")
|
||||
)
|
||||
|
||||
for resObj in RESOURCE_OBJECTS:
|
||||
catalogService.registerResourceObject(
|
||||
featureCode=FEATURE_CODE,
|
||||
objectKey=resObj["objectKey"],
|
||||
label=resObj["label"],
|
||||
meta=resObj.get("meta")
|
||||
)
|
||||
|
||||
logger.info(f"Feature '{FEATURE_CODE}' registered {len(UI_OBJECTS)} UI objects and {len(RESOURCE_OBJECTS)} resource objects")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to register feature '{FEATURE_CODE}': {e}")
|
||||
return False
|
||||
|
|
@ -8,8 +8,8 @@ import logging
|
|||
from modules.auth import limiter, getRequestContext, RequestContext
|
||||
|
||||
# Import interfaces
|
||||
from modules.datamodels.datamodelNeutralizer import DataNeutraliserConfig, DataNeutralizerAttributes
|
||||
from modules.features.neutralizePlayground.mainNeutralizePlayground import NeutralizationPlayground
|
||||
from .datamodelFeatureNeutralizer import DataNeutraliserConfig, DataNeutralizerAttributes
|
||||
from .mainNeutralizePlayground import NeutralizationPlayground
|
||||
|
||||
# Configure logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -13,14 +13,14 @@ import re
|
|||
import json
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
from modules.datamodels.datamodelNeutralizer import DataNeutraliserConfig, DataNeutralizerAttributes
|
||||
from modules.features.neutralizer.datamodelFeatureNeutralizer import DataNeutraliserConfig, DataNeutralizerAttributes
|
||||
|
||||
# Import all necessary classes and functions for neutralization
|
||||
from modules.services.serviceNeutralization.subProcessCommon import CommonUtils, NeutralizationResult, NeutralizationAttribute
|
||||
from modules.services.serviceNeutralization.subProcessText import TextProcessor, PlainText
|
||||
from modules.services.serviceNeutralization.subProcessList import ListProcessor, TableData
|
||||
from modules.services.serviceNeutralization.subProcessBinary import BinaryProcessor
|
||||
from modules.services.serviceNeutralization.subPatterns import HeaderPatterns, DataPatterns, TextTablePatterns
|
||||
from .subProcessCommon import CommonUtils, NeutralizationResult, NeutralizationAttribute
|
||||
from .subProcessText import TextProcessor, PlainText
|
||||
from .subProcessList import ListProcessor, TableData
|
||||
from .subProcessBinary import BinaryProcessor
|
||||
from .subPatterns import HeaderPatterns, DataPatterns, TextTablePatterns
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ Handles pattern matching and replacement for emails, phones, addresses, IDs and
|
|||
import re
|
||||
import uuid
|
||||
from typing import Dict, List, Tuple, Any
|
||||
from modules.services.serviceNeutralization.subPatterns import DataPatterns, findPatternsInText
|
||||
from .subPatterns import DataPatterns, findPatternsInText
|
||||
|
||||
class StringParser:
|
||||
"""Handles string parsing and replacement operations"""
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue