refactored features phase II

This commit is contained in:
ValueOn AG 2026-01-23 01:10:00 +01:00
parent bb9630d6c4
commit 280cafd54a
179 changed files with 5422 additions and 1098 deletions

3
app.py
View file

@ -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/

View file

@ -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:

View file

@ -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,

View file

@ -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"},
},
)

View file

@ -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

View 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"},
},
)

View file

@ -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

View file

@ -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"},
},
)

View file

@ -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

View file

@ -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)

View file

@ -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),

View file

@ -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}

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -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]

View file

@ -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

View file

@ -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}")

View file

@ -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"])

View file

@ -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)

View file

@ -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,

View file

@ -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)}

View file

@ -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:

View file

@ -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(

View file

@ -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)

View file

@ -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()

View file

@ -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:

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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

View file

@ -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 [)

View file

@ -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

View file

@ -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):

View file

@ -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,

View file

@ -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 = {

View file

@ -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

View file

@ -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