access extracted from interface and all routes in separate modules

This commit is contained in:
ValueOn AG 2025-05-14 23:37:33 +02:00
parent 8ea05780d1
commit 5d78faf7ff
14 changed files with 392 additions and 1330 deletions

75
app.py
View file

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

111
modules/gatewayAccess.py Normal file
View file

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

View file

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

131
modules/lucydomAccess.py Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

48
notes/produce_diagrams.md Normal file
View file

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

View file

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

View file

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

81
routes/routeGeneral.py Normal file
View file

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