refactored features phase II
This commit is contained in:
parent
bb9630d6c4
commit
280cafd54a
179 changed files with 5422 additions and 1098 deletions
3
app.py
3
app.py
|
|
@ -485,6 +485,9 @@ app.include_router(rbacAdminExportRouter)
|
||||||
from modules.routes.routeGdpr import router as gdprRouter
|
from modules.routes.routeGdpr import router as gdprRouter
|
||||||
app.include_router(gdprRouter)
|
app.include_router(gdprRouter)
|
||||||
|
|
||||||
|
from modules.routes.routeChat import router as chatRouter
|
||||||
|
app.include_router(chatRouter)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# PLUG&PLAY FEATURE ROUTERS
|
# PLUG&PLAY FEATURE ROUTERS
|
||||||
# Dynamically load routers from feature containers in modules/features/
|
# Dynamically load routers from feature containers in modules/features/
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,6 @@ class AiOpenai(BaseConnectorAi):
|
||||||
promptContent = messages[0]["content"] if messages else ""
|
promptContent = messages[0]["content"] if messages else ""
|
||||||
|
|
||||||
# Parse prompt using AiCallPromptImage model
|
# Parse prompt using AiCallPromptImage model
|
||||||
from modules.datamodels.datamodelAi import AiCallPromptImage
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -184,7 +184,6 @@ class AiPerplexity(BaseConnectorAi):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Create a model call for testing
|
# Create a model call for testing
|
||||||
from modules.datamodels.datamodelAi import AiCallOptions
|
|
||||||
model = self.getModels()[0] # Get first model for testing
|
model = self.getModels()[0] # Get first model for testing
|
||||||
testCall = AiModelCall(
|
testCall = AiModelCall(
|
||||||
messages=testMessages,
|
messages=testMessages,
|
||||||
|
|
@ -1022,40 +1022,3 @@ registerModelLabels(
|
||||||
"placeholders": {"en": "Placeholders", "fr": "Espaces réservés"},
|
"placeholders": {"en": "Placeholders", "fr": "Espaces réservés"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AutomationDefinition(BaseModel):
|
|
||||||
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
mandateId: str = Field(description="Mandate ID", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
featureInstanceId: str = Field(description="ID of the feature instance this automation belongs to", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
label: str = Field(description="User-friendly name", json_schema_extra={"frontend_type": "text", "frontend_required": True})
|
|
||||||
schedule: str = Field(description="Cron schedule pattern", json_schema_extra={"frontend_type": "select", "frontend_required": True, "frontend_options": [
|
|
||||||
{"value": "0 */4 * * *", "label": {"en": "Every 4 hours", "fr": "Toutes les 4 heures"}},
|
|
||||||
{"value": "0 22 * * *", "label": {"en": "Daily at 22:00", "fr": "Quotidien à 22:00"}},
|
|
||||||
{"value": "0 10 * * 1", "label": {"en": "Weekly Monday 10:00", "fr": "Hebdomadaire lundi 10:00"}}
|
|
||||||
]})
|
|
||||||
template: str = Field(description="JSON template with placeholders (format: {{KEY:PLACEHOLDER_NAME}})", json_schema_extra={"frontend_type": "textarea", "frontend_required": True})
|
|
||||||
placeholders: Dict[str, str] = Field(default_factory=dict, description="Dictionary of placeholder key/value pairs (e.g., {'connectionName': 'MyConnection', 'sharepointFolderNameSource': '/folder/path', 'webResearchUrl': 'https://...', 'webResearchPrompt': '...', 'documentPrompt': '...'})", json_schema_extra={"frontend_type": "text"})
|
|
||||||
active: bool = Field(default=False, description="Whether automation should be launched in event handler", json_schema_extra={"frontend_type": "checkbox", "frontend_required": False})
|
|
||||||
eventId: Optional[str] = Field(None, description="Event ID from event management (None if not registered)", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
status: Optional[str] = Field(None, description="Status: 'active' if event is registered, 'inactive' if not (computed, readonly)", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
executionLogs: List[Dict[str, Any]] = Field(default_factory=list, description="List of execution logs, each containing timestamp, workflowId, status, and messages", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
|
|
||||||
|
|
||||||
registerModelLabels(
|
|
||||||
"AutomationDefinition",
|
|
||||||
{"en": "Automation Definition", "fr": "Définition d'automatisation"},
|
|
||||||
{
|
|
||||||
"id": {"en": "ID", "fr": "ID"},
|
|
||||||
"mandateId": {"en": "Mandate ID", "fr": "ID du mandat"},
|
|
||||||
"featureInstanceId": {"en": "Feature Instance ID", "fr": "ID de l'instance de fonctionnalité"},
|
|
||||||
"label": {"en": "Label", "fr": "Libellé"},
|
|
||||||
"schedule": {"en": "Schedule", "fr": "Planification"},
|
|
||||||
"template": {"en": "Template", "fr": "Modèle"},
|
|
||||||
"placeholders": {"en": "Placeholders", "fr": "Espaces réservés"},
|
|
||||||
"active": {"en": "Active", "fr": "Actif"},
|
|
||||||
"eventId": {"en": "Event ID", "fr": "ID de l'événement"},
|
|
||||||
"status": {"en": "Status", "fr": "Statut"},
|
|
||||||
"executionLogs": {"en": "Execution Logs", "fr": "Journaux d'exécution"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
from typing import Optional, Any, Union, List, Dict, Callable, Awaitable
|
from typing import Optional, Any, Union, List, Dict, Callable, Awaitable
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from modules.aichat.datamodelFeatureAiChat import ActionResult
|
from modules.datamodels.datamodelChat import ActionResult
|
||||||
from modules.shared.frontendTypes import FrontendType
|
from modules.shared.frontendTypes import FrontendType
|
||||||
from modules.shared.attributeUtils import registerModelLabels
|
from modules.shared.attributeUtils import registerModelLabels
|
||||||
|
|
||||||
|
|
|
||||||
45
modules/features/automation/datamodelFeatureAutomation.py
Normal file
45
modules/features/automation/datamodelFeatureAutomation.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Copyright (c) 2025 Patrick Motsch
|
||||||
|
# All rights reserved.
|
||||||
|
"""Automation models: AutomationDefinition."""
|
||||||
|
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from modules.shared.attributeUtils import registerModelLabels
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class AutomationDefinition(BaseModel):
|
||||||
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
||||||
|
mandateId: str = Field(description="Mandate ID", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
||||||
|
featureInstanceId: str = Field(description="ID of the feature instance this automation belongs to", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
||||||
|
label: str = Field(description="User-friendly name", json_schema_extra={"frontend_type": "text", "frontend_required": True})
|
||||||
|
schedule: str = Field(description="Cron schedule pattern", json_schema_extra={"frontend_type": "select", "frontend_required": True, "frontend_options": [
|
||||||
|
{"value": "0 */4 * * *", "label": {"en": "Every 4 hours", "fr": "Toutes les 4 heures"}},
|
||||||
|
{"value": "0 22 * * *", "label": {"en": "Daily at 22:00", "fr": "Quotidien à 22:00"}},
|
||||||
|
{"value": "0 10 * * 1", "label": {"en": "Weekly Monday 10:00", "fr": "Hebdomadaire lundi 10:00"}}
|
||||||
|
]})
|
||||||
|
template: str = Field(description="JSON template with placeholders (format: {{KEY:PLACEHOLDER_NAME}})", json_schema_extra={"frontend_type": "textarea", "frontend_required": True})
|
||||||
|
placeholders: Dict[str, str] = Field(default_factory=dict, description="Dictionary of placeholder key/value pairs (e.g., {'connectionName': 'MyConnection', 'sharepointFolderNameSource': '/folder/path', 'webResearchUrl': 'https://...', 'webResearchPrompt': '...', 'documentPrompt': '...'})", json_schema_extra={"frontend_type": "text"})
|
||||||
|
active: bool = Field(default=False, description="Whether automation should be launched in event handler", json_schema_extra={"frontend_type": "checkbox", "frontend_required": False})
|
||||||
|
eventId: Optional[str] = Field(None, description="Event ID from event management (None if not registered)", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
||||||
|
status: Optional[str] = Field(None, description="Status: 'active' if event is registered, 'inactive' if not (computed, readonly)", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
||||||
|
executionLogs: List[Dict[str, Any]] = Field(default_factory=list, description="List of execution logs, each containing timestamp, workflowId, status, and messages", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
||||||
|
|
||||||
|
|
||||||
|
registerModelLabels(
|
||||||
|
"AutomationDefinition",
|
||||||
|
{"en": "Automation Definition", "fr": "Définition d'automatisation"},
|
||||||
|
{
|
||||||
|
"id": {"en": "ID", "fr": "ID"},
|
||||||
|
"mandateId": {"en": "Mandate ID", "fr": "ID du mandat"},
|
||||||
|
"featureInstanceId": {"en": "Feature Instance ID", "fr": "ID de l'instance de fonctionnalité"},
|
||||||
|
"label": {"en": "Label", "fr": "Libellé"},
|
||||||
|
"schedule": {"en": "Schedule", "fr": "Planification"},
|
||||||
|
"template": {"en": "Template", "fr": "Modèle"},
|
||||||
|
"placeholders": {"en": "Placeholders", "fr": "Espaces réservés"},
|
||||||
|
"active": {"en": "Active", "fr": "Actif"},
|
||||||
|
"eventId": {"en": "Event ID", "fr": "ID de l'événement"},
|
||||||
|
"status": {"en": "Status", "fr": "Statut"},
|
||||||
|
"executionLogs": {"en": "Execution Logs", "fr": "Journaux d'exécution"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
@ -13,9 +13,10 @@ import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# Import interfaces and models
|
# Import interfaces and models
|
||||||
from modules.aichat.interfaceFeatureAiChat import getInterface as getChatInterface
|
from modules.interfaces.interfaceDbChat import getInterface as getChatInterface
|
||||||
from modules.auth import getCurrentUser, limiter
|
from modules.auth import getCurrentUser, limiter
|
||||||
from modules.aichat.datamodelFeatureAiChat import AutomationDefinition, ChatWorkflow
|
from modules.features.automation.datamodelFeatureAutomation import AutomationDefinition
|
||||||
|
from modules.datamodels.datamodelChat import ChatWorkflow
|
||||||
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResponse, PaginationMetadata, normalize_pagination_dict
|
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResponse, PaginationMetadata, normalize_pagination_dict
|
||||||
from modules.shared.attributeUtils import getModelAttributeDefinitions
|
from modules.shared.attributeUtils import getModelAttributeDefinitions
|
||||||
from modules.workflows.automation import executeAutomation
|
from modules.workflows.automation import executeAutomation
|
||||||
|
|
|
||||||
|
|
@ -1022,40 +1022,3 @@ registerModelLabels(
|
||||||
"placeholders": {"en": "Placeholders", "fr": "Espaces réservés"},
|
"placeholders": {"en": "Placeholders", "fr": "Espaces réservés"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AutomationDefinition(BaseModel):
|
|
||||||
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
mandateId: str = Field(description="Mandate ID", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
featureInstanceId: str = Field(description="ID of the feature instance this automation belongs to", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
label: str = Field(description="User-friendly name", json_schema_extra={"frontend_type": "text", "frontend_required": True})
|
|
||||||
schedule: str = Field(description="Cron schedule pattern", json_schema_extra={"frontend_type": "select", "frontend_required": True, "frontend_options": [
|
|
||||||
{"value": "0 */4 * * *", "label": {"en": "Every 4 hours", "fr": "Toutes les 4 heures"}},
|
|
||||||
{"value": "0 22 * * *", "label": {"en": "Daily at 22:00", "fr": "Quotidien à 22:00"}},
|
|
||||||
{"value": "0 10 * * 1", "label": {"en": "Weekly Monday 10:00", "fr": "Hebdomadaire lundi 10:00"}}
|
|
||||||
]})
|
|
||||||
template: str = Field(description="JSON template with placeholders (format: {{KEY:PLACEHOLDER_NAME}})", json_schema_extra={"frontend_type": "textarea", "frontend_required": True})
|
|
||||||
placeholders: Dict[str, str] = Field(default_factory=dict, description="Dictionary of placeholder key/value pairs (e.g., {'connectionName': 'MyConnection', 'sharepointFolderNameSource': '/folder/path', 'webResearchUrl': 'https://...', 'webResearchPrompt': '...', 'documentPrompt': '...'})", json_schema_extra={"frontend_type": "text"})
|
|
||||||
active: bool = Field(default=False, description="Whether automation should be launched in event handler", json_schema_extra={"frontend_type": "checkbox", "frontend_required": False})
|
|
||||||
eventId: Optional[str] = Field(None, description="Event ID from event management (None if not registered)", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
status: Optional[str] = Field(None, description="Status: 'active' if event is registered, 'inactive' if not (computed, readonly)", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
executionLogs: List[Dict[str, Any]] = Field(default_factory=list, description="List of execution logs, each containing timestamp, workflowId, status, and messages", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False})
|
|
||||||
|
|
||||||
|
|
||||||
registerModelLabels(
|
|
||||||
"AutomationDefinition",
|
|
||||||
{"en": "Automation Definition", "fr": "Définition d'automatisation"},
|
|
||||||
{
|
|
||||||
"id": {"en": "ID", "fr": "ID"},
|
|
||||||
"mandateId": {"en": "Mandate ID", "fr": "ID du mandat"},
|
|
||||||
"featureInstanceId": {"en": "Feature Instance ID", "fr": "ID de l'instance de fonctionnalité"},
|
|
||||||
"label": {"en": "Label", "fr": "Libellé"},
|
|
||||||
"schedule": {"en": "Schedule", "fr": "Planification"},
|
|
||||||
"template": {"en": "Template", "fr": "Modèle"},
|
|
||||||
"placeholders": {"en": "Placeholders", "fr": "Espaces réservés"},
|
|
||||||
"active": {"en": "Active", "fr": "Actif"},
|
|
||||||
"eventId": {"en": "Event ID", "fr": "ID de l'événement"},
|
|
||||||
"status": {"en": "Status", "fr": "Statut"},
|
|
||||||
"executionLogs": {"en": "Execution Logs", "fr": "Journaux d'exécution"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ from .datamodelFeatureChatbot import (
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
ChatWorkflow,
|
ChatWorkflow,
|
||||||
WorkflowModeEnum,
|
WorkflowModeEnum,
|
||||||
AutomationDefinition,
|
|
||||||
UserInputRequest
|
UserInputRequest
|
||||||
)
|
)
|
||||||
|
from modules.features.automation.datamodelFeatureAutomation import AutomationDefinition
|
||||||
import json
|
import json
|
||||||
from modules.datamodels.datamodelUam import User
|
from modules.datamodels.datamodelUam import User
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ import asyncio
|
||||||
import re
|
import re
|
||||||
from typing import Optional, Dict, Any, List
|
from typing import Optional, Dict, Any, List
|
||||||
|
|
||||||
from modules.aichat.datamodelFeatureAiChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum, ChatLog, ChatDocument
|
from modules.features.chatbot.datamodelFeatureChatbot import ChatWorkflow, UserInputRequest, WorkflowModeEnum, ChatLog, ChatDocument
|
||||||
from modules.datamodels.datamodelUam import User
|
from modules.datamodels.datamodelUam import User
|
||||||
from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationTypeEnum, ProcessingModeEnum
|
from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationTypeEnum, ProcessingModeEnum
|
||||||
from modules.datamodels.datamodelDocref import DocumentReferenceList, DocumentItemReference
|
from modules.datamodels.datamodelDocref import DocumentReferenceList, DocumentItemReference
|
||||||
|
|
@ -439,7 +439,6 @@ async def _emit_log_and_event(
|
||||||
# Emit event directly for streaming (using correct signature)
|
# Emit event directly for streaming (using correct signature)
|
||||||
if created_log and event_manager:
|
if created_log and event_manager:
|
||||||
try:
|
try:
|
||||||
from modules.aichat.datamodelFeatureAiChat import ChatLog
|
|
||||||
# Convert to dict if it's a Pydantic model
|
# Convert to dict if it's a Pydantic model
|
||||||
if hasattr(created_log, "model_dump"):
|
if hasattr(created_log, "model_dump"):
|
||||||
log_dict = created_log.model_dump()
|
log_dict = created_log.model_dump()
|
||||||
|
|
@ -1248,7 +1247,6 @@ async def _processChatbotMessage(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Retry analysis with empty results context - create NEW analysis with alternative strategies
|
# Retry analysis with empty results context - create NEW analysis with alternative strategies
|
||||||
from modules.features.chatbot.chatbotConstants import get_empty_results_retry_instructions
|
|
||||||
|
|
||||||
# Build retry prompt with progressively different strategies
|
# Build retry prompt with progressively different strategies
|
||||||
empty_count = len(sql_queries)
|
empty_count = len(sql_queries)
|
||||||
|
|
|
||||||
|
|
@ -432,7 +432,6 @@ async def get_chatbot_threads(
|
||||||
normalized_workflows.append(normalized_wf)
|
normalized_workflows.append(normalized_wf)
|
||||||
|
|
||||||
# Create paginated response
|
# Create paginated response
|
||||||
from modules.datamodels.datamodelPagination import PaginationMetadata
|
|
||||||
metadata = PaginationMetadata(
|
metadata = PaginationMetadata(
|
||||||
currentPage=paginationParams.page if paginationParams else 1,
|
currentPage=paginationParams.page if paginationParams else 1,
|
||||||
pageSize=paginationParams.pageSize if paginationParams else len(workflows),
|
pageSize=paginationParams.pageSize if paginationParams else len(workflows),
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,6 @@ class SharepointProcessor:
|
||||||
|
|
||||||
async def _getSharepointConnection(self, sharepointPath: str = None):
|
async def _getSharepointConnection(self, sharepointPath: str = None):
|
||||||
try:
|
try:
|
||||||
from modules.datamodels.datamodelUam import UserConnection
|
|
||||||
connections = self.services.interfaceDbApp.db.getRecordset(
|
connections = self.services.interfaceDbApp.db.getRecordset(
|
||||||
UserConnection,
|
UserConnection,
|
||||||
recordFilter={"userId": self.services.interfaceDbApp.userId}
|
recordFilter={"userId": self.services.interfaceDbApp.userId}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import time
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from modules.aichat.aicore.aicoreModelRegistry import modelRegistry
|
from modules.aicore.aicoreModelRegistry import modelRegistry
|
||||||
from modules.aichat.aicore.aicoreModelSelector import modelSelector
|
from modules.aicore.aicoreModelSelector import modelSelector
|
||||||
from modules.datamodels.datamodelAi import (
|
from modules.datamodels.datamodelAi import (
|
||||||
AiModel,
|
AiModel,
|
||||||
AiCallOptions,
|
AiCallOptions,
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,16 @@ from modules.security.rbac import RbacClass
|
||||||
from modules.datamodels.datamodelRbac import AccessRuleContext
|
from modules.datamodels.datamodelRbac import AccessRuleContext
|
||||||
from modules.datamodels.datamodelUam import AccessLevel
|
from modules.datamodels.datamodelUam import AccessLevel
|
||||||
|
|
||||||
from .datamodelFeatureAiChat import (
|
from modules.datamodels.datamodelChat import (
|
||||||
ChatDocument,
|
ChatDocument,
|
||||||
ChatStat,
|
ChatStat,
|
||||||
ChatLog,
|
ChatLog,
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
ChatWorkflow,
|
ChatWorkflow,
|
||||||
WorkflowModeEnum,
|
WorkflowModeEnum,
|
||||||
AutomationDefinition,
|
|
||||||
UserInputRequest
|
UserInputRequest
|
||||||
)
|
)
|
||||||
|
from modules.features.automation.datamodelFeatureAutomation import AutomationDefinition
|
||||||
import json
|
import json
|
||||||
from modules.datamodels.datamodelUam import User
|
from modules.datamodels.datamodelUam import User
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ from fastapi import status
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# Import interfaces and models from feature containers
|
# Import interfaces and models from feature containers
|
||||||
import modules.aichat.interfaceFeatureAiChat as interfaceDbChat
|
import modules.interfaces.interfaceDbChat as interfaceDbChat
|
||||||
from modules.auth import limiter, getRequestContext, requireSysAdmin, RequestContext
|
from modules.auth import limiter, getRequestContext, requireSysAdmin, RequestContext
|
||||||
from modules.datamodels.datamodelUam import User
|
from modules.datamodels.datamodelUam import User
|
||||||
|
|
||||||
|
|
@ -76,7 +76,6 @@ async def sync_all_automation_events(
|
||||||
This will register/remove events based on active flags.
|
This will register/remove events based on active flags.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from modules.aichat.interfaceFeatureAiChat import getInterface as getChatInterface
|
|
||||||
from modules.interfaces.interfaceDbApp import getRootInterface
|
from modules.interfaces.interfaceDbApp import getRootInterface
|
||||||
from modules.workflows.automation import syncAutomationEvents
|
from modules.workflows.automation import syncAutomationEvents
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -799,7 +799,6 @@ async def listFeatureInstanceUsers(
|
||||||
# Get all FeatureAccess records for this instance
|
# Get all FeatureAccess records for this instance
|
||||||
from modules.datamodels.datamodelMembership import FeatureAccess, FeatureAccessRole
|
from modules.datamodels.datamodelMembership import FeatureAccess, FeatureAccessRole
|
||||||
from modules.datamodels.datamodelRbac import Role
|
from modules.datamodels.datamodelRbac import Role
|
||||||
from modules.datamodels.datamodelUam import UserInDB
|
|
||||||
|
|
||||||
featureAccesses = rootInterface.db.getRecordset(
|
featureAccesses = rootInterface.db.getRecordset(
|
||||||
FeatureAccess,
|
FeatureAccess,
|
||||||
|
|
@ -899,7 +898,6 @@ async def addUserToFeatureInstance(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify user exists
|
# Verify user exists
|
||||||
from modules.datamodels.datamodelUam import UserInDB
|
|
||||||
users = rootInterface.db.getRecordset(UserInDB, recordFilter={"id": data.userId})
|
users = rootInterface.db.getRecordset(UserInDB, recordFilter={"id": data.userId})
|
||||||
if not users:
|
if not users:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
|
||||||
|
|
@ -364,7 +364,6 @@ async def listUsersWithRoles(
|
||||||
|
|
||||||
# Get all users (SysAdmin sees all)
|
# Get all users (SysAdmin sees all)
|
||||||
# Use db.getRecordset with UserInDB (the actual database model)
|
# Use db.getRecordset with UserInDB (the actual database model)
|
||||||
from modules.datamodels.datamodelUam import User, UserInDB
|
|
||||||
allUsersData = interface.db.getRecordset(UserInDB)
|
allUsersData = interface.db.getRecordset(UserInDB)
|
||||||
# Convert to User objects, filtering out sensitive fields
|
# Convert to User objects, filtering out sensitive fields
|
||||||
users = []
|
users = []
|
||||||
|
|
@ -376,7 +375,6 @@ async def listUsersWithRoles(
|
||||||
|
|
||||||
# Filter by mandate if specified (via UserMandate table)
|
# Filter by mandate if specified (via UserMandate table)
|
||||||
if mandateId:
|
if mandateId:
|
||||||
from modules.datamodels.datamodelMembership import UserMandate
|
|
||||||
userMandates = interface.db.getRecordset(UserMandate, recordFilter={"mandateId": mandateId})
|
userMandates = interface.db.getRecordset(UserMandate, recordFilter={"mandateId": mandateId})
|
||||||
mandateUserIds = {str(um["userId"]) for um in userMandates}
|
mandateUserIds = {str(um["userId"]) for um in userMandates}
|
||||||
users = [u for u in users if str(u.id) in mandateUserIds]
|
users = [u for u in users if str(u.id) in mandateUserIds]
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from modules.auth import limiter, getRequestContext, RequestContext
|
||||||
from . import interfaceFeatureAiChat as interfaceDbChat
|
from . import interfaceFeatureAiChat as interfaceDbChat
|
||||||
|
|
||||||
# Import models
|
# Import models
|
||||||
from .datamodelFeatureAiChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum
|
from modules.datamodels.datamodelChat import ChatWorkflow, UserInputRequest, WorkflowModeEnum
|
||||||
|
|
||||||
# Import workflow control functions
|
# Import workflow control functions
|
||||||
from modules.workflows.automation import chatStart, chatStop
|
from modules.workflows.automation import chatStart, chatStop
|
||||||
|
|
@ -182,7 +182,6 @@ async def get_connections(
|
||||||
|
|
||||||
# Perform silent token refresh for expired OAuth connections
|
# Perform silent token refresh for expired OAuth connections
|
||||||
try:
|
try:
|
||||||
from modules.auth import token_refresh_service
|
|
||||||
refresh_result = await token_refresh_service.refresh_expired_tokens(currentUser.id)
|
refresh_result = await token_refresh_service.refresh_expired_tokens(currentUser.id)
|
||||||
if refresh_result.get("refreshed", 0) > 0:
|
if refresh_result.get("refreshed", 0) > 0:
|
||||||
logger.info(f"Silently refreshed {refresh_result['refreshed']} tokens for user {currentUser.id}")
|
logger.info(f"Silently refreshed {refresh_result['refreshed']} tokens for user {currentUser.id}")
|
||||||
|
|
|
||||||
|
|
@ -291,7 +291,6 @@ async def delete_mandate(
|
||||||
)
|
)
|
||||||
|
|
||||||
# MULTI-TENANT: Delete all UserMandate entries for this mandate first
|
# MULTI-TENANT: Delete all UserMandate entries for this mandate first
|
||||||
from modules.datamodels.datamodelMembership import UserMandate
|
|
||||||
userMandates = appInterface.db.getRecordset(UserMandate, recordFilter={"mandateId": mandateId})
|
userMandates = appInterface.db.getRecordset(UserMandate, recordFilter={"mandateId": mandateId})
|
||||||
for um in userMandates:
|
for um in userMandates:
|
||||||
appInterface.db.deleteRecord(UserMandate, um["id"])
|
appInterface.db.deleteRecord(UserMandate, um["id"])
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,6 @@ async def get_users(
|
||||||
elif context.isSysAdmin:
|
elif context.isSysAdmin:
|
||||||
# SysAdmin without mandateId sees all users
|
# SysAdmin without mandateId sees all users
|
||||||
# Get all users directly from database using UserInDB (the actual database model)
|
# Get all users directly from database using UserInDB (the actual database model)
|
||||||
from modules.datamodels.datamodelUam import UserInDB
|
|
||||||
allUsers = appInterface.db.getRecordset(UserInDB)
|
allUsers = appInterface.db.getRecordset(UserInDB)
|
||||||
# Convert to cleaned dictionaries first for filtering
|
# Convert to cleaned dictionaries first for filtering
|
||||||
cleanedUsers = []
|
cleanedUsers = []
|
||||||
|
|
@ -378,7 +377,6 @@ async def create_user(
|
||||||
appInterface = interfaceDbApp.getInterface(context.user)
|
appInterface = interfaceDbApp.getInterface(context.user)
|
||||||
|
|
||||||
# Extract fields from request model and call createUser with individual parameters
|
# Extract fields from request model and call createUser with individual parameters
|
||||||
from modules.datamodels.datamodelUam import AuthAuthority
|
|
||||||
newUser = appInterface.createUser(
|
newUser = appInterface.createUser(
|
||||||
username=userData.username,
|
username=userData.username,
|
||||||
password=userData.password,
|
password=userData.password,
|
||||||
|
|
@ -512,7 +510,6 @@ async def reset_user_password(
|
||||||
|
|
||||||
# SECURITY: Automatically revoke all tokens for the user after password reset
|
# SECURITY: Automatically revoke all tokens for the user after password reset
|
||||||
try:
|
try:
|
||||||
from modules.datamodels.datamodelUam import AuthAuthority
|
|
||||||
revoked_count = appInterface.revokeTokensByUser(
|
revoked_count = appInterface.revokeTokensByUser(
|
||||||
userId=userId,
|
userId=userId,
|
||||||
authority=None, # Revoke all authorities
|
authority=None, # Revoke all authorities
|
||||||
|
|
@ -593,7 +590,6 @@ async def change_password(
|
||||||
|
|
||||||
# SECURITY: Automatically revoke all tokens for the user after password change
|
# SECURITY: Automatically revoke all tokens for the user after password change
|
||||||
try:
|
try:
|
||||||
from modules.datamodels.datamodelUam import AuthAuthority
|
|
||||||
revoked_count = appInterface.revokeTokensByUser(
|
revoked_count = appInterface.revokeTokensByUser(
|
||||||
userId=str(context.user.id),
|
userId=str(context.user.id),
|
||||||
authority=None, # Revoke all authorities
|
authority=None, # Revoke all authorities
|
||||||
|
|
@ -654,7 +650,6 @@ async def sendPasswordLink(
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from modules.shared.configuration import APP_CONFIG
|
from modules.shared.configuration import APP_CONFIG
|
||||||
from modules.interfaces.interfaceDbApp import getRootInterface
|
|
||||||
|
|
||||||
# Get user interface
|
# Get user interface
|
||||||
appInterface = interfaceDbApp.getInterface(context.user)
|
appInterface = interfaceDbApp.getInterface(context.user)
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query, Respon
|
||||||
from modules.auth import limiter, getCurrentUser
|
from modules.auth import limiter, getCurrentUser
|
||||||
|
|
||||||
# Import interfaces from feature containers
|
# Import interfaces from feature containers
|
||||||
import modules.aichat.interfaceFeatureAiChat as interfaceDbChat
|
import modules.interfaces.interfaceDbChat as interfaceDbChat
|
||||||
from modules.aichat.interfaceFeatureAiChat import getInterface
|
from modules.interfaces.interfaceDbChat import getInterface
|
||||||
from modules.interfaces.interfaceRbac import getRecordsetWithRBAC
|
from modules.interfaces.interfaceRbac import getRecordsetWithRBAC
|
||||||
|
|
||||||
# Import models from feature containers
|
# Import models from feature containers
|
||||||
from modules.aichat.datamodelFeatureAiChat import (
|
from modules.datamodels.datamodelChat import (
|
||||||
ChatWorkflow,
|
ChatWorkflow,
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
ChatLog,
|
ChatLog,
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,6 @@ async def exportUserData(
|
||||||
mandateId = um.get("mandateId")
|
mandateId = um.get("mandateId")
|
||||||
|
|
||||||
# Get mandate details
|
# Get mandate details
|
||||||
from modules.datamodels.datamodelUam import Mandate
|
|
||||||
mandateRecords = rootInterface.db.getRecordset(
|
mandateRecords = rootInterface.db.getRecordset(
|
||||||
Mandate,
|
Mandate,
|
||||||
recordFilter={"id": mandateId}
|
recordFilter={"id": mandateId}
|
||||||
|
|
@ -278,7 +277,6 @@ async def exportPortableData(
|
||||||
|
|
||||||
affiliations = []
|
affiliations = []
|
||||||
for um in userMandates:
|
for um in userMandates:
|
||||||
from modules.datamodels.datamodelUam import Mandate
|
|
||||||
mandateRecords = rootInterface.db.getRecordset(
|
mandateRecords = rootInterface.db.getRecordset(
|
||||||
Mandate,
|
Mandate,
|
||||||
recordFilter={"id": um.get("mandateId")}
|
recordFilter={"id": um.get("mandateId")}
|
||||||
|
|
@ -418,7 +416,6 @@ async def deleteAccount(
|
||||||
deletedData.append(f"Tokens deleted: {len(userTokens)}")
|
deletedData.append(f"Tokens deleted: {len(userTokens)}")
|
||||||
|
|
||||||
# 5. Delete user connections (OAuth)
|
# 5. Delete user connections (OAuth)
|
||||||
from modules.datamodels.datamodelUam import UserConnection
|
|
||||||
userConnections = rootInterface.db.getRecordset(
|
userConnections = rootInterface.db.getRecordset(
|
||||||
UserConnection,
|
UserConnection,
|
||||||
recordFilter={"userId": str(currentUser.id)}
|
recordFilter={"userId": str(currentUser.id)}
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,6 @@ async def login(
|
||||||
try:
|
try:
|
||||||
if connectionId:
|
if connectionId:
|
||||||
rootInterface = getRootInterface()
|
rootInterface = getRootInterface()
|
||||||
from modules.datamodels.datamodelUam import UserConnection
|
|
||||||
records = rootInterface.db.getRecordset(UserConnection, recordFilter={"id": connectionId})
|
records = rootInterface.db.getRecordset(UserConnection, recordFilter={"id": connectionId})
|
||||||
if records:
|
if records:
|
||||||
record = records[0]
|
record = records[0]
|
||||||
|
|
@ -356,7 +355,6 @@ async def auth_callback(code: str, state: str, request: Request, response: Respo
|
||||||
|
|
||||||
# Decode token to get jti for database record
|
# Decode token to get jti for database record
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
from modules.auth import SECRET_KEY, ALGORITHM
|
|
||||||
payload = jwt.decode(jwt_token, SECRET_KEY, algorithms=[ALGORITHM])
|
payload = jwt.decode(jwt_token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||||
jti = payload.get("jti")
|
jti = payload.get("jti")
|
||||||
|
|
||||||
|
|
@ -494,7 +492,6 @@ async def auth_callback(code: str, state: str, request: Request, response: Respo
|
||||||
connection.externalEmail = user_info.get("email")
|
connection.externalEmail = user_info.get("email")
|
||||||
|
|
||||||
# Update connection record directly
|
# Update connection record directly
|
||||||
from modules.datamodels.datamodelUam import UserConnection
|
|
||||||
rootInterface.db.recordModify(UserConnection, connection_id, connection.model_dump())
|
rootInterface.db.recordModify(UserConnection, connection_id, connection.model_dump())
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -667,7 +664,6 @@ async def verify_token(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get a fresh token via TokenManager convenience method
|
# Get a fresh token via TokenManager convenience method
|
||||||
from modules.auth import TokenManager
|
|
||||||
current_token = TokenManager().getFreshToken(google_connection.id)
|
current_token = TokenManager().getFreshToken(google_connection.id)
|
||||||
|
|
||||||
if not current_token:
|
if not current_token:
|
||||||
|
|
@ -741,7 +737,6 @@ async def refresh_token(
|
||||||
logger.debug(f"Found Google connection: {google_connection.id}, status={google_connection.status}")
|
logger.debug(f"Found Google connection: {google_connection.id}, status={google_connection.status}")
|
||||||
|
|
||||||
# Get the token for this specific connection (fresh if expiring soon)
|
# Get the token for this specific connection (fresh if expiring soon)
|
||||||
from modules.auth import TokenManager
|
|
||||||
current_token = TokenManager().getFreshToken(google_connection.id)
|
current_token = TokenManager().getFreshToken(google_connection.id)
|
||||||
|
|
||||||
if not current_token:
|
if not current_token:
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,6 @@ async def login(
|
||||||
rootInterface = getRootInterface()
|
rootInterface = getRootInterface()
|
||||||
|
|
||||||
# Get default mandate ID
|
# Get default mandate ID
|
||||||
from modules.datamodels.datamodelUam import Mandate
|
|
||||||
defaultMandateId = rootInterface.getInitialId(Mandate)
|
defaultMandateId = rootInterface.getInitialId(Mandate)
|
||||||
if not defaultMandateId:
|
if not defaultMandateId:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -267,7 +266,6 @@ async def register_user(
|
||||||
appInterface = getRootInterface()
|
appInterface = getRootInterface()
|
||||||
|
|
||||||
# Get default mandate ID
|
# Get default mandate ID
|
||||||
from modules.datamodels.datamodelUam import Mandate
|
|
||||||
defaultMandateId = appInterface.getInitialId(Mandate)
|
defaultMandateId = appInterface.getInitialId(Mandate)
|
||||||
if not defaultMandateId:
|
if not defaultMandateId:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
|
||||||
|
|
@ -364,7 +364,6 @@ async def auth_callback(code: str, state: str, request: Request, response: Respo
|
||||||
|
|
||||||
# Decode token to get jti for database record
|
# Decode token to get jti for database record
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
from modules.auth import SECRET_KEY, ALGORITHM
|
|
||||||
payload = jwt.decode(jwt_token, SECRET_KEY, algorithms=[ALGORITHM])
|
payload = jwt.decode(jwt_token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||||
jti = payload.get("jti")
|
jti = payload.get("jti")
|
||||||
|
|
||||||
|
|
@ -726,7 +725,6 @@ async def refresh_token(
|
||||||
logger.debug(f"Found Microsoft connection: {msft_connection.id}, status={msft_connection.status}")
|
logger.debug(f"Found Microsoft connection: {msft_connection.id}, status={msft_connection.status}")
|
||||||
|
|
||||||
# Get a fresh token via TokenManager convenience method
|
# Get a fresh token via TokenManager convenience method
|
||||||
from modules.auth import TokenManager
|
|
||||||
current_token = TokenManager().getFreshToken(msft_connection.id)
|
current_token = TokenManager().getFreshToken(msft_connection.id)
|
||||||
|
|
||||||
if not current_token:
|
if not current_token:
|
||||||
|
|
@ -738,7 +736,6 @@ async def refresh_token(
|
||||||
|
|
||||||
|
|
||||||
# Always attempt refresh (as per your requirement)
|
# Always attempt refresh (as per your requirement)
|
||||||
from modules.auth import TokenManager
|
|
||||||
token_manager = TokenManager()
|
token_manager = TokenManager()
|
||||||
|
|
||||||
refreshedToken = token_manager.refreshToken(current_token)
|
refreshedToken = token_manager.refreshToken(current_token)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import logging
|
||||||
from modules.datamodels.datamodelUam import User
|
from modules.datamodels.datamodelUam import User
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from modules.aichat.datamodelFeatureAiChat import ChatWorkflow
|
from modules.datamodels.datamodelChat import ChatWorkflow
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -79,6 +79,12 @@ class Services:
|
||||||
|
|
||||||
self.rbac = self.interfaceDbApp.rbac if self.interfaceDbApp else None
|
self.rbac = self.interfaceDbApp.rbac if self.interfaceDbApp else None
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CENTRAL INTERFACE (Chat/Workflow)
|
||||||
|
# ============================================================
|
||||||
|
from modules.interfaces.interfaceDbChat import getInterface as getChatInterface
|
||||||
|
self.interfaceDbChat = getChatInterface(user, mandateId=mandateId)
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# SHARED SERVICES (from modules/services/)
|
# SHARED SERVICES (from modules/services/)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
@ -101,7 +107,22 @@ class Services:
|
||||||
self.messaging = PublicService(MessagingService(self))
|
self.messaging = PublicService(MessagingService(self))
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# FEATURE SERVICES (dynamically loaded by filename discovery)
|
# AI SERVICES (from modules/services/)
|
||||||
|
# ============================================================
|
||||||
|
from .serviceAi.mainServiceAi import AiService
|
||||||
|
self.ai = PublicService(AiService(self), functionsOnly=False)
|
||||||
|
|
||||||
|
from .serviceExtraction.mainServiceExtraction import ExtractionService
|
||||||
|
self.extraction = PublicService(ExtractionService(self))
|
||||||
|
|
||||||
|
from .serviceGeneration.mainServiceGeneration import GenerationService
|
||||||
|
self.generation = PublicService(GenerationService(self))
|
||||||
|
|
||||||
|
from .serviceWeb.mainServiceWeb import WebService
|
||||||
|
self.web = PublicService(WebService(self))
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# FEATURE INTERFACES (dynamically loaded)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
self._loadFeatureInterfaces()
|
self._loadFeatureInterfaces()
|
||||||
self._loadFeatureServices()
|
self._loadFeatureServices()
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ async def onStart(eventUser) -> None:
|
||||||
Initializes AI connectors for model registry.
|
Initializes AI connectors for model registry.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from .aicore.aicoreModelRegistry import modelRegistry
|
from modules.aicore.aicoreModelRegistry import modelRegistry
|
||||||
modelRegistry.ensureConnectorsRegistered()
|
modelRegistry.ensureConnectorsRegistered()
|
||||||
logger.info(f"Feature '{FEATURE_CODE}' started - AI connectors initialized")
|
logger.info(f"Feature '{FEATURE_CODE}' started - AI connectors initialized")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -6,8 +6,8 @@ import re
|
||||||
import time
|
import time
|
||||||
import base64
|
import base64
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
from modules.aichat.datamodelFeatureAiChat import PromptPlaceholder, ChatDocument
|
from modules.datamodels.datamodelChat import PromptPlaceholder, ChatDocument
|
||||||
from modules.aichat.serviceExtraction.mainServiceExtraction import ExtractionService
|
from modules.services.serviceExtraction.mainServiceExtraction import ExtractionService
|
||||||
from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum
|
from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum
|
||||||
from modules.datamodels.datamodelExtraction import ContentPart, DocumentIntent
|
from modules.datamodels.datamodelExtraction import ContentPart, DocumentIntent
|
||||||
from modules.datamodels.datamodelWorkflow import AiResponse, AiResponseMetadata, DocumentData
|
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]
|
parentOperationId: Optional[str]
|
||||||
) -> AiResponse:
|
) -> AiResponse:
|
||||||
"""Handle IMAGE_GENERATE operation type using image generation path."""
|
"""Handle IMAGE_GENERATE operation type using image generation path."""
|
||||||
from modules.aichat.serviceGeneration.paths.imagePath import ImageGenerationPath
|
from modules.services.serviceGeneration.paths.imagePath import ImageGenerationPath
|
||||||
|
|
||||||
imagePath = ImageGenerationPath(self.services)
|
imagePath = ImageGenerationPath(self.services)
|
||||||
|
|
||||||
|
|
@ -514,7 +514,7 @@ Respond with ONLY a JSON object in this exact format:
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from modules.aichat.serviceGeneration.mainServiceGeneration import GenerationService
|
from modules.services.serviceGeneration.mainServiceGeneration import GenerationService
|
||||||
|
|
||||||
generationService = GenerationService(self.services)
|
generationService = GenerationService(self.services)
|
||||||
|
|
||||||
|
|
@ -829,7 +829,7 @@ Respond with ONLY a JSON object in this exact format:
|
||||||
parentOperationId: Optional[str]
|
parentOperationId: Optional[str]
|
||||||
) -> AiResponse:
|
) -> AiResponse:
|
||||||
"""Handle code generation using code generation path."""
|
"""Handle code generation using code generation path."""
|
||||||
from modules.aichat.serviceGeneration.paths.codePath import CodeGenerationPath
|
from modules.services.serviceGeneration.paths.codePath import CodeGenerationPath
|
||||||
|
|
||||||
codePath = CodeGenerationPath(self.services)
|
codePath = CodeGenerationPath(self.services)
|
||||||
return await codePath.generateCode(
|
return await codePath.generateCode(
|
||||||
|
|
@ -852,7 +852,7 @@ Respond with ONLY a JSON object in this exact format:
|
||||||
parentOperationId: Optional[str]
|
parentOperationId: Optional[str]
|
||||||
) -> AiResponse:
|
) -> AiResponse:
|
||||||
"""Handle document generation using document generation path."""
|
"""Handle document generation using document generation path."""
|
||||||
from modules.aichat.serviceGeneration.paths.documentPath import DocumentGenerationPath
|
from modules.services.serviceGeneration.paths.documentPath import DocumentGenerationPath
|
||||||
|
|
||||||
# Set compression options for document generation
|
# Set compression options for document generation
|
||||||
options.compressPrompt = False
|
options.compressPrompt = False
|
||||||
|
|
@ -14,7 +14,7 @@ import logging
|
||||||
import base64
|
import base64
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument
|
from modules.datamodels.datamodelChat import ChatDocument
|
||||||
from modules.datamodels.datamodelExtraction import ContentPart, DocumentIntent
|
from modules.datamodels.datamodelExtraction import ContentPart, DocumentIntent
|
||||||
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
||||||
|
|
||||||
|
|
@ -387,7 +387,6 @@ class ContentExtractor:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Führe Extraktion aus
|
# Führe Extraktion aus
|
||||||
from modules.datamodels.datamodelExtraction import ExtractionOptions, MergeStrategy
|
|
||||||
|
|
||||||
extractionOptions = ExtractionOptions(
|
extractionOptions = ExtractionOptions(
|
||||||
prompt=extractionPrompt,
|
prompt=extractionPrompt,
|
||||||
|
|
@ -12,7 +12,7 @@ import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument
|
from modules.datamodels.datamodelChat import ChatDocument
|
||||||
from modules.datamodels.datamodelExtraction import DocumentIntent
|
from modules.datamodels.datamodelExtraction import DocumentIntent
|
||||||
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
||||||
|
|
||||||
|
|
@ -181,7 +181,6 @@ class DocumentIntentAnalyzer:
|
||||||
logger.debug(f"JSON document {document.id} does not have actionType='context.extractContent' (got: {actionType})")
|
logger.debug(f"JSON document {document.id} does not have actionType='context.extractContent' (got: {actionType})")
|
||||||
|
|
||||||
if documentData:
|
if documentData:
|
||||||
from modules.datamodels.datamodelExtraction import ContentExtracted
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Stelle sicher, dass "id" vorhanden ist
|
# Stelle sicher, dass "id" vorhanden ist
|
||||||
|
|
@ -1357,7 +1357,6 @@ class JsonResponseHandler:
|
||||||
logger.debug(f"Modular merger failed, using fallback: {e}")
|
logger.debug(f"Modular merger failed, using fallback: {e}")
|
||||||
|
|
||||||
# Fallback to legacy merger (simplified)
|
# Fallback to legacy merger (simplified)
|
||||||
from modules.shared.jsonUtils import normalizeJsonText, stripCodeFences, closeJsonStructures, tryParseJson
|
|
||||||
|
|
||||||
accumulatedExtracted = stripCodeFences(normalizeJsonText(accumulated)).strip()
|
accumulatedExtracted = stripCodeFences(normalizeJsonText(accumulated)).strip()
|
||||||
newFragmentExtracted = stripCodeFences(normalizeJsonText(newFragment)).strip()
|
newFragmentExtracted = stripCodeFences(normalizeJsonText(newFragment)).strip()
|
||||||
|
|
@ -1450,7 +1449,6 @@ class JsonResponseHandler:
|
||||||
if not jsonString:
|
if not jsonString:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
from modules.shared.jsonUtils import tryParseJson, repairBrokenJson, closeJsonStructures
|
|
||||||
|
|
||||||
# Try to parse directly first
|
# Try to parse directly first
|
||||||
try:
|
try:
|
||||||
|
|
@ -1535,7 +1533,6 @@ class JsonResponseHandler:
|
||||||
# Strategy 1: Check if it's an array fragment
|
# Strategy 1: Check if it's an array fragment
|
||||||
if jsonStripped.startswith('['):
|
if jsonStripped.startswith('['):
|
||||||
# Try to parse as array
|
# Try to parse as array
|
||||||
from modules.shared.jsonUtils import tryParseJson, closeJsonStructures
|
|
||||||
|
|
||||||
# Close incomplete structures
|
# Close incomplete structures
|
||||||
closed = closeJsonStructures(jsonStripped)
|
closed = closeJsonStructures(jsonStripped)
|
||||||
|
|
@ -1579,7 +1576,6 @@ class JsonResponseHandler:
|
||||||
# Strategy 2: Check if it's a partial object (cut mid-structure)
|
# Strategy 2: Check if it's a partial object (cut mid-structure)
|
||||||
# Look for patterns like: {"elements": [...] or {"type": "table"...
|
# Look for patterns like: {"elements": [...] or {"type": "table"...
|
||||||
if jsonStripped.startswith('{'):
|
if jsonStripped.startswith('{'):
|
||||||
from modules.shared.jsonUtils import tryParseJson, closeJsonStructures
|
|
||||||
|
|
||||||
# Try to close and parse
|
# Try to close and parse
|
||||||
closed = closeJsonStructures(jsonStripped)
|
closed = closeJsonStructures(jsonStripped)
|
||||||
|
|
@ -1690,7 +1686,6 @@ class JsonResponseHandler:
|
||||||
if not accumulatedElements and not newFragmentElements:
|
if not accumulatedElements and not newFragmentElements:
|
||||||
# No elements found - try to extract from raw strings
|
# No elements found - try to extract from raw strings
|
||||||
# Try to extract any valid JSON structure from raw strings
|
# Try to extract any valid JSON structure from raw strings
|
||||||
from modules.shared.jsonUtils import tryParseJson, closeJsonStructures
|
|
||||||
|
|
||||||
# Try accumulated first
|
# Try accumulated first
|
||||||
if accumulatedRaw:
|
if accumulatedRaw:
|
||||||
|
|
@ -2019,7 +2014,6 @@ class JsonResponseHandler:
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
# Pattern 4: Try to parse as JSON array (handles complete arrays)
|
# Pattern 4: Try to parse as JSON array (handles complete arrays)
|
||||||
from modules.shared.jsonUtils import tryParseJson, closeJsonStructures
|
|
||||||
|
|
||||||
# Try to close incomplete structures
|
# Try to close incomplete structures
|
||||||
closed = closeJsonStructures(fragmentRaw.strip())
|
closed = closeJsonStructures(fragmentRaw.strip())
|
||||||
|
|
@ -2292,7 +2286,6 @@ class JsonResponseHandler:
|
||||||
if not jsonString:
|
if not jsonString:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
from modules.shared.jsonUtils import stripCodeFences, normalizeJsonText, tryParseJson, closeJsonStructures
|
|
||||||
|
|
||||||
# Extract and normalize JSON
|
# Extract and normalize JSON
|
||||||
extracted = stripCodeFences(normalizeJsonText(jsonString)).strip()
|
extracted = stripCodeFences(normalizeJsonText(jsonString)).strip()
|
||||||
|
|
@ -2366,7 +2359,6 @@ class JsonResponseHandler:
|
||||||
if not continuationJson:
|
if not continuationJson:
|
||||||
return accumulated
|
return accumulated
|
||||||
|
|
||||||
from modules.shared.jsonUtils import stripCodeFences, normalizeJsonText, tryParseJson, closeJsonStructures
|
|
||||||
|
|
||||||
# Normalize accumulated
|
# Normalize accumulated
|
||||||
accumulatedExtracted = stripCodeFences(normalizeJsonText(accumulated)).strip()
|
accumulatedExtracted = stripCodeFences(normalizeJsonText(accumulated)).strip()
|
||||||
|
|
@ -2429,7 +2421,6 @@ class JsonResponseHandler:
|
||||||
if not jsonString or not jsonString.strip():
|
if not jsonString or not jsonString.strip():
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
from modules.shared.jsonUtils import tryParseJson, closeJsonStructures
|
|
||||||
|
|
||||||
# Strategy 1: Try progressive truncation to find longest valid JSON
|
# Strategy 1: Try progressive truncation to find longest valid JSON
|
||||||
# Use binary search-like approach for efficiency
|
# Use binary search-like approach for efficiency
|
||||||
|
|
@ -2484,7 +2475,6 @@ class JsonResponseHandler:
|
||||||
|
|
||||||
if jsonStripped.startswith('{') or jsonStripped.startswith('['):
|
if jsonStripped.startswith('{') or jsonStripped.startswith('['):
|
||||||
# Try to extract balanced JSON
|
# Try to extract balanced JSON
|
||||||
from modules.shared.jsonUtils import extractFirstBalancedJson
|
|
||||||
balanced = extractFirstBalancedJson(jsonStripped)
|
balanced = extractFirstBalancedJson(jsonStripped)
|
||||||
if balanced and balanced != jsonStripped:
|
if balanced and balanced != jsonStripped:
|
||||||
try:
|
try:
|
||||||
|
|
@ -2554,7 +2544,6 @@ class JsonResponseHandler:
|
||||||
if not accumulated or not newFragment:
|
if not accumulated or not newFragment:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
from modules.shared.jsonUtils import closeJsonStructures, tryParseJson
|
|
||||||
|
|
||||||
# Extract valid JSON prefixes from both
|
# Extract valid JSON prefixes from both
|
||||||
accumulatedValid = JsonResponseHandler._extractValidJsonPrefix(accumulated)
|
accumulatedValid = JsonResponseHandler._extractValidJsonPrefix(accumulated)
|
||||||
|
|
@ -2647,7 +2636,6 @@ class JsonResponseHandler:
|
||||||
if not newFragment:
|
if not newFragment:
|
||||||
return accumulated
|
return accumulated
|
||||||
|
|
||||||
from modules.shared.jsonUtils import tryParseJson, closeJsonStructures
|
|
||||||
|
|
||||||
# Strategy 1: Try to extract valid JSON parts from both fragments
|
# Strategy 1: Try to extract valid JSON parts from both fragments
|
||||||
# This handles random cuts better by finding the longest valid prefix/suffix
|
# This handles random cuts better by finding the longest valid prefix/suffix
|
||||||
|
|
@ -2894,7 +2882,6 @@ class JsonResponseHandler:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Use existing JSON completion function to close incomplete structures
|
# Use existing JSON completion function to close incomplete structures
|
||||||
from modules.shared.jsonUtils import extractJsonString, closeJsonStructures
|
|
||||||
|
|
||||||
# Extract JSON string and complete it with missing closing elements
|
# Extract JSON string and complete it with missing closing elements
|
||||||
extracted = extractJsonString(jsonString)
|
extracted = extractJsonString(jsonString)
|
||||||
|
|
@ -2531,7 +2531,7 @@ CRITICAL:
|
||||||
List of accepted section content types (e.g., ["table", "code_block"])
|
List of accepted section content types (e.g., ["table", "code_block"])
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from modules.aichat.serviceGeneration.renderers.registry import getRenderer
|
from modules.services.serviceGeneration.renderers.registry import getRenderer
|
||||||
|
|
||||||
# Get renderer for this format
|
# Get renderer for this format
|
||||||
renderer = getRenderer(outputFormat, self.services)
|
renderer = getRenderer(outputFormat, self.services)
|
||||||
|
|
@ -231,7 +231,7 @@ CRITICAL:
|
||||||
raise ValueError("Structure has no documents - cannot generate without documents")
|
raise ValueError("Structure has no documents - cannot generate without documents")
|
||||||
|
|
||||||
# Import renderer registry for format validation (existing infrastructure)
|
# Import renderer registry for format validation (existing infrastructure)
|
||||||
from modules.aichat.serviceGeneration.renderers.registry import getRenderer
|
from modules.services.serviceGeneration.renderers.registry import getRenderer
|
||||||
|
|
||||||
# Validate and fix each document
|
# Validate and fix each document
|
||||||
for doc in documents:
|
for doc in documents:
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from modules.datamodels.datamodelUam import User, UserConnection
|
from modules.datamodels.datamodelUam import User, UserConnection
|
||||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument, ChatMessage, ChatStat, ChatLog
|
from modules.datamodels.datamodelChat import ChatDocument, ChatMessage, ChatStat, ChatLog
|
||||||
from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum
|
from modules.datamodels.datamodelAi import AiCallOptions, OperationTypeEnum, PriorityEnum, ProcessingModeEnum
|
||||||
from modules.shared.progressLogger import ProgressLogger
|
from modules.shared.progressLogger import ProgressLogger
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ import json
|
||||||
from .subRegistry import ExtractorRegistry, ChunkerRegistry
|
from .subRegistry import ExtractorRegistry, ChunkerRegistry
|
||||||
from .subPipeline import runExtraction
|
from .subPipeline import runExtraction
|
||||||
from modules.datamodels.datamodelExtraction import ContentExtracted, ContentPart, MergeStrategy, ExtractionOptions, PartResult, DocumentIntent
|
from modules.datamodels.datamodelExtraction import ContentExtracted, ContentPart, MergeStrategy, ExtractionOptions, PartResult, DocumentIntent
|
||||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument
|
from modules.datamodels.datamodelChat import ChatDocument
|
||||||
from modules.datamodels.datamodelAi import AiCallResponse, AiCallRequest, AiCallOptions, OperationTypeEnum, AiModelCall
|
from modules.datamodels.datamodelAi import AiCallResponse, AiCallRequest, AiCallOptions, OperationTypeEnum, AiModelCall
|
||||||
from modules.aichat.aicore.aicoreModelRegistry import modelRegistry
|
from modules.aicore.aicoreModelRegistry import modelRegistry
|
||||||
from modules.aichat.aicore.aicoreModelSelector import modelSelector
|
from modules.aicore.aicoreModelSelector import modelSelector
|
||||||
from modules.shared.jsonUtils import stripCodeFences
|
from modules.shared.jsonUtils import stripCodeFences
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -692,7 +692,6 @@ class ExtractionService:
|
||||||
isGenerationResponse = False
|
isGenerationResponse = False
|
||||||
if options and hasattr(options, 'operationType'):
|
if options and hasattr(options, 'operationType'):
|
||||||
# Generation responses use DATA_GENERATE operation type
|
# Generation responses use DATA_GENERATE operation type
|
||||||
from modules.datamodels.datamodelAi import OperationTypeEnum
|
|
||||||
isGenerationResponse = options.operationType == OperationTypeEnum.DATA_GENERATE
|
isGenerationResponse = options.operationType == OperationTypeEnum.DATA_GENERATE
|
||||||
|
|
||||||
# Also check if content looks like JSON (starts with { or [)
|
# Also check if content looks like JSON (starts with { or [)
|
||||||
|
|
@ -13,7 +13,7 @@ from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, Operati
|
||||||
# Type hint for renderer parameter
|
# Type hint for renderer parameter
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from modules.aichat.serviceGeneration.renderers.documentRendererBaseTemplate import BaseRenderer
|
from modules.services.serviceGeneration.renderers.documentRendererBaseTemplate import BaseRenderer
|
||||||
_RendererLike = BaseRenderer
|
_RendererLike = BaseRenderer
|
||||||
else:
|
else:
|
||||||
_RendererLike = Any
|
_RendererLike = Any
|
||||||
|
|
@ -71,7 +71,7 @@ class ExtractorRegistry:
|
||||||
module_name = file_path.stem
|
module_name = file_path.stem
|
||||||
try:
|
try:
|
||||||
# Import the module
|
# Import the module
|
||||||
module = importlib.import_module(f".{module_name}", package="modules.aichat.serviceExtraction.extractors")
|
module = importlib.import_module(f".{module_name}", package="modules.services.serviceExtraction.extractors")
|
||||||
|
|
||||||
# Find all extractor classes in the module
|
# Find all extractor classes in the module
|
||||||
for attr_name in dir(module):
|
for attr_name in dir(module):
|
||||||
|
|
@ -6,8 +6,8 @@ import base64
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Any, Dict, List, Optional, Callable
|
from typing import Any, Dict, List, Optional, Callable
|
||||||
from modules.datamodels.datamodelDocument import RenderedDocument
|
from modules.datamodels.datamodelDocument import RenderedDocument
|
||||||
from modules.aichat.datamodelFeatureAiChat import ChatDocument
|
from modules.datamodels.datamodelChat import ChatDocument
|
||||||
from modules.aichat.serviceGeneration.subDocumentUtility import (
|
from modules.services.serviceGeneration.subDocumentUtility import (
|
||||||
getFileExtension,
|
getFileExtension,
|
||||||
getMimeTypeFromExtension,
|
getMimeTypeFromExtension,
|
||||||
detectMimeTypeFromContent,
|
detectMimeTypeFromContent,
|
||||||
|
|
@ -414,7 +414,7 @@ class GenerationService:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check output style classification (code/document/image/etc.) from renderer
|
# Check output style classification (code/document/image/etc.) from renderer
|
||||||
from modules.aichat.serviceGeneration.renderers.registry import getOutputStyle
|
from modules.services.serviceGeneration.renderers.registry import getOutputStyle
|
||||||
outputStyle = getOutputStyle(docFormat)
|
outputStyle = getOutputStyle(docFormat)
|
||||||
if outputStyle:
|
if outputStyle:
|
||||||
logger.debug(f"Document {doc.get('id', docIndex)} format '{docFormat}' classified as '{outputStyle}' style")
|
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
|
Complete document structure with populated elements ready for rendering
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from modules.aichat.serviceGeneration.subStructureGenerator import StructureGenerator
|
from modules.services.serviceGeneration.subStructureGenerator import StructureGenerator
|
||||||
from modules.aichat.serviceGeneration.subContentGenerator import ContentGenerator
|
from modules.services.serviceGeneration.subContentGenerator import ContentGenerator
|
||||||
|
|
||||||
# Phase 1: Generate structure skeleton
|
# Phase 1: Generate structure skeleton
|
||||||
if progressCallback:
|
if progressCallback:
|
||||||
|
|
@ -537,7 +537,7 @@ class GenerationService:
|
||||||
aiService=None
|
aiService=None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Get adaptive extraction prompt."""
|
"""Get adaptive extraction prompt."""
|
||||||
from modules.aichat.serviceExtraction.subPromptBuilderExtraction import buildExtractionPrompt
|
from modules.services.serviceExtraction.subPromptBuilderExtraction import buildExtractionPrompt
|
||||||
return await buildExtractionPrompt(
|
return await buildExtractionPrompt(
|
||||||
outputFormat=outputFormat,
|
outputFormat=outputFormat,
|
||||||
userPrompt=userPrompt,
|
userPrompt=userPrompt,
|
||||||
|
|
@ -920,7 +920,7 @@ CRITICAL:
|
||||||
|
|
||||||
def _getCodeRenderer(self, fileType: str):
|
def _getCodeRenderer(self, fileType: str):
|
||||||
"""Get code renderer for file type."""
|
"""Get code renderer for file type."""
|
||||||
from modules.aichat.serviceGeneration.renderers.registry import getRenderer
|
from modules.services.serviceGeneration.renderers.registry import getRenderer
|
||||||
|
|
||||||
# Map file types to renderer formats
|
# Map file types to renderer formats
|
||||||
formatMap = {
|
formatMap = {
|
||||||
|
|
@ -81,7 +81,6 @@ class BaseRenderer(ABC):
|
||||||
Valid types: "table", "bullet_list", "heading", "paragraph", "code_block", "image"
|
Valid types: "table", "bullet_list", "heading", "paragraph", "code_block", "image"
|
||||||
"""
|
"""
|
||||||
# Default: accept all section types
|
# Default: accept all section types
|
||||||
from modules.datamodels.datamodelJson import supportedSectionTypes
|
|
||||||
return list(supportedSectionTypes)
|
return list(supportedSectionTypes)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
@ -12,7 +12,7 @@ import base64
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Dict, Any, Optional, List, Callable
|
from typing import Dict, Any, Optional, List, Callable
|
||||||
from modules.aichat.serviceGeneration.subContentIntegrator import ContentIntegrator
|
from modules.services.serviceGeneration.subContentIntegrator import ContentIntegrator
|
||||||
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
from modules.workflows.processing.shared.stateTools import checkWorkflowStopped
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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