diff --git a/app.py b/app.py index a9a4c40c..3fb810f2 100644 --- a/app.py +++ b/app.py @@ -102,7 +102,7 @@ app = FastAPI( # CORS configuration using environment variables app.add_middleware( CORSMiddleware, - allow_origins= get_allowed_origins(), # ["http://localhost:8080","http://localhost:8081"], #get_allowed_origins(), + allow_origins= get_allowed_origins(), allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["*"], @@ -118,80 +118,16 @@ os.makedirs(staticFolder, exist_ok=True) # Mount static files with proper configuration app.mount("/static", StaticFiles(directory=str(staticFolder), html=True), name="static") -# Add favicon route -@app.get("/favicon.ico") -async def favicon(): - return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon") - -# General Elements -@app.get("/", tags=["General"]) -async def root(): - """API status endpoint""" - return {"status": "online", "message": "Data Platform API is active"} - -@app.get("/api/test", tags=["General"]) -async def getTest(): - return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}" - -@app.options("/{fullPath:path}", tags=["General"]) -async def optionsRoute(fullPath: str): - return Response(status_code=200) - -@app.get("/api/environment", tags=["General"]) -async def get_environment(): - """Get environment configuration for frontend""" - return { - "apiBaseUrl": APP_CONFIG.get("APP_API_URL", ""), - "environment": APP_CONFIG.get("APP_ENV", "development"), - "instanceLabel": APP_CONFIG.get("APP_ENV_LABEL", "Development"), - # Add other environment variables the frontend might need - } - -# Token endpoint for login -@app.post("/api/token", response_model=gatewayModel.Token, tags=["General"]) -async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()): - # Initialize Gateway interface without context - gateway = getGatewayInterface() - - # Authenticate user - user = gateway.authenticateUser(formData.username, formData.password) - - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - - # Create token with tenant ID - accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - accessToken = createAccessToken( - data={ - "sub": user["username"], - "mandateId": user["mandateId"] - }, - expiresDelta=accessTokenExpires - ) - - return {"accessToken": accessToken, "tokenType": "bearer"} - -# Get user info -@app.get("/api/user/me", response_model=Dict[str, Any], tags=["General"]) -async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): - return currentUser - # Include all routers +from routes.routeGeneral import router as generalRouter +app.include_router(generalRouter) + from routes.routeAttributes import router as attributesRouter app.include_router(attributesRouter) -gateway = getGatewayInterface() - from routes.routeMandates import router as mandateRouter app.include_router(mandateRouter) -gateway = getGatewayInterface() - - from routes.routeUsers import router as userRouter app.include_router(userRouter) @@ -206,6 +142,3 @@ app.include_router(workflowRouter) from routes.routeMsft import router as msftRouter app.include_router(msftRouter) - -#if __name__ == "__main__": -# uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True) \ No newline at end of file diff --git a/modules/gatewayAccess.py b/modules/gatewayAccess.py new file mode 100644 index 00000000..bf93d58f --- /dev/null +++ b/modules/gatewayAccess.py @@ -0,0 +1,111 @@ +""" +Access control functions for the Gateway system. +Manages user access and permissions. +""" + +from typing import Dict, Any, List, Optional + +def _uam(currentUser: Dict[str, Any], table: str, recordset: List[Dict[str, Any]], mandateId: int, userId: int, db) -> List[Dict[str, Any]]: + """ + Unified user access management function that filters data based on user privileges + and adds access control attributes. + + Args: + currentUser: Current user information dictionary + table: Name of the table + recordset: Recordset to filter based on access rules + mandateId: Current mandate ID + userId: Current user ID + db: Database connector instance + + Returns: + Filtered recordset with access control attributes + """ + userPrivilege = currentUser.get("privilege", "user") + filtered_records = [] + + # Apply filtering based on privilege + if userPrivilege == "sysadmin": + filtered_records = recordset # System admins see all records + elif userPrivilege == "admin": + # Admins see records in their mandate + filtered_records = [r for r in recordset if r.get("mandateId") == mandateId] + else: # Regular users + # Users only see records they own within their mandate + filtered_records = [r for r in recordset + if r.get("mandateId") == mandateId and r.get("userId") == userId] + + # Add access control attributes to each record + for record in filtered_records: + record_id = record.get("id") + + # Set access control flags based on user permissions + if table == "mandates": + record["_hideView"] = False # Everyone can view + record["_hideEdit"] = not _canModify(currentUser, "mandates", record_id, mandateId, userId, db) + record["_hideDelete"] = not _canModify(currentUser, "mandates", record_id, mandateId, userId, db) + elif table == "users": + record["_hideView"] = False # Everyone can view + record["_hideEdit"] = not _canModify(currentUser, "users", record_id, mandateId, userId, db) + record["_hideDelete"] = not _canModify(currentUser, "users", record_id, mandateId, userId, db) + else: + # Default access control for other tables + record["_hideView"] = False + record["_hideEdit"] = not _canModify(currentUser, table, record_id, mandateId, userId, db) + record["_hideDelete"] = not _canModify(currentUser, table, record_id, mandateId, userId, db) + + return filtered_records + +def _canModify(currentUser: Dict[str, Any], table: str, recordId: Optional[int] = None, mandateId: int = None, userId: int = None, db = None) -> bool: + """ + Checks if the current user can modify (create/update/delete) records in a table. + + Args: + currentUser: Current user information dictionary + table: Name of the table + recordId: Optional record ID for specific record check + mandateId: Current mandate ID + userId: Current user ID + db: Database connector instance + + Returns: + Boolean indicating permission + """ + userPrivilege = currentUser.get("privilege", "user") + + # System admins can modify anything + if userPrivilege == "sysadmin": + return True + + # Check specific record permissions + if recordId is not None: + # Get the record to check ownership + records = db.getRecordset(table, recordFilter={"id": recordId}) + if not records: + return False + + record = records[0] + + # Admins can modify anything in their mandate + if userPrivilege == "admin" and record.get("mandateId") == mandateId: + # Exception: Can't modify Root mandate unless you are a sysadmin + if table == "mandates" and recordId == 1 and userPrivilege != "sysadmin": + return False + return True + + # Users can only modify their own records + if (record.get("mandateId") == mandateId and + record.get("userId") == userId): + return True + + return False + else: + # For general table modify permission (e.g., create) + # Admins can create anything in their mandate + if userPrivilege == "admin": + return True + + # Regular users can create most entities + if table == "mandates": + return False # Regular users can't create mandates + return True \ No newline at end of file diff --git a/modules/gatewayInterface.py b/modules/gatewayInterface.py index 8008e1f9..92e0c91f 100644 --- a/modules/gatewayInterface.py +++ b/modules/gatewayInterface.py @@ -11,6 +11,7 @@ from passlib.context import CryptContext from connectors.connectorDbJson import DatabaseConnector from modules.configuration import APP_CONFIG +from modules.gatewayAccess import _uam, _canModify logger = logging.getLogger(__name__) @@ -133,40 +134,7 @@ class GatewayInterface: Returns: Filtered recordset with access control attributes """ - userPrivilege = self.currentUser.get("privilege", "user") - filtered_records = [] - - # Apply filtering based on privilege - if userPrivilege == "sysadmin": - filtered_records = recordset # System admins see all records - elif userPrivilege == "admin": - # Admins see records in their mandate - filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId] - else: # Regular users - # Users only see records they own within their mandate - filtered_records = [r for r in recordset - if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId] - - # Add access control attributes to each record - for record in filtered_records: - record_id = record.get("id") - - # Set access control flags based on user permissions - if table == "mandates": - record["_hideView"] = False # Everyone can view - record["_hideEdit"] = not self._canModify("mandates", record_id) - record["_hideDelete"] = not self._canModify("mandates", record_id) - elif table == "users": - record["_hideView"] = False # Everyone can view - record["_hideEdit"] = not self._canModify("users", record_id) - record["_hideDelete"] = not self._canModify("users", record_id) - else: - # Default access control for other tables - record["_hideView"] = False - record["_hideEdit"] = not self._canModify(table, record_id) - record["_hideDelete"] = not self._canModify(table, record_id) - - return filtered_records + return _uam(self.currentUser, table, recordset, self.mandateId, self.userId, self.db) def _canModify(self, table: str, recordId: Optional[int] = None) -> bool: """ @@ -179,44 +147,7 @@ class GatewayInterface: Returns: Boolean indicating permission """ - userPrivilege = self.currentUser.get("privilege", "user") - - # System admins can modify anything - if userPrivilege == "sysadmin": - return True - - # Check specific record permissions - if recordId is not None: - # Get the record to check ownership - records = self.db.getRecordset(table, recordFilter={"id": recordId}) - if not records: - return False - - record = records[0] - - # Admins can modify anything in their mandate - if userPrivilege == "admin" and record.get("mandateId") == self.mandateId: - # Exception: Can't modify Root mandate unless you are a sysadmin - if table == "mandates" and recordId == 1 and userPrivilege != "sysadmin": - return False - return True - - # Users can only modify their own records - if (record.get("mandateId") == self.mandateId and - record.get("userId") == self.userId): - return True - - return False - else: - # For general table modify permission (e.g., create) - # Admins can create anything in their mandate - if userPrivilege == "admin": - return True - - # Regular users can create most entities - if table == "mandates": - return False # Regular users can't create mandates - return True + return _canModify(self.currentUser, table, recordId, self.mandateId, self.userId, self.db) def getInitialId(self, table: str) -> Optional[int]: """Returns the initial ID for a table.""" diff --git a/modules/lucydomAccess.py b/modules/lucydomAccess.py new file mode 100644 index 00000000..2c018c52 --- /dev/null +++ b/modules/lucydomAccess.py @@ -0,0 +1,131 @@ +""" +Access control module for LucyDOM interface. +Handles user access management and permission checks. +""" + +from typing import Dict, Any, List, Optional + +class LucyDOMAccess: + """ + Access control class for LucyDOM interface. + Handles user access management and permission checks. + """ + + def __init__(self, currentUser: Dict[str, Any], mandateId: int, userId: int): + """Initialize with user context.""" + self.currentUser = currentUser + self.mandateId = mandateId + self.userId = userId + + def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """ + Unified user access management function that filters data based on user privileges + and adds access control attributes. + + Args: + table: Name of the table + recordset: Recordset to filter based on access rules + + Returns: + Filtered recordset with access control attributes + """ + userPrivilege = self.currentUser.get("privilege", "user") + filtered_records = [] + + # Apply filtering based on privilege + if userPrivilege == "sysadmin": + filtered_records = recordset # System admins see all records + elif userPrivilege == "admin": + # Admins see records in their mandate + filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId] + else: # Regular users + # To see all prompts from mandate 0 and own + if table == "prompts": + filtered_records = [r for r in recordset if + (r.get("mandateId") == self.mandateId and r.get("userId") == self.userId) + or + (r.get("mandateId") == 0) + ] + else: + # Users see only their records + filtered_records = [r for r in recordset + if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId] + + # Add access control attributes to each record + for record in filtered_records: + record_id = record.get("id") + + # Set access control flags based on user permissions + if table == "prompts": + record["_hideView"] = False # Everyone can view + record["_hideEdit"] = not self._canModify("prompts", record_id) + record["_hideDelete"] = not self._canModify("prompts", record_id) + elif table == "files": + record["_hideView"] = False # Everyone can view + record["_hideEdit"] = not self._canModify("files", record_id) + record["_hideDelete"] = not self._canModify("files", record_id) + record["_hideDownload"] = not self._canModify("files", record_id) + elif table == "workflows": + record["_hideView"] = False # Everyone can view + record["_hideEdit"] = not self._canModify("workflows", record_id) + record["_hideDelete"] = not self._canModify("workflows", record_id) + elif table == "workflowMessages": + record["_hideView"] = False # Everyone can view + record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId")) + record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId")) + elif table == "workflowLogs": + record["_hideView"] = False # Everyone can view + record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId")) + record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId")) + else: + # Default access control for other tables + record["_hideView"] = False + record["_hideEdit"] = not self._canModify(table, record_id) + record["_hideDelete"] = not self._canModify(table, record_id) + + return filtered_records + + def _canModify(self, table: str, recordId: Optional[int] = None) -> bool: + """ + Checks if the current user can modify (create/update/delete) records in a table. + + Args: + table: Name of the table + recordId: Optional record ID for specific record check + + Returns: + Boolean indicating permission + """ + userPrivilege = self.currentUser.get("privilege", "user") + + # System admins can modify anything + if userPrivilege == "sysadmin": + return True + + # For regular users and admins, check specific cases + if recordId is not None: + # Get the record to check ownership + records = self.db.getRecordset(table, recordFilter={"id": recordId}) + if not records: + return False + + record = records[0] + + # Admins can modify anything in their mandate + if userPrivilege == "admin" and record.get("mandateId") == self.mandateId: + return True + + # Regular users can only modify their own records + if (record.get("mandateId") == self.mandateId and + record.get("userId") == self.userId): + return True + + return False + else: + # For general modification permission (e.g., create) + # Admins can create anything in their mandate + if userPrivilege == "admin": + return True + + # Regular users can create in most tables + return True \ No newline at end of file diff --git a/modules/lucydomInterface.py b/modules/lucydomInterface.py index 91d7769a..e63ef736 100644 --- a/modules/lucydomInterface.py +++ b/modules/lucydomInterface.py @@ -14,6 +14,7 @@ import hashlib import json from modules.mimeUtils import isTextMimeType, determineContentEncoding +from modules.lucydomAccess import LucyDOMAccess # DYNAMIC PART: Connectors to the Interface from connectors.connectorDbJson import DatabaseConnector @@ -74,6 +75,10 @@ class LucyDOMInterface: # Load user information self.currentUser = self._getCurrentUserInfo() + # Initialize access control + self.access = LucyDOMAccess(self.currentUser, self.mandateId, self.userId) + self.access.db = self.db # Share database connection + # Initialize standard database records if needed self._initRecords() @@ -161,117 +166,12 @@ class LucyDOMInterface: logger.info(f"Prompt '{promptData.get('name', 'Standard')}' was created with ID {createdPrompt['id']}") def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """ - Unified user access management function that filters data based on user privileges - and adds access control attributes. - - Args: - table: Name of the table - recordset: Recordset to filter based on access rules - - Returns: - Filtered recordset with access control attributes - """ - userPrivilege = self.currentUser.get("privilege", "user") - filtered_records = [] - - # Apply filtering based on privilege - if userPrivilege == "sysadmin": - filtered_records = recordset # System admins see all records - elif userPrivilege == "admin": - # Admins see records in their mandate - filtered_records = [r for r in recordset if r.get("mandateId") == self.mandateId] - else: # Regular users - # To see all prompts from mandate 0 and own - if table == "prompts": - filtered_records = [r for r in recordset if - (r.get("mandateId") == self.mandateId and r.get("userId") == self.userId) - or - (r.get("mandateId") == 0) - ] - else: - # Users see only their records - filtered_records = [r for r in recordset - if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId] - - # Add access control attributes to each record - for record in filtered_records: - record_id = record.get("id") - - # Set access control flags based on user permissions - if table == "prompts": - record["_hideView"] = False # Everyone can view - record["_hideEdit"] = not self._canModify("prompts", record_id) - record["_hideDelete"] = not self._canModify("prompts", record_id) - elif table == "files": - record["_hideView"] = False # Everyone can view - record["_hideEdit"] = not self._canModify("files", record_id) - record["_hideDelete"] = not self._canModify("files", record_id) - record["_hideDownload"] = not self._canModify("files", record_id) - elif table == "workflows": - record["_hideView"] = False # Everyone can view - record["_hideEdit"] = not self._canModify("workflows", record_id) - record["_hideDelete"] = not self._canModify("workflows", record_id) - elif table == "workflowMessages": - record["_hideView"] = False # Everyone can view - record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId")) - record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId")) - elif table == "workflowLogs": - record["_hideView"] = False # Everyone can view - record["_hideEdit"] = not self._canModify("workflows", record.get("workflowId")) - record["_hideDelete"] = not self._canModify("workflows", record.get("workflowId")) - else: - # Default access control for other tables - record["_hideView"] = False - record["_hideEdit"] = not self._canModify(table, record_id) - record["_hideDelete"] = not self._canModify(table, record_id) - - return filtered_records + """Delegate to access control module.""" + return self.access._uam(table, recordset) def _canModify(self, table: str, recordId: Optional[int] = None) -> bool: - """ - Checks if the current user can modify (create/update/delete) records in a table. - - Args: - table: Name of the table - recordId: Optional record ID for specific record check - - Returns: - Boolean indicating permission - """ - userPrivilege = self.currentUser.get("privilege", "user") - - # System admins can modify anything - if userPrivilege == "sysadmin": - return True - - # For regular users and admins, check specific cases - if recordId is not None: - # Get the record to check ownership - records = self.db.getRecordset(table, recordFilter={"id": recordId}) - if not records: - return False - - record = records[0] - - # Admins can modify anything in their mandate - if userPrivilege == "admin" and record.get("mandateId") == self.mandateId: - return True - - # Regular users can only modify their own records - if (record.get("mandateId") == self.mandateId and - record.get("userId") == self.userId): - return True - - return False - else: - # For general modification permission (e.g., create) - # Admins can create anything in their mandate - if userPrivilege == "admin": - return True - - # Regular users can create in most tables - return True + """Delegate to access control module.""" + return self.access._canModify(table, recordId) # Language support method diff --git a/notes/azuresetup.txt b/notes/azuresetup.txt deleted file mode 100644 index 17570483..00000000 --- a/notes/azuresetup.txt +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/bash - -# Variables -SUBSCRIPTION_ID="213596c9-34b2-4677-a712-45ed127cdae5" -RESOURCE_GROUP="volucy-group" -APP_NAME="poweron-gateway" -DOMAIN_NAME="gateway.poweron-center.net" -CERT_PASSWORD="TheSecurePass$(date +%s)" # Unique password with timestamp - -# Login to Azure (uncomment if not already logged in) -# az login - -# Set subscription -echo "Setting subscription..." -az account set --subscription "$SUBSCRIPTION_ID" - -# Create directory for certificate files -mkdir -p cert-files -cd cert-files - -# Create OpenSSL config file with required extensions -cat > openssl.cnf << EOF -[ req ] -default_bits = 2048 -distinguished_name = req_distinguished_name -req_extensions = req_ext -[ req_distinguished_name ] -countryName = Country Name (2 letter code) -stateOrProvinceName = State or Province Name (full name) -localityName = Locality Name (eg, city) -organizationName = Organization Name (eg, company) -commonName = Common Name (e.g. server FQDN) -[ req_ext ] -subjectAltName = @alt_names -extendedKeyUsage = serverAuth -[alt_names] -DNS.1 = ${DOMAIN_NAME} -EOF - -# Generate private key -openssl genrsa -out private.key 2048 - -# Create CSR with config file -openssl req -new -key private.key -out request.csr -config openssl.cnf -subj "/C=US/ST=State/L=City/O=Organization/CN=${DOMAIN_NAME}" - -# Generate self-signed certificate with extensions -openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.crt \ - -extfile openssl.cnf -extensions req_ext - -# Create PFX file -openssl pkcs12 -export -out self-signed-cert.pfx -inkey private.key -in certificate.crt -passout pass:$CERT_PASSWORD - -cd .. - -# Upload certificate to App Service -echo "Uploading certificate..." -UPLOAD_RESULT=$(az webapp config ssl upload \ - --resource-group "$RESOURCE_GROUP" \ - --name "$APP_NAME" \ - --certificate-file "cert-files/self-signed-cert.pfx" \ - --certificate-password "$CERT_PASSWORD") - -# Extract thumbprint from upload result -CERT_THUMBPRINT=$(echo $UPLOAD_RESULT | jq -r '.thumbprint') - -echo "Certificate uploaded successfully with thumbprint: $CERT_THUMBPRINT" - -# If the thumbprint is empty, try to find it another way -if [ -z "$CERT_THUMBPRINT" ] || [ "$CERT_THUMBPRINT" == "null" ]; then - echo "Thumbprint not found in upload result. Trying to list certificates..." - CERT_LIST=$(az webapp config ssl list --resource-group "$RESOURCE_GROUP") - - # Look for the most recently uploaded certificate - CERT_THUMBPRINT=$(echo $CERT_LIST | jq -r 'sort_by(.expirationDate) | reverse | .[0].thumbprint') - - if [ -z "$CERT_THUMBPRINT" ] || [ "$CERT_THUMBPRINT" == "null" ]; then - echo "Error: Could not find certificate thumbprint." - exit 1 - fi -fi - -echo "Using certificate thumbprint: $CERT_THUMBPRINT" - -# Make sure the custom domain is added -echo "Checking if custom domain exists..." -DOMAIN_EXISTS=$(az webapp config hostname list --resource-group "$RESOURCE_GROUP" --webapp-name "$APP_NAME" | jq -r ".[] | select(.name==\"$DOMAIN_NAME\") | .name") - -if [ -z "$DOMAIN_EXISTS" ]; then - echo "Adding custom domain..." - az webapp config hostname add \ - --resource-group "$RESOURCE_GROUP" \ - --webapp-name "$APP_NAME" \ - --hostname "$DOMAIN_NAME" -fi - -# Add IP-based SSL binding -echo "Creating IP-based SSL binding..." -az webapp config ssl bind \ - --resource-group "$RESOURCE_GROUP" \ - --name "$APP_NAME" \ - --certificate-thumbprint "$CERT_THUMBPRINT" \ - --ssl-type "IP" - -echo "SSL binding completed. Your domain should now be secured." \ No newline at end of file diff --git a/notes/changelog.txt b/notes/changelog.txt index 75cb1e2d..25434eae 100644 --- a/notes/changelog.txt +++ b/notes/changelog.txt @@ -3,7 +3,7 @@ agentDocumentation delivers a ".docx" file, but the content is a ".md" text markup file -access management to extract into separate modules "lucydomAccess.py" and "gatewayAccess.py". Here to move the functions from "*Interface.py", which define what access which role has. +agentDocumentation to extract data per chapter check data extraction tabelle im pdf @@ -22,8 +22,6 @@ sharepoint connector with document search, content search, content extraction PRIO2: -sharepoint connector with document search, content search, content extraction - Split big files into content-parts Integrate NDA Text as modal form - Data governance agreement by login with checkbox @@ -36,11 +34,13 @@ Tools to transfer incl funds: - Google SERPAPI (shelly) - Anthropic Claude (valueon + shelly) - Cursor Pro +- Mermaid + + ----------------------- DONE - FRONTEND: - login page and register page withoug fallback. they have mandatory to load their login.html or register.html pages to work (not html in the code). diff --git a/notes/doc_frontend.md b/notes/doc_frontend.md deleted file mode 100644 index cae1f927..00000000 --- a/notes/doc_frontend.md +++ /dev/null @@ -1,46 +0,0 @@ -Architectural Flow - -workflow.js - Entry point that: - -Creates the central state object -Initializes API and UI components -Connects components together -Exposes required public functions - - -workflow_state.js - State management that: - -Maintains a single source of truth for workflow data -Implements an observer pattern for state changes -Handles state transitions and business logic -Validates state updates - - -workflow_api.js - API communication that: - -Abstracts all API calls to backend services -Manages polling for workflow status -Processes API responses -Tracks data transfer statistics -Updates state with API results - - -workflow_ui.js - UI layer that: - -Renders UI based on current state -Sets up event listeners for user interactions -Handles DOM manipulation -Controls layout adjustments -Triggers state changes via user actions - - - -Data Flow - -User initiates action in the UI -UI controller handles the action and calls relevant API methods -API communicates with backend and updates state -State notifies observers about changes -UI reacts to state changes and updates display -Polling continues until completed state is reached -Final UI update happens when workflow completes diff --git a/notes/doc_statemachine_backend.md b/notes/doc_statemachine_backend.md deleted file mode 100644 index 4556b8f3..00000000 --- a/notes/doc_statemachine_backend.md +++ /dev/null @@ -1,366 +0,0 @@ -# State Machine Documentation for Backend Chat Workflow - -## Overview - -The Chat Workflow system implements a state machine that processes user inputs through a sequence of well-defined steps. The system orchestrates interactions between users, project managers, and specialized agents to produce final outputs. - -## Core Objects - -### Workflow Object -```json -{ - "id": "uuid-string", - "mandateId": int, - "userId": int, - "name": "Workflow name", - "startedAt": "ISO-datetime", - "messages": [], // References to messages - "messageIds": [], // List of message IDs - "logs": [], // Log entries - "dataStats": {}, // Performance metrics - "currentRound": int, // Increments with each interaction - "status": "string", // running, completed, failed, stopped - "lastActivity": "ISO-datetime" -} -``` - -### Message Object -```json -{ - "id": "msg_uuid-string", - "workflowId": "workflow-uuid", - "role": "string", // user, assistant - "agentName": "string", // Empty for user, agent name for assistant - "content": "string", // The message text - "documents": [], // List of document objects - "timestamp": "ISO-datetime", - "sequenceNo": int, // Position in conversation - "status": "string" // first, step, last -} -``` - -### Log Entry Object -```json -{ - "id": "log_uuid-string", - "workflowId": "workflow-uuid", - "message": "string", - "progress": int, // Optional, 0-100 - "type": "string", // info, warning, error - "timestamp": "ISO-datetime", - "agentName": "string", // Name of the agent that generated the log - "status": "string" // current workflow status (running, completed, failed, stopped) -} -``` - -### Document Object -```json -{ - "id": "doc_uuid-string", - "fileId": int, - "name": "string", // Filename without extension - "ext": "string", // File extension - "data": "base64-encoded-string", // File contents - "contents": [] // Extracted content items in text format -} -``` - -### Content Item Object -```json -{ - "sequenceNr": int, // Sequence in the document - "name": "string", - "ext": "string", - "contentType": "string", // mime type - "data": "string|base64", // Original content - "dataExtracted": "string", // Optional AI-processed content based on extraction requirement - "metadata": { - "isText": boolean, - "base64Encoded": boolean, - "aiProcessed": boolean, - // Optional metadata specific to content type - }, - "summary": "string" // AI-generated static summary of the content -} -``` - -## State Machine Workflow - -### 1. Workflow Initialization -- **Trigger**: User message received via `/api/workflows/start` OR `/api/workflows/start?id=string` -- **Input**: `UserInputRequest` with `prompt` and optional `listFileId` -- **Process**: - - If `id` existing and workflow exists for `id`==`workflowId`: Load workflow, increment `currentRound`, set status "running" - - Else: Create new workflow with "currentRound"=1, status "running" -- **Logs**: "Workflow initialized" or "Running workflow", progress 0% -- **API Responses**: - - Success: 200 OK with workflow ID - - Error: 400 Bad Request if input invalid, 404 Not Found if workflow ID not found - -### 2. Workflow Exception -- **Trigger**: - - User stopped workflow via API - - An exception happened -- **Process**: - - If status=="stopped": Set workflow status to "stopped", add message with status "last", update lastActivity, stop execution immediately - - If status=="failed": Set workflow status to "failed", add message with status "last", update lastActivity, stop execution immediately - - Else: Continue normally -- **Logs**: "Workflow failure reported", progress 100% -- **API Responses**: - - For stop request: 200 OK when workflow successfully stopped - - For exceptions: 500 Internal Server Error with error details - -### 3. User Message Processing -- **Process**: - - Transform user input into message object with documents, message status "first" - - Extract contents from files using `getDocumentContents()` - - Generate static summaries for each content item -- **State Changes**: - - Add user message to `workflow.messages` array - - Add message ID to `workflow.messageIds` array - - Update `workflow.lastActivity` -- **Logs**: "Workflow processing started", progress 0% - -### 4. Project Manager Analysis -- **Process**: - - Generate prompt for project manager AI - - Project manager analyzes request and documents - - Project manager generates work plan and response -- **Outputs**: - - `objFinalDocuments`: List of str "filename.ext" for expected final output documents - - `objWorkplan`: List of agent tasks - - `objUserResponse`: Text response to user - - `userLanguage`: Detected language code (e.g. en) -- **State Changes**: - - Add assistant message with project manager response, status "step" - - Set user language in mydom interface -- **Logs**: "Analyzing request and planning work" (10%), "Planned outputs" (20%), "Work plan created" (25%) - -### 5. Agent Execution -- **Process** (For each task in workplan): - - Prepare input documents for agent - - Execute agent with standardized task object - - Save produced documents - - Create assistant message with agent response, status "step" -- **Agent Task Object**: -```json -{ - "taskId": "uuid-string", - "workflowId": "workflow-uuid", - "prompt": "string", - "inputDocuments": [], // list of documents including original document data and all content items data with original (attribute "data") and based on prompt (attribute "dataExtracted") - "outputSpecifications": [ - { - "label": "filename.ext", - "description": "string" - } - ], - "context": { - "workflowRound": int, - "agentType": "string", - "timestamp": "ISO-datetime", - "language": "language-code" - } -} -``` -- **Agent Result Object**: -```json -{ - "feedback": "string", // Text describing what the agent did - "documents": [ - { - "label": "filename.ext", - "content": "string|binary" // Document contents - } - ] -} -``` -- **State Changes**: Add assistant message for each agent with agentName set, status "step" -- **Logs**: "Running task X/Y: agentName" with progress updates from 30% to 90% - -### 6. Final Response Generation -- **Process**: - - Create final message reviewing promised and delivered documents - - Add documents to workflow -- **State Changes**: Add final assistant message from projectManager, status "last" -- **Logs**: "Creating final response" (90%) - -### 7. Workflow Completion -- **Process**: - - Finalize workflow and update status -- **State Changes**: - - Set workflow status to "completed" - - Update `workflow.lastActivity` -- **Logs**: "Workflow completed successfully" with progress 100% -- **API Responses**: - - A message with status "last" is included in the response - - Status endpoint will return "completed" - -### 8. Workflow Stopped -- **Trigger**: `/api/workflows/{workflowId}/stop` endpoint called -- **Process**: - - Immediately interrupt workflow execution - - Save current state and mark as stopped -- **State Changes**: - - Set workflow status to "stopped" - - Update lastActivity timestamp -- **Logs**: "Workflow stopped by user" with progress 100% -- **API Responses**: - - 200 OK with confirmation message - -### 9. Workflow Failed -- **Trigger**: Exception during workflow execution -- **Process**: - - Log error details - - Set workflow status to "failed" -- **State Changes**: - - Set workflow status to "failed" - - Update lastActivity timestamp -- **Logs**: Detailed error message with progress 100% -- **API Responses**: - - Status endpoint will return "failed" with error context - -### 10. Workflow Resumption -- **Trigger**: `/api/workflows/start?id={workflowId}` endpoint called with existing workflow ID -- **Process**: - - Load existing workflow - - Increment currentRound counter - - Start processing from user message -- **State Changes**: - - Set status to "running" - - Increment currentRound - - Add new user message -- **Logs**: "Resuming workflow, round {currentRound}" with progress 0% -- **API Responses**: - - Same as workflow initialization - -### 11. Workflow Reset/Deletion -- **Trigger**: `/api/workflows/{workflowId}` DELETE endpoint called -- **Process**: - - Remove all workflow data from storage -- **State Changes**: - - Workflow no longer exists in the system -- **Logs**: Log in system log that workflow was deleted -- **API Responses**: - - 200 OK if successful - - 404 Not Found if workflow didn't exist - -## API Endpoints and Polling Support - -### Main Workflow Endpoints -- `POST /api/workflows/start?id=string`: Submit user input to start a new workflow, optional with workflow id to continue existing workflow -- `POST /api/workflows/{workflowId}/stop`: Stop a running workflow: Immediately to set workflow status to "stopped" -- `DELETE /api/workflows/{workflowId}`: Delete a workflow -- `GET /api/workflows/{workflowId}/status`: Get workflow status (running, completed, failed, stopped) -- `GET /api/workflows/{workflowId}/logs?id=string`: Get workflow logs, optional with log id to get only logs produced after and including log with log id -- `GET /api/workflows/{workflowId}/messages?id=string`: Get workflow messages, optional with message id to get only messages produced after and including message with log id - -### Document Management -- `DELETE /api/workflows/{workflowId}/messages/{messageId}`: Delete a message -- `DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}`: Remove file from message - -### Backend Support for Frontend Polling - -The backend implements efficient support for frontend polling mechanisms: - -1. **Selective Data Transfer**: - - Both `/logs` and `/messages` endpoints accept an optional `id` parameter - - When provided, only records with IDs equal to or newer than the specified ID are returned - - This minimizes data transfer and improves performance - -2. **Log Storage**: - - Each log entry includes timestamp, progress indicators, and status - - Frontend can accurately track workflow progress and update UI accordingly - - Logs are stored in chronological order with monotonically increasing IDs - -3. **Message Handling**: - - Messages include a status field ("first", "step", "last") - - The "last" status indicates completion of the current workflow round - - Frontend uses this to determine when to enable user input - -4. **Status Endpoint**: - - Lightweight endpoint that returns only the current workflow status - - Used by frontend to detect state changes without transferring all data - - Also includes lastActivity timestamp to detect stalled workflows - -5. **Caching Layer**: - - Backend implements caching for frequent polling requests - - Reduces database load and improves response times - - Cache invalidation occurs when workflow status changes - -6. **Batch Processing**: - - Large log or message sets are paginated automatically - - Frontend receives data in manageable chunks - - Prevents memory issues with long-running workflows - -## Document Object Structure Clarification -The Document Object contains both raw data and processed contents: -- `data`: Contains the base64-encoded binary representation of the entire original file -- `contents`: Contains an array of structured Content Item objects extracted from the original file - -The relationship works as follows: -1. When a file is uploaded, its binary data is stored in the `data` field -2. The original file's complete data is always preserved in the document's `data` field -3. The file is then processed by content extractors based on file type (PDF, image, text, etc.) -4. Each logically separate piece of content is added to the `contents` array -5. For text files, there might be just one content item -6. For PDFs, there might be multiple content items (one per page or per embedded image) -7. For complex documents, content items might represent different sections or formats -8. Each content item contains its own `data` field with the specific extracted content; for agents convenience it contains the additional field `dataExtracted` with extracted data based on agents task prompt - -This dual structure allows agents to: -- Access the complete original file when needed -- Work with pre-processed, extracted content for efficiency -- Process specific sections of a document without loading the entire file - -## State Transitions - -``` -[null] → [running] // New workflow created -[running] → [completed] // Workflow completes successfully -[running] → [stopped] // User manually stops workflow -[running] → [failed] // Error occurs during workflow -[completed] → [running] // User continues workflow with new input (new round) -[stopped] → [running] // User continues after manual stop (new round) -[failed] → [running] // User retries workflow despite error (new round) -[any] → [null] // Workflow deleted -``` - -## Exception Handling - -### Rules -- Workflow status changes to "failed" on exceptions, all message and workflow generation exceptions to handle to ensure data consistency in the database -- Errors are logged in workflow logs with type "error" -- Produced project manager analysis output, inputs to agents, output from agents, workflow items, message items are all logged for debugging in the logger with type "debug" -- HTTP exceptions are returned to the client with appropriate status codes -- Failed agent tasks are recorded but don't stop the workflow - -### Workflow Stop Conditions -- User explicitly cancels the workflow via the stop endpoint - -Action to take: -- workflow to set to "stopped" status - -### Workflow Failure Conditions -- Unhandled exceptions in the main workflow execution path -- Project manager analysis fails to generate a valid workplan -- More than 50% of the agent tasks in the workplan fail to complete -- Timeout exceeded (workflow runs longer than the configured maximum duration) -- System resource limits exceeded (memory, CPU, etc.) - -Action to take, when a workflow fails: -- The last log entry will contain details about the failure reason -- workflow to set to "failed" status - -### Workflow Exception Checkpoints -At the following points in the code the Workflow Execution routine is called: -- Before adding or updating a message to the workflow -- Before doing an API call - -## Special Notes - -1. **Document Processing**: Files uploaded by users are processed with content extraction to make them accessible to agents. -2. **AI Language Support**: The system detects and adapts to the user's language. -3. **Round Counting**: Each interaction increments the `currentRound` counter. -4. **Agent Registry**: Agents are loaded dynamically and registered in the AgentRegistry. -5. **Standardized Task Processing**: All agents implement the same task processing interface. diff --git a/notes/doc_statemachine_frontend.md b/notes/doc_statemachine_frontend.md deleted file mode 100644 index 0fa1f374..00000000 --- a/notes/doc_statemachine_frontend.md +++ /dev/null @@ -1,453 +0,0 @@ -# State Machine Documentation for Frontend Chat Workflow - -## Overview - -The Chat Workflow frontend implements a state machine that manages user interactions through a well-defined sequence of states. This system coordinates the user interface components, handles file attachments, and manages communication with the backend to provide a seamless multi-agent chat experience. - -## Core Objects - -### Frontend Workflow State Object -```json -{ - "status": "string", // null or "running", "completed", "failed", "stopped" - "workflowId": "string", // null or Unique workflow identifier - "logs": [], // Log entries - "chatMessages": [], // Chat messages - "lastPolledLogId": "string", // ID of last polled log - "lastPolledMessageId": "string", // ID of last polled message - "dataStats": { - "bytesSent": int, // Data sent to backend - "bytesReceived": int, // Data received from backend - "tokensUsed": float // Used tokens required for billing - } -} -``` - -### User Input State Object -```json -{ - "promptText": "string", // Current user input text - "additionalFiles": [], // List of file IDs to attach - "domElements": {} // References to UI DOM elements -} -``` - -### Log Entry Object -```json -{ - "id": "log_timestamp", - "message": "string", - "progress": int, // Optional, 0-100 - "type": "string", // info, warning, error - "timestamp": "ISO-datetime", - "agentName": "string", // Name of the agent that generated the log - "waiting": boolean, // Whether this log shows a waiting indicator - "highlighted": boolean // Whether this log should be visually highlighted -} -``` - -### Chat Message Object -```json -{ - "id": "msg_type_timestamp", - "role": "string", // user, assistant - "agentName": "string", - "content": "string", - "documents": [], // List of document objects - "timestamp": "ISO-datetime", - "status": "string" // first, step, last ("workflowComplete" can be asked as `status`=="last") -} -``` - -### File Object -```json -{ - "id": "file_uuid-string", - "name": "filename.ext", - "size": int, - "fileId": int, - "contentType": "string", // mime type -} -``` - -## State Machine Workflow - -### 1. Initial State -- **State**: `null` (No workflow active) -- **UI Elements**: - - Empty chat container with placeholder - - Prompt input field enabled - - "Start" button enabled - - "Stop" button hidden - - File upload area enabled - - Empty log panel -- **User Actions**: - - Enter prompt text - - Add files via upload or drag & drop - - Select pre-defined prompts from dropdown - - Click Start button -- **API Interactions**: None in this state - -### 2. Prompt Preparation -- **State**: `null` (transitioning to `running`) -- **Triggers**: - - User typing in prompt field (updates `userInputState.promptText`) - - File uploads/drag & drop (adds to `userInputState.additionalFiles`) - - Prompt selection from dropdown (sets `userInputState.promptText`) -- **Process**: - - Files are uploaded to backend via `uploadAndAddFile()` - - Files appear in attachment list with name, size, remove option - - Prompt visualization is updated with file count and names -- **State Changes**: - - `userInputState.additionalFiles` array updated - - `userInputState.promptText` updated -- **UI Updates**: - - File attachment list rendered - - Prompt preview area shows attached files -- **API Interactions**: - - `POST /api/files/upload`: For each file being attached, an API call is made to upload the file and get a file ID - - Response contains file metadata which is stored in the frontend state - -### 3. Workflow Initialization -- **State**: `running` -- **Trigger**: User clicks "Start" button or presses Enter -- **Process**: - - Validate user input (ensures non-empty prompt) - - Set loading state (disable start button, show spinner) - - Submit prompt and files - - Receive workflow ID from backend -- **State Changes**: - - `status` → `running` - - `workflowId` set to returned ID - - `userInputState.additionalFiles` reset to empty array - - `userInputState.promptText` reset to empty string -- **UI Updates**: - - Chat messages container shown - - User message appears in chat - - Log entry added: "Workflow started" - - System chat message added: "Multi-Agent Chat has been started" - - Start button becomes Send button, deactivated and with animation - - Stop button becomes visible - - Input field disabled temporarily -- **API Interactions**: - - `POST /api/workflows/start`: Submit user prompt and list of file IDs - - Request payload: `{ prompt: string, fileIds: array }` - - Response contains `workflowId` which is stored for future API calls - - For continuing an existing workflow: `POST /api/workflows/start?id={workflowId}` - -### 4. Polling for Updates -- **State**: `running` -- **Process**: - - `pollWorkflowStatus()` initiated with workflow ID - - `pollWorkflowLogs()` and `pollWorkflowMessages()` called periodically - - Last log and message IDs tracked to request only new items - - New logs and messages added to state -- **State Changes**: - - `logs` array updated with new log entries - - `chatMessages` array updated with new messages - - `lastPolledLogId` and `lastPolledMessageId` updated - - `dataStats` updated with sent/received bytes and tokens -- **UI Updates**: - - Updated and new logs rendered in log panel - - Updated and new chat messages rendered in chat area - - Waiting animation shown on latest log entry - - Data statistics counters updated -- **API Interactions**: - - `GET /api/workflows/{workflowId}/status`: Polls workflow status (every 2000ms) - - `GET /api/workflows/{workflowId}/logs?id={lastPolledLogId}`: Gets only logs newer than the last polled log - - `GET /api/workflows/{workflowId}/messages?id={lastPolledMessageId}`: Gets only messages newer than the last polled message - - Polling continues until workflow status changes from "running" or a message with status "last" is received - -### 5. Workflow Running -- **State**: `running` -- **Process**: - - Backend processes user input through agent workflow - - Frontend continuously polls for updates - - Log entries show progress of agents - - Chat messages display agent responses -- **UI Elements**: - - Stop button visible and enabled - - Input field disabled - - Log panel shows processing steps with progress indicators - - Chat displays multi-agent conversation -- **User Actions**: - - Click Stop button to interrupt workflow - - View file attachments in messages - - Preview or download files -- **API Interactions**: - - Continued polling via endpoints described in State 4 - - `POST /api/workflows/{workflowId}/stop`: If user clicks Stop button - -### 6. Workflow Completion -- **State**: `completed` -- **Trigger**: Backend returns workflow status "completed" or message with status "last" -- **Process**: - - Final message displayed, input enabled for new prompt - - Polling stops -- **State Changes**: - - `status` → `completed` -- **UI Updates**: - - Stop button hidden - - Start button enabled - - Input field enabled and focused - - Final log shows "Workflow completed" -- **API Interactions**: - - No further API calls until user inputs new prompt - -### 7. Workflow Failure -- **State**: `failed` -- **Trigger**: Backend returns workflow status "failed" -- **Process**: - - Error message displayed, option to retry - - Polling stops -- **State Changes**: - - `status` → `failed` -- **UI Updates**: - - Error indicators in UI - - Retry option enabled - - Input field enabled -- **API Interactions**: - - No further API calls until user initiates retry - -### 8. Workflow Stopped -- **State**: `stopped` -- **Trigger**: User clicks Stop button or backend returns "stopped" -- **Process**: - - Workflow processing interrupted - - Polling stops -- **State Changes**: - - `status` → `stopped` -- **UI Updates**: - - Resume option enabled - - Start button enabled - - Input field enabled -- **API Interactions**: - - No further API calls until user continues or resets - -### 9. User Input Requested -- **State**: Varies based on workflow state -- **Trigger**: Received message with status "last" or workflow status not "running" -- **Process**: - - Special log entry added: "Waiting for user input to continue" - - Input field enabled for user response -- **State Changes**: - - `status` → The status from the workflow -- **UI Updates**: - - Stop Polling - - Waiting animation stopped - - Input field enabled and focused - - Send button enabled - - Send button shows "Start" for new workflow or "Send" for continuation - - Input field may show specific placeholder - - Stop button hidden -- **API Interactions**: - - No API calls until user provides input - -### 10. Continuation Preparation -- **State**: `completed`, `failed`, or `stopped` (transitioning to `running`) -- **Trigger**: User enters new input after previous workflow cycle -- **Process**: - - Prepare continuation with existing workflow ID - - Similar to Prompt Preparation but preserves context -- **State Changes**: - - `userInputState.promptText` updated - - `userInputState.additionalFiles` updated if new files added -- **UI Updates**: - - File attachment list updated if changed - - Send button ready for continuation -- **API Interactions**: - - Same as Prompt Preparation if new files are added - -### 11. Workflow Resumption -- **State**: `running` -- **Trigger**: User continues workflow after stop/failure/completion -- **Process**: - - Submit new input with existing workflow ID -- **State Changes**: - - `status` → `running` - - New user message added -- **UI Updates**: - - Similar to Workflow Initialization but preserves history - - Polling resumes -- **API Interactions**: - - `POST /api/workflows/start?id={workflowId}`: Sends continuation prompt with existing workflow ID - - Polling endpoints resume as in State 4 - -### 12. Workflow Reset -- **State**: `null` -- **Trigger**: User clicks Reset button -- **Process**: - - All state data cleared - - UI reset to initial state -- **State Changes**: - - `status` → `null` - - `workflowId` → `null` - - `logs` → `[]` - - `chatMessages` → `[]` -- **UI Updates**: - - Empty chat state shown - - Log panel cleared - - Input field reset - - Chat area cleared - - File attachments removed -- **API Interactions**: - - Optional `DELETE /api/workflows/{workflowId}` to clean up server resources - -## API Interaction Details - -### Polling Implementation -The frontend implements a sophisticated polling mechanism that efficiently retrieves only the new data: - -1. **Initialization**: - - When a workflow starts, the frontend stores the workflow ID - - Initial polling begins immediately after receiving workflow ID - -2. **Selective Data Retrieval**: - - Frontend tracks `lastPolledLogId` and `lastPolledMessageId` - - Each polling request includes the last ID to receive only newer items - - This significantly reduces bandwidth and processing requirements - -3. **Polling Schedule**: - - Status polling: Every 2000ms while in `running` state - - Logs polling: Every 1000ms while in `running` state - - Messages polling: Every 1000ms while in `running` state - - All polling stops when workflow status changes from `running` - -4. **Retry Mechanism**: - - Network failures trigger exponential backoff - - Starting at 1000ms, doubling up to 16000ms max - - After 5 consecutive failures, displays connection error - -5. **Polling Suspension**: - - Polling automatically pauses when browser tab is inactive - - Resumes when tab becomes active again - - Can be manually suspended with `suspendPolling()` during UI transitions - -### API Endpoints Used - -| Frontend Function | Backend Endpoint | Parameters | Description | -|-------------------|------------------|------------|-------------| -| `startWorkflow()` | `POST /api/workflows/start` | `{ prompt: string, fileIds: [] }` | Starts new workflow | -| `continueWorkflow()` | `POST /api/workflows/start?id={workflowId}` | `{ prompt: string, fileIds: [] }` | Continues existing workflow | -| `pollWorkflowStatus()` | `GET /api/workflows/{workflowId}/status` | None | Gets current workflow status | -| `pollWorkflowLogs()` | `GET /api/workflows/{workflowId}/logs?id={lastLogId}` | Optional log ID | Gets logs newer than specified ID | -| `pollWorkflowMessages()` | `GET /api/workflows/{workflowId}/messages?id={lastMessageId}` | Optional message ID | Gets messages newer than specified ID | -| `stopWorkflow()` | `POST /api/workflows/{workflowId}/stop` | None | Interrupts running workflow | -| `resetWorkflow()` | `DELETE /api/workflows/{workflowId}` | None | Cleans up workflow resources | -| `uploadFile()` | `POST /api/files/upload` | FormData with file | Uploads file and returns file ID | -| `deleteMessage()` | `DELETE /api/workflows/{workflowId}/messages/{messageId}` | None | Removes message from workflow | -| `removeFileFromMessage()` | `DELETE /api/workflows/{workflowId}/messages/{messageId}/files/{fileId}` | None | Removes file from message | - -## Special Features - -### File Handling -1. **File Upload**: - - Direct upload via button or drag & drop - - FileId added to `userInputState.additionalFiles` - - UI renders file in attachment list - - Each file can be removed individually - -2. **File Preview**: - - Files in messages can be previewed - - Preview shows content based on file type (text, image, PDF) - - Files can be downloaded or copied to clipboard - - Various file formats supported with appropriate visualizations - -### Chat Message Rendering -1. **Message Types**: - - User messages (grey background) - - Agent messages (white background) - - Moderator questions (specially formatted) - -2. **Message Features**: - - Collapsible for long content - - Symbol to delete message - - File attachments with preview options - - Markdown-like formatting support - - Agent name and timestamp display - -### Log Panel Features -1. **Log Types**: - - Info (blue) - - Warning (orange) - - Error (red) - -2. **Log Features**: - - Log items are read selectively from API: Only the last log entry and newer ones - - Progress indicators for agent tasks; before a next log message is rendered, the last log message progress is set to 100% - - Waiting animation for ongoing processes - - Agent-specific highlighting - - Collapsible detailed information, meaning the first n (e.g., 40) characters are by default displayed with "..." at the end. By clicking on "..." the additional part can be expanded/collapsed - - Timestamp display - -### Waiting States -1. **Animation**: - - Dots animation in log entries - - Spinner in Send button during loading - - Progress indicators in agent logs - -2. **State Management**: - - `waiting` flag in log objects - - `waitingDotsInterval` controls animation - - `setLoadingState()` manages UI element states - -## State Transitions - -``` -[null] → [running] // Initial prompt submission -[running] → [running] // Ongoing polling updates -[running] → [completed] // Workflow completes successfully -[running] → [stopped] // User manually stops workflow -[running] → [failed] // Error occurs during workflow -[completed] → [running] // User continues workflow with new input -[stopped] → [running] // User continues after manual stop -[failed] → [running] // User retries workflow despite error -[any] → [null] // User resets workflow -``` - -## Error Handling - -### Client-side Errors -1. **Network Errors**: - - Retry mechanism in polling functions - - Exponential backoff for repeated failures - - Informative error logs for debugging - -2. **UI Errors**: - - Validation before state changes - - Fallback DOM element selection - - Error boundaries for component isolation - -### Backend Communication Errors -1. **API Failures**: - - Error logging in console - - User-friendly error messages in UI - - Error state with option to retry - - Toast notifications for transient errors - -2. **Data Inconsistencies**: - - ID matching with fallback to partial matching - - Multiple file reference methods - - Content extraction fallbacks - -## Implementation Notes - -1. **Module Structure**: - - `workflow.js`: Main initialization and coordination - - `workflowState.js`: State management - - `workflowApi.js`: Backend communication - - `workflowUi.js`: UI rendering - - `workflowUtils.js`: Helper functions - -2. **Key Functions**: - - `workflowInit()`: Entry point for initialization - - `workflowStatusUpdate()`: Core state transition function - - `workflowStatusPoll()`: Main update loop - - `workflowUserInput()`: Handles user input submission - - `renderLogs()` and `renderMessages()`: UI update functions - -3. **Performance Optimizations**: - - Selective DOM updates - - Scroll position preservation - - Data size estimation for statistics - - Conditional re-rendering diff --git a/notes/produce_diagrams.md b/notes/produce_diagrams.md new file mode 100644 index 00000000..f4f02abc --- /dev/null +++ b/notes/produce_diagrams.md @@ -0,0 +1,48 @@ +MERMAID DIAGRAM: + +can you make chart "wiki/diagramm_komponenten.mermaid". produce an component diagram, based on current code in poweron/* +if document existsadd missing components, remove obsolete components. + +in box texts to use
instead of \n + +for all subgraphs to to add path on a separate line to find the module in the code. + +read all code modules caerfully to identify all components and their relations. + +connectors without texts, only lines. + +to add connector between frontend and backend (apiCalls.js -> app.py) + +to connect app.py (Main application module) with the route*.py + +to put all items of frontend into subgraph "Frontend" +to put all items of gateway into subgraph "Gateway" + + + + + + +to put following boxes to a dedicated subgraph within their existing subgraph: +- workflowManager.py, workflowAgentsRegistry.py, documentProcessor.py, --> "Workflow" +- mimeUtils.py, defAttributes.py, configuration.py, autho.py --> "Shared" +- agent*.py --> "Agents" +- workflow*.js --> "Workflow" +- all *.js in js/modules/ not starting with workflow* --> "Administration" +- formGeneric.js not to put to subgraph "Shared", but to a separated subgraph "Shared + +to connect the main.js (main app in the frontend) to nativation.js, globalState.js, login.js, register.js, msftCall.js, config.js + +to connect navigation.js to moduleLoader.js + +to connect moduleLoader.js to workflow.js, and all *.js in js/modules/ not starting with workflow* + +to connect all *.js in js/modules/ not starting with workflow* --> formGeneric.js + +to connect fomrGeneric.js --> apiCalls.js + + +to use underscores (e.g. Backend_Python, Workflow_Modules, etc.) for all subgraph titles. + +if adding legend, then to give same colors like references to legend + diff --git a/notes/readme.md b/notes/readme.md index 44186a3d..677e78ed 100644 --- a/notes/readme.md +++ b/notes/readme.md @@ -90,6 +90,7 @@ Für größere Installationen die JSON-basierte Datenbank ersetzen durch: ### git permanent login with vs code git remote set-url origin https://valueon@github.com/valueonag/gateway git remote set-url origin https://valueon@github.com/valueonag/frontend_agents +git remote set-url origin https://valueon@github.com/valueonag/wiki ## Lizenz diff --git a/notes/start.sh b/notes/start.sh deleted file mode 100644 index c8486531..00000000 --- a/notes/start.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/bash - -# Erkennen des Betriebssystems -case "$(uname -s)" in - CYGWIN*|MINGW*|MSYS*|Windows*) - OS="Windows" - VENV_PATH="gwserver/venv/Scripts" - ACTIVATE_CMD="$VENV_PATH/activate" - ;; - *) - OS="Unix" - VENV_PATH="gwserver/venv/bin" - ACTIVATE_CMD="$VENV_PATH/activate" - ;; -esac - -echo "Erkanntes Betriebssystem: $OS" - -# Prüfen, ob Python installiert ist -PYTHON_CMD="" -if command -v python3 &>/dev/null; then - PYTHON_CMD="python3" -elif command -v python &>/dev/null; then - PYTHON_CMD="python" -else - echo "Python ist nicht installiert. Bitte installieren Sie Python 3.8 oder höher." - exit 1 -fi - -# Virtuelle Umgebung erstellen, falls sie nicht existiert -if [ ! -d "gwserver/venv" ]; then - echo "Erstelle virtuelle Python-Umgebung..." - cd gwserver - $PYTHON_CMD -m venv venv - cd .. -fi - -# Virtuelle Umgebung aktivieren -echo "Aktiviere virtuelle Umgebung..." -if [ "$OS" = "Windows" ]; then - source "$ACTIVATE_CMD" 2>/dev/null || . "$ACTIVATE_CMD" 2>/dev/null || echo "Warnung: Aktivierung der virtuellen Umgebung fehlgeschlagen. Fahre fort..." -else - source "$ACTIVATE_CMD" 2>/dev/null || . "$ACTIVATE_CMD" 2>/dev/null || echo "Warnung: Aktivierung der virtuellen Umgebung fehlgeschlagen. Fahre fort..." -fi - -# Abhängigkeiten installieren -echo "Installiere Abhängigkeiten..." -pip install -r requirements.txt - -# gwserver als Hintergrundprozess starten -echo "Starte gwserver-Server..." - -# START CORE -cd gwserver -if [ "$OS" = "Windows" ]; then - # Windows: starte in einem separaten Prozess - start uvicorn app:app --reload --host 0.0.0.0 --port 8000 - # Keine PID-Erfassung nötig unter Windows mit 'start' - GWSERVER_PID="" -else - # Unix: starte im Hintergrund und erfasse PID - uvicorn app:app --reload --host 0.0.0.0 --port 8000 & - GWSERVER_PID=$! -fi -# END CORE - -cd .. -echo "gwserver API läuft auf: http://localhost:8000" -echo "API-Dokumentation: http://localhost:8000/docs" -echo "Drücke STRG+C, um Server zu beenden" - -# Funktion zum Beenden der Server bei STRG+C -cleanup() { - echo -e "\nBeende Server..." - if [ "$OS" = "Windows" ]; then - # Windows: Taskkill für uvicorn - taskkill //F //IM uvicorn.exe 2>/dev/null || echo "Konnte uvicorn nicht beenden" - else - # Unix: Verwende die erfasste PID - if [ -n "$GWSERVER_PID" ]; then - kill $GWSERVER_PID 2>/dev/null || echo "Konnte Prozess $GWSERVER_PID nicht beenden" - fi - fi - echo "Server wurden beendet" - exit 0 -} - -# Signal-Handler für STRG+C -trap cleanup SIGINT - -# Warten auf Benutzeraktion -if [ "$OS" = "Windows" ]; then - # Unter Windows: Warte auf Eingabe - read -p "Drücke Enter zum Beenden..." dummy - cleanup -else - # Unter Unix: Warte auf Prozess - if [ -n "$GWSERVER_PID" ]; then - wait $GWSERVER_PID - else - # Fallback: einfach warten - read -p "Drücke Enter zum Beenden..." dummy - cleanup - fi -fi \ No newline at end of file diff --git a/routes/routeGeneral.py b/routes/routeGeneral.py new file mode 100644 index 00000000..0b5d3d33 --- /dev/null +++ b/routes/routeGeneral.py @@ -0,0 +1,81 @@ +from fastapi import APIRouter, HTTPException, Depends, Body, status, Response +from fastapi.responses import FileResponse +from fastapi.security import OAuth2PasswordRequestForm +from typing import Dict, Any +from datetime import timedelta +import pathlib +import os + +from modules.configuration import APP_CONFIG +from modules.auth import ( + createAccessToken, + getCurrentActiveUser, + getUserContext, + ACCESS_TOKEN_EXPIRE_MINUTES +) +import modules.gatewayModel as gatewayModel +from modules.gatewayInterface import getGatewayInterface + +router = APIRouter() + +# Static folder for favicon +baseDir = pathlib.Path(__file__).parent.parent +staticFolder = baseDir / "static" + +@router.get("/favicon.ico") +async def favicon(): + return FileResponse(str(staticFolder / "favicon.ico"), media_type="image/x-icon") + +@router.get("/", tags=["General"]) +async def root(): + """API status endpoint""" + return {"status": "online", "message": "Data Platform API is active"} + +@router.get("/api/test", tags=["General"]) +async def getTest(): + return f"Status: OK. Alowed origins: {APP_CONFIG.get('APP_ALLOWED_ORIGINS')}" + +@router.options("/{fullPath:path}", tags=["General"]) +async def optionsRoute(fullPath: str): + return Response(status_code=200) + +@router.get("/api/environment", tags=["General"]) +async def get_environment(): + """Get environment configuration for frontend""" + return { + "apiBaseUrl": APP_CONFIG.get("APP_API_URL", ""), + "environment": APP_CONFIG.get("APP_ENV", "development"), + "instanceLabel": APP_CONFIG.get("APP_ENV_LABEL", "Development"), + # Add other environment variables the frontend might need + } + +@router.post("/api/token", response_model=gatewayModel.Token, tags=["General"]) +async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()): + # Initialize Gateway interface without context + gateway = getGatewayInterface() + + # Authenticate user + user = gateway.authenticateUser(formData.username, formData.password) + + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # Create token with tenant ID + accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + accessToken = createAccessToken( + data={ + "sub": user["username"], + "mandateId": user["mandateId"] + }, + expiresDelta=accessTokenExpires + ) + + return {"accessToken": accessToken, "tokenType": "bearer"} + +@router.get("/api/user/me", response_model=Dict[str, Any], tags=["General"]) +async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)): + return currentUser \ No newline at end of file