mvp 1.2 ready for single test

This commit is contained in:
ValueOn AG 2025-04-26 02:13:22 +02:00
parent 3b303944b1
commit 64d1c083e0
92 changed files with 10279 additions and 15147 deletions

194
_SAVE_app copy.py Normal file
View file

@ -0,0 +1,194 @@
import os
os.environ["NUMEXPR_MAX_THREADS"] = "12"
from fastapi import FastAPI, HTTPException, Depends, Body, status, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.security import OAuth2PasswordRequestForm
from contextlib import asynccontextmanager
from typing import Dict, Any
import logging
from logging.handlers import RotatingFileHandler
from datetime import timedelta
import pathlib
from modules.configuration import APP_CONFIG
# Import auth module
from modules.auth import (
createAccessToken,
getCurrentActiveUser,
getUserContext,
ACCESS_TOKEN_EXPIRE_MINUTES
)
# Import models - import generically for INITIALIZATION
import modules.gatewayModel as gatewayModel
from modules.gatewayInterface import getGatewayInterface
def initLogging():
# Get log level from config (default to INFO if not found)
logLevelName = APP_CONFIG.get("Logging_LOG_LEVEL", "WARNING")
logLevel = getattr(logging, logLevelName)
# Configure handlers based on config
handlers = []
# Add console handler if enabled
if APP_CONFIG.get("Logging_CONSOLE_ENABLED", True):
consoleHandler = logging.StreamHandler()
handlers.append(consoleHandler)
# Add file handler if enabled
if APP_CONFIG.get("Logging_FILE_ENABLED", True):
logFile = APP_CONFIG.get("Logging_LOG_FILE", "app.log")
rotationSize = int(APP_CONFIG.get("Logging_ROTATION_SIZE", 10485760)) # Default: 10MB
backupCount = int(APP_CONFIG.get("Logging_BACKUP_COUNT", 5))
fileHandler = RotatingFileHandler(
logFile,
maxBytes=rotationSize,
backupCount=backupCount
)
handlers.append(fileHandler)
# Configure the logger
logging.basicConfig(
level=logLevel,
format=APP_CONFIG.get("Logging_FORMAT", "%(asctime)s - %(levelname)s - %(name)s - %(message)s"),
datefmt=APP_CONFIG.get("Logging_DATE_FORMAT", "%Y-%m-%d %H:%M:%S"),
handlers=handlers
)
# Silence noisy third-party libraries - use the same level as the root logger
noisyLoggers = ["httpx", "urllib3", "asyncio", "fastapi.security.oauth2"]
for loggerName in noisyLoggers:
logging.getLogger(loggerName).setLevel(logLevel)
# Initialize logging
initLogging()
logger = logging.getLogger(__name__)
instanceLabel = APP_CONFIG.get("APP_ENV_LABEL")
# Define lifespan context manager for application startup/shutdown events
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup logic (if any)
logger.info("Application is starting up")
yield
# Shutdown logic
logger.info("Application has been shut down")
# Parse CORS origins from environment variable
def get_allowed_origins():
origins_str = APP_CONFIG.get("APP_ALLOWED_ORIGINS", "http://localhost:8080")
# Split by comma and strip whitespace
origins = [origin.strip() for origin in origins_str.split(",")]
logger.info(f"CORS allowed origins: {origins}")
return origins
# START APP
app = FastAPI(
title="PowerOn | Data Platform API",
description=f"Backend API for the Multi-Agent Platform by ValueOn AG ({instanceLabel})",
lifespan=lifespan
)
# CORS configuration using environment variables
app.add_middleware(
CORSMiddleware,
allow_origins=get_allowed_origins(),
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
expose_headers=["*"],
max_age=86400 # Increased caching for preflight requests
)
# Static folder for frontend - work with absolute path
baseDir = pathlib.Path(__file__).parent
staticFolder = baseDir / "static"
os.makedirs(staticFolder, exist_ok=True)
app.mount("/static", StaticFiles(directory=str(staticFolder)), name="static")
# 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 "OK 1.5"
@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.routeAttributes import router as attributesRouter
app.include_router(attributesRouter)
from routes.routeMandates import router as mandateRouter
app.include_router(mandateRouter)
from routes.routeUsers import router as userRouter
app.include_router(userRouter)
from routes.routeFiles import router as fileRouter
app.include_router(fileRouter)
from routes.routePrompts import router as promptRouter
app.include_router(promptRouter)
from routes.routeWorkflows import router as workflowRouter
app.include_router(workflowRouter)
#if __name__ == "__main__":
# uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)

164
app.py
View file

@ -8,7 +8,6 @@ from fastapi.staticfiles import StaticFiles
from fastapi.security import OAuth2PasswordRequestForm
from contextlib import asynccontextmanager
import uvicorn
from typing import Dict, Any
import logging
from logging.handlers import RotatingFileHandler
@ -16,66 +15,65 @@ from datetime import timedelta
import pathlib
from modules.configuration import APP_CONFIG
from modules.gateway_interface import get_gateway_interface
# Import auth module
from modules.auth import (
create_access_token,
get_current_active_user,
get_user_context,
ACCESS_TOKEN_EXPIRE_MINUTES
)
# Import models - import generically for INITIALIZATION, even if dummy!
import modules.gateway_model as gateway_model
#from modules.lucydom_interface import get_lucydom_interface as dom_interface
def init_logging():
def initLogging():
# Get log level from config (default to INFO if not found)
log_level_name = APP_CONFIG.get("Logging_LOG_LEVEL", "WARNING")
log_level = getattr(logging, log_level_name)
logLevelName = APP_CONFIG.get("Logging_LOG_LEVEL", "WARNING")
logLevel = getattr(logging, logLevelName)
# Configure handlers based on config
handlers = []
# Add console handler if enabled
if APP_CONFIG.get("Logging_CONSOLE_ENABLED", True):
console_handler = logging.StreamHandler()
handlers.append(console_handler)
consoleHandler = logging.StreamHandler()
handlers.append(consoleHandler)
# Add file handler if enabled
if APP_CONFIG.get("Logging_FILE_ENABLED", True):
log_file = APP_CONFIG.get("Logging_LOG_FILE", "app.log")
rotation_size = int(APP_CONFIG.get("Logging_ROTATION_SIZE", 10485760)) # Default: 10MB
backup_count = int(APP_CONFIG.get("Logging_BACKUP_COUNT", 5))
logFile = APP_CONFIG.get("Logging_LOG_FILE", "app.log")
rotationSize = int(APP_CONFIG.get("Logging_ROTATION_SIZE", 10485760)) # Default: 10MB
backupCount = int(APP_CONFIG.get("Logging_BACKUP_COUNT", 5))
file_handler = RotatingFileHandler(
log_file,
maxBytes=rotation_size,
backupCount=backup_count
fileHandler = RotatingFileHandler(
logFile,
maxBytes=rotationSize,
backupCount=backupCount
)
handlers.append(file_handler)
handlers.append(fileHandler)
# Configure the logger
logging.basicConfig(
level=log_level,
level=logLevel,
format=APP_CONFIG.get("Logging_FORMAT", "%(asctime)s - %(levelname)s - %(name)s - %(message)s"),
datefmt=APP_CONFIG.get("Logging_DATE_FORMAT", "%Y-%m-%d %H:%M:%S"),
handlers=handlers
)
# Silence noisy third-party libraries - use the same level as the root logger
noisy_loggers = ["httpx", "urllib3", "asyncio", "fastapi.security.oauth2"]
for logger_name in noisy_loggers:
logging.getLogger(logger_name).setLevel(log_level)
noisyLoggers = ["httpx", "urllib3", "asyncio", "fastapi.security.oauth2"]
for loggerName in noisyLoggers:
logging.getLogger(loggerName).setLevel(logLevel)
# Initialize logging
init_logging()
initLogging()
logger = logging.getLogger(__name__)
instance_label = APP_CONFIG.get("APP_ENV_LABEL")
instanceLabel = APP_CONFIG.get("APP_ENV_LABEL")
# Import auth module
from modules.auth import (
createAccessToken,
getCurrentActiveUser,
getUserContext,
ACCESS_TOKEN_EXPIRE_MINUTES
)
# Import models - import generically for INITIALIZATION
import modules.gatewayModel as gatewayModel
from modules.gatewayInterface import getGatewayInterface
gateway = getGatewayInterface()
# Define lifespan context manager for application startup/shutdown events
@asynccontextmanager
@ -86,17 +84,25 @@ async def lifespan(app: FastAPI):
# Shutdown logic
logger.info("Application has been shut down")
# Parse CORS origins from environment variable
def get_allowed_origins():
origins_str = APP_CONFIG.get("APP_ALLOWED_ORIGINS", "http://localhost:8080")
# Split by comma and strip whitespace
origins = [origin.strip() for origin in origins_str.split(",")]
logger.info(f"CORS allowed origins: {origins}")
return origins
# START APP
app = FastAPI(
title="PowerOn | Data Platform API",
description=f"Backend API for the Multi-Agent Platform by ValueOn AG ({instance_label})",
description=f"Backend API for the Multi-Agent Platform by ValueOn AG ({instanceLabel})",
lifespan=lifespan
)
# CORS configuration for frontend requests
# CORS configuration using environment variables
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:8080","https://poweron-lucyagents-xxx.germanywestcentral-01.azurewebsites.net"],
allow_origins=get_allowed_origins(), # ["http://localhost:8080","http://localhost:8081"], #get_allowed_origins(),
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
@ -105,16 +111,10 @@ app.add_middleware(
)
# Static folder for frontend - work with absolute path
base_dir = pathlib.Path(__file__).parent
static_folder = base_dir / "static"
os.makedirs(static_folder, exist_ok=True)
app.mount("/static", StaticFiles(directory=str(static_folder)), name="static")
# Add a specific route for favicon.ico
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
favicon_path = static_folder / "favicon.ico"
return FileResponse(str(favicon_path))
baseDir = pathlib.Path(__file__).parent
staticFolder = baseDir / "static"
os.makedirs(staticFolder, exist_ok=True)
app.mount("/static", StaticFiles(directory=str(staticFolder)), name="static")
# General Elements
@app.get("/", tags=["General"])
@ -123,22 +123,31 @@ async def root():
return {"status": "online", "message": "Data Platform API is active"}
@app.get("/api/test", tags=["General"])
async def get_test():
async def getTest():
return "OK 1.5"
@app.options("/{full_path:path}", tags=["General"])
async def options_route(full_path: str):
@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=gateway_model.Token, tags=["General"])
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
@app.post("/api/token", response_model=gatewayModel.Token, tags=["General"])
async def loginForAccessToken(formData: OAuth2PasswordRequestForm = Depends()):
# Initialize Gateway interface without context
gateway = get_gateway_interface()
gateway = getGatewayInterface()
# Authenticate user
user = gateway.authenticate_user(form_data.username, form_data.password)
user = gateway.authenticateUser(formData.username, formData.password)
if not user:
raise HTTPException(
@ -148,40 +157,45 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
)
# Create token with tenant ID
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
accessTokenExpires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
accessToken = createAccessToken(
data={
"sub": user["username"],
"mandate_id": user["mandate_id"]
"mandateId": user["mandateId"]
},
expires_delta=access_token_expires
expiresDelta=accessTokenExpires
)
return {"access_token": access_token, "token_type": "bearer"}
return {"accessToken": accessToken, "tokenType": "bearer"}
# Get user info
@app.get("/api/user/me", response_model=Dict[str, Any], tags=["General"])
async def read_user_me(current_user: Dict[str, Any] = Depends(get_current_active_user)):
return current_user
async def readUserMe(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
return currentUser
# Include all routers
from routes.attributes import router as attributes_router
app.include_router(attributes_router)
from routes.routeAttributes import router as attributesRouter
app.include_router(attributesRouter)
from routes.mandates import router as mandate_router
app.include_router(mandate_router)
gateway = getGatewayInterface()
from routes.users import router as user_router
app.include_router(user_router)
from routes.routeMandates import router as mandateRouter
app.include_router(mandateRouter)
from routes.files import router as file_router
app.include_router(file_router)
gateway = getGatewayInterface()
from routes.prompts import router as prompt_router
app.include_router(prompt_router)
from routes.workflows import router as workflow_router
app.include_router(workflow_router)
from routes.routeUsers import router as userRouter
app.include_router(userRouter)
from routes.routeFiles import router as fileRouter
app.include_router(fileRouter)
from routes.routePrompts import router as promptRouter
app.include_router(promptRouter)
from routes.routeWorkflows import router as workflowRouter
app.include_router(workflowRouter)
#if __name__ == "__main__":
# uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)

View file

@ -0,0 +1,546 @@
import json
import os
from typing import List, Dict, Any, Optional, Union
import logging
logger = logging.getLogger(__name__)
class DatabaseConnector:
"""
A connector for JSON-based data storage.
Provides generic database operations with tenant and user context support.
"""
def __init__(self, dbHost: str, dbDatabase: str, dbUser: str = None, dbPassword: str = None, mandateId: int = None, userId: int = None):
"""
Initializes the JSON database connector.
Args:
dbHost: Directory for the JSON files
dbDatabase: Database name
dbUser: Username for authentication (optional)
dbPassword: API key for authentication (optional)
mandateId: Context parameter for the tenant
userId: Context parameter for the user
"""
# Store the input parameters
self.dbHost = dbHost
self.dbDatabase = dbDatabase
self.dbUser = dbUser
self.dbPassword = dbPassword
# Check if context parameters are set
if mandateId is None or userId is None:
raise ValueError("mandateId and userId must be set")
# Ensure the database directory exists
self.dbFolder = os.path.join(self.dbHost, self.dbDatabase)
os.makedirs(self.dbFolder, exist_ok=True)
# Cache for loaded data
self._tablesCache = {}
# Initialize system table
self._systemTableName = "_system"
self._initializeSystemTable()
# Temporarily store mandateId and userId
self._mandateId = mandateId
self._userId = userId
# If mandateId or userId are 0, try to use the initial IDs
if mandateId == 0:
initialMandateId = self.getInitialId("mandates")
if initialMandateId is not None:
self._mandateId = initialMandateId
logger.info(f"Using initial mandateId: {initialMandateId} instead of 0")
if userId == 0:
initialUserId = self.getInitialId("users")
if initialUserId is not None:
self._userId = initialUserId
logger.info(f"Using initial userId: {initialUserId} instead of 0")
# Set the effective IDs as properties
self.mandateId = self._mandateId
self.userId = self._userId
logger.info(f"DatabaseConnector initialized for directory: {self.dbFolder}")
logger.debug(f"Context: mandateId={self.mandateId}, userId={self.userId}")
def _initializeSystemTable(self):
"""Initializes the system table if it doesn't exist yet."""
systemTablePath = self._getTablePath(self._systemTableName)
if not os.path.exists(systemTablePath):
emptySystemTable = {}
self._saveSystemTable(emptySystemTable)
logger.info(f"System table initialized in {systemTablePath}")
else:
# Load existing system table to ensure it's available
self._loadSystemTable()
logger.debug(f"Existing system table loaded from {systemTablePath}")
def _loadSystemTable(self) -> Dict[str, int]:
"""Loads the system table with the initial IDs."""
systemTablePath = self._getTablePath(self._systemTableName)
try:
if os.path.exists(systemTablePath):
with open(systemTablePath, 'r', encoding='utf-8') as f:
return json.load(f)
else:
return {}
except Exception as e:
logger.error(f"Error loading the system table: {e}")
return {}
def _saveSystemTable(self, data: Dict[str, int]) -> bool:
"""Saves the system table with the initial IDs."""
systemTablePath = self._getTablePath(self._systemTableName)
try:
with open(systemTablePath, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
logger.error(f"Error saving the system table: {e}")
return False
def _getTablePath(self, table: str) -> str:
"""Returns the full path to a table file"""
return os.path.join(self.dbFolder, f"{table}.json")
def _loadTable(self, table: str) -> List[Dict[str, Any]]:
"""Loads a table from the corresponding JSON file"""
path = self._getTablePath(table)
# If the table is the system table, load it directly
if table == self._systemTableName:
return [] # The system table is not treated like normal tables
# If the table is already in the cache, use the cache
if table in self._tablesCache:
return self._tablesCache[table]
# Otherwise load the file
try:
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
self._tablesCache[table] = data
# If data was loaded and no initial ID is registered yet,
# register the ID of the first record (if available)
if data and not self.hasInitialId(table):
if "id" in data[0]:
self._registerInitialId(table, data[0]["id"])
logger.info(f"Initial ID {data[0]['id']} for table {table} retroactively registered")
return data
else:
# If the file doesn't exist, create an empty table
logger.info(f"New table {table}")
self._tablesCache[table] = []
self._saveTable(table, [])
return []
except Exception as e:
logger.error(f"Error loading table {table}: {e}")
return []
def _saveTable(self, table: str, data: List[Dict[str, Any]]) -> bool:
"""Saves a table to the corresponding JSON file"""
# The system table is handled specially
if table == self._systemTableName:
return False
path = self._getTablePath(table)
try:
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Update the cache
self._tablesCache[table] = data
return True
except Exception as e:
logger.error(f"Error saving table {table}: {e}")
return False
def _filterByContext(self, records: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Filters records by tenant and user context,
if these fields exist in the record.
"""
filteredRecords = []
for record in records:
# Check if mandateId exists in the record and is not null
hasMandate = "mandateId" in record and record["mandateId"] is not None and record["mandateId"] != ""
# Check if userId exists in the record and is not null
hasUser = "userId" in record and record["userId"] is not None and record["userId"] != ""
# If both exist, filter accordingly
if hasMandate and hasUser:
if record["mandateId"] == self.mandateId:
filteredRecords.append(record)
# If only mandateId exists
elif hasMandate and not hasUser:
if record["mandateId"] == self.mandateId:
filteredRecords.append(record)
# If neither mandateId nor userId exist, add the record
elif not hasMandate and not hasUser:
filteredRecords.append(record)
return filteredRecords
def _applyRecordFilter(self, records: List[Dict[str, Any]], recordFilter: Dict[str, Any] = None) -> List[Dict[str, Any]]:
"""Applies a record filter to the records"""
if not recordFilter:
return records
filteredRecords = []
for record in records:
match = True
for field, value in recordFilter.items():
# Check if the field exists
if field not in record:
match = False
break
# If the filter value is an integer string and the record field is an integer
if isinstance(value, str) and value.isdigit() and isinstance(record[field], int):
if record[field] != int(value):
match = False
break
# Otherwise direct comparison
elif record[field] != value:
match = False
break
if match:
filteredRecords.append(record)
return filteredRecords
def _registerInitialId(self, table: str, initialId: int) -> bool:
"""
Registers the initial ID for a table.
Args:
table: Name of the table
initialId: The initial ID
Returns:
True on success, False on error
"""
try:
# Load the current system table
systemData = self._loadSystemTable()
# Only register if not already present
if table not in systemData:
systemData[table] = initialId
success = self._saveSystemTable(systemData)
if success:
logger.info(f"Initial ID {initialId} for table {table} registered")
return success
return True # If already present, this is not an error
except Exception as e:
logger.error(f"Error registering the initial ID for table {table}: {e}")
return False
def _removeInitialId(self, table: str) -> bool:
"""
Removes the initial ID for a table from the system table.
Args:
table: Name of the table
Returns:
True on success, False on error
"""
try:
# Load the current system table
systemData = self._loadSystemTable()
# Remove the entry if it exists
if table in systemData:
del systemData[table]
success = self._saveSystemTable(systemData)
if success:
logger.info(f"Initial ID for table {table} removed from system table")
return success
return True # If not present, this is not an error
except Exception as e:
logger.error(f"Error removing initial ID for table {table}: {e}")
return False
# Public API
def getTables(self) -> List[str]:
"""
Returns a list of all available tables.
Returns:
List of table names
"""
tables = []
try:
for filename in os.listdir(self.dbFolder):
if filename.endswith('.json') and not filename.startswith('_'):
tableName = filename[:-5] # Remove the .json extension
tables.append(tableName)
except Exception as e:
logger.error(f"Error reading the database directory: {e}")
return tables
def getFields(self, table: str) -> List[str]:
"""
Returns a list of all fields in a table.
Args:
table: Name of the table
Returns:
List of field names
"""
# Load the table data
data = self._loadTable(table)
if not data:
return []
# Take the first record as a reference for the fields
fields = list(data[0].keys()) if data else []
return fields
def getSchema(self, table: str, language: str = None) -> Dict[str, Dict[str, Any]]:
"""
Returns a schema object for a table with data types and labels.
Args:
table: Name of the table
language: Language for the labels (optional)
Returns:
Schema object with fields, data types and labels
"""
# Load the table data
data = self._loadTable(table)
schema = {}
if not data:
return schema
# Take the first record as a reference for the fields and data types
firstRecord = data[0]
for field, value in firstRecord.items():
# Determine the data type
dataType = type(value).__name__
# Create label (default is the field name)
label = field
schema[field] = {
"type": dataType,
"label": label
}
return schema
def getRecordset(self, table: str, fieldFilter: List[str] = None, recordFilter: Dict[str, Any] = None) -> List[Dict[str, Any]]:
"""
Returns a list of records from a table, filtered by criteria.
Args:
table: Name of the table
fieldFilter: Filter for fields (which fields should be returned)
recordFilter: Filter for records (which records should be returned)
Returns:
List of filtered records
"""
# Load the table data
data = self._loadTable(table)
# Filter by tenant and user context
filteredData = self._filterByContext(data)
# Apply recordFilter if available
if recordFilter:
filteredData = self._applyRecordFilter(filteredData, recordFilter)
# If fieldFilter is available, reduce the fields
if fieldFilter and isinstance(fieldFilter, list):
result = []
for record in filteredData:
filteredRecord = {}
for field in fieldFilter:
if field in record:
filteredRecord[field] = record[field]
result.append(filteredRecord)
return result
return filteredData
def recordCreate(self, table: str, recordData: Dict[str, Any]) -> Dict[str, Any]:
"""
Creates a new record in the table.
Args:
table: Name of the table
recordData: Data for the new record
Returns:
The created record
"""
# Load the table data
data = self._loadTable(table)
# Add mandateId and userId if not present or 0
if "mandateId" not in recordData or recordData["mandateId"] == 0:
recordData["mandateId"] = self.mandateId
if "userId" not in recordData or recordData["userId"] == 0:
recordData["userId"] = self.userId
# Determine the next ID if not present
if "id" not in recordData:
nextId = 1
if data:
nextId = max(record["id"] for record in data if "id" in record) + 1
recordData["id"] = nextId
# If the table is empty and a system ID should be registered
if not data:
self._registerInitialId(table, recordData["id"])
logger.info(f"Initial ID {recordData['id']} for table {table} has been registered")
# Add the new record
data.append(recordData)
# Save the updated table
if self._saveTable(table, data):
return recordData
else:
raise ValueError(f"Error creating the record in table {table}")
def recordDelete(self, table: str, recordId: Union[str, int]) -> bool:
"""
Deletes a record from the table.
Args:
table: Name of the table
recordId: ID of the record to delete
Returns:
True on success, False on error
"""
# Load table data
data = self._loadTable(table)
# Search for the record
for i, record in enumerate(data):
if "id" in record and record["id"] == recordId:
# Check if the record belongs to the current mandate
if "mandateId" in record and record["mandateId"] != self.mandateId:
raise ValueError("Not your mandate")
# Check if it's an initial record
initialId = self.getInitialId(table)
if initialId is not None and initialId == recordId:
# Remove this entry from the system table
self._removeInitialId(table)
logger.info(f"Initial ID {recordId} for table {table} has been removed from the system table")
# Delete the record
del data[i]
# Save the updated table
return self._saveTable(table, data)
# Record not found
return False
def recordModify(self, table: str, recordId: Union[str, int], recordData: Dict[str, Any]) -> Dict[str, Any]:
"""
Modifies a record in the table.
Args:
table: Name of the table
recordId: ID of the record to modify
recordData: New data for the record
Returns:
The updated record
"""
# Load table data
data = self._loadTable(table)
# Search for the record
for i, record in enumerate(data):
if "id" in record and record["id"] == recordId:
# Check if the record belongs to the current mandate
if "mandateId" in record and record["mandateId"] != self.mandateId:
raise ValueError("Not your mandate")
# Prevent changing the ID
if "id" in recordData and recordData["id"] != recordId:
raise ValueError(f"The ID of a record in table {table} cannot be changed")
# Update the record
for key, value in recordData.items():
data[i][key] = value
# Save the updated table
if self._saveTable(table, data):
return data[i]
else:
raise ValueError(f"Error updating record in table {table}")
# Record not found
raise ValueError(f"Record with ID {recordId} not found in table {table}")
def hasInitialId(self, table: str) -> bool:
"""
Checks if an initial ID is registered for a table.
Args:
table: Name of the table
Returns:
True if an initial ID is registered, otherwise False
"""
systemData = self._loadSystemTable()
return table in systemData
def getInitialId(self, table: str) -> Optional[int]:
"""
Returns the initial ID for a table.
Args:
table: Name of the table
Returns:
The initial ID or None if not present
"""
systemData = self._loadSystemTable()
initialId = systemData.get(table)
print("SysTable table",table,"Value",initialId)
if initialId is None:
logger.debug(f"No initial ID found for table {table}")
return initialId
def getAllInitialIds(self) -> Dict[str, int]:
"""
Returns all registered initial IDs.
Returns:
Dictionary with table names as keys and initial IDs as values
"""
systemData = self._loadSystemTable()
return systemData.copy() # Return a copy to protect the original

View file

@ -1,54 +1,52 @@
import logging
import httpx
from typing import Dict, Any, List, Optional, Union
from typing import Dict, Any, List, Union
from fastapi import HTTPException
from modules.configuration import APP_CONFIG
# Configure logger
logger = logging.getLogger(__name__)
# Load configuration data
def load_config_data():
def loadConfigData():
"""Load configuration data for Anthropic connector"""
return {
"api_key": APP_CONFIG.get('Connector_AiAnthropic_API_SECRET'),
"api_url": APP_CONFIG.get('Connector_AiAnthropic_API_URL'),
"model_name": APP_CONFIG.get('Connector_AiAnthropic_MODEL_NAME'),
"apiKey": APP_CONFIG.get('Connector_AiAnthropic_API_SECRET'),
"apiUrl": APP_CONFIG.get('Connector_AiAnthropic_API_URL'),
"modelName": APP_CONFIG.get('Connector_AiAnthropic_MODEL_NAME'),
"temperature": float(APP_CONFIG.get('Connector_AiAnthropic_TEMPERATURE')),
"max_tokens": int(APP_CONFIG.get('Connector_AiAnthropic_MAX_TOKENS'))
"maxTokens": int(APP_CONFIG.get('Connector_AiAnthropic_MAX_TOKENS'))
}
class ChatService:
"""
Connector for communication with the Anthropic API.
"""
"""Connector for communication with the Anthropic API."""
def __init__(self):
# Load configuration
self.config = load_config_data()
self.api_key = self.config["api_key"]
self.api_url = self.config["api_url"]
self.model_name = self.config["model_name"]
self.config = loadConfigData()
self.apiKey = self.config["apiKey"]
self.apiUrl = self.config["apiUrl"]
self.modelName = self.config["modelName"]
# HttpClient for API calls
self.http_client = httpx.AsyncClient(
self.httpClient = httpx.AsyncClient(
timeout=120.0, # Longer timeout for complex requests
headers={
"x-api-key": self.api_key,
"x-api-key": self.apiKey,
"anthropic-version": "2023-06-01", # Anthropic API Version
"Content-Type": "application/json"
}
)
logger.info(f"Anthropic Connector initialized with model: {self.model_name}")
logger.info(f"Anthropic Connector initialized with model: {self.modelName}")
async def call_api(self, messages: List[Dict[str, Any]], temperature: float = None, max_tokens: int = None) -> Dict[str, Any]:
async def callApi(self, messages: List[Dict[str, Any]], temperature: float = None, maxTokens: int = None) -> Dict[str, Any]:
"""
Calls the Anthropic API with the given messages.
Args:
messages: List of messages in OpenAI format (role, content)
temperature: Temperature for response generation (0.0-1.0)
max_tokens: Maximum number of tokens in the response
maxTokens: Maximum number of tokens in the response
Returns:
The response converted to OpenAI format
@ -58,25 +56,25 @@ class ChatService:
"""
try:
# Convert OpenAI format to Anthropic format
formatted_messages = self._convert_to_anthropic_format(messages)
formattedMessages = self._convertToAnthropicFormat(messages)
# Use parameters from configuration if none were overridden
if temperature is None:
temperature = self.config.get("temperature", 0.2)
if max_tokens is None:
max_tokens = self.config.get("max_tokens", 2000)
if maxTokens is None:
maxTokens = self.config.get("maxTokens", 2000)
# Create Anthropic API payload
payload = {
"model": self.model_name,
"messages": formatted_messages,
"model": self.modelName,
"messages": formattedMessages,
"temperature": temperature,
"max_tokens": max_tokens
"max_tokens": maxTokens
}
response = await self.http_client.post(
self.api_url,
response = await self.httpClient.post(
self.apiUrl,
json=payload
)
@ -85,16 +83,16 @@ class ChatService:
raise HTTPException(status_code=500, detail="Error communicating with Anthropic API")
# Convert response from Anthropic format to OpenAI format
anthropic_response = response.json()
openai_formatted_response = self._convert_to_openai_format(anthropic_response)
anthropicResponse = response.json()
openaiFormattedResponse = self._convertToOpenaiFormat(anthropicResponse)
return openai_formatted_response
return openaiFormattedResponse
except Exception as e:
logger.error(f"Error calling Anthropic API: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error calling Anthropic API: {str(e)}")
def _convert_to_anthropic_format(self, openai_messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
def _convertToAnthropicFormat(self, openaiMessages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Converts messages from OpenAI format to Anthropic format.
@ -110,16 +108,16 @@ class ChatService:
Note: Anthropic has no direct system message equivalent,
so we add system messages to the first user message.
"""
anthropic_messages = []
system_content = ""
anthropicMessages = []
systemContent = ""
# First extract all system messages
for msg in openai_messages:
for msg in openaiMessages:
if msg.get("role") == "system":
system_content += msg.get("content", "") + "\n\n"
systemContent += msg.get("content", "") + "\n\n"
# Convert the remaining messages
for i, msg in enumerate(openai_messages):
for msg in openaiMessages:
role = msg.get("role")
content = msg.get("content", "")
@ -128,49 +126,49 @@ class ChatService:
continue
# For the first user message: prepend system content if available
if role == "user" and system_content and not any(m.get("role") == "user" for m in anthropic_messages):
if role == "user" and systemContent and not any(m.get("role") == "user" for m in anthropicMessages):
if isinstance(content, str):
content = system_content + content
content = systemContent + content
elif isinstance(content, list):
# If content is an array (for multimodal messages)
text_parts = []
textParts = []
for part in content:
if part.get("type") == "text":
text_parts.append(part)
textParts.append(part)
if text_parts:
text_parts[0]["text"] = system_content + text_parts[0].get("text", "")
if textParts:
textParts[0]["text"] = systemContent + textParts[0].get("text", "")
# Anthropic only supports "user" and "assistant" roles
if role not in ["user", "assistant"]:
role = "user"
anthropic_messages.append({"role": role, "content": content})
anthropicMessages.append({"role": role, "content": content})
return anthropic_messages
return anthropicMessages
def _convert_to_openai_format(self, anthropic_response: Dict[str, Any]) -> Dict[str, Any]:
def _convertToOpenaiFormat(self, anthropicResponse: Dict[str, Any]) -> Dict[str, Any]:
"""
Converts a response from Anthropic format to OpenAI format.
"""
# Extract content from Anthropic response
content = ""
if "content" in anthropic_response:
if isinstance(anthropic_response["content"], list):
if "content" in anthropicResponse:
if isinstance(anthropicResponse["content"], list):
# Content is a list of parts (in newer API versions)
for part in anthropic_response["content"]:
for part in anthropicResponse["content"]:
if part.get("type") == "text":
content += part.get("text", "")
else:
# Direct content as string (in older API versions)
content = anthropic_response["content"]
content = anthropicResponse["content"]
# Create OpenAI-formatted response
return {
"id": anthropic_response.get("id", ""),
"id": anthropicResponse.get("id", ""),
"object": "chat.completion",
"created": anthropic_response.get("created", 0),
"model": anthropic_response.get("model", self.model_name),
"created": anthropicResponse.get("created", 0),
"model": anthropicResponse.get("model", self.modelName),
"choices": [
{
"message": {
@ -183,33 +181,33 @@ class ChatService:
]
}
async def analyze_image(self, image_data: Union[str, bytes], mime_type: str = None, prompt: str = "Describe this image") -> str:
async def analyzeImage(self, imageData: Union[str, bytes], mimeType: str = None, prompt: str = "Describe this image") -> str:
"""
Analyzes an image with the OpenAI Vision API.
Analyzes an image using Anthropic's vision capabilities.
Args:
image_data: Either a file path (str) or image data (bytes)
mime_type: The MIME type of the image (optional, only for binary data)
imageData: Either a file path (str) or image data (bytes)
mimeType: The MIME type of the image (optional, only for binary data)
prompt: The prompt for analysis
Returns:
The response from the OpenAI Vision API as text
The analysis response as text
"""
try:
# Distinguish between file path and binary data
if isinstance(image_data, str):
if isinstance(imageData, str):
# It's a file path - import filehandling only when needed
from modules import agentservice_filemanager as file_handler
base64_data, auto_mime_type = file_handler.encode_file_to_base64(image_data)
mime_type = mime_type or auto_mime_type
from modules import agentserviceFilemanager as fileHandler
base64Data, autoMimeType = fileHandler.encodeFileToBase64(imageData)
mimeType = mimeType or autoMimeType
else:
# It's binary data
import base64
base64_data = base64.b64encode(image_data).decode('utf-8')
base64Data = base64.b64encode(imageData).decode('utf-8')
# MIME type must be specified for binary data
if not mime_type:
if not mimeType:
# Fallback to generic image type
mime_type = "image/png"
mimeType = "image/png"
# Prepare the payload for the Vision API
messages = [
@ -220,15 +218,15 @@ class ChatService:
{
"type": "image_url",
"image_url": {
"url": f"data:{mime_type};base64,{base64_data}"
"url": f"data:{mimeType};base64,{base64Data}"
}
}
]
}
]
# Use the existing call_api function with the Vision model
response = await self.call_api(messages)
# Use the existing callApi function with the Vision model
response = await self.callApi(messages)
# Extract and return content
return response["choices"][0]["message"]["content"]

View file

@ -1,52 +1,50 @@
import logging
import httpx
from typing import Dict, Any, List, Optional, Union
from typing import Dict, Any, List, Union
from fastapi import HTTPException
from modules.configuration import APP_CONFIG
# Configure logger
logger = logging.getLogger(__name__)
# Load configuration data
def load_config_data():
def loadConfigData():
"""Load configuration data for OpenAI connector"""
return {
"api_key": APP_CONFIG.get('Connector_AiOpenai_API_SECRET'),
"api_url": APP_CONFIG.get('Connector_AiOpenai_API_URL'),
"model_name": APP_CONFIG.get('Connector_AiOpenai_MODEL_NAME'),
"apiKey": APP_CONFIG.get('Connector_AiOpenai_API_SECRET'),
"apiUrl": APP_CONFIG.get('Connector_AiOpenai_API_URL'),
"modelName": APP_CONFIG.get('Connector_AiOpenai_MODEL_NAME'),
"temperature": float(APP_CONFIG.get('Connector_AiOpenai_TEMPERATURE')),
"max_tokens": int(APP_CONFIG.get('Connector_AiOpenai_MAX_TOKENS'))
"maxTokens": int(APP_CONFIG.get('Connector_AiOpenai_MAX_TOKENS'))
}
class ChatService:
"""
Connector for communication with the OpenAI API.
"""
"""Connector for communication with the OpenAI API."""
def __init__(self):
# Load configuration
self.config = load_config_data()
self.api_key = self.config["api_key"]
self.api_url = self.config["api_url"]
self.model_name = self.config["model_name"]
self.config = loadConfigData()
self.apiKey = self.config["apiKey"]
self.apiUrl = self.config["apiUrl"]
self.modelName = self.config["modelName"]
# HttpClient for API calls
self.http_client = httpx.AsyncClient(
self.httpClient = httpx.AsyncClient(
timeout=120.0, # Longer timeout for complex requests
headers={
"Authorization": f"Bearer {self.api_key}",
"Authorization": f"Bearer {self.apiKey}",
"Content-Type": "application/json"
}
)
logger.info(f"OpenAI Connector initialized with model: {self.model_name}")
logger.info(f"OpenAI Connector initialized with model: {self.modelName}")
async def call_api(self, messages: List[Dict[str, Any]], temperature: float = None, max_tokens: int = None) -> str:
async def callApi(self, messages: List[Dict[str, Any]], temperature: float = None, maxTokens: int = None) -> str:
"""
Calls the OpenAI API with the given messages.
Args:
messages: List of messages in OpenAI format (role, content)
temperature: Temperature for response generation (0.0-1.0)
max_tokens: Maximum number of tokens in the response
maxTokens: Maximum number of tokens in the response
Returns:
The response from the OpenAI API
@ -59,18 +57,18 @@ class ChatService:
if temperature is None:
temperature = self.config.get("temperature", 0.2)
if max_tokens is None:
max_tokens = self.config.get("max_tokens", 2000)
if maxTokens is None:
maxTokens = self.config.get("maxTokens", 2000)
payload = {
"model": self.model_name,
"model": self.modelName,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens
"max_tokens": maxTokens
}
response = await self.http_client.post(
self.api_url,
response = await self.httpClient.post(
self.apiUrl,
json=payload
)
@ -78,8 +76,8 @@ class ChatService:
logger.error(f"OpenAI API error: {response.status_code} - {response.text}")
raise HTTPException(status_code=500, detail="Error communicating with OpenAI API")
response_json = response.json()
content = response_json["choices"][0]["message"]["content"]
responseJson = response.json()
content = responseJson["choices"][0]["message"]["content"]
return content
except Exception as e:
@ -88,15 +86,15 @@ class ChatService:
async def close(self):
"""Closes the HTTP client when the application exits"""
await self.http_client.aclose()
await self.httpClient.aclose()
async def analyze_image(self, image_data: Union[str, bytes], mime_type: str = None, prompt: str = "Describe this image") -> str:
async def analyzeImage(self, imageData: Union[str, bytes], mimeType: str = None, prompt: str = "Describe this image") -> str:
"""
Analyzes an image with the OpenAI Vision API.
Args:
image_data: Either a file path (str) or image data (bytes)
mime_type: The MIME type of the image (optional, only for binary data)
imageData: Either a file path (str) or image data (bytes)
mimeType: The MIME type of the image (optional, only for binary data)
prompt: The prompt for analysis
Returns:
@ -105,19 +103,19 @@ class ChatService:
try:
logger.debug("Starting image analysis...")
# Distinguish between file path and binary data
if isinstance(image_data, str):
if isinstance(imageData, str):
# It's a file path - import filehandling only when needed
from modules import agentservice_filemanager as file_handler
base64_data, auto_mime_type = file_handler.encode_file_to_base64(image_data)
mime_type = mime_type or auto_mime_type
from modules import agentserviceFilemanager as fileHandler
base64Data, autoMimeType = fileHandler.encodeFileToBase64(imageData)
mimeType = mimeType or autoMimeType
else:
# It's binary data
import base64
base64_data = base64.b64encode(image_data).decode('utf-8')
base64Data = base64.b64encode(imageData).decode('utf-8')
# MIME type must be specified for binary data
if not mime_type:
if not mimeType:
# Fallback to generic image type
mime_type = "image/png"
mimeType = "image/png"
# Prepare the payload for the Vision API
messages = [
@ -128,17 +126,17 @@ class ChatService:
{
"type": "image_url",
"image_url": {
"url": f"data:{mime_type};base64,{base64_data}"
"url": f"data:{mimeType};base64,{base64Data}"
}
}
]
}
]
# Use the existing call_api function with the Vision model
response = await self.call_api(messages)
# Use the existing callApi function with the Vision model
response = await self.callApi(messages)
# Extract and return content
# Return content
return response
except Exception as e:

View file

@ -0,0 +1,561 @@
import json
import os
from typing import List, Dict, Any, Optional, Union
import logging
logger = logging.getLogger(__name__)
class DatabaseConnector:
"""
A connector for JSON-based data storage.
Provides generic database operations with tenant and user context support.
"""
def __init__(self, dbHost: str, dbDatabase: str, dbUser: str = None, dbPassword: str = None,
mandateId: int = None, userId: int = None, skipInitialIdLookup: bool = False):
"""
Initializes the JSON database connector.
Args:
dbHost: Directory for the JSON files
dbDatabase: Database name
dbUser: Username for authentication (optional)
dbPassword: API key for authentication (optional)
mandateId: Context parameter for the tenant
userId: Context parameter for the user
skipInitialIdLookup: When True, skips looking up initial IDs for mandateId and userId
"""
# Store the input parameters
self.dbHost = dbHost
self.dbDatabase = dbDatabase
self.dbUser = dbUser
self.dbPassword = dbPassword
self.skipInitialIdLookup = skipInitialIdLookup
# Check if context parameters are set
if mandateId is None or userId is None:
raise ValueError("mandateId and userId must be set")
# Ensure the database directory exists
self.dbFolder = os.path.join(self.dbHost, self.dbDatabase)
os.makedirs(self.dbFolder, exist_ok=True)
# Cache for loaded data
self._tablesCache = {}
# Initialize system table
self._systemTableName = "_system"
self._initializeSystemTable()
# Temporarily store mandateId and userId
self._mandateId = mandateId
self._userId = userId
# If mandateId or userId are 0 and we're not skipping ID lookup, try to use the initial IDs
if not skipInitialIdLookup:
if mandateId == 0:
initialMandateId = self.getInitialId("mandates")
if initialMandateId is not None:
self._mandateId = initialMandateId
logger.info(f"Using initial mandateId: {initialMandateId} instead of 0")
if userId == 0:
initialUserId = self.getInitialId("users")
if initialUserId is not None:
self._userId = initialUserId
logger.info(f"Using initial userId: {initialUserId} instead of 0")
# Set the effective IDs as properties
self.mandateId = self._mandateId
self.userId = self._userId
logger.info(f"DatabaseConnector initialized for directory: {self.dbFolder}")
logger.debug(f"Context: mandateId={self.mandateId}, userId={self.userId}")
def _initializeSystemTable(self):
"""Initializes the system table if it doesn't exist yet."""
systemTablePath = self._getTablePath(self._systemTableName)
if not os.path.exists(systemTablePath):
emptySystemTable = {}
self._saveSystemTable(emptySystemTable)
logger.info(f"System table initialized in {systemTablePath}")
else:
# Load existing system table to ensure it's available
self._loadSystemTable()
logger.debug(f"Existing system table loaded from {systemTablePath}")
def _loadSystemTable(self) -> Dict[str, int]:
"""Loads the system table with the initial IDs."""
# Check if system table is in cache
if f"_{self._systemTableName}" in self._tablesCache:
return self._tablesCache[f"_{self._systemTableName}"]
systemTablePath = self._getTablePath(self._systemTableName)
try:
if os.path.exists(systemTablePath):
with open(systemTablePath, 'r', encoding='utf-8') as f:
data = json.load(f)
# Store in cache with special prefix to avoid collision with regular tables
self._tablesCache[f"_{self._systemTableName}"] = data
return data
else:
self._tablesCache[f"_{self._systemTableName}"] = {}
return {}
except Exception as e:
logger.error(f"Error loading the system table: {e}")
self._tablesCache[f"_{self._systemTableName}"] = {}
return {}
def _saveSystemTable(self, data: Dict[str, int]) -> bool:
"""Saves the system table with the initial IDs."""
systemTablePath = self._getTablePath(self._systemTableName)
try:
with open(systemTablePath, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Update cache
self._tablesCache[f"_{self._systemTableName}"] = data
return True
except Exception as e:
logger.error(f"Error saving the system table: {e}")
return False
def _getTablePath(self, table: str) -> str:
"""Returns the full path to a table file"""
return os.path.join(self.dbFolder, f"{table}.json")
def _loadTable(self, table: str) -> List[Dict[str, Any]]:
"""Loads a table from the corresponding JSON file"""
path = self._getTablePath(table)
# If the table is the system table, load it directly
if table == self._systemTableName:
return [] # The system table is not treated like normal tables
# If the table is already in the cache, use the cache
if table in self._tablesCache:
return self._tablesCache[table]
# Otherwise load the file
try:
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
self._tablesCache[table] = data
# If data was loaded and no initial ID is registered yet,
# register the ID of the first record (if available)
if data and not self.hasInitialId(table):
if "id" in data[0]:
self._registerInitialId(table, data[0]["id"])
logger.info(f"Initial ID {data[0]['id']} for table {table} retroactively registered")
return data
else:
# If the file doesn't exist, create an empty table
logger.info(f"New table {table}")
self._tablesCache[table] = []
self._saveTable(table, [])
return []
except Exception as e:
logger.error(f"Error loading table {table}: {e}")
return []
def _saveTable(self, table: str, data: List[Dict[str, Any]]) -> bool:
"""Saves a table to the corresponding JSON file"""
# The system table is handled specially
if table == self._systemTableName:
return False
path = self._getTablePath(table)
try:
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Update the cache
self._tablesCache[table] = data
return True
except Exception as e:
logger.error(f"Error saving table {table}: {e}")
return False
def _filterByContext(self, records: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Filters records by tenant and user context,
if these fields exist in the record.
"""
filteredRecords = []
for record in records:
# Check if mandateId exists in the record and is not null
hasMandate = "mandateId" in record and record["mandateId"] is not None and record["mandateId"] != ""
# Check if userId exists in the record and is not null
hasUser = "userId" in record and record["userId"] is not None and record["userId"] != ""
# If both exist, filter accordingly
if hasMandate and hasUser:
if record["mandateId"] == self.mandateId:
filteredRecords.append(record)
# If only mandateId exists
elif hasMandate and not hasUser:
if record["mandateId"] == self.mandateId:
filteredRecords.append(record)
# If neither mandateId nor userId exist, add the record
elif not hasMandate and not hasUser:
filteredRecords.append(record)
return filteredRecords
def _applyRecordFilter(self, records: List[Dict[str, Any]], recordFilter: Dict[str, Any] = None) -> List[Dict[str, Any]]:
"""Applies a record filter to the records"""
if not recordFilter:
return records
filteredRecords = []
for record in records:
match = True
for field, value in recordFilter.items():
# Check if the field exists
if field not in record:
match = False
break
# If the filter value is an integer string and the record field is an integer
if isinstance(value, str) and value.isdigit() and isinstance(record[field], int):
if record[field] != int(value):
match = False
break
# Otherwise direct comparison
elif record[field] != value:
match = False
break
if match:
filteredRecords.append(record)
return filteredRecords
def _registerInitialId(self, table: str, initialId: int) -> bool:
"""
Registers the initial ID for a table.
Args:
table: Name of the table
initialId: The initial ID
Returns:
True on success, False on error
"""
try:
# Load the current system table
systemData = self._loadSystemTable()
# Only register if not already present
if table not in systemData:
systemData[table] = initialId
success = self._saveSystemTable(systemData)
if success:
logger.info(f"Initial ID {initialId} for table {table} registered")
return success
return True # If already present, this is not an error
except Exception as e:
logger.error(f"Error registering the initial ID for table {table}: {e}")
return False
def _removeInitialId(self, table: str) -> bool:
"""
Removes the initial ID for a table from the system table.
Args:
table: Name of the table
Returns:
True on success, False on error
"""
try:
# Load the current system table
systemData = self._loadSystemTable()
# Remove the entry if it exists
if table in systemData:
del systemData[table]
success = self._saveSystemTable(systemData)
if success:
logger.info(f"Initial ID for table {table} removed from system table")
return success
return True # If not present, this is not an error
except Exception as e:
logger.error(f"Error removing initial ID for table {table}: {e}")
return False
# Public API
def getTables(self) -> List[str]:
"""
Returns a list of all available tables.
Returns:
List of table names
"""
tables = []
try:
for filename in os.listdir(self.dbFolder):
if filename.endswith('.json') and not filename.startswith('_'):
tableName = filename[:-5] # Remove the .json extension
tables.append(tableName)
except Exception as e:
logger.error(f"Error reading the database directory: {e}")
return tables
def getFields(self, table: str) -> List[str]:
"""
Returns a list of all fields in a table.
Args:
table: Name of the table
Returns:
List of field names
"""
# Load the table data
data = self._loadTable(table)
if not data:
return []
# Take the first record as a reference for the fields
fields = list(data[0].keys()) if data else []
return fields
def getSchema(self, table: str, language: str = None) -> Dict[str, Dict[str, Any]]:
"""
Returns a schema object for a table with data types and labels.
Args:
table: Name of the table
language: Language for the labels (optional)
Returns:
Schema object with fields, data types and labels
"""
# Load the table data
data = self._loadTable(table)
schema = {}
if not data:
return schema
# Take the first record as a reference for the fields and data types
firstRecord = data[0]
for field, value in firstRecord.items():
# Determine the data type
dataType = type(value).__name__
# Create label (default is the field name)
label = field
schema[field] = {
"type": dataType,
"label": label
}
return schema
def getRecordset(self, table: str, fieldFilter: List[str] = None, recordFilter: Dict[str, Any] = None) -> List[Dict[str, Any]]:
"""
Returns a list of records from a table, filtered by criteria.
Args:
table: Name of the table
fieldFilter: Filter for fields (which fields should be returned)
recordFilter: Filter for records (which records should be returned)
Returns:
List of filtered records
"""
# Load the table data
data = self._loadTable(table)
# Filter by tenant and user context
filteredData = self._filterByContext(data)
# Apply recordFilter if available
if recordFilter:
filteredData = self._applyRecordFilter(filteredData, recordFilter)
# If fieldFilter is available, reduce the fields
if fieldFilter and isinstance(fieldFilter, list):
result = []
for record in filteredData:
filteredRecord = {}
for field in fieldFilter:
if field in record:
filteredRecord[field] = record[field]
result.append(filteredRecord)
return result
return filteredData
def recordCreate(self, table: str, recordData: Dict[str, Any]) -> Dict[str, Any]:
"""
Creates a new record in the table.
Args:
table: Name of the table
recordData: Data for the new record
Returns:
The created record
"""
# Load the table data
data = self._loadTable(table)
# Add mandateId and userId if not present or 0
if "mandateId" not in recordData or recordData["mandateId"] == 0:
recordData["mandateId"] = self.mandateId
if "userId" not in recordData or recordData["userId"] == 0:
recordData["userId"] = self.userId
# Determine the next ID if not present
if "id" not in recordData:
nextId = 1
if data:
nextId = max(record["id"] for record in data if "id" in record) + 1
recordData["id"] = nextId
# If the table is empty and a system ID should be registered
if not data:
self._registerInitialId(table, recordData["id"])
logger.info(f"Initial ID {recordData['id']} for table {table} has been registered")
# Add the new record
data.append(recordData)
# Save the updated table
if self._saveTable(table, data):
return recordData
else:
raise ValueError(f"Error creating the record in table {table}")
def recordDelete(self, table: str, recordId: Union[str, int]) -> bool:
"""
Deletes a record from the table.
Args:
table: Name of the table
recordId: ID of the record to delete
Returns:
True on success, False on error
"""
# Load table data
data = self._loadTable(table)
# Search for the record
for i, record in enumerate(data):
if "id" in record and record["id"] == recordId:
# Check if the record belongs to the current mandate
if "mandateId" in record and record["mandateId"] != self.mandateId:
raise ValueError("Not your mandate")
# Check if it's an initial record
initialId = self.getInitialId(table)
if initialId is not None and initialId == recordId:
# Remove this entry from the system table
self._removeInitialId(table)
logger.info(f"Initial ID {recordId} for table {table} has been removed from the system table")
# Delete the record
del data[i]
# Save the updated table
return self._saveTable(table, data)
# Record not found
return False
def recordModify(self, table: str, recordId: Union[str, int], recordData: Dict[str, Any]) -> Dict[str, Any]:
"""
Modifies a record in the table.
Args:
table: Name of the table
recordId: ID of the record to modify
recordData: New data for the record
Returns:
The updated record
"""
# Load table data
data = self._loadTable(table)
# Search for the record
for i, record in enumerate(data):
if "id" in record and record["id"] == recordId:
# Check if the record belongs to the current mandate
if "mandateId" in record and record["mandateId"] != self.mandateId:
raise ValueError("Not your mandate")
# Prevent changing the ID
if "id" in recordData and recordData["id"] != recordId:
raise ValueError(f"The ID of a record in table {table} cannot be changed")
# Update the record
for key, value in recordData.items():
data[i][key] = value
# Save the updated table
if self._saveTable(table, data):
return data[i]
else:
raise ValueError(f"Error updating record in table {table}")
# Record not found
raise ValueError(f"Record with ID {recordId} not found in table {table}")
def hasInitialId(self, table: str) -> bool:
"""
Checks if an initial ID is registered for a table.
Args:
table: Name of the table
Returns:
True if an initial ID is registered, otherwise False
"""
systemData = self._loadSystemTable()
return table in systemData
def getInitialId(self, table: str) -> Optional[int]:
"""
Returns the initial ID for a table.
Args:
table: Name of the table
Returns:
The initial ID or None if not present
"""
systemData = self._loadSystemTable()
initialId = systemData.get(table)
logger.debug(f"Database '{self.dbDatabase}': Initial ID for table '{table}' is {initialId}")
if initialId is None:
logger.debug(f"No initial ID found for table {table}")
return initialId
def getAllInitialIds(self) -> Dict[str, int]:
"""
Returns all registered initial IDs.
Returns:
Dictionary with table names as keys and initial IDs as values
"""
systemData = self._loadSystemTable()
return systemData.copy() # Return a copy to protect the original

View file

@ -1,557 +0,0 @@
import json
import os
from typing import List, Dict, Any, Optional, Union
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
class DatabaseConnector:
"""
A connector for JSON-based data storage.
Provides generic database operations.
"""
def __init__(self, db_host: str, db_database: str, db_user: str = None, db_password: str = None, mandate_id: int = None, user_id: int = None):
"""
Initializes the JSON database connector.
Args:
db_host: Directory for the JSON files
db_database = Database name
db_user: Username for authentication (optional)
db_password: API key for authentication (optional)
mandate_id: Context parameter for the tenant
user_id: Context parameter for the user
"""
# Store the input parameters
self.db_host = db_host
self.db_database = db_database
self.db_user = db_user
self.db_password = db_password
# Check if context parameters are set
if mandate_id is None or user_id is None:
raise ValueError("mandate_id and user_id must be set")
# Ensure the database directory exists
self.db_folder=os.path.join(self.db_host,self.db_database)
os.makedirs(self.db_folder, exist_ok=True)
# Cache for loaded data
self._tables_cache = {}
# Initialize system table
self._system_table_name = "_system"
self._initialize_system_table()
# Temporarily store mandate_id and user_id
self._mandate_id = mandate_id
self._user_id = user_id
# If mandate_id or user_id are 0, try to use the initial IDs
if mandate_id == 0:
initial_mandate_id = self.get_initial_id("mandates")
if initial_mandate_id is not None:
self._mandate_id = initial_mandate_id
logger.info(f"Using initial mandate_id: {initial_mandate_id} instead of 0")
if user_id == 0:
initial_user_id = self.get_initial_id("users")
if initial_user_id is not None:
self._user_id = initial_user_id
logger.info(f"Using initial user_id: {initial_user_id} instead of 0")
# Set the effective IDs as properties
self.mandate_id = self._mandate_id
self.user_id = self._user_id
logger.info(f"DatabaseConnector initialized for directory: {self.db_folder}")
logger.debug(f"Context: mandate_id={self.mandate_id}, user_id={self.user_id}")
def _initialize_system_table(self):
"""Initializes the system table if it doesn't exist yet."""
system_table_path = self._get_table_path(self._system_table_name)
if not os.path.exists(system_table_path):
empty_system_table = {}
self._save_system_table(empty_system_table)
logger.info(f"System table initialized in {system_table_path}")
def _load_system_table(self) -> Dict[str, int]:
"""Loads the system table with the initial IDs."""
system_table_path = self._get_table_path(self._system_table_name)
try:
if os.path.exists(system_table_path):
with open(system_table_path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
return {}
except Exception as e:
logger.error(f"Error loading the system table: {e}")
return {}
def _save_system_table(self, data: Dict[str, int]) -> bool:
"""Saves the system table with the initial IDs."""
system_table_path = self._get_table_path(self._system_table_name)
try:
with open(system_table_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
logger.error(f"Error saving the system table: {e}")
return False
def _get_table_path(self, table: str) -> str:
"""Returns the full path to a table file"""
return os.path.join(self.db_folder, f"{table}.json")
def _load_table(self, table: str) -> List[Dict[str, Any]]:
"""Loads a table from the corresponding JSON file"""
path = self._get_table_path(table)
# If the table is the system table, load it directly
if table == self._system_table_name:
return [] # The system table is not treated like normal tables
# If the table is already in the cache, use the cache
if table in self._tables_cache:
# logger.info(f"Loading table {table} from cache")
return self._tables_cache[table]
# Otherwise load the file
try:
if os.path.exists(path):
# logger.info(f"Loading table {table} from JSON {path}")
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
self._tables_cache[table] = data
# If data was loaded and no initial ID is registered yet,
# register the ID of the first record (if available)
if data and not self.has_initial_id(table):
if "id" in data[0]:
self._register_initial_id(table, data[0]["id"])
logger.info(f"Initial ID {data[0]['id']} for table {table} retroactively registered")
return data
else:
# If the file doesn't exist, create an empty table
logger.info(f"New table {table}")
self._tables_cache[table] = []
self._save_table(table, [])
return []
except Exception as e:
logger.error(f"Error loading table {table}: {e}")
return []
def _save_table(self, table: str, data: List[Dict[str, Any]]) -> bool:
"""Saves a table to the corresponding JSON file"""
# The system table is handled specially
if table == self._system_table_name:
return False
path = self._get_table_path(table)
try:
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Update the cache
self._tables_cache[table] = data
return True
except Exception as e:
logger.error(f"Error saving table {table}: {e}")
return False
def _filter_by_context(self, records: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Filters records by tenant and user context,
if these fields exist in the record.
"""
filtered_records = []
for record in records:
# Check if mandate_id exists in the record and is not null
has_mandate = "mandate_id" in record and record["mandate_id"] is not None and record["mandate_id"] != ""
# Check if user_id exists in the record and is not null
has_user = "user_id" in record and record["user_id"] is not None and record["user_id"] != ""
# If both exist, filter accordingly
if has_mandate and has_user:
if record["mandate_id"] == self.mandate_id:
filtered_records.append(record)
# If only mandate_id exists
elif has_mandate and not has_user:
if record["mandate_id"] == self.mandate_id:
filtered_records.append(record)
# If neither mandate_id nor user_id exist, add the record
elif not has_mandate and not has_user:
filtered_records.append(record)
return filtered_records
def _apply_record_filter(self, records: List[Dict[str, Any]], record_filter: Dict[str, Any] = None) -> List[Dict[str, Any]]:
"""Applies a record filter to the records"""
if not record_filter:
return records
filtered_records = []
for record in records:
match = True
for field, value in record_filter.items():
# Check if the field exists
if field not in record:
match = False
break
# If the filter value is an integer string and the record field is an integer
if isinstance(value, str) and value.isdigit() and isinstance(record[field], int):
if record[field] != int(value):
match = False
break
# Otherwise direct comparison
elif record[field] != value:
match = False
break
if match:
filtered_records.append(record)
return filtered_records
def _register_initial_id(self, table: str, initial_id: int) -> bool:
"""
Registers the initial ID for a table.
Args:
table: Name of the table
initial_id: The initial ID
Returns:
True on success, False on error
"""
try:
# Load the current system table
system_data = self._load_system_table()
# Only register if not already present
if table not in system_data:
system_data[table] = initial_id
success = self._save_system_table(system_data)
if success:
logger.info(f"Initial ID {initial_id} for table {table} registered")
return success
return True # If already present, this is not an error
except Exception as e:
logger.error(f"Error registering the initial ID for table {table}: {e}")
return False
def _remove_initial_id(self, table: str) -> bool:
"""
Removes the initial ID for a table from the system table.
Args:
table: Name of the table
Returns:
True on success, False on error
"""
try:
# Load the current system table
system_data = self._load_system_table()
# Remove the entry if it exists
if table in system_data:
del system_data[table]
success = self._save_system_table(system_data)
if success:
logger.info(f"Initial ID for table {table} removed from system table")
return success
return True # If not present, this is not an error
except Exception as e:
logger.error(f"Error removing initial ID for table {table}: {e}")
return False
# Public API
def get_tables(self, filter_criteria: Dict[str, Any] = None) -> List[str]:
"""
Returns a list of all available tables.
Args:
filter_criteria: Optional filter criteria (not implemented)
Returns:
List of table names
"""
tables = []
try:
for filename in os.listdir(self.db_folder):
if filename.endswith('.json') and not filename.startswith('_'):
table_name = filename[:-5] # Remove the .json extension
tables.append(table_name)
except Exception as e:
logger.error(f"Error reading the database directory: {e}")
return tables
def get_fields(self, table: str, filter_criteria: Dict[str, Any] = None) -> List[str]:
"""
Returns a list of all fields in a table.
Args:
table: Name of the table
filter_criteria: Optional filter criteria (not implemented)
Returns:
List of field names
"""
# Load the table data
data = self._load_table(table)
if not data:
return []
# Take the first record as a reference for the fields
fields = list(data[0].keys()) if data else []
return fields
def get_schema(self, table: str, language: str = None, filter_criteria: Dict[str, Any] = None) -> Dict[str, Dict[str, Any]]:
"""
Returns a schema object for a table with data types and labels.
Args:
table: Name of the table
language: Language for the labels (optional)
filter_criteria: Optional filter criteria (not implemented)
Returns:
Schema object with fields, data types and labels
"""
# Load the table data
data = self._load_table(table)
schema = {}
if not data:
return schema
# Take the first record as a reference for the fields and data types
first_record = data[0]
for field, value in first_record.items():
# Determine the data type
data_type = type(value).__name__
# Create label (default is the field name)
label = field
# If model_info is available, try to get the label from the model
# Implementation depends on the actual model
schema[field] = {
"type": data_type,
"label": label
}
return schema
def get_recordset(self, table: str, field_filter: Dict[str, Any] = None, record_filter: Dict[str, Any] = None) -> List[Dict[str, Any]]:
"""
Returns a list of records from a table, filtered by criteria.
Args:
table: Name of the table
field_filter: Filter for fields (which fields should be returned)
record_filter: Filter for records (which records should be returned)
Returns:
List of filtered records
"""
# Load the table data
data = self._load_table(table)
# Filter by tenant and user context
filtered_data = self._filter_by_context(data)
# Apply record_filter if available
if record_filter:
filtered_data = self._apply_record_filter(filtered_data, record_filter)
# If field_filter is available, reduce the fields
if field_filter and isinstance(field_filter, list):
result = []
for record in filtered_data:
filtered_record = {}
for field in field_filter:
if field in record:
filtered_record[field] = record[field]
result.append(filtered_record)
return result
return filtered_data
def record_create(self, table: str, record_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Creates a new record in the table.
Args:
table: Name of the table
record_data: Data for the new record
Returns:
The created record
"""
# Load the table data
data = self._load_table(table)
# Add mandate_id and user_id if not present or 0
if "mandate_id" not in record_data or record_data["mandate_id"] == 0:
record_data["mandate_id"] = self.mandate_id
if "user_id" not in record_data or record_data["user_id"] == 0:
record_data["user_id"] = self.user_id
# Determine the next ID if not present
if "id" not in record_data:
next_id = 1
if data:
next_id = max(record["id"] for record in data if "id" in record) + 1
record_data["id"] = next_id
# If the table is empty and a system ID should be registered
if not data:
self._register_initial_id(table, record_data["id"])
logger.info(f"Initial ID {record_data['id']} for table {table} has been registered")
# Add the new record
data.append(record_data)
# Save the updated table
if self._save_table(table, data):
return record_data
else:
raise ValueError(f"Error creating the record in table {table}")
def record_delete(self, table: str, record_id: Union[str, int]) -> bool:
"""
Deletes a record from the table.
Args:
table: Name of the table
record_id: ID of the record to delete
Returns:
True on success, False on error
"""
# Load table data
data = self._load_table(table)
# Search for the record
for i, record in enumerate(data):
if "id" in record and record["id"] == record_id:
# Check if the record belongs to the current mandate
if "mandate_id" in record and record["mandate_id"] != self.mandate_id:
raise ValueError("Not your mandate")
# Check if it's an initial record
initial_id = self.get_initial_id(table)
if initial_id is not None and initial_id == record_id:
# Remove this entry from the system table
self._remove_initial_id(table)
logger.info(f"Initial ID {record_id} for table {table} has been removed from the system table")
# Delete the record
del data[i]
# Save the updated table
return self._save_table(table, data)
# Record not found
return False
def record_modify(self, table: str, record_id: Union[str, int], record_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Modifies a record in the table.
Args:
table: Name of the table
record_id: ID of the record to modify
record_data: New data for the record
Returns:
The updated record
"""
# Load table data
data = self._load_table(table)
# Search for the record
for i, record in enumerate(data):
if "id" in record and record["id"] == record_id:
# Check if the record belongs to the current mandate
if "mandate_id" in record and record["mandate_id"] != self.mandate_id:
raise ValueError("Not your mandate")
# Prevent changing the ID
if "id" in record_data and record_data["id"] != record_id:
raise ValueError(f"The ID of a record in table {table} cannot be changed")
# Update the record
for key, value in record_data.items():
data[i][key] = value
# Save the updated table
if self._save_table(table, data):
return data[i]
else:
raise ValueError(f"Error updating record in table {table}")
# Record not found
raise ValueError(f"Record with ID {record_id} not found in table {table}")
def has_initial_id(self, table: str) -> bool:
"""
Checks if an initial ID is registered for a table.
Args:
table: Name of the table
Returns:
True if an initial ID is registered, otherwise False
"""
system_data = self._load_system_table()
return table in system_data
def get_initial_id(self, table: str) -> Optional[int]:
"""
Returns the initial ID for a table.
Args:
table: Name of the table
Returns:
The initial ID or None if not present
"""
system_data = self._load_system_table()
initial_id = system_data.get(table)
if initial_id is None:
logger.debug(f"No initial ID found for table {table}")
return initial_id
def get_all_initial_ids(self) -> Dict[str, int]:
"""
Returns all registered initial IDs.
Returns:
Dictionary with table names as keys and initial IDs as values
"""
system_data = self._load_system_table()
return system_data.copy() # Return a copy to protect the original

View file

@ -22,4 +22,4 @@ APP_JWT_SECRET_SECRET=dev_jwt_secret_token
APP_TOKEN_EXPIRY=300
# CORS Configuration
APP_ALLOWED_ORIGINS=["http://localhost:8080","http://localhost:3000"]
APP_ALLOWED_ORIGINS="http://localhost:8080","http://localhost:3000"

View file

@ -22,4 +22,4 @@ APP_JWT_SECRET_SECRET=dev_jwt_secret_token
APP_TOKEN_EXPIRY=300
# CORS Configuration
APP_ALLOWED_ORIGINS=["http://localhost:8080","http://localhost:3000"]
APP_ALLOWED_ORIGINS="http://localhost:8080","http://localhost:3000"

View file

@ -0,0 +1,261 @@
"""
Interface to the Gateway system.
Manages users and mandates for authentication.
"""
import os
import logging
from typing import Dict, Any, List, Optional, Union
import importlib
from passlib.context import CryptContext
from connectors.connectorDbJson import DatabaseConnector
from modules.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
# Password-Hashing
pwdContext = CryptContext(schemes=["argon2"], deprecated="auto")
class GatewayInterface:
"""
Interface to the Gateway system.
Manages users and mandates.
"""
def __init__(self, mandateId: int = None, userId: int = None):
"""
Initializes the Gateway Interface with optional mandate and user context.
Args:
mandateId: ID of the current mandate (optional)
userId: ID of the current user (optional)
"""
# Context can be empty during initialization
self.mandateId = mandateId
self.userId = userId
# Import data model module
try:
self.modelModule = importlib.import_module("modules.gatewayModel")
logger.info("gatewayModel successfully imported")
except ImportError as e:
logger.error(f"Error importing gatewayModel: {e}")
raise
# Initialize database
self._initializeDatabase()
def _initializeDatabase(self):
"""
Initializes the database with minimal objects
"""
self.db = DatabaseConnector(
dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"),
dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
dbUser=APP_CONFIG.get("DB_SYSTEM_USER"),
dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
mandateId=self.mandateId if self.mandateId else 0,
userId=self.userId if self.userId else 0
)
# Create Root mandate if needed
existingMandateId = self.getInitialId("mandates")
mandates = self.db.getRecordset("mandates")
if existingMandateId is None or not mandates:
logger.info("Creating Root mandate")
rootMandate = {
"name": "Root",
"language": "de"
}
createdMandate = self.db.recordCreate("mandates", rootMandate)
logger.info(f"Root mandate created with ID {createdMandate['id']}")
# Update mandate context
self.mandateId = createdMandate['id']
self.userId = createdMandate['userId']
# Recreate connector with correct context
self.db = DatabaseConnector(
dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"),
dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
dbUser=APP_CONFIG.get("DB_SYSTEM_USER"),
dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
mandateId=self.mandateId,
userId=self.userId
)
# Create Admin user if needed
existingUserId = self.getInitialId("users")
users = self.db.getRecordset("users")
if existingUserId is None or not users:
logger.info("Creating Admin user")
adminUser = {
"mandateId": self.mandateId,
"username": "admin",
"email": "admin@example.com",
"fullName": "Administrator",
"disabled": False,
"language": "de",
"privilege": "sysadmin", # SysAdmin privilege
"hashedPassword": self._getPasswordHash("admin") # Use a secure password in production!
}
createdUser = self.db.recordCreate("users", adminUser)
logger.info(f"Admin user created with ID {createdUser['id']}")
# Update user context
self.userId = createdUser['id']
# Recreate connector with correct context
self.db = DatabaseConnector(
dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"),
dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
dbUser=APP_CONFIG.get("DB_SYSTEM_USER"),
dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
mandateId=self.mandateId,
userId=self.userId
)
def getInitialId(self, table: str) -> Optional[int]:
"""Returns the initial ID for a table"""
return self.db.getInitialId(table)
def _getPasswordHash(self, password: str) -> str:
"""Creates a hash for a password"""
return pwdContext.hash(password)
def _verifyPassword(self, plainPassword: str, hashedPassword: str) -> bool:
"""Checks if the password matches the hash"""
return pwdContext.verify(plainPassword, hashedPassword)
def _getCurrentTimestamp(self) -> str:
"""Returns the current timestamp in ISO format"""
from datetime import datetime
return datetime.now().isoformat()
# Mandate methods
def getAllMandates(self) -> List[Dict[str, Any]]:
"""Returns all mandates"""
return self.db.getRecordset("mandates")
def getMandate(self, mandateId: int) -> Optional[Dict[str, Any]]:
"""Returns a mandate by its ID"""
mandates = self.db.getRecordset("mandates", recordFilter={"id": mandateId})
if mandates:
return mandates[0]
return None
def createMandate(self, name: str, language: str = "de") -> Dict[str, Any]:
"""Creates a new mandate"""
mandateData = {
"name": name,
"language": language
}
return self.db.recordCreate("mandates", mandateData)
# User methods
def getAllUsers(self) -> List[Dict[str, Any]]:
"""Returns all users"""
users = self.db.getRecordset("users")
# Remove password hashes from the response
for user in users:
if "hashedPassword" in user:
del user["hashedPassword"]
return users
def getUsersByMandate(self, mandateId: int) -> List[Dict[str, Any]]:
"""
Returns all users of a specific mandate
Args:
mandateId: The ID of the mandate
Returns:
List[Dict[str, Any]]: List of users in the mandate
"""
users = self.db.getRecordset("users", recordFilter={"mandateId": mandateId})
# Remove password hashes from the response
for user in users:
if "hashedPassword" in user:
del user["hashedPassword"]
return users
def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]:
"""Returns a user by username"""
users = self.db.getRecordset("users")
for user in users:
if user.get("username") == username:
return user
return None
def getUser(self, userId: int) -> Optional[Dict[str, Any]]:
"""Returns a user by ID"""
users = self.db.getRecordset("users", recordFilter={"id": userId})
if users:
user = users[0]
# Remove password hash from the API response
if "hashedPassword" in user:
userCopy = user.copy()
del userCopy["hashedPassword"]
return userCopy
return user
return None
def authenticateUser(self, username: str, password: str) -> Optional[Dict[str, Any]]:
"""
Authenticates a user by username and password
Args:
username: The username
password: The password
Returns:
Optional[Dict[str, Any]]: The user data or None if authentication fails
"""
user = self.getUserByUsername(username)
if not user:
return None
if not self._verifyPassword(password, user.get("hashedPassword", "")):
return None
# Check if the user is disabled
if user.get("disabled", False):
return None
# Create a copy without password hash
authenticatedUser = {**user}
if "hashedPassword" in authenticatedUser:
del authenticatedUser["hashedPassword"]
return authenticatedUser
# Singleton factory for GatewayInterface instances per context
_gatewayInterfaces = {}
def getGatewayInterface(mandateId: int = None, userId: int = None) -> GatewayInterface:
"""
Returns a GatewayInterface instance for the specified context.
Reuses existing instances.
Args:
mandateId: ID of the mandate
userId: ID of the user
Returns:
GatewayInterface instance
"""
contextKey = f"{mandateId}_{userId}"
if contextKey not in _gatewayInterfaces:
_gatewayInterfaces[contextKey] = GatewayInterface(mandateId, userId)
return _gatewayInterfaces[contextKey]
# Initialize the interface
getGatewayInterface()

View file

@ -12,7 +12,7 @@ import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from modules.chat_registry import AgentBase
from modules.workflowAgentsRegistry import AgentBase
logger = logging.getLogger(__name__)
@ -25,26 +25,26 @@ class AgentAnalyst(AgentBase):
self.name = "analyst"
self.description = "Analyzes data using AI-powered insights and visualizations, produce diagrams and visualizations"
self.capabilities = [
"data_analysis",
"dataAnalysis",
"statistics",
"visualization",
"data_interpretation",
"report_generation"
"dataInterpretation",
"reportGeneration"
]
# Set default visualization settings
plt.style.use('seaborn-v0_8-whitegrid')
def set_dependencies(self, mydom=None):
def setDependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.mydom = mydom
async def process_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""
Process a task by focusing on required outputs and using AI to generate them.
Args:
task: Task dictionary with prompt, input_documents, output_specifications
task: Task dictionary with prompt, inputDocuments, outputSpecifications
Returns:
Dictionary with feedback and documents
@ -52,8 +52,8 @@ class AgentAnalyst(AgentBase):
try:
# Extract task information
prompt = task.get("prompt", "")
input_documents = task.get("input_documents", [])
output_specs = task.get("output_specifications", [])
inputDocuments = task.get("inputDocuments", [])
outputSpecs = task.get("outputSpecifications", [])
# Check AI service
if not self.mydom:
@ -62,52 +62,52 @@ class AgentAnalyst(AgentBase):
"documents": []
}
# Extract data from documents - focusing only on data_extracted
datasets, document_context = self._extract_data(input_documents)
# Extract data from documents - focusing only on dataExtracted
datasets, documentContext = self._extractData(inputDocuments)
# Generate task analysis to understand what's needed
analysis_plan = await self._analyze_task(prompt, document_context, datasets, output_specs)
analysisPlan = await self._analyzeTask(prompt, documentContext, datasets, outputSpecs)
# Generate all required output documents
documents = []
# If no output specs provided, create default analysis outputs
if not output_specs:
output_specs = []
if not outputSpecs:
outputSpecs = []
# Process each output specification
for spec in output_specs:
output_label = spec.get("label", "")
output_description = spec.get("description", "")
for spec in outputSpecs:
outputLabel = spec.get("label", "")
outputDescription = spec.get("description", "")
# Determine type based on file extension
output_type = output_label.split('.')[-1].lower() if '.' in output_label else "txt"
outputType = outputLabel.split('.')[-1].lower() if '.' in outputLabel else "txt"
# Generate appropriate content based on output type
if output_type in ['png', 'jpg', 'jpeg', 'svg']:
if outputType in ['png', 'jpg', 'jpeg', 'svg']:
# Create visualization
document = await self._create_visualization(
datasets, prompt, output_label, analysis_plan, output_description
document = await self._createVisualization(
datasets, prompt, outputLabel, analysisPlan, outputDescription
)
documents.append(document)
elif output_type in ['csv', 'json', 'xlsx']:
elif outputType in ['csv', 'json', 'xlsx']:
# Create data document
document = await self._create_data_document(
datasets, prompt, output_label, analysis_plan, output_description
document = await self._createDataDocument(
datasets, prompt, outputLabel, analysisPlan, outputDescription
)
documents.append(document)
else:
# Create text document (report, analysis, etc.)
document = await self._create_text_document(
datasets, document_context, prompt, output_label,
output_type, analysis_plan, output_description
document = await self._createTextDocument(
datasets, documentContext, prompt, outputLabel,
outputType, analysisPlan, outputDescription
)
documents.append(document)
# Generate feedback
feedback = f"{analysis_plan.get('analysis_approach')}"
if analysis_plan.get("key_insights"):
feedback += f"\n\n{analysis_plan.get('key_insights')}"
feedback = f"{analysisPlan.get('analysisApproach')}"
if analysisPlan.get("keyInsights"):
feedback += f"\n\n{analysisPlan.get('keyInsights')}"
return {
"feedback": feedback,
@ -121,9 +121,9 @@ class AgentAnalyst(AgentBase):
"documents": []
}
def _extract_data(self, documents: List[Dict[str, Any]]) -> tuple:
def _extractData(self, documents: List[Dict[str, Any]]) -> tuple:
"""
Extract data from documents, focusing on data_extracted fields.
Extract data from documents, focusing on dataExtracted fields.
Args:
documents: List of input documents
@ -132,70 +132,70 @@ class AgentAnalyst(AgentBase):
Tuple of (datasets dictionary, document context text)
"""
datasets = {}
document_context = ""
documentContext = ""
# Process each document
for doc in documents:
doc_name = doc.get("name", "unnamed")
docName = doc.get("name", "unnamed")
if doc.get("ext"):
doc_name = f"{doc_name}.{doc.get('ext')}"
docName = f"{docName}.{doc.get('ext')}"
document_context += f"\n\n--- {doc_name} ---\n"
documentContext += f"\n\n--- {docName} ---\n"
# Process contents
for content in doc.get("contents", []):
# Focus only on data_extracted
if content.get("data_extracted"):
extracted_text = content.get("data_extracted", "")
document_context += extracted_text
# Focus only on dataExtracted
if content.get("dataExtracted"):
extractedText = content.get("dataExtracted", "")
documentContext += extractedText
# Try to parse as structured data if appropriate
if doc_name.lower().endswith(('.csv', '.tsv')):
if docName.lower().endswith(('.csv', '.tsv')):
try:
df = pd.read_csv(io.StringIO(extracted_text))
datasets[doc_name] = df
df = pd.read_csv(io.StringIO(extractedText))
datasets[docName] = df
except:
pass
elif doc_name.lower().endswith('.json'):
elif docName.lower().endswith('.json'):
try:
json_data = json.loads(extracted_text)
if isinstance(json_data, list):
df = pd.DataFrame(json_data)
datasets[doc_name] = df
elif isinstance(json_data, dict):
jsonData = json.loads(extractedText)
if isinstance(jsonData, list):
df = pd.DataFrame(jsonData)
datasets[docName] = df
elif isinstance(jsonData, dict):
# Handle nested JSON structures
if any(isinstance(v, list) for v in json_data.values()):
for key, value in json_data.items():
if any(isinstance(v, list) for v in jsonData.values()):
for key, value in jsonData.items():
if isinstance(value, list) and len(value) > 0:
df = pd.DataFrame(value)
datasets[f"{doc_name}:{key}"] = df
datasets[f"{docName}:{key}"] = df
else:
df = pd.DataFrame([json_data])
datasets[doc_name] = df
df = pd.DataFrame([jsonData])
datasets[docName] = df
except:
pass
# Try to detect tabular data in text content
if doc_name not in datasets and len(extracted_text.splitlines()) > 2:
lines = extracted_text.splitlines()
if docName not in datasets and len(extractedText.splitlines()) > 2:
lines = extractedText.splitlines()
if any(',' in line for line in lines[:5]):
try:
df = pd.read_csv(io.StringIO(extracted_text))
df = pd.read_csv(io.StringIO(extractedText))
if len(df.columns) > 1:
datasets[doc_name] = df
datasets[docName] = df
except:
pass
elif any('\t' in line for line in lines[:5]):
try:
df = pd.read_csv(io.StringIO(extracted_text), sep='\t')
df = pd.read_csv(io.StringIO(extractedText), sep='\t')
if len(df.columns) > 1:
datasets[doc_name] = df
datasets[docName] = df
except:
pass
return datasets, document_context
return datasets, documentContext
async def _analyze_task(self, prompt: str, context: str, datasets: Dict, output_specs: List) -> Dict:
async def _analyzeTask(self, prompt: str, context: str, datasets: Dict, outputSpecs: List) -> Dict:
"""
Use AI to analyze the task and create a plan for analysis.
@ -203,106 +203,106 @@ class AgentAnalyst(AgentBase):
prompt: The task prompt
context: Document context text
datasets: Dictionary of extracted datasets
output_specs: Output specifications
outputSpecs: Output specifications
Returns:
Analysis plan dictionary
"""
# Prepare dataset information
dataset_info = {}
datasetInfo = {}
for name, df in datasets.items():
try:
dataset_info[name] = {
datasetInfo[name] = {
"shape": df.shape,
"columns": df.columns.tolist(),
"dtypes": {col: str(df[col].dtype) for col in df.columns},
"sample": df.head(3).to_dict(orient='records')
}
except:
dataset_info[name] = {"error": "Could not process dataset"}
datasetInfo[name] = {"error": "Could not process dataset"}
analysis_prompt = f"""
analysisPrompt = f"""
Analyze this data analysis task and create a plan.
TASK: {prompt}
AVAILABLE DATA:
{json.dumps(dataset_info, indent=2)}
{json.dumps(datasetInfo, indent=2)}
DOCUMENT CONTEXT:
{context[:1000]}... (truncated)
OUTPUT REQUIREMENTS:
{json.dumps(output_specs, indent=2)}
{json.dumps(outputSpecs, indent=2)}
Create a detailed analysis plan in JSON format with the following structure:
{{
"analysis_type": "statistical|trend|comparative|predictive|cluster|general",
"key_questions": ["question1", "question2"],
"recommended_visualizations": [{{
"analysisType": "statistical|trend|comparative|predictive|cluster|general",
"keyQuestions": ["question1", "question2"],
"recommendedVisualizations": [{{
"type": "chart_type",
"data_source": "dataset_name",
"dataSource": "dataset_name",
"variables": ["col1", "col2"],
"purpose": "explanation"
}}],
"key_insights": "brief summary of initial insights",
"analysis_approach": "brief description of recommended approach"
"keyInsights": "brief summary of initial insights",
"analysisApproach": "brief description of recommended approach"
}}
Only return valid JSON. No preamble or explanations.
"""
try:
response = await self.mydom.call_ai([
response = await self.mydom.callAi([
{"role": "system", "content": "You are a data analysis expert. Respond with valid JSON only."},
{"role": "user", "content": analysis_prompt}
], produce_user_answer = True)
{"role": "user", "content": analysisPrompt}
], produceUserAnswer = True)
# Extract JSON from response
json_start = response.find('{')
json_end = response.rfind('}') + 1
jsonStart = response.find('{')
jsonEnd = response.rfind('}') + 1
if json_start >= 0 and json_end > json_start:
plan = json.loads(response[json_start:json_end])
if jsonStart >= 0 and jsonEnd > jsonStart:
plan = json.loads(response[jsonStart:jsonEnd])
return plan
else:
# Fallback if JSON not found
return {
"analysis_type": "general",
"key_questions": ["What insights can be extracted from this data?"],
"recommended_visualizations": [],
"key_insights": "Analysis plan could not be created",
"analysis_approach": "General exploratory analysis"
"analysisType": "general",
"keyQuestions": ["What insights can be extracted from this data?"],
"recommendedVisualizations": [],
"keyInsights": "Analysis plan could not be created",
"analysisApproach": "General exploratory analysis"
}
except Exception as e:
logger.warning(f"Error creating analysis plan: {str(e)}")
return {
"analysis_type": "general",
"key_questions": ["What insights can be extracted from this data?"],
"recommended_visualizations": [],
"key_insights": "Analysis plan could not be created",
"analysis_approach": "General exploratory analysis"
"analysisType": "general",
"keyQuestions": ["What insights can be extracted from this data?"],
"recommendedVisualizations": [],
"keyInsights": "Analysis plan could not be created",
"analysisApproach": "General exploratory analysis"
}
async def _create_visualization(self, datasets: Dict, prompt: str, output_label: str,
analysis_plan: Dict, description: str) -> Dict:
async def _createVisualization(self, datasets: Dict, prompt: str, outputLabel: str,
analysisPlan: Dict, description: str) -> Dict:
"""
Create visualization document using AI guidance.
Args:
datasets: Dictionary of datasets
prompt: Original task prompt
output_label: Output filename
analysis_plan: Analysis plan from AI
outputLabel: Output filename
analysisPlan: Analysis plan from AI
description: Output description
Returns:
Visualization document
"""
# Determine format from filename
format_type = output_label.split('.')[-1].lower()
if format_type not in ['png', 'jpg', 'jpeg', 'svg']:
format_type = 'png'
formatType = outputLabel.split('.')[-1].lower()
if formatType not in ['png', 'jpg', 'jpeg', 'svg']:
formatType = 'png'
# If no datasets available, create error message image
if not datasets:
@ -310,58 +310,58 @@ class AgentAnalyst(AgentBase):
plt.text(0.5, 0.5, "No data available for visualization",
ha='center', va='center', fontsize=14)
plt.tight_layout()
img_data = self._get_image_base64(format_type)
imgData = self._getImageBase64(formatType)
plt.close()
return {
"label": output_label,
"content": img_data,
"label": outputLabel,
"content": imgData,
"metadata": {
"content_type": f"image/{format_type}"
"contentType": f"image/{formatType}"
}
}
# Get recommended visualization from plan
recommended_viz = analysis_plan.get("recommended_visualizations", [])
recommendedViz = analysisPlan.get("recommendedVisualizations", [])
# Prepare dataset info for the first dataset if none specified
if not recommended_viz and datasets:
if not recommendedViz and datasets:
name, df = next(iter(datasets.items()))
recommended_viz = [{
recommendedViz = [{
"type": "auto",
"data_source": name,
"dataSource": name,
"variables": df.columns.tolist()[:5],
"purpose": "general analysis"
}]
# Create visualization code prompt
viz_prompt = f"""
vizPrompt = f"""
Generate Python matplotlib/seaborn code to create a visualization for:
TASK: {prompt}
VISUALIZATION REQUIREMENTS:
- Output format: {format_type}
- Filename: {output_label}
- Output format: {formatType}
- Filename: {outputLabel}
- Description: {description}
RECOMMENDED VISUALIZATION:
{json.dumps(recommended_viz, indent=2)}
{json.dumps(recommendedViz, indent=2)}
AVAILABLE DATASETS:
"""
# Add dataset info for recommended sources
for viz in recommended_viz:
data_source = viz.get("data_source")
if data_source in datasets:
df = datasets[data_source]
viz_prompt += f"\nDataset '{data_source}':\n"
viz_prompt += f"- Shape: {df.shape}\n"
viz_prompt += f"- Columns: {df.columns.tolist()}\n"
viz_prompt += f"- Sample data: {df.head(3).to_dict(orient='records')}\n"
for viz in recommendedViz:
dataSource = viz.get("dataSource")
if dataSource in datasets:
df = datasets[dataSource]
vizPrompt += f"\nDataset '{dataSource}':\n"
vizPrompt += f"- Shape: {df.shape}\n"
vizPrompt += f"- Columns: {df.columns.tolist()}\n"
vizPrompt += f"- Sample data: {df.head(3).to_dict(orient='records')}\n"
viz_prompt += """
vizPrompt += """
Generate ONLY Python code that:
1. Uses matplotlib and/or seaborn to create a clear visualization
2. Sets figure size to (10, 6)
@ -374,19 +374,19 @@ class AgentAnalyst(AgentBase):
try:
# Get visualization code from AI
viz_code = await self.mydom.call_ai([
vizCode = await self.mydom.callAi([
{"role": "system", "content": "You are a data visualization expert. Provide only executable Python code."},
{"role": "user", "content": viz_prompt}
], produce_user_answer = True)
{"role": "user", "content": vizPrompt}
], produceUserAnswer = True)
# Clean code
viz_code = viz_code.replace("```python", "").replace("```", "").strip()
vizCode = vizCode.replace("```python", "").replace("```", "").strip()
# Execute visualization code
plt.figure(figsize=(10, 6))
# Make local variables available to the code
local_vars = {
localVars = {
"plt": plt,
"sns": sns,
"pd": pd,
@ -396,27 +396,27 @@ class AgentAnalyst(AgentBase):
# Add datasets to local variables
for name, df in datasets.items():
# Create a sanitized variable name
var_name = ''.join(c if c.isalnum() else '_' for c in name)
local_vars[var_name] = df
varName = ''.join(c if c.isalnum() else '_' for c in name)
localVars[varName] = df
# Also add with standard names for simpler code
if "df" not in local_vars:
local_vars["df"] = df
elif "df2" not in local_vars:
local_vars["df2"] = df
if "df" not in localVars:
localVars["df"] = df
elif "df2" not in localVars:
localVars["df2"] = df
# Execute the visualization code
exec(viz_code, globals(), local_vars)
exec(vizCode, globals(), localVars)
# Capture the image
img_data = self._get_image_base64(format_type)
imgData = self._getImageBase64(formatType)
plt.close()
return {
"label": output_label,
"content": img_data,
"label": outputLabel,
"content": imgData,
"metadata": {
"content_type": f"image/{format_type}"
"contentType": f"image/{formatType}"
}
}
@ -428,70 +428,70 @@ class AgentAnalyst(AgentBase):
plt.text(0.5, 0.5, f"Visualization error: {str(e)}",
ha='center', va='center', fontsize=12)
plt.tight_layout()
img_data = self._get_image_base64(format_type)
imgData = self._getImageBase64(formatType)
plt.close()
return {
"label": output_label,
"content": img_data,
"label": outputLabel,
"content": imgData,
"metadata": {
"content_type": f"image/{format_type}"
"contentType": f"image/{formatType}"
}
}
async def _create_data_document(self, datasets: Dict, prompt: str, output_label: str,
analysis_plan: Dict, description: str) -> Dict:
async def _createDataDocument(self, datasets: Dict, prompt: str, outputLabel: str,
analysisPlan: Dict, description: str) -> Dict:
"""
Create a data document (e.g., CSV, JSON) based on analysis.
Args:
datasets: Dictionary of datasets
prompt: Original task prompt
output_label: Output filename
analysis_plan: Analysis plan from AI
outputLabel: Output filename
analysisPlan: Analysis plan from AI
description: Output description
Returns:
Data document
"""
# Determine format from filename
format_type = output_label.split('.')[-1].lower()
formatType = outputLabel.split('.')[-1].lower()
# If no datasets available, return error message
if not datasets:
return {
"label": output_label,
"content": f"No data available for processing into {format_type} format.",
"label": outputLabel,
"content": f"No data available for processing into {formatType} format.",
"metadata": {
"content_type": "text/plain"
"contentType": "text/plain"
}
}
# Generate data processing instructions
data_prompt = f"""
Create Python code to process datasets and generate a {format_type} file for:
dataPrompt = f"""
Create Python code to process datasets and generate a {formatType} file for:
TASK: {prompt}
OUTPUT REQUIREMENTS:
- Format: {format_type}
- Filename: {output_label}
- Format: {formatType}
- Filename: {outputLabel}
- Description: {description}
ANALYSIS CONTEXT:
{json.dumps(analysis_plan, indent=2)}
{json.dumps(analysisPlan, indent=2)}
AVAILABLE DATASETS:
"""
# Add dataset info
for name, df in datasets.items():
data_prompt += f"\nDataset '{name}':\n"
data_prompt += f"- Shape: {df.shape}\n"
data_prompt += f"- Columns: {df.columns.tolist()}\n"
data_prompt += f"- Sample data: {df.head(3).to_dict(orient='records')}\n"
dataPrompt += f"\nDataset '{name}':\n"
dataPrompt += f"- Shape: {df.shape}\n"
dataPrompt += f"- Columns: {df.columns.tolist()}\n"
dataPrompt += f"- Sample data: {df.head(3).to_dict(orient='records')}\n"
data_prompt += """
dataPrompt += """
Generate Python code that:
1. Processes the available dataset(s)
2. Performs necessary transformations, aggregations, or calculations
@ -503,46 +503,46 @@ class AgentAnalyst(AgentBase):
try:
# Get data processing code from AI
data_code = await self.mydom.call_ai([
dataCode = await self.mydom.callAi([
{"role": "system", "content": "You are a data processing expert. Provide only executable Python code."},
{"role": "user", "content": data_prompt}
], produce_user_answer = True)
{"role": "user", "content": dataPrompt}
], produceUserAnswer = True)
# Clean code
data_code = data_code.replace("```python", "").replace("```", "").strip()
dataCode = dataCode.replace("```python", "").replace("```", "").strip()
# Setup execution environment
local_vars = {"pd": pd, "np": __import__('numpy'), "io": io}
localVars = {"pd": pd, "np": __import__('numpy'), "io": io}
# Add datasets to local variables
for name, df in datasets.items():
# Create a sanitized variable name
var_name = ''.join(c if c.isalnum() else '_' for c in name)
local_vars[var_name] = df
varName = ''.join(c if c.isalnum() else '_' for c in name)
localVars[varName] = df
# Also add with standard names for simpler code
if "df" not in local_vars:
local_vars["df"] = df
elif "df2" not in local_vars:
local_vars["df2"] = df
if "df" not in localVars:
localVars["df"] = df
elif "df2" not in localVars:
localVars["df2"] = df
# Execute the code
exec(data_code, globals(), local_vars)
exec(dataCode, globals(), localVars)
# Get the result
result = local_vars.get("result", "No output was generated.")
result = localVars.get("result", "No output was generated.")
# Determine content type
content_type = "text/csv" if format_type == "csv" else \
"application/json" if format_type == "json" else \
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" if format_type == "xlsx" else \
contentType = "text/csv" if formatType == "csv" else \
"application/json" if formatType == "json" else \
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" if formatType == "xlsx" else \
"text/plain"
return {
"label": output_label,
"label": outputLabel,
"content": result,
"metadata": {
"content_type": content_type
"contentType": contentType
}
}
@ -550,16 +550,16 @@ class AgentAnalyst(AgentBase):
logger.error(f"Error creating data document: {str(e)}", exc_info=True)
return {
"label": output_label,
"content": f"Error generating {format_type} document: {str(e)}",
"label": outputLabel,
"content": f"Error generating {formatType} document: {str(e)}",
"metadata": {
"content_type": "text/plain"
"contentType": "text/plain"
}
}
async def _create_text_document(self, datasets: Dict, context: str, prompt: str,
output_label: str, format_type: str,
analysis_plan: Dict, description: str) -> Dict:
async def _createTextDocument(self, datasets: Dict, context: str, prompt: str,
outputLabel: str, formatType: str,
analysisPlan: Dict, description: str) -> Dict:
"""
Create a text document (report, analysis, etc.) based on analysis.
@ -567,52 +567,52 @@ class AgentAnalyst(AgentBase):
datasets: Dictionary of datasets
context: Document context text
prompt: Original task prompt
output_label: Output filename
format_type: Output format type
analysis_plan: Analysis plan from AI
outputLabel: Output filename
formatType: Output format type
analysisPlan: Analysis plan from AI
description: Output description
Returns:
Text document
"""
# Create dataset summaries
dataset_summaries = []
datasetSummaries = []
for name, df in datasets.items():
summary = f"Dataset: {name}\n"
summary += f"- Shape: {df.shape[0]} rows, {df.shape[1]} columns\n"
summary += f"- Columns: {', '.join(df.columns.tolist())}\n"
# Basic statistics for numeric columns
numeric_cols = df.select_dtypes(include=['number']).columns
if len(numeric_cols) > 0:
numericCols = df.select_dtypes(include=['number']).columns
if len(numericCols) > 0:
summary += "- Numeric Columns Stats:\n"
for col in numeric_cols[:3]: # Limit to first 3
for col in numericCols[:3]: # Limit to first 3
stats = df[col].describe()
summary += f" - {col}: min={stats['min']:.2f}, max={stats['max']:.2f}, mean={stats['mean']:.2f}\n"
dataset_summaries.append(summary)
datasetSummaries.append(summary)
# Determine content type based on format
content_type = "text/markdown" if format_type in ["md", "markdown"] else \
"text/html" if format_type == "html" else \
contentType = "text/markdown" if formatType in ["md", "markdown"] else \
"text/html" if formatType == "html" else \
"text/plain"
# Generate analysis prompt
analysis_prompt = f"""
Create a detailed {format_type} document for:
analysisPrompt = f"""
Create a detailed {formatType} document for:
TASK: {prompt}
OUTPUT REQUIREMENTS:
- Format: {format_type}
- Filename: {output_label}
- Format: {formatType}
- Filename: {outputLabel}
- Description: {description}
ANALYSIS CONTEXT:
{json.dumps(analysis_plan, indent=2)}
{json.dumps(analysisPlan, indent=2)}
DATASET SUMMARIES:
{"".join(dataset_summaries)}
{"".join(datasetSummaries)}
DOCUMENT CONTEXT:
{context[:2000]}... (truncated)
@ -629,22 +629,22 @@ class AgentAnalyst(AgentBase):
try:
# Get document content from AI
document_content = await self.mydom.call_ai([
{"role": "system", "content": f"You are a data analysis expert creating a {format_type} document."},
{"role": "user", "content": analysis_prompt}
], produce_user_answer = True)
documentContent = await self.mydom.callAi([
{"role": "system", "content": f"You are a data analysis expert creating a {formatType} document."},
{"role": "user", "content": analysisPrompt}
], produceUserAnswer = True)
# Clean HTML or Markdown if needed
if format_type in ["md", "markdown"] and not document_content.strip().startswith("#"):
document_content = f"# Analysis Report\n\n{document_content}"
elif format_type == "html" and not "<html" in document_content.lower():
document_content = f"<html><body>{document_content}</body></html>"
if formatType in ["md", "markdown"] and not documentContent.strip().startswith("#"):
documentContent = f"# Analysis Report\n\n{documentContent}"
elif formatType == "html" and not "<html" in documentContent.lower():
documentContent = f"<html><body>{documentContent}</body></html>"
return {
"label": output_label,
"content": document_content,
"label": outputLabel,
"content": documentContent,
"metadata": {
"content_type": content_type
"contentType": contentType
}
}
@ -652,43 +652,43 @@ class AgentAnalyst(AgentBase):
logger.error(f"Error creating text document: {str(e)}", exc_info=True)
# Create a simple error document
if format_type in ["md", "markdown"]:
if formatType in ["md", "markdown"]:
content = f"# Error in Analysis\n\nThere was an error generating the analysis: {str(e)}"
elif format_type == "html":
elif formatType == "html":
content = f"<html><body><h1>Error in Analysis</h1><p>There was an error generating the analysis: {str(e)}</p></body></html>"
else:
content = f"Error in Analysis\n\nThere was an error generating the analysis: {str(e)}"
return {
"label": output_label,
"label": outputLabel,
"content": content,
"metadata": {
"content_type": content_type
"contentType": contentType
}
}
def _get_image_base64(self, format_type: str = 'png') -> str:
def _getImageBase64(self, formatType: str = 'png') -> str:
"""
Convert current matplotlib figure to base64 string.
Args:
format_type: Image format
formatType: Image format
Returns:
Base64 encoded string of the image
"""
buffer = io.BytesIO()
plt.savefig(buffer, format=format_type, dpi=100)
plt.savefig(buffer, format=formatType, dpi=100)
buffer.seek(0)
image_data = buffer.getvalue()
imageData = buffer.getvalue()
buffer.close()
# Convert to base64
image_base64 = base64.b64encode(image_data).decode('utf-8')
return image_base64
imageBase64 = base64.b64encode(imageData).decode('utf-8')
return imageBase64
# Factory function for the Analyst agent
def get_analyst_agent():
def getAgentAnalyst():
"""Returns an instance of the Analyst agent."""
return AgentAnalyst()

View file

@ -11,7 +11,7 @@ import shutil
import sys
from typing import Dict, Any, List, Tuple
from modules.chat_registry import AgentBase
from modules.workflowAgentsRegistry import AgentBase
from modules.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
@ -33,30 +33,30 @@ class AgentCoder(AgentBase):
]
# Executor settings
self.executor_timeout = int(APP_CONFIG.get("Agent_Coder_EXECUTION_TIMEOUT")) # seconds
self.execution_retry_limit = int(APP_CONFIG.get("Agent_Coder_EXECUTION_RETRY")) # max retries
self.temp_dir = None
self.executorTimeout = int(APP_CONFIG.get("Agent_Coder_EXECUTION_TIMEOUT")) # seconds
self.executionRetryLimit = int(APP_CONFIG.get("Agent_Coder_EXECUTION_RETRY")) # max retries
self.tempDir = None
def set_dependencies(self, mydom=None):
def setDependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.mydom = mydom
async def process_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""
Process a task and perform code development/execution.
First checks if the task can be completed without code execution,
then falls back to code generation if needed.
Args:
task: Task dictionary with prompt, input_documents, output_specifications
task: Task dictionary with prompt, inputDocuments, outputSpecifications
Returns:
Dictionary with feedback and documents
"""
# 1. Extract task information
prompt = task.get("prompt", "")
input_documents = task.get("input_documents", [])
output_specs = task.get("output_specifications", [])
inputDocuments = task.get("inputDocuments", [])
outputSpecs = task.get("outputSpecifications", [])
# Check if AI service is available
if not self.mydom:
@ -67,59 +67,59 @@ class AgentCoder(AgentBase):
}
# 2. Extract data from documents in separate categories
document_data = [] # For raw file data (for code execution)
content_data = [] # For content data (later use)
content_extraction = [] # For AI-extracted data (for quick completion)
documentData = [] # For raw file data (for code execution)
contentData = [] # For content data (later use)
contentExtraction = [] # For AI-extracted data (for quick completion)
for doc in input_documents:
for doc in inputDocuments:
# Create proper filename from name and ext
filename = f"{doc.get('name')}.{doc.get('ext')}" if doc.get('ext') else doc.get('name')
# Add main document data to document_data if it exists
doc_data = doc.get('data', '')
if doc_data:
is_base64 = True # Assume base64 encoded for document data
document_data.append([filename, doc_data, is_base64])
# Add main document data to documentData if it exists
docData = doc.get('data', '')
if docData:
isBase64 = True # Assume base64 encoded for document data
documentData.append([filename, docData, isBase64])
# Process contents for different uses
if doc.get('contents'):
for content in doc.get('contents', []):
content_name = content.get('name', 'unnamed')
contentName = content.get('name', 'unnamed')
# For AI-extracted data (quick completion)
if content.get('data_extracted'):
content_extraction.append({
if content.get('dataExtracted'):
contentExtraction.append({
"filename": filename,
"content_name": content_name,
"content_data": content.get('data_extracted', ''),
"content_type": content.get('content_type', ''),
"contentName": contentName,
"contentData": content.get('dataExtracted', ''),
"contentType": content.get('contentType', ''),
"summary": content.get('summary', '')
})
# For raw content data
if content.get('data'):
raw_data = content.get('data', '')
is_base64 = content.get('metadata', {}).get('base64_encoded', False)
content_data.append({
rawData = content.get('data', '')
isBase64 = content.get('metadata', {}).get('base64Encoded', False)
contentData.append({
"filename": filename,
"content_name": content_name,
"data": raw_data,
"is_base64": is_base64,
"content_type": content.get('content_type', '')
"contentName": contentName,
"data": rawData,
"isBase64": isBase64,
"contentType": content.get('contentType', '')
})
# Also add to document_data for code execution if not already added
if not doc_data or doc_data != raw_data:
document_data.append([filename, raw_data, is_base64])
# Also add to documentData for code execution if not already added
if not docData or docData != rawData:
documentData.append([filename, rawData, isBase64])
# 3. Check if task can be completed without code execution
quick_completion = await self._check_quick_completion(prompt, content_extraction, output_specs)
quickCompletion = await self._checkQuickCompletion(prompt, contentExtraction, outputSpecs)
if quick_completion and quick_completion.get("complete") == 1:
if quickCompletion and quickCompletion.get("complete") == 1:
logger.info("Task completed without code execution")
return {
"feedback": quick_completion.get("prompt", "Task completed successfully."),
"documents": quick_completion.get("documents", [])
"feedback": quickCompletion.get("prompt", "Task completed successfully."),
"documents": quickCompletion.get("documents", [])
}
else:
logger.debug(f"Code to generate, no quick check")
@ -128,7 +128,7 @@ class AgentCoder(AgentBase):
logger.info("Generating code to solve the task")
# 4. Generate code using AI
code, requirements = await self._generate_code(prompt)
code, requirements = await self._generateCode(prompt)
if not code:
return {
@ -136,53 +136,53 @@ class AgentCoder(AgentBase):
"documents": []
}
# 5. Replace the placeholder with actual input_files data
document_data_json = repr(document_data)
code_with_data = code.replace("input_files = \"=== JSONLOAD ===\"", f"input_files = {document_data_json}")
# 5. Replace the placeholder with actual inputFiles data
documentDataJson = repr(documentData)
codeWithData = code.replace("inputFiles = \"=== JSONLOAD ===\"", f"inputFiles = {documentDataJson}")
# 6. Execute code with retry logic
retry_count = 0
max_retries = self.execution_retry_limit
execution_history = []
retryCount = 0
maxRetries = self.executionRetryLimit
executionHistory = []
while retry_count <= max_retries:
execution_result = self._execute_code(code_with_data, requirements)
execution_history.append({
"attempt": retry_count + 1,
"code": code_with_data,
"result": execution_result
while retryCount <= maxRetries:
executionResult = self._executeCode(codeWithData, requirements)
executionHistory.append({
"attempt": retryCount + 1,
"code": codeWithData,
"result": executionResult
})
# Check if execution was successful
if execution_result.get("success", False):
logger.info(f"Code execution succeeded on attempt {retry_count + 1}")
if executionResult.get("success", False):
logger.info(f"Code execution succeeded on attempt {retryCount + 1}")
break
# If we've reached max retries, exit the loop
if retry_count >= max_retries:
logger.info(f"Reached maximum retry limit ({max_retries}). Giving up.")
if retryCount >= maxRetries:
logger.info(f"Reached maximum retry limit ({maxRetries}). Giving up.")
break
# Log the error and attempt to improve the code
error = execution_result.get("error", "Unknown error")
logger.info(f"Execution attempt {retry_count + 1} failed: {error}. Attempting to improve code.")
error = executionResult.get("error", "Unknown error")
logger.info(f"Execution attempt {retryCount + 1} failed: {error}. Attempting to improve code.")
# Generate improved code based on error
improved_code, improved_requirements = await self._improve_code(
original_code=code_with_data,
improvedCode, improvedRequirements = await self._improveCode(
originalCode=codeWithData,
error=error,
execution_result=execution_result,
attempt=retry_count + 1
executionResult=executionResult,
attempt=retryCount + 1
)
if improved_code:
code_with_data = improved_code
requirements = improved_requirements
logger.info(f"Code improved for retry {retry_count + 2}")
if improvedCode:
codeWithData = improvedCode
requirements = improvedRequirements
logger.info(f"Code improved for retry {retryCount + 2}")
else:
logger.warning("Failed to improve code, using original code for retry")
retry_count += 1
retryCount += 1
# 7. Process results and create output documents
documents = []
@ -190,32 +190,32 @@ class AgentCoder(AgentBase):
# Always add the final code document
documents.append({
"label": "generated_code.py",
"content": code_with_data
"content": codeWithData
})
# Add execution history document
execution_history_str = json.dumps(execution_history, indent=2)
executionHistoryStr = json.dumps(executionHistory, indent=2)
documents.append({
"label": "execution_history.json",
"content": execution_history_str
"content": executionHistoryStr
})
# Create documents based on execution results
if execution_result.get("success", False):
result_data = execution_result.get("result")
if executionResult.get("success", False):
resultData = executionResult.get("result")
# Create documents based on output specifications
if output_specs:
for spec in output_specs:
if outputSpecs:
for spec in outputSpecs:
label = spec.get("label", "output.txt")
# Extract content from result if available
content = ""
if isinstance(result_data, dict) and label in result_data:
content = result_data[label]
if isinstance(resultData, dict) and label in resultData:
content = resultData[label]
else:
# Default to execution output
content = execution_result.get("output", "")
content = executionResult.get("output", "")
documents.append({
"label": label,
@ -225,23 +225,23 @@ class AgentCoder(AgentBase):
# No output specs, create default output document
documents.append({
"label": "execution_output.txt",
"content": execution_result.get("output", "")
"content": executionResult.get("output", "")
})
if retry_count > 0:
feedback = f"Code executed successfully after {retry_count + 1} attempts. Generated output files based on specifications."
if retryCount > 0:
feedback = f"Code executed successfully after {retryCount + 1} attempts. Generated output files based on specifications."
else:
feedback = "Code executed successfully. Generated output files based on specifications."
else:
# Execution failed
error = execution_result.get("error", "Unknown error")
error = executionResult.get("error", "Unknown error")
documents.append({
"label": "execution_error.txt",
"content": f"Error executing code:\n\n{error}"
})
if retry_count > 0:
feedback = f"Error during code execution after {retry_count + 1} attempts: {error}"
if retryCount > 0:
feedback = f"Error during code execution after {retryCount + 1} attempts: {error}"
else:
feedback = f"Error during code execution: {error}"
@ -250,31 +250,31 @@ class AgentCoder(AgentBase):
"documents": documents
}
async def _improve_code(self, original_code: str, error: str, execution_result: Dict[str, Any], attempt: int) -> Tuple[str, List[str]]:
async def _improveCode(self, originalCode: str, error: str, executionResult: Dict[str, Any], attempt: int) -> Tuple[str, List[str]]:
"""
Improve code based on execution error.
Args:
original_code: The code that failed to execute
originalCode: The code that failed to execute
error: The error message
execution_result: Complete execution result dictionary
executionResult: Complete execution result dictionary
attempt: Current attempt number
Returns:
Tuple of (improved_code, requirements)
Tuple of (improvedCode, requirements)
"""
# Create prompt for code improvement
improvement_prompt = f"""
improvementPrompt = f"""
Fix the following Python code that failed during execution. This is attempt {attempt} to fix the code.
ORIGINAL CODE:
{original_code}
{originalCode}
ERROR MESSAGE:
{error}
STDOUT:
{execution_result.get('output', '')}
{executionResult.get('output', '')}
INSTRUCTIONS:
1. Fix all errors identified in the error message
@ -284,13 +284,13 @@ INSTRUCTIONS:
- Error handling and edge cases
- Resource management (file handles, etc.)
- Syntax errors and typos
4. Keep the input_files handling logic intact
4. Keep the inputFiles handling logic intact
5. Maintain the same overall structure and purpose
OUTPUT:
- Your improved code MUST still define a 'result' variable as a dictionary
- Each output file should be a key in the result dictionary
- DO NOT remove the input_files assignment line structure
- DO NOT remove the inputFiles assignment line structure
REQUIREMENTS:
Required packages should be specified as:
@ -303,66 +303,66 @@ Return ONLY Python code without explanations or markdown.
# Call AI service
messages = [
{"role": "system", "content": "You are an expert Python code debugger. Provide only fixed Python code without explanations or formatting."},
{"role": "user", "content": improvement_prompt}
{"role": "user", "content": improvementPrompt}
]
try:
improved_content = await self.mydom.call_ai(messages, temperature=0.2)
improvedContent = await self.mydom.callAi(messages, temperature=0.2)
# Extract code and requirements
improved_code = self._clean_code(improved_content)
improvedCode = self._cleanCode(improvedContent)
# Extract requirements
requirements = []
for line in improved_code.split('\n'):
for line in improvedCode.split('\n'):
if line.strip().startswith("# REQUIREMENTS:"):
req_str = line.replace("# REQUIREMENTS:", "").strip()
requirements = [r.strip() for r in req_str.split(',') if r.strip()]
reqStr = line.replace("# REQUIREMENTS:", "").strip()
requirements = [r.strip() for r in reqStr.split(',') if r.strip()]
break
return improved_code, requirements
return improvedCode, requirements
except Exception as e:
logger.error(f"Error improving code: {str(e)}")
return None, []
async def _check_quick_completion(self, prompt: str, content_extraction: List[Dict], output_specs: List[Dict]) -> Dict:
async def _checkQuickCompletion(self, prompt: str, contentExtraction: List[Dict], outputSpecs: List[Dict]) -> Dict:
"""
Check if the task can be completed without writing and executing code.
Args:
prompt: The task prompt
content_extraction: List of extracted content data with content_name and data_extracted
output_specs: List of output specifications
contentExtraction: List of extracted content data with contentName and dataExtracted
outputSpecs: List of output specifications
Returns:
Dictionary with completion status and results, or None if no quick completion
"""
# If no data or no output specs, can't do a quick completion
if not content_extraction or not output_specs:
if not contentExtraction or not outputSpecs:
return None
# Create a prompt for the AI to check if this can be completed directly
specs_json = json.dumps(output_specs)
data_json = json.dumps(content_extraction)
specsJson = json.dumps(outputSpecs)
dataJson = json.dumps(contentExtraction)
check_prompt = f"""
checkPrompt = f"""
Analyze this task and determine if it can be completed directly without writing code.
TASK:
{prompt}
EXTRACTED DATA AVAILABLE:
{data_json}
{dataJson}
Each entry in the extracted data contains:
- filename: The source file name
- content_name: The specific content section name
- content_data: The AI-extracted text from the content
- content_type: The type of content (text, csv, etc.)
- contentName: The specific content section name
- contentData: The AI-extracted text from the content
- contentType: The type of content (text, csv, etc.)
- summary: A brief summary of the content
REQUIRED OUTPUT:
{specs_json}
{specsJson}
If the task can be completed directly with the available extracted data, respond with:
{{"complete": 1, "prompt": "Brief explanation of the solution", "documents": [
@ -376,26 +376,26 @@ Only return valid JSON. Your entire response must be parseable as JSON.
"""
# Call AI service
logger.debug(f"Checking if task can be completed without code execution: {check_prompt}")
logger.debug(f"Checking if task can be completed without code execution: {checkPrompt}")
messages = [
{"role": "system", "content": "You are an AI assistant that determines if tasks require code execution. Reply with JSON only."},
{"role": "user", "content": check_prompt}
{"role": "user", "content": checkPrompt}
]
try:
# Use a lower temperature for more deterministic response
response = await self.mydom.call_ai(messages, produce_user_answer = True, temperature=0.1)
response = await self.mydom.callAi(messages, produceUserAnswer = True, temperature=0.1)
# Parse response as JSON
if response:
try:
# Find JSON in response if there's any text around it
json_start = response.find('{')
json_end = response.rfind('}') + 1
jsonStart = response.find('{')
jsonEnd = response.rfind('}') + 1
if json_start >= 0 and json_end > json_start:
json_str = response[json_start:json_end]
result = json.loads(json_str)
if jsonStart >= 0 and jsonEnd > jsonStart:
jsonStr = response[jsonStart:jsonEnd]
result = json.loads(jsonStr)
# Check if this is a proper response
if "complete" in result:
@ -410,28 +410,27 @@ Only return valid JSON. Your entire response must be parseable as JSON.
# Default to requiring code execution
return None
async def _generate_code(self, prompt: str) -> Tuple[str, List[str]]:
async def _generateCode(self, prompt: str) -> Tuple[str, List[str]]:
"""
Generate Python code from a prompt with the input_files placeholder.
Generate Python code from a prompt with the inputFiles placeholder.
Args:
prompt: The task prompt
input_files: List of [filename, data, is_base64] items
Returns:
Tuple of (code, requirements)
"""
# Create prompt for code generation
ai_prompt = f"""
aiPrompt = f"""
Generate Python code to solve the following task:
TASK:
{prompt}
INPUT FILES:
- 'input_files' variable is provided as [[filename, data, is_base64], ...]
- For text files (is_base64=False): use data directly as string
- For binary files (is_base64=True): use base64.b64decode(data)
- 'inputFiles' variable is provided as [[filename, data, isBase64], ...]
- For text files (isBase64=False): use data directly as string
- For binary files (isBase64=True): use base64.b64decode(data)
CODE QUALITY:
- Use explicit type conversions where needed (int/float/str)
@ -446,7 +445,7 @@ OUTPUT:
- For example: result = {{"output.txt": "output text", "results.json": json_string}}
Your code must start with:
input_files = "=== JSONLOAD ===" # DO NOT CHANGE THIS LINE
inputFiles = "=== JSONLOAD ===" # DO NOT CHANGE THIS LINE
REQUIREMENTS:
Required packages should be specified as:
@ -460,25 +459,25 @@ Return ONLY Python code without explanations or markdown.
# Call AI service
messages = [
{"role": "system", "content": "You are a Python code generator. Provide only valid Python code without explanations or formatting."},
{"role": "user", "content": ai_prompt}
{"role": "user", "content": aiPrompt}
]
generated_content = await self.mydom.call_ai(messages, temperature=0.1)
generatedContent = await self.mydom.callAi(messages, temperature=0.1)
# Extract code and requirements
code = self._clean_code(generated_content)
code = self._cleanCode(generatedContent)
# Extract requirements
requirements = []
for line in code.split('\n'):
if line.strip().startswith("# REQUIREMENTS:"):
req_str = line.replace("# REQUIREMENTS:", "").strip()
requirements = [r.strip() for r in req_str.split(',') if r.strip()]
reqStr = line.replace("# REQUIREMENTS:", "").strip()
requirements = [r.strip() for r in reqStr.split(',') if r.strip()]
break
return code, requirements
def _execute_code(self, code: str, requirements: List[str] = None) -> Dict[str, Any]:
def _executeCode(self, code: str, requirements: List[str] = None) -> Dict[str, Any]:
"""
Execute Python code in a virtual environment.
Integrated executor functionality.
@ -492,24 +491,24 @@ Return ONLY Python code without explanations or markdown.
"""
try:
# 1. Create temp directory and virtual environment
self.temp_dir = tempfile.mkdtemp(prefix="code_exec_")
venv_path = os.path.join(self.temp_dir, "venv")
self.tempDir = tempfile.mkdtemp(prefix="code_exec_")
venvPath = os.path.join(self.tempDir, "venv")
# Create venv
logger.debug(f"Creating virtual environment at {venv_path}")
subprocess.run([sys.executable, "-m", "venv", venv_path],
logger.debug(f"Creating virtual environment at {venvPath}")
subprocess.run([sys.executable, "-m", "venv", venvPath],
check=True, capture_output=True)
# Get Python executable path
python_exe = os.path.join(venv_path, "Scripts", "python.exe") if os.name == 'nt' else os.path.join(venv_path, "bin", "python")
pythonExe = os.path.join(venvPath, "Scripts", "python.exe") if os.name == 'nt' else os.path.join(venvPath, "bin", "python")
# 2. Install requirements if provided
if requirements:
logger.info(f"Installing requirements: {requirements}")
# Create requirements.txt
req_file = os.path.join(self.temp_dir, "requirements.txt")
with open(req_file, "w") as f:
reqFile = os.path.join(self.tempDir, "requirements.txt")
with open(reqFile, "w") as f:
f.write("\n".join(requirements))
x="\n".join(requirements)
@ -517,38 +516,38 @@ Return ONLY Python code without explanations or markdown.
# Install requirements
try:
pip_result = subprocess.run(
[python_exe, "-m", "pip", "install", "-r", req_file],
pipResult = subprocess.run(
[pythonExe, "-m", "pip", "install", "-r", reqFile],
capture_output=True,
text=True,
timeout=int(APP_CONFIG.get("Agent_Coder_INSTALL_TIMEOUT"))
)
if pip_result.returncode != 0:
logger.debug(f"Error installing requirements: {pip_result.stderr}")
if pipResult.returncode != 0:
logger.debug(f"Error installing requirements: {pipResult.stderr}")
else:
logger.debug(f"Requirements installed successfully")
# Log installed packages if in debug mode
if logger.isEnabledFor(logging.DEBUG):
pip_list = subprocess.run(
[python_exe, "-m", "pip", "list"],
pipList = subprocess.run(
[pythonExe, "-m", "pip", "list"],
capture_output=True,
text=True
)
logger.debug(f"Installed packages:\n{pip_list.stdout}")
logger.debug(f"Installed packages:\n{pipList.stdout}")
except Exception as e:
logger.debug(f"Exception during requirements installation: {str(e)}")
# 3. Write code to file
code_file = os.path.join(self.temp_dir, "code.py")
with open(code_file, "w", encoding="utf-8") as f:
codeFile = os.path.join(self.tempDir, "code.py")
with open(codeFile, "w", encoding="utf-8") as f:
f.write(code)
# 4. Execute code
logger.debug(f"Executing code with timeout of {self.executor_timeout} seconds. Code: {code}")
logger.debug(f"Executing code with timeout of {self.executorTimeout} seconds. Code: {code}")
process = subprocess.run(
[python_exe, code_file],
timeout=self.executor_timeout,
[pythonExe, codeFile],
timeout=self.executorTimeout,
capture_output=True,
text=True
)
@ -558,7 +557,7 @@ Return ONLY Python code without explanations or markdown.
stderr = process.stderr
# Try to extract result from stdout
result_data = None
resultData = None
if process.returncode == 0:
try:
# Find the last line that might be JSON
@ -566,8 +565,8 @@ Return ONLY Python code without explanations or markdown.
line = line.strip()
if line and line[0] in '{[' and line[-1] in '}]':
try:
result_data = json.loads(line)
logger.debug(f"Extracted result data from stdout: {type(result_data)}")
resultData = json.loads(line)
logger.debug(f"Extracted result data from stdout: {type(resultData)}")
break
except json.JSONDecodeError:
continue
@ -579,18 +578,18 @@ Return ONLY Python code without explanations or markdown.
"success": process.returncode == 0,
"output": stdout,
"error": stderr if process.returncode != 0 else "",
"result": result_data,
"exit_code": process.returncode
"result": resultData,
"exitCode": process.returncode
}
except subprocess.TimeoutExpired:
logger.error(f"Execution timed out after {self.executor_timeout} seconds")
logger.error(f"Execution timed out after {self.executorTimeout} seconds")
return {
"success": False,
"output": "",
"error": f"Execution timed out after {self.executor_timeout} seconds",
"error": f"Execution timed out after {self.executorTimeout} seconds",
"result": None,
"exit_code": -1
"exitCode": -1
}
except Exception as e:
logger.error(f"Execution error: {str(e)}")
@ -599,44 +598,44 @@ Return ONLY Python code without explanations or markdown.
"output": "",
"error": f"Execution error: {str(e)}",
"result": None,
"exit_code": -1
"exitCode": -1
}
finally:
# Clean up resources
self._cleanup_execution()
self._cleanupExecution()
def _cleanup_execution(self):
def _cleanupExecution(self):
"""Clean up temporary resources from code execution."""
if self.temp_dir and os.path.exists(self.temp_dir):
if self.tempDir and os.path.exists(self.tempDir):
try:
logger.debug(f"Cleaning up temporary directory: {self.temp_dir}")
shutil.rmtree(self.temp_dir)
self.temp_dir = None
logger.debug(f"Cleaning up temporary directory: {self.tempDir}")
shutil.rmtree(self.tempDir)
self.tempDir = None
except Exception as e:
logger.warning(f"Error cleaning up temp directory: {str(e)}")
def _clean_code(self, code: str) -> str:
def _cleanCode(self, code: str) -> str:
"""Remove any markdown formatting or explanations."""
# Remove code block markers
code = code.replace("```python", "").replace("```", "")
# Remove explanations before or after code
lines = code.strip().split('\n')
start_index = 0
end_index = len(lines)
startIndex = 0
endIndex = len(lines)
# Find start of actual code
for i, line in enumerate(lines):
if line.strip().startswith("input_files =") or line.strip().startswith("# REQUIREMENTS:"):
start_index = i
if line.strip().startswith("inputFiles =") or line.strip().startswith("# REQUIREMENTS:"):
startIndex = i
break
# Clean code
cleaned_code = '\n'.join(lines[start_index:end_index])
return cleaned_code.strip()
cleanedCode = '\n'.join(lines[startIndex:endIndex])
return cleanedCode.strip()
# Factory function for the Coder agent
def get_coder_agent():
def getAgentCoder():
"""Returns an instance of the Coder agent."""
return AgentCoder()

View file

@ -7,7 +7,7 @@ import logging
import json
from typing import Dict, Any, List
from modules.chat_registry import AgentBase
from modules.workflowAgentsRegistry import AgentBase
logger = logging.getLogger(__name__)
@ -27,16 +27,16 @@ class AgentDocumentation(AgentBase):
"knowledge_organization"
]
def set_dependencies(self, mydom=None):
def setDependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.mydom = mydom
async def process_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""
Process a task by focusing on required outputs and using AI to generate them.
Args:
task: Task dictionary with prompt, input_documents, output_specifications
task: Task dictionary with prompt, inputDocuments, outputSpecifications
Returns:
Dictionary with feedback and documents
@ -44,8 +44,8 @@ class AgentDocumentation(AgentBase):
try:
# Extract task information
prompt = task.get("prompt", "")
input_documents = task.get("input_documents", [])
output_specs = task.get("output_specifications", [])
inputDocuments = task.get("inputDocuments", [])
outputSpecs = task.get("outputSpecifications", [])
# Check AI service
if not self.mydom:
@ -54,43 +54,43 @@ class AgentDocumentation(AgentBase):
"documents": []
}
# Extract context from input documents - focusing only on data_extracted
document_context = self._extract_document_context(input_documents)
# Extract context from input documents - focusing only on dataExtracted
documentContext = self._extractDocumentContext(inputDocuments)
# Create task analysis to understand the requirements
documentation_plan = await self._analyze_task(prompt, document_context, output_specs)
documentationPlan = await self._analyzeTask(prompt, documentContext, outputSpecs)
# Generate all required output documents
documents = []
# If no output specs provided, create default document
if not output_specs:
default_format = documentation_plan.get("recommended_format", "markdown")
default_title = documentation_plan.get("title", "Documentation")
safe_title = self._sanitize_filename(default_title)
if not outputSpecs:
defaultFormat = documentationPlan.get("recommendedFormat", "markdown")
defaultTitle = documentationPlan.get("title", "Documentation")
safeTitle = self._sanitizeFilename(defaultTitle)
output_specs = [
{"label": f"{safe_title}.{default_format}", "description": "Comprehensive documentation"}
outputSpecs = [
{"label": f"{safeTitle}.{defaultFormat}", "description": "Comprehensive documentation"}
]
# Process each output specification
for spec in output_specs:
output_label = spec.get("label", "")
output_description = spec.get("description", "")
for spec in outputSpecs:
outputLabel = spec.get("label", "")
outputDescription = spec.get("description", "")
# Generate the document using multi-step approach
document = await self._create_document_multi_step(
document = await self._createDocumentMultiStep(
prompt,
document_context,
output_label,
output_description,
documentation_plan
documentContext,
outputLabel,
outputDescription,
documentationPlan
)
documents.append(document)
# Generate feedback
feedback = documentation_plan.get("feedback", f"Created {len(documents)} documents based on your requirements.")
feedback = documentationPlan.get("feedback", f"Created {len(documents)} documents based on your requirements.")
return {
"feedback": feedback,
@ -104,9 +104,9 @@ class AgentDocumentation(AgentBase):
"documents": []
}
def _extract_document_context(self, documents: List[Dict[str, Any]]) -> str:
def _extractDocumentContext(self, documents: List[Dict[str, Any]]) -> str:
"""
Extract context from input documents, focusing on data_extracted.
Extract context from input documents, focusing on dataExtracted.
Args:
documents: List of document objects
@ -114,23 +114,23 @@ class AgentDocumentation(AgentBase):
Returns:
Extracted context as text
"""
context_parts = []
contextParts = []
for doc in documents:
doc_name = doc.get("name", "unnamed")
docName = doc.get("name", "unnamed")
if doc.get("ext"):
doc_name = f"{doc_name}.{doc.get('ext')}"
docName = f"{docName}.{doc.get('ext')}"
context_parts.append(f"\n\n--- {doc_name} ---\n")
contextParts.append(f"\n\n--- {docName} ---\n")
# Process contents for data_extracted
# Process contents for dataExtracted
for content in doc.get("contents", []):
if content.get("data_extracted"):
context_parts.append(content.get("data_extracted", ""))
if content.get("dataExtracted"):
contextParts.append(content.get("dataExtracted", ""))
return "\n".join(context_parts)
return "\n".join(contextParts)
def _sanitize_filename(self, filename: str) -> str:
def _sanitizeFilename(self, filename: str) -> str:
"""
Sanitize a filename by removing invalid characters.
@ -141,8 +141,8 @@ class AgentDocumentation(AgentBase):
Sanitized filename
"""
# Replace invalid characters with underscores
invalid_chars = r'<>:"/\|?*'
for char in invalid_chars:
invalidChars = r'<>:"/\|?*'
for char in invalidChars:
filename = filename.replace(char, '_')
# Trim filename if too long
@ -151,19 +151,19 @@ class AgentDocumentation(AgentBase):
return filename
async def _analyze_task(self, prompt: str, context: str, output_specs: List) -> Dict:
async def _analyzeTask(self, prompt: str, context: str, outputSpecs: List) -> Dict:
"""
Use AI to analyze the task and create a documentation plan.
Args:
prompt: The task prompt
context: Document context
output_specs: Output specifications
outputSpecs: Output specifications
Returns:
Documentation plan dictionary
"""
analysis_prompt = f"""
analysisPrompt = f"""
Analyze this documentation task and create a detailed plan.
TASK: {prompt}
@ -172,28 +172,28 @@ class AgentDocumentation(AgentBase):
{context[:1000]}... (truncated)
OUTPUT REQUIREMENTS:
{json.dumps(output_specs, indent=2)}
{json.dumps(outputSpecs, indent=2)}
Create a detailed documentation plan in JSON format with the following structure:
{{
"title": "Document Title",
"document_type": "report|manual|guide|whitepaper|etc",
"documentType": "report|manual|guide|whitepaper|etc",
"audience": "technical|general|executive|etc",
"detailed_structure": [
"detailedStructure": [
{{
"title": "Chapter/Section Title",
"key_points": ["point1", "point2", ...],
"keyPoints": ["point1", "point2", ...],
"subsections": ["subsection1", "subsection2", ...],
"importance": "high|medium|low",
"estimated_length": "short|medium|long"
"estimatedLength": "short|medium|long"
}},
... more sections ...
],
"key_topics": ["topic1", "topic2", ...],
"keyTopics": ["topic1", "topic2", ...],
"tone": "formal|conversational|instructional|etc",
"recommended_format": "markdown|html|text|etc",
"formatting_requirements": ["requirement1", "requirement2", ...],
"executive_summary": "Brief description of what the document will cover",
"recommendedFormat": "markdown|html|text|etc",
"formattingRequirements": ["requirement1", "requirement2", ...],
"executiveSummary": "Brief description of what the document will cover",
"feedback": "Brief message explaining the documentation approach"
}}
@ -201,52 +201,52 @@ class AgentDocumentation(AgentBase):
"""
try:
response = await self.mydom.call_ai([
response = await self.mydom.callAi([
{"role": "system", "content": "You are a documentation expert. Respond with valid JSON only."},
{"role": "user", "content": analysis_prompt}
{"role": "user", "content": analysisPrompt}
])
# Extract JSON from response
json_start = response.find('{')
json_end = response.rfind('}') + 1
jsonStart = response.find('{')
jsonEnd = response.rfind('}') + 1
if json_start >= 0 and json_end > json_start:
plan = json.loads(response[json_start:json_end])
if jsonStart >= 0 and jsonEnd > jsonStart:
plan = json.loads(response[jsonStart:jsonEnd])
return plan
else:
# Fallback if JSON not found
return {
"title": "Documentation",
"document_type": "report",
"documentType": "report",
"audience": "general",
"detailed_structure": [
"detailedStructure": [
{
"title": "Introduction",
"key_points": ["Purpose", "Scope"],
"keyPoints": ["Purpose", "Scope"],
"subsections": [],
"importance": "high",
"estimated_length": "short"
"estimatedLength": "short"
},
{
"title": "Main Content",
"key_points": ["Core Information"],
"keyPoints": ["Core Information"],
"subsections": ["Key Findings", "Analysis"],
"importance": "high",
"estimated_length": "long"
"estimatedLength": "long"
},
{
"title": "Conclusion",
"key_points": ["Summary", "Next Steps"],
"keyPoints": ["Summary", "Next Steps"],
"subsections": [],
"importance": "medium",
"estimated_length": "short"
"estimatedLength": "short"
}
],
"key_topics": ["General Information"],
"keyTopics": ["General Information"],
"tone": "formal",
"recommended_format": "markdown",
"formatting_requirements": ["Clear headings", "Professional formatting"],
"executive_summary": "A comprehensive documentation covering the requested topics.",
"recommendedFormat": "markdown",
"formattingRequirements": ["Clear headings", "Professional formatting"],
"executiveSummary": "A comprehensive documentation covering the requested topics.",
"feedback": "Created documentation based on your requirements."
}
@ -254,59 +254,59 @@ class AgentDocumentation(AgentBase):
logger.warning(f"Error creating documentation plan: {str(e)}")
return {
"title": "Documentation",
"document_type": "report",
"documentType": "report",
"audience": "general",
"detailed_structure": [
"detailedStructure": [
{
"title": "Introduction",
"key_points": ["Purpose", "Scope"],
"keyPoints": ["Purpose", "Scope"],
"subsections": [],
"importance": "high",
"estimated_length": "short"
"estimatedLength": "short"
},
{
"title": "Main Content",
"key_points": ["Core Information"],
"keyPoints": ["Core Information"],
"subsections": ["Key Findings", "Analysis"],
"importance": "high",
"estimated_length": "long"
"estimatedLength": "long"
},
{
"title": "Conclusion",
"key_points": ["Summary", "Next Steps"],
"keyPoints": ["Summary", "Next Steps"],
"subsections": [],
"importance": "medium",
"estimated_length": "short"
"estimatedLength": "short"
}
],
"key_topics": ["General Information"],
"keyTopics": ["General Information"],
"tone": "formal",
"recommended_format": "markdown",
"formatting_requirements": ["Clear headings", "Professional formatting"],
"executive_summary": "A comprehensive documentation covering the requested topics.",
"recommendedFormat": "markdown",
"formattingRequirements": ["Clear headings", "Professional formatting"],
"executiveSummary": "A comprehensive documentation covering the requested topics.",
"feedback": "Created documentation based on your requirements."
}
async def _create_document_multi_step(self, prompt: str, context: str, output_label: str,
output_description: str, documentation_plan: Dict) -> Dict:
async def _createDocumentMultiStep(self, prompt: str, context: str, outputLabel: str,
outputDescription: str, documentationPlan: Dict) -> Dict:
"""
Create a document using a multi-step approach with separate AI calls for each section.
Args:
prompt: Original task prompt
context: Document context
output_label: Output filename
output_description: Description of desired output
documentation_plan: Documentation plan from AI
outputLabel: Output filename
outputDescription: Description of desired output
documentationPlan: Documentation plan from AI
Returns:
Document object
"""
# Determine format from filename
format_type = output_label.split('.')[-1].lower() if '.' in output_label else "md"
formatType = outputLabel.split('.')[-1].lower() if '.' in outputLabel else "md"
# Map format to content_type
content_type_map = {
# Map format to contentType
contentTypeMap = {
"md": "text/markdown",
"markdown": "text/markdown",
"html": "text/html",
@ -316,49 +316,49 @@ class AgentDocumentation(AgentBase):
"csv": "text/csv"
}
content_type = content_type_map.get(format_type, "text/plain")
contentType = contentTypeMap.get(formatType, "text/plain")
# Get document information
title = documentation_plan.get("title", "Documentation")
document_type = documentation_plan.get("document_type", "document")
audience = documentation_plan.get("audience", "general")
tone = documentation_plan.get("tone", "formal")
key_topics = documentation_plan.get("key_topics", [])
formatting_requirements = documentation_plan.get("formatting_requirements", [])
title = documentationPlan.get("title", "Documentation")
documentType = documentationPlan.get("documentType", "document")
audience = documentationPlan.get("audience", "general")
tone = documentationPlan.get("tone", "formal")
keyTopics = documentationPlan.get("keyTopics", [])
formattingRequirements = documentationPlan.get("formattingRequirements", [])
# Get the detailed structure
detailed_structure = documentation_plan.get("detailed_structure", [])
if not detailed_structure:
detailedStructure = documentationPlan.get("detailedStructure", [])
if not detailedStructure:
# Fallback structure if none provided
detailed_structure = [
detailedStructure = [
{
"title": "Introduction",
"key_points": ["Purpose", "Scope"],
"keyPoints": ["Purpose", "Scope"],
"importance": "high"
},
{
"title": "Main Content",
"key_points": ["Core Information"],
"keyPoints": ["Core Information"],
"importance": "high"
},
{
"title": "Conclusion",
"key_points": ["Summary", "Next Steps"],
"keyPoints": ["Summary", "Next Steps"],
"importance": "medium"
}
]
try:
# Step 1: Generate document introduction
intro_prompt = f"""
Create the introduction for a {document_type} titled "{title}".
introPrompt = f"""
Create the introduction for a {documentType} titled "{title}".
DOCUMENT OVERVIEW:
- Type: {document_type}
- Type: {documentType}
- Audience: {audience}
- Tone: {tone}
- Key Topics: {', '.join(key_topics)}
- Format: {format_type}
- Key Topics: {', '.join(keyTopics)}
- Format: {formatType}
TASK CONTEXT: {prompt}
@ -368,23 +368,23 @@ class AgentDocumentation(AgentBase):
3. Outline what the reader will find in the document
4. Set the appropriate tone for the {audience} audience
The introduction should be professional and engaging, formatted according to {format_type} standards.
The introduction should be professional and engaging, formatted according to {formatType} standards.
"""
introduction = await self.mydom.call_ai([
{"role": "system", "content": f"You are a documentation expert creating an introduction in {format_type} format."},
{"role": "user", "content": intro_prompt}
], produce_user_answer = True)
introduction = await self.mydom.callAi([
{"role": "system", "content": f"You are a documentation expert creating an introduction in {formatType} format."},
{"role": "user", "content": introPrompt}
], produceUserAnswer = True)
# Step 2: Generate executive summary (if applicable)
if document_type in ["report", "whitepaper", "case study"]:
summary_prompt = f"""
Create an executive summary for a {document_type} titled "{title}".
if documentType in ["report", "whitepaper", "case study"]:
summaryPrompt = f"""
Create an executive summary for a {documentType} titled "{title}".
DOCUMENT OVERVIEW:
- Type: {document_type}
- Type: {documentType}
- Audience: {audience}
- Key Topics: {', '.join(key_topics)}
- Key Topics: {', '.join(keyTopics)}
TASK CONTEXT: {prompt}
@ -392,44 +392,44 @@ class AgentDocumentation(AgentBase):
1. Provide a concise overview of the entire document
2. Highlight key findings, recommendations, or conclusions
3. Be suitable for executives or busy readers who may only read this section
4. Be professionally formatted according to {format_type} standards
4. Be professionally formatted according to {formatType} standards
Keep the summary focused and impactful, approximately 200-300 words.
"""
executive_summary = await self.mydom.call_ai([
{"role": "system", "content": f"You are a documentation expert creating an executive summary in {format_type} format."},
{"role": "user", "content": summary_prompt}
], produce_user_answer = True)
executiveSummary = await self.mydom.callAi([
{"role": "system", "content": f"You are a documentation expert creating an executive summary in {formatType} format."},
{"role": "user", "content": summaryPrompt}
], produceUserAnswer = True)
else:
executive_summary = ""
executiveSummary = ""
# Step 3: Generate each section
sections = []
for section in detailed_structure:
section_title = section.get("title", "Section")
key_points = section.get("key_points", [])
for section in detailedStructure:
sectionTitle = section.get("title", "Section")
keyPoints = section.get("keyPoints", [])
subsections = section.get("subsections", [])
importance = section.get("importance", "medium")
# Adjust depth based on importance
detail_level = "high" if importance == "high" else "medium"
detailLevel = "high" if importance == "high" else "medium"
section_prompt = f"""
Create the "{section_title}" section for a {document_type} titled "{title}".
sectionPrompt = f"""
Create the "{sectionTitle}" section for a {documentType} titled "{title}".
SECTION DETAILS:
- Title: {section_title}
- Key Points to Cover: {', '.join(key_points)}
- Title: {sectionTitle}
- Key Points to Cover: {', '.join(keyPoints)}
- Subsections: {', '.join(subsections)}
- Detail Level: {detail_level}
- Detail Level: {detailLevel}
DOCUMENT CONTEXT:
- Type: {document_type}
- Type: {documentType}
- Audience: {audience}
- Tone: {tone}
- Format: {format_type}
- Format: {formatType}
TASK CONTEXT: {prompt}
@ -441,27 +441,27 @@ class AgentDocumentation(AgentBase):
2. Cover all the key points listed
3. Include the specified subsections with appropriate headings
4. Maintain a {tone} tone suitable for the {audience} audience
5. Be properly formatted according to {format_type} standards
5. Be properly formatted according to {formatType} standards
6. Include specific examples, data, or evidence where appropriate
Be thorough in your coverage of this section, providing substantive content.
"""
section_content = await self.mydom.call_ai([
{"role": "system", "content": f"You are a documentation expert creating detailed content for the {section_title} section."},
{"role": "user", "content": section_prompt}
], produce_user_answer = True)
sectionContent = await self.mydom.callAi([
{"role": "system", "content": f"You are a documentation expert creating detailed content for the {sectionTitle} section."},
{"role": "user", "content": sectionPrompt}
], produceUserAnswer = True)
sections.append(section_content)
sections.append(sectionContent)
# Step 4: Generate conclusion
conclusion_prompt = f"""
Create the conclusion for a {document_type} titled "{title}".
conclusionPrompt = f"""
Create the conclusion for a {documentType} titled "{title}".
DOCUMENT OVERVIEW:
- Type: {document_type}
- Type: {documentType}
- Audience: {audience}
- Key Topics: {', '.join(key_topics)}
- Key Topics: {', '.join(keyTopics)}
TASK CONTEXT: {prompt}
@ -471,71 +471,71 @@ class AgentDocumentation(AgentBase):
3. Include any relevant recommendations or next steps
4. Leave the reader with a clear understanding of the document's significance
The conclusion should be professional and impactful, formatted according to {format_type} standards.
The conclusion should be professional and impactful, formatted according to {formatType} standards.
"""
conclusion = await self.mydom.call_ai([
{"role": "system", "content": f"You are a documentation expert creating a conclusion in {format_type} format."},
{"role": "user", "content": conclusion_prompt}
], produce_user_answer = True)
conclusion = await self.mydom.callAi([
{"role": "system", "content": f"You are a documentation expert creating a conclusion in {formatType} format."},
{"role": "user", "content": conclusionPrompt}
], produceUserAnswer = True)
# Step 5: Assemble the complete document
if format_type in ["md", "markdown"]:
if formatType in ["md", "markdown"]:
# Markdown format
document_content = f"# {title}\n\n"
documentContent = f"# {title}\n\n"
if executive_summary:
document_content += f"## Executive Summary\n\n{executive_summary}\n\n"
if executiveSummary:
documentContent += f"## Executive Summary\n\n{executiveSummary}\n\n"
document_content += f"{introduction}\n\n"
documentContent += f"{introduction}\n\n"
for i, section_content in enumerate(sections):
for i, sectionContent in enumerate(sections):
# Ensure section starts with heading if not already
section_title = detailed_structure[i].get("title", f"Section {i+1}")
if not section_content.strip().startswith("#"):
document_content += f"## {section_title}\n\n"
document_content += f"{section_content}\n\n"
sectionTitle = detailedStructure[i].get("title", f"Section {i+1}")
if not sectionContent.strip().startswith("#"):
documentContent += f"## {sectionTitle}\n\n"
documentContent += f"{sectionContent}\n\n"
document_content += f"## Conclusion\n\n{conclusion}\n"
documentContent += f"## Conclusion\n\n{conclusion}\n"
elif format_type == "html":
elif formatType == "html":
# HTML format
document_content = f"<html>\n<head>\n<title>{title}</title>\n</head>\n<body>\n"
document_content += f"<h1>{title}</h1>\n\n"
documentContent = f"<html>\n<head>\n<title>{title}</title>\n</head>\n<body>\n"
documentContent += f"<h1>{title}</h1>\n\n"
if executive_summary:
document_content += f"<h2>Executive Summary</h2>\n<div>{executive_summary}</div>\n\n"
if executiveSummary:
documentContent += f"<h2>Executive Summary</h2>\n<div>{executiveSummary}</div>\n\n"
document_content += f"<div>{introduction}</div>\n\n"
documentContent += f"<div>{introduction}</div>\n\n"
for i, section_content in enumerate(sections):
section_title = detailed_structure[i].get("title", f"Section {i+1}")
document_content += f"<h2>{section_title}</h2>\n<div>{section_content}</div>\n\n"
for i, sectionContent in enumerate(sections):
sectionTitle = detailedStructure[i].get("title", f"Section {i+1}")
documentContent += f"<h2>{sectionTitle}</h2>\n<div>{sectionContent}</div>\n\n"
document_content += f"<h2>Conclusion</h2>\n<div>{conclusion}</div>\n"
document_content += "</body>\n</html>"
documentContent += f"<h2>Conclusion</h2>\n<div>{conclusion}</div>\n"
documentContent += "</body>\n</html>"
else:
# Plain text format
document_content = f"{title}\n{'=' * len(title)}\n\n"
documentContent = f"{title}\n{'=' * len(title)}\n\n"
if executive_summary:
document_content += f"EXECUTIVE SUMMARY\n{'-' * 17}\n\n{executive_summary}\n\n"
if executiveSummary:
documentContent += f"EXECUTIVE SUMMARY\n{'-' * 17}\n\n{executiveSummary}\n\n"
document_content += f"{introduction}\n\n"
documentContent += f"{introduction}\n\n"
for i, section_content in enumerate(sections):
section_title = detailed_structure[i].get("title", f"Section {i+1}")
document_content += f"{section_title}\n{'-' * len(section_title)}\n\n{section_content}\n\n"
for i, sectionContent in enumerate(sections):
sectionTitle = detailedStructure[i].get("title", f"Section {i+1}")
documentContent += f"{sectionTitle}\n{'-' * len(sectionTitle)}\n\n{sectionContent}\n\n"
document_content += f"CONCLUSION\n{'-' * 10}\n\n{conclusion}\n"
documentContent += f"CONCLUSION\n{'-' * 10}\n\n{conclusion}\n"
# Create document object
return {
"label": output_label,
"content": document_content,
"label": outputLabel,
"content": documentContent,
"metadata": {
"content_type": content_type
"contentType": contentType
}
}
@ -543,23 +543,23 @@ class AgentDocumentation(AgentBase):
logger.error(f"Error creating document: {str(e)}", exc_info=True)
# Create a simple error document
if format_type in ["md", "markdown"]:
if formatType in ["md", "markdown"]:
content = f"# Error in Documentation\n\nThere was an error generating the documentation: {str(e)}"
elif format_type == "html":
elif formatType == "html":
content = f"<html><body><h1>Error in Documentation</h1><p>There was an error generating the documentation: {str(e)}</p></body></html>"
else:
content = f"Error in Documentation\n\nThere was an error generating the documentation: {str(e)}"
return {
"label": output_label,
"label": outputLabel,
"content": content,
"metadata": {
"content_type": content_type
"contentType": contentType
}
}
# Factory function for the Documentation agent
def get_documentation_agent():
def getAgentDocumentation():
"""Returns an instance of the Documentation agent."""
return AgentDocumentation()

View file

@ -14,7 +14,7 @@ from bs4 import BeautifulSoup
import requests
import markdown
from modules.chat_registry import AgentBase
from modules.workflowAgentsRegistry import AgentBase
from modules.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
@ -28,31 +28,31 @@ class AgentWebcrawler(AgentBase):
self.name = "webcrawler"
self.description = "Conducts web research and collects information from online sources"
self.capabilities = [
"web_search",
"information_retrieval",
"data_collection",
"search_results_analysis",
"webpage_content_extraction"
"webSearch",
"informationRetrieval",
"dataCollection",
"searchResultsAnalysis",
"webpageContentExtraction"
]
# Web crawling configuration
self.max_url = int(APP_CONFIG.get("Agent_Webcrawler_MAX_URLS", "5"))
self.max_search_terms = int(APP_CONFIG.get("Agent_Webcrawler_MAX_SEARCH_KEYWORDS", "3"))
self.max_results = int(APP_CONFIG.get("Agent_Webcrawler_MAX_SEARCH_RESULTS", "5"))
self.maxUrl = int(APP_CONFIG.get("Agent_Webcrawler_MAX_URLS", "5"))
self.maxSearchTerms = int(APP_CONFIG.get("Agent_Webcrawler_MAX_SEARCH_KEYWORDS", "3"))
self.maxResults = int(APP_CONFIG.get("Agent_Webcrawler_MAX_SEARCH_RESULTS", "5"))
self.timeout = int(APP_CONFIG.get("Agent_Webcrawler_TIMEOUT", "30"))
self.search_engine = APP_CONFIG.get("Agent_Webcrawler_SEARCH_ENGINE", "https://html.duckduckgo.com/html/?q=")
self.user_agent = APP_CONFIG.get("Agent_Webcrawler_USER_AGENT", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
self.searchEngine = APP_CONFIG.get("Agent_Webcrawler_SEARCH_ENGINE", "https://html.duckduckgo.com/html/?q=")
self.userAgent = APP_CONFIG.get("Agent_Webcrawler_USER_AGENT", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
def set_dependencies(self, mydom=None):
def setDependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.mydom = mydom
async def process_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""
Process a task by focusing on required outputs and using AI to guide the research process.
Args:
task: Task dictionary with prompt, input_documents, output_specifications
task: Task dictionary with prompt, inputDocuments, outputSpecifications
Returns:
Dictionary with feedback and documents
@ -60,7 +60,7 @@ class AgentWebcrawler(AgentBase):
try:
# Extract task information
prompt = task.get("prompt", "")
output_specs = task.get("output_specifications", [])
outputSpecs = task.get("outputSpecifications", [])
# Check AI service
if not self.mydom:
@ -70,28 +70,28 @@ class AgentWebcrawler(AgentBase):
}
# Create research plan
research_plan = await self._create_research_plan(prompt)
researchPlan = await self._createResearchPlan(prompt)
# Check if this is truly a web research task
if not research_plan.get("requires_web_research", True):
if not researchPlan.get("requiresWebResearch", True):
return {
"feedback": "This task doesn't appear to require web research. Please try a different agent.",
"documents": []
}
# Gather raw material through web research
raw_results = await self._gather_research_material(research_plan)
rawResults = await self._gatherResearchMaterial(researchPlan)
# Format results into requested output documents
documents = await self._create_output_documents(
documents = await self._createOutputDocuments(
prompt,
raw_results,
output_specs,
research_plan
rawResults,
outputSpecs,
researchPlan
)
# Generate feedback
feedback = research_plan.get("feedback", f"I conducted web research on '{prompt[:50]}...' and gathered information from {len(raw_results)} relevant sources.")
feedback = researchPlan.get("feedback", f"I conducted web research on '{prompt[:50]}...' and gathered information from {len(rawResults)} relevant sources.")
return {
"feedback": feedback,
@ -105,7 +105,7 @@ class AgentWebcrawler(AgentBase):
"documents": []
}
async def _create_research_plan(self, prompt: str) -> Dict[str, Any]:
async def _createResearchPlan(self, prompt: str) -> Dict[str, Any]:
"""
Use AI to create a detailed research plan.
@ -115,17 +115,17 @@ class AgentWebcrawler(AgentBase):
Returns:
Research plan dictionary
"""
research_prompt = f"""
researchPrompt = f"""
Create a detailed web research plan for this task: "{prompt}"
Analyze the request carefully and create a structured plan in JSON format with the following elements:
{{
"requires_web_research": true/false, # Whether this genuinely requires web research
"research_questions": ["question1", "question2", ...], # 2-4 specific questions to answer
"search_terms": ["term1", "term2", ...], # Up to {self.max_search_terms} effective search terms
"direct_urls": ["url1", "url2", ...], # Any URLs directly mentioned in the request (up to {self.max_url})
"expected_sources": ["type1", "type2", ...], # Types of sources that would be most valuable
"content_focus": "what specific content to extract or focus on",
"requiresWebResearch": true/false, # Whether this genuinely requires web research
"researchQuestions": ["question1", "question2", ...], # 2-4 specific questions to answer
"searchTerms": ["term1", "term2", ...], # Up to {self.maxSearchTerms} effective search terms
"directUrls": ["url1", "url2", ...], # Any URLs directly mentioned in the request (up to {self.maxUrl})
"expectedSources": ["type1", "type2", ...], # Types of sources that would be most valuable
"contentFocus": "what specific content to extract or focus on",
"feedback": "explanation of how the research will be conducted"
}}
@ -134,37 +134,37 @@ class AgentWebcrawler(AgentBase):
try:
# Get research plan from AI
response = await self.mydom.call_ai([
response = await self.mydom.callAi([
{"role": "system", "content": "You are a web research planning expert. Create precise research plans in JSON format only."},
{"role": "user", "content": research_prompt}
{"role": "user", "content": researchPrompt}
])
# Extract JSON
json_start = response.find('{')
json_end = response.rfind('}') + 1
jsonStart = response.find('{')
jsonEnd = response.rfind('}') + 1
if json_start >= 0 and json_end > json_start:
plan = json.loads(response[json_start:json_end])
if jsonStart >= 0 and jsonEnd > jsonStart:
plan = json.loads(response[jsonStart:jsonEnd])
# Ensure we have the expected fields with defaults if missing
if "search_terms" not in plan:
plan["search_terms"] = [prompt]
if "direct_urls" not in plan:
plan["direct_urls"] = []
if "research_questions" not in plan:
plan["research_questions"] = ["What information can be found about this topic?"]
if "searchTerms" not in plan:
plan["searchTerms"] = [prompt]
if "directUrls" not in plan:
plan["directUrls"] = []
if "researchQuestions" not in plan:
plan["researchQuestions"] = ["What information can be found about this topic?"]
return plan
else:
# Fallback plan
logger.warning(f"Not able creating research plan, generating fallback plan")
return {
"requires_web_research": True,
"research_questions": ["What information can be found about this topic?"],
"search_terms": [prompt],
"direct_urls": [],
"expected_sources": ["Web pages", "Articles"],
"content_focus": "Relevant information about the topic",
"requiresWebResearch": True,
"researchQuestions": ["What information can be found about this topic?"],
"searchTerms": [prompt],
"directUrls": [],
"expectedSources": ["Web pages", "Articles"],
"contentFocus": "Relevant information about the topic",
"feedback": f"I'll conduct web research on '{prompt}' and gather relevant information."
}
@ -172,45 +172,45 @@ class AgentWebcrawler(AgentBase):
logger.warning(f"Error creating research plan: {str(e)}")
# Simple fallback plan
return {
"requires_web_research": True,
"research_questions": ["What information can be found about this topic?"],
"search_terms": [prompt],
"direct_urls": [],
"expected_sources": ["Web pages", "Articles"],
"content_focus": "Relevant information about the topic",
"requiresWebResearch": True,
"researchQuestions": ["What information can be found about this topic?"],
"searchTerms": [prompt],
"directUrls": [],
"expectedSources": ["Web pages", "Articles"],
"contentFocus": "Relevant information about the topic",
"feedback": f"I'll conduct web research on '{prompt}' and gather relevant information."
}
async def _gather_research_material(self, research_plan: Dict[str, Any]) -> List[Dict[str, Any]]:
async def _gatherResearchMaterial(self, researchPlan: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
Gather research material based on the research plan.
Args:
research_plan: Research plan dictionary
researchPlan: Research plan dictionary
Returns:
List of research results
"""
all_results = []
allResults = []
# Process direct URLs
direct_urls = research_plan.get("direct_urls", [])[:self.max_url]
for url in direct_urls:
directUrls = researchPlan.get("directUrls", [])[:self.maxUrl]
for url in directUrls:
logger.info(f"Processing direct URL: {url}")
try:
# Fetch and extract content
soup = self._read_url(url)
soup = self._readUrl(url)
if soup:
# Extract title and content
title = self._extract_title(soup, url)
content = self._extract_main_content(soup)
title = self._extractTitle(soup, url)
content = self._extractMainContent(soup)
# Add to results
all_results.append({
allResults.append({
"title": title,
"url": url,
"source_type": "direct_url",
"sourceType": "directUrl",
"content": content,
"summary": "" # Will be filled later
})
@ -218,48 +218,48 @@ class AgentWebcrawler(AgentBase):
logger.warning(f"Error processing URL {url}: {str(e)}")
# Process search terms
search_terms = research_plan.get("search_terms", [])[:self.max_search_terms]
for term in search_terms:
searchTerms = researchPlan.get("searchTerms", [])[:self.maxSearchTerms]
for term in searchTerms:
logger.info(f"Searching for: {term}")
try:
# Perform search
search_results = self._search_web(term)
searchResults = self._searchWeb(term)
# Process each search result
for result in search_results:
for result in searchResults:
# Check if URL is already in results
if not any(r["url"] == result["url"] for r in all_results):
all_results.append({
if not any(r["url"] == result["url"] for r in allResults):
allResults.append({
"title": result["title"],
"url": result["url"],
"source_type": "search_result",
"sourceType": "searchResult",
"content": result["data"],
"snippet": result["snippet"],
"summary": "" # Will be filled later
})
# Stop if we've reached the maximum results
if len(all_results) >= self.max_results:
if len(allResults) >= self.maxResults:
break
except Exception as e:
logger.warning(f"Error searching for {term}: {str(e)}")
# Stop if we've reached the maximum results
if len(all_results) >= self.max_results:
if len(allResults) >= self.maxResults:
break
# Create summaries in parallel for all results
all_results = await self._summarize_all_results(all_results, research_plan)
allResults = await self._summarizeAllResults(allResults, researchPlan)
return all_results
return allResults
async def _summarize_all_results(self, results: List[Dict[str, Any]], research_plan: Dict[str, Any]) -> List[Dict[str, Any]]:
async def _summarizeAllResults(self, results: List[Dict[str, Any]], researchPlan: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
Create summaries for all research results.
Args:
results: List of research results
research_plan: Research plan with questions and focus
researchPlan: Research plan with questions and focus
Returns:
Results with added summaries
@ -269,16 +269,16 @@ class AgentWebcrawler(AgentBase):
try:
# Limit content length to avoid token issues
content = self._limit_text(result.get("content", ""), max_chars=8000)
research_questions = research_plan.get("research_questions", ["What relevant information does this page contain?"])
content_focus = research_plan.get("content_focus", "Relevant information")
content = self._limitText(result.get("content", ""), maxChars=8000)
researchQuestions = researchPlan.get("researchQuestions", ["What relevant information does this page contain?"])
contentFocus = researchPlan.get("contentFocus", "Relevant information")
# Create summary using AI
summary_prompt = f"""
summaryPrompt = f"""
Summarize this web page content based on these research questions:
{', '.join(research_questions)}
{', '.join(researchQuestions)}
Focus on: {content_focus}
Focus on: {contentFocus}
Web page: {result['url']}
Title: {result['title']}
@ -296,9 +296,9 @@ class AgentWebcrawler(AgentBase):
"""
if self.mydom:
summary = await self.mydom.call_ai([
summary = await self.mydom.callAi([
{"role": "system", "content": "You summarize web content accurately and concisely, focusing only on what is actually in the content."},
{"role": "user", "content": summary_prompt}
{"role": "user", "content": summaryPrompt}
])
# Store the summary
@ -314,24 +314,24 @@ class AgentWebcrawler(AgentBase):
return results
async def _create_output_documents(self, prompt: str, results: List[Dict[str, Any]],
output_specs: List[Dict[str, Any]], research_plan: Dict[str, Any]) -> List[Dict[str, Any]]:
async def _createOutputDocuments(self, prompt: str, results: List[Dict[str, Any]],
outputSpecs: List[Dict[str, Any]], researchPlan: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
Create output documents based on research results and specifications.
Args:
prompt: Original research prompt
results: List of research results
output_specs: Output specifications
research_plan: Research plan
outputSpecs: Output specifications
researchPlan: Research plan
Returns:
List of output documents
"""
# If no output specs provided, create default output
if not output_specs:
output_specs = [{
"label": "web_research_results.md",
if not outputSpecs:
outputSpecs = [{
"label": "webResearchResults.md",
"description": "Comprehensive web research results"
}]
@ -339,66 +339,66 @@ class AgentWebcrawler(AgentBase):
documents = []
# Process each output specification
for spec in output_specs:
output_label = spec.get("label", "")
output_description = spec.get("description", "")
for spec in outputSpecs:
outputLabel = spec.get("label", "")
outputDescription = spec.get("description", "")
# Determine format based on file extension
format_type = self._determine_format_type(output_label)
formatType = self._determineFormatType(outputLabel)
# Create appropriate document based on format
if format_type == "json":
if formatType == "json":
# JSON output - structured data
document = await self._create_json_document(prompt, results, research_plan, output_label)
elif format_type == "csv":
document = await self._createJsonDocument(prompt, results, researchPlan, outputLabel)
elif formatType == "csv":
# CSV output - tabular data
document = await self._create_csv_document(results, output_label)
document = await self._createCsvDocument(results, outputLabel)
else:
# Text-based output (markdown, html, text) - narrative report
document = await self._create_narrative_document(
prompt, results, research_plan, format_type, output_label, output_description
document = await self._createNarrativeDocument(
prompt, results, researchPlan, formatType, outputLabel, outputDescription
)
documents.append(document)
return documents
async def _create_narrative_document(self, prompt: str, results: List[Dict[str, Any]],
research_plan: Dict[str, Any], format_type: str,
output_label: str, output_description: str) -> Dict[str, Any]:
async def _createNarrativeDocument(self, prompt: str, results: List[Dict[str, Any]],
researchPlan: Dict[str, Any], formatType: str,
outputLabel: str, outputDescription: str) -> Dict[str, Any]:
"""
Create a narrative document (markdown, html, text) from research results.
Args:
prompt: Original research prompt
results: Research results
research_plan: Research plan
format_type: Output format (markdown, html, text)
output_label: Output filename
output_description: Output description
researchPlan: Research plan
formatType: Output format (markdown, html, text)
outputLabel: Output filename
outputDescription: Output description
Returns:
Document object
"""
# Create content based on format
if format_type == "markdown":
content_type = "text/markdown"
template_format = "markdown"
elif format_type == "html":
content_type = "text/html"
template_format = "html"
if formatType == "markdown":
contentType = "text/markdown"
templateFormat = "markdown"
elif formatType == "html":
contentType = "text/html"
templateFormat = "html"
else:
content_type = "text/plain"
template_format = "text"
contentType = "text/plain"
templateFormat = "text"
# Prepare research context
research_questions = research_plan.get("research_questions", [])
search_terms = research_plan.get("search_terms", [])
researchQuestions = researchPlan.get("researchQuestions", [])
searchTerms = researchPlan.get("searchTerms", [])
# Create document structure based on results
sources_summary = []
sourcesSummary = []
for result in results:
sources_summary.append({
sourcesSummary.append({
"title": result.get("title", "Untitled"),
"url": result.get("url", ""),
"summary": result.get("summary", ""),
@ -406,35 +406,35 @@ class AgentWebcrawler(AgentBase):
})
# Truncate content for prompt
sources_json = json.dumps(sources_summary, indent=2)
if len(sources_json) > 10000:
sourcesJson = json.dumps(sourcesSummary, indent=2)
if len(sourcesJson) > 10000:
# Logic to truncate each summary while preserving structure
for i in range(len(sources_summary)):
if len(sources_json) <= 10000:
for i in range(len(sourcesSummary)):
if len(sourcesJson) <= 10000:
break
# Gradually truncate summaries
sources_summary[i]["summary"] = sources_summary[i]["summary"][:500] + "..."
sources_json = json.dumps(sources_summary, indent=2)
sourcesSummary[i]["summary"] = sourcesSummary[i]["summary"][:500] + "..."
sourcesJson = json.dumps(sourcesSummary, indent=2)
# Create report prompt
report_prompt = f"""
Create a comprehensive {format_type} research report based on the following web research:
reportPrompt = f"""
Create a comprehensive {formatType} research report based on the following web research:
TASK: {prompt}
RESEARCH QUESTIONS:
{', '.join(research_questions)}
{', '.join(researchQuestions)}
SEARCH TERMS USED:
{', '.join(search_terms)}
{', '.join(searchTerms)}
SOURCES AND FINDINGS:
{sources_json}
{sourcesJson}
REPORT DETAILS:
- Format: {template_format}
- Filename: {output_label}
- Description: {output_description}
- Format: {templateFormat}
- Filename: {outputLabel}
- Description: {outputDescription}
Create a well-structured report that:
1. Includes an executive summary of key findings
@ -442,188 +442,188 @@ class AgentWebcrawler(AgentBase):
3. Integrates information from all relevant sources
4. Cites sources appropriately for each piece of information
5. Provides a comprehensive synthesis of the research
6. Is formatted professionally and appropriately for {template_format}
6. Is formatted professionally and appropriately for {templateFormat}
The report should be scholarly, accurate, and focused on the original research task.
"""
try:
# Generate report with AI
report_content = await self.mydom.call_ai([
{"role": "system", "content": f"You create professional research reports in {template_format} format."},
{"role": "user", "content": report_prompt}
reportContent = await self.mydom.callAi([
{"role": "system", "content": f"You create professional research reports in {templateFormat} format."},
{"role": "user", "content": reportPrompt}
])
# Convert to HTML if needed
if format_type == "html" and not report_content.lower().startswith("<html"):
if formatType == "html" and not reportContent.lower().startswith("<html"):
# Check if it's markdown that needs conversion
if report_content.startswith("#"):
report_content = markdown.markdown(report_content)
if reportContent.startswith("#"):
reportContent = markdown.markdown(reportContent)
# Wrap in basic HTML structure if needed
if not report_content.lower().startswith("<html"):
report_content = f"<html><head><title>Web Research Results</title></head><body>{report_content}</body></html>"
if not reportContent.lower().startswith("<html"):
reportContent = f"<html><head><title>Web Research Results</title></head><body>{reportContent}</body></html>"
return {
"label": output_label,
"content": report_content,
"label": outputLabel,
"content": reportContent,
"metadata": {
"content_type": content_type
"contentType": contentType
}
}
except Exception as e:
logger.error(f"Error creating narrative document: {str(e)}")
# Create error document
if format_type == "markdown":
if formatType == "markdown":
content = f"# Web Research Error\n\nAn error occurred: {str(e)}"
elif format_type == "html":
elif formatType == "html":
content = f"<html><body><h1>Web Research Error</h1><p>An error occurred: {str(e)}</p></body></html>"
else:
content = f"WEB RESEARCH ERROR\n\nAn error occurred: {str(e)}"
return {
"label": output_label,
"label": outputLabel,
"content": content,
"metadata": {
"content_type": content_type
"contentType": contentType
}
}
async def _create_json_document(self, prompt: str, results: List[Dict[str, Any]],
research_plan: Dict[str, Any], output_label: str) -> Dict[str, Any]:
async def _createJsonDocument(self, prompt: str, results: List[Dict[str, Any]],
researchPlan: Dict[str, Any], outputLabel: str) -> Dict[str, Any]:
"""
Create a JSON document from research results.
Args:
prompt: Original research prompt
results: Research results
research_plan: Research plan
output_label: Output filename
researchPlan: Research plan
outputLabel: Output filename
Returns:
Document object
"""
try:
# Create structured data
sources_data = []
sourcesData = []
for result in results:
sources_data.append({
sourcesData.append({
"title": result.get("title", "Untitled"),
"url": result.get("url", ""),
"summary": result.get("summary", ""),
"snippet": result.get("snippet", ""),
"source_type": result.get("source_type", "")
"sourceType": result.get("sourceType", "")
})
# Create metadata
metadata = {
"query": prompt,
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
"research_questions": research_plan.get("research_questions", []),
"search_terms": research_plan.get("search_terms", [])
"researchQuestions": researchPlan.get("researchQuestions", []),
"searchTerms": researchPlan.get("searchTerms", [])
}
# Compile complete report object
json_content = {
jsonContent = {
"metadata": metadata,
"summary": research_plan.get("feedback", "Web research results"),
"sources": sources_data
"summary": researchPlan.get("feedback", "Web research results"),
"sources": sourcesData
}
# Convert to JSON string
content = json.dumps(json_content, indent=2)
content = json.dumps(jsonContent, indent=2)
return {
"label": output_label,
"label": outputLabel,
"content": content,
"metadata": {
"content_type": "application/json"
"contentType": "application/json"
}
}
except Exception as e:
logger.error(f"Error creating JSON document: {str(e)}")
return {
"label": output_label,
"label": outputLabel,
"content": json.dumps({"error": str(e)}),
"metadata": {
"content_type": "application/json"
"contentType": "application/json"
}
}
async def _create_csv_document(self, results: List[Dict[str, Any]], output_label: str) -> Dict[str, Any]:
async def _createCsvDocument(self, results: List[Dict[str, Any]], outputLabel: str) -> Dict[str, Any]:
"""
Create a CSV document from research results.
Args:
results: Research results
output_label: Output filename
outputLabel: Output filename
Returns:
Document object
"""
try:
# Create CSV header
csv_lines = ["Title,URL,Source Type,Snippet"]
csvLines = ["Title,URL,Source Type,Snippet"]
# Add results
for result in results:
# Escape CSV fields
title = result.get("title", "").replace('"', '""')
url = result.get("url", "").replace('"', '""')
source_type = result.get("source_type", "").replace('"', '""')
sourceType = result.get("sourceType", "").replace('"', '""')
snippet = result.get("snippet", "").replace('"', '""')
csv_lines.append(f'"{title}","{url}","{source_type}","{snippet}"')
csvLines.append(f'"{title}","{url}","{sourceType}","{snippet}"')
# Combine into CSV content
content = "\n".join(csv_lines)
content = "\n".join(csvLines)
return {
"label": output_label,
"label": outputLabel,
"content": content,
"metadata": {
"content_type": "text/csv"
"contentType": "text/csv"
}
}
except Exception as e:
logger.error(f"Error creating CSV document: {str(e)}")
return {
"label": output_label,
"label": outputLabel,
"content": "Error,Error\nFailed to create CSV,{0}".format(str(e)),
"metadata": {
"content_type": "text/csv"
"contentType": "text/csv"
}
}
def _determine_format_type(self, output_label: str) -> str:
def _determineFormatType(self, outputLabel: str) -> str:
"""
Determine the format type based on the filename.
Args:
output_label: Output filename
outputLabel: Output filename
Returns:
Format type (markdown, html, text, json, csv)
"""
output_label_lower = output_label.lower()
outputLabelLower = outputLabel.lower()
if output_label_lower.endswith(".md"):
if outputLabelLower.endswith(".md"):
return "markdown"
elif output_label_lower.endswith(".html"):
elif outputLabelLower.endswith(".html"):
return "html"
elif output_label_lower.endswith(".txt"):
elif outputLabelLower.endswith(".txt"):
return "text"
elif output_label_lower.endswith(".json"):
elif outputLabelLower.endswith(".json"):
return "json"
elif output_label_lower.endswith(".csv"):
elif outputLabelLower.endswith(".csv"):
return "csv"
else:
# Default to markdown
return "markdown"
def _search_web(self, query: str) -> List[Dict[str, str]]:
def _searchWeb(self, query: str) -> List[Dict[str, str]]:
"""
Conduct a web search and return the results.
@ -633,11 +633,11 @@ class AgentWebcrawler(AgentBase):
Returns:
List of search results
"""
formatted_query = quote_plus(query)
url = f"{self.search_engine}{formatted_query}"
formattedQuery = quote_plus(query)
url = f"{self.searchEngine}{formattedQuery}"
search_results_soup = self._read_url(url)
if not search_results_soup or not search_results_soup.select('.result'):
searchResultsSoup = self._readUrl(url)
if not searchResultsSoup or not searchResultsSoup.select('.result'):
logger.warning(f"No search results found for: {query}")
return []
@ -645,59 +645,59 @@ class AgentWebcrawler(AgentBase):
results = []
# Find all result containers
result_elements = search_results_soup.select('.result')
resultElements = searchResultsSoup.select('.result')
for result in result_elements:
for result in resultElements:
# Extract title
title_element = result.select_one('.result__a')
title = title_element.text.strip() if title_element else 'No title'
titleElement = result.select_one('.result__a')
title = titleElement.text.strip() if titleElement else 'No title'
# Extract URL (DuckDuckGo uses redirects)
url_element = title_element.get('href') if title_element else ''
extracted_url = 'No URL'
urlElement = titleElement.get('href') if titleElement else ''
extractedUrl = 'No URL'
if url_element:
if urlElement:
# Extract actual URL from DuckDuckGo's redirect
if url_element.startswith('/d.js?q='):
start = url_element.find('?q=') + 3
end = url_element.find('&', start) if '&' in url_element[start:] else None
extracted_url = unquote(url_element[start:end])
if urlElement.startswith('/d.js?q='):
start = urlElement.find('?q=') + 3
end = urlElement.find('&', start) if '&' in urlElement[start:] else None
extractedUrl = unquote(urlElement[start:end])
# Ensure URL has correct protocol prefix
if not extracted_url.startswith(('http://', 'https://')):
if not extracted_url.startswith('//'):
extracted_url = 'https://' + extracted_url
if not extractedUrl.startswith(('http://', 'https://')):
if not extractedUrl.startswith('//'):
extractedUrl = 'https://' + extractedUrl
else:
extracted_url = 'https:' + extracted_url
extractedUrl = 'https:' + extractedUrl
else:
extracted_url = url_element
extractedUrl = urlElement
# Extract snippet directly from search results page
snippet_element = result.select_one('.result__snippet')
snippet = snippet_element.text.strip() if snippet_element else 'No description'
snippetElement = result.select_one('.result__snippet')
snippet = snippetElement.text.strip() if snippetElement else 'No description'
# Get actual page content
try:
target_page_soup = self._read_url(extracted_url)
content = self._extract_main_content(target_page_soup)
targetPageSoup = self._readUrl(extractedUrl)
content = self._extractMainContent(targetPageSoup)
except Exception as e:
logger.warning(f"Error extracting content from {extracted_url}: {str(e)}")
logger.warning(f"Error extracting content from {extractedUrl}: {str(e)}")
content = f"Error extracting content: {str(e)}"
results.append({
'title': title,
'url': extracted_url,
'url': extractedUrl,
'snippet': snippet,
'data': content
})
# Limit number of results
if len(results) >= self.max_results:
if len(results) >= self.maxResults:
break
return results
def _read_url(self, url: str) -> BeautifulSoup:
def _readUrl(self, url: str) -> BeautifulSoup:
"""
Read a URL and return a BeautifulSoup parser for the content.
@ -711,7 +711,7 @@ class AgentWebcrawler(AgentBase):
return None
headers = {
'User-Agent': self.user_agent,
'User-Agent': self.userAgent,
'Accept': 'text/html,application/xhtml+xml,application/xml',
'Accept-Language': 'en-US,en;q=0.9',
}
@ -723,10 +723,10 @@ class AgentWebcrawler(AgentBase):
# Handling for status 202
if response.status_code == 202:
# Retry with backoff
backoff_times = [0.5, 1.0, 2.0, 5.0]
backoffTimes = [0.5, 1.0, 2.0, 5.0]
for wait_time in backoff_times:
time.sleep(wait_time)
for waitTime in backoffTimes:
time.sleep(waitTime)
response = requests.get(url, headers=headers, timeout=self.timeout)
if response.status_code != 202:
@ -742,7 +742,7 @@ class AgentWebcrawler(AgentBase):
logger.error(f"Error reading URL {url}: {str(e)}")
return None
def _extract_title(self, soup: BeautifulSoup, url: str) -> str:
def _extractTitle(self, soup: BeautifulSoup, url: str) -> str:
"""
Extract the title from a webpage.
@ -757,24 +757,24 @@ class AgentWebcrawler(AgentBase):
return f"Error with {url}"
# Extract title from title tag
title_tag = soup.find('title')
title = title_tag.text.strip() if title_tag else "No title"
titleTag = soup.find('title')
title = titleTag.text.strip() if titleTag else "No title"
# Alternative: Also look for h1 tags if title tag is missing
if title == "No title":
h1_tag = soup.find('h1')
if h1_tag:
title = h1_tag.text.strip()
h1Tag = soup.find('h1')
if h1Tag:
title = h1Tag.text.strip()
return title
def _extract_main_content(self, soup: BeautifulSoup, max_chars: int = 10000) -> str:
def _extractMainContent(self, soup: BeautifulSoup, maxChars: int = 10000) -> str:
"""
Extract the main content from an HTML page.
Args:
soup: BeautifulSoup object of the webpage
max_chars: Maximum number of characters
maxChars: Maximum number of characters
Returns:
Extracted main content as a string
@ -783,34 +783,34 @@ class AgentWebcrawler(AgentBase):
return ""
# Try to find main content elements in priority order
main_content = None
mainContent = None
for selector in ['main', 'article', '#content', '.content', '#main', '.main']:
content = soup.select_one(selector)
if content:
main_content = content
mainContent = content
break
# If no main content found, use the body
if not main_content:
main_content = soup.find('body') or soup
if not mainContent:
mainContent = soup.find('body') or soup
# Remove script, style, nav, footer elements that don't contribute to main content
for element in main_content.select('script, style, nav, footer, header, aside, .sidebar, #sidebar, .comments, #comments, .advertisement, .ads, iframe'):
for element in mainContent.select('script, style, nav, footer, header, aside, .sidebar, #sidebar, .comments, #comments, .advertisement, .ads, iframe'):
element.extract()
# Extract text content
text_content = main_content.get_text(separator=' ', strip=True)
textContent = mainContent.get_text(separator=' ', strip=True)
# Limit to max_chars
return text_content[:max_chars]
# Limit to maxChars
return textContent[:maxChars]
def _limit_text(self, text: str, max_chars: int = 10000) -> str:
def _limitText(self, text: str, maxChars: int = 10000) -> str:
"""
Limit text to a maximum number of characters.
Args:
text: Input text
max_chars: Maximum number of characters
maxChars: Maximum number of characters
Returns:
Limited text
@ -819,14 +819,14 @@ class AgentWebcrawler(AgentBase):
return ""
# If text is already under the limit, return unchanged
if len(text) <= max_chars:
if len(text) <= maxChars:
return text
# Otherwise limit text to max_chars
return text[:max_chars] + "... [Content truncated due to length]"
# Otherwise limit text to maxChars
return text[:maxChars] + "... [Content truncated due to length]"
# Factory function for the Webcrawler agent
def get_webcrawler_agent():
def getAgentWebcrawler():
"""Returns an instance of the Webcrawler agent."""
return AgentWebcrawler()

View file

@ -1,3 +1,8 @@
"""
Authentication module for backend API.
Handles JWT-based authentication, token generation, and user context.
"""
from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, Any, Tuple
from fastapi import Depends, HTTPException, status
@ -5,7 +10,7 @@ from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
import logging
from modules.gateway_interface import get_gateway_interface
from modules.gatewayInterface import getGatewayInterface
from modules.configuration import APP_CONFIG
# Get Config Data
@ -13,39 +18,36 @@ SECRET_KEY = APP_CONFIG.get("APP_JWT_SECRET_SECRET")
ALGORITHM = APP_CONFIG.get("Auth_ALGORITHM")
ACCESS_TOKEN_EXPIRE_MINUTES = int(APP_CONFIG.get("APP_TOKEN_EXPIRY"))
# OAuth2 Setup
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
oauth2Scheme = OAuth2PasswordBearer(tokenUrl="token")
# Logger
logger = logging.getLogger(__name__)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
def createAccessToken(data: dict, expiresDelta: Optional[timedelta] = None) -> str:
"""
Creates a JWT Access Token.
Args:
data: Data to encode (usually user ID or username)
expires_delta: Validity duration of the token (optional)
expiresDelta: Validity duration of the token (optional)
Returns:
JWT Token as string
"""
to_encode = data.copy()
toEncode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
if expiresDelta:
expire = datetime.now(timezone.utc) + expiresDelta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
toEncode.update({"exp": expire})
encodedJwt = jwt.encode(toEncode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
return encodedJwt
async def get_current_user(token: str = Depends(oauth2_scheme)) -> Dict[str, Any]:
async def getCurrentUser(token: str = Depends(oauth2Scheme)) -> Dict[str, Any]:
"""
Extracts and validates the current user from the JWT token.
@ -58,7 +60,7 @@ async def get_current_user(token: str = Depends(oauth2_scheme)) -> Dict[str, Any
Raises:
HTTPException: For invalid token or user
"""
credentials_exception = HTTPException(
credentialsException = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
@ -71,24 +73,24 @@ async def get_current_user(token: str = Depends(oauth2_scheme)) -> Dict[str, Any
# Extract username from token
username: str = payload.get("sub")
if username is None:
raise credentials_exception
raise credentialsException
# Extract mandate ID from token (if present)
mandate_id: int = payload.get("mandate_id", 1) # Default: Root mandate
mandateId: int = payload.get("mandateId", 1) # Default: Root mandate
except JWTError:
logger.warning("Invalid JWT Token")
raise credentials_exception
raise credentialsException
# Initialize Gateway Interface without context
gateway = get_gateway_interface()
gateway = getGatewayInterface()
# Retrieve user from database
user = gateway.get_user_by_username(username)
user = gateway.getUserByUsername(username)
if user is None:
logger.warning(f"User {username} not found")
raise credentials_exception
raise credentialsException
if user.get("disabled", False):
logger.warning(f"User {username} is disabled")
@ -96,13 +98,12 @@ async def get_current_user(token: str = Depends(oauth2_scheme)) -> Dict[str, Any
return user
async def get_current_active_user(current_user: Dict[str, Any] = Depends(get_current_user)) -> Dict[str, Any]:
async def getCurrentActiveUser(currentUser: Dict[str, Any] = Depends(getCurrentUser)) -> Dict[str, Any]:
"""
Ensures that the user is active.
Args:
current_user: Current user data
currentUser: Current user data
Returns:
User data
@ -110,50 +111,48 @@ async def get_current_active_user(current_user: Dict[str, Any] = Depends(get_cur
Raises:
HTTPException: If the user is disabled
"""
if current_user.get("disabled", False):
if currentUser.get("disabled", False):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is disabled")
return current_user
return currentUser
async def get_user_context(current_user: Dict[str, Any]) -> Tuple[int, int]:
async def getUserContext(currentUser: Dict[str, Any]) -> Tuple[int, int]:
"""
Extracts the mandate ID and user ID from the current user.
Enhanced with better logging.
Args:
current_user: The current user
currentUser: The current user
Returns:
Tuple of (mandate_id, user_id)
Tuple of (mandateId, userId)
"""
# Default values
default_mandate_id = 0
default_user_id = 0
defaultMandateId = 0
defaultUserId = 0
# Extract mandate_id
mandate_id = current_user.get("mandate_id", None)
if mandate_id is None:
logger.warning(f"No mandate_id found in current_user, using default: {default_mandate_id}")
mandate_id = default_mandate_id
# Extract mandateId
mandateId = currentUser.get("mandateId", None)
if mandateId is None:
logger.warning(f"No mandateId found in currentUser, using default: {defaultMandateId}")
mandateId = defaultMandateId
else:
try:
mandate_id = int(mandate_id)
mandateId = int(mandateId)
except (ValueError, TypeError):
logger.error(f"Invalid mandate_id value: {mandate_id}, using default: {default_mandate_id}")
mandate_id = default_mandate_id
logger.error(f"Invalid mandateId value: {mandateId}, using default: {defaultMandateId}")
mandateId = defaultMandateId
# Extract user_id
user_id = current_user.get("id", None)
if user_id is None:
logger.warning(f"No user_id found in current_user, using default: {default_user_id}")
user_id = default_user_id
# Extract userId
userId = currentUser.get("id", None)
if userId is None:
logger.warning(f"No userId found in currentUser, using default: {defaultUserId}")
userId = defaultUserId
else:
try:
user_id = int(user_id)
userId = int(userId)
except (ValueError, TypeError):
logger.error(f"Invalid user_id value: {user_id}, using default: {default_user_id}")
user_id = default_user_id
logger.error(f"Invalid userId value: {userId}, using default: {defaultUserId}")
userId = defaultUserId
# logger.info(f"User context: mandate_id={mandate_id}, user_id={user_id}")
return mandate_id, user_id
return mandateId, userId

File diff suppressed because it is too large Load diff

View file

@ -1,778 +0,0 @@
"""
Module for extracting content from various file formats.
Provides specialized functions for processing text, PDF, Office documents, images, etc.
"""
import logging
import os
import io
from typing import Dict, Any, List, Optional, Union, Tuple
import base64
# Configure logger
logger = logging.getLogger(__name__)
# Optional imports - only loaded when needed
pdf_extractor_loaded = False
office_extractor_loaded = False
image_processor_loaded = False
def get_document_contents(file_metadata: Dict[str, Any], file_content: bytes) -> List[Dict[str, Any]]:
"""
Main function for extracting content from a file based on its MIME type.
Delegates to specialized extraction functions.
Args:
file_metadata: File metadata (Name, MIME type, etc.)
file_content: Binary data of the file
Returns:
List of Document-Content objects with metadata and is_text flag
"""
try:
mime_type = file_metadata.get("mime_type", "application/octet-stream")
file_name = file_metadata.get("name", "unknown")
logger.info(f"Extracting content from file '{file_name}' (MIME type: {mime_type})")
# Extract content based on MIME type
contents = []
# Text-based formats
if mime_type.startswith("text/") or mime_type in [
"application/json",
"application/xml",
"application/javascript",
"application/x-python"
]:
contents.extend(extract_text_content(file_name, file_content, mime_type))
# CSV Format
elif mime_type == "text/csv":
contents.extend(extract_csv_content(file_name, file_content))
# Images
elif mime_type.startswith("image/"):
contents.extend(extract_image_content(file_name, file_content, mime_type))
# PDF Documents
elif mime_type == "application/pdf":
contents.extend(extract_pdf_content(file_name, file_content))
# Word Documents
elif mime_type in [
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/msword"
]:
contents.extend(extract_word_content(file_name, file_content, mime_type))
# Excel Documents
elif mime_type in [
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-excel"
]:
contents.extend(extract_excel_content(file_name, file_content, mime_type))
# PowerPoint Documents
elif mime_type in [
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.ms-powerpoint"
]:
contents.extend(extract_powerpoint_content(file_name, file_content, mime_type))
# Binary data as fallback for unknown formats
else:
contents.extend(extract_binary_content(file_name, file_content, mime_type))
# Fallback when no content could be extracted
if not contents:
logger.warning(f"No content extracted from file '{file_name}', using binary fallback")
contents.append({
"sequence_nr": 1,
"name": '1_undefined',
"ext": os.path.splitext(file_name)[1][1:] if os.path.splitext(file_name)[1] else "bin",
"content_type": mime_type,
"data": file_content,
"metadata": {
"is_text": False
}
})
# Add generic attributes for all documents
for content in contents:
if isinstance(content.get("data"), bytes):
content["data"] = base64.b64encode(content["data"]).decode('utf-8')
# Add base64 flag
if "metadata" not in content:
content["metadata"] = {}
content["metadata"]["base64_encoded"] = True
logger.info(f"Successfully extracted {len(contents)} content items from file '{file_name}'")
return contents
except Exception as e:
logger.error(f"Error during content extraction: {str(e)}")
# Fallback on error - return original data
return [{
"sequence_nr": 1,
"name": file_metadata.get("name", "unknown"),
"ext": os.path.splitext(file_metadata.get("name", ""))[1][1:] if os.path.splitext(file_metadata.get("name", ""))[1] else "bin",
"content_type": file_metadata.get("mime_type", "application/octet-stream"),
"data": file_content,
"metadata": {
"is_text": False
}
}]
def _load_pdf_extractor():
"""Loads PDF extraction libraries when needed"""
global pdf_extractor_loaded
if not pdf_extractor_loaded:
try:
global PyPDF2, fitz
import PyPDF2
import fitz # PyMuPDF for more extensive PDF processing
pdf_extractor_loaded = True
logger.info("PDF extraction libraries successfully loaded")
except ImportError as e:
logger.warning(f"PDF extraction libraries could not be loaded: {e}")
def _load_office_extractor():
"""Loads Office document extraction libraries when needed"""
global office_extractor_loaded
if not office_extractor_loaded:
try:
global docx, openpyxl
import docx # python-docx for Word documents
import openpyxl # for Excel files
office_extractor_loaded = True
logger.info("Office extraction libraries successfully loaded")
except ImportError as e:
logger.warning(f"Office extraction libraries could not be loaded: {e}")
def _load_image_processor():
"""Loads image processing libraries when needed"""
global image_processor_loaded
if not image_processor_loaded:
try:
global PIL, Image
from PIL import Image
image_processor_loaded = True
logger.info("Image processing libraries successfully loaded")
except ImportError as e:
logger.warning(f"Image processing libraries could not be loaded: {e}")
def extract_text_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
"""
Extracts text from text files.
Args:
file_name: Name of the file
file_content: Binary data of the file
mime_type: MIME type of the file
Returns:
List of Text-Content objects with metadata.is_text = True
"""
try:
# Keep original file extension
file_extension = os.path.splitext(file_name)[1][1:] if os.path.splitext(file_name)[1] else "txt"
# Extract text content
text_content = file_content.decode('utf-8')
return [{
"sequence_nr": 1,
"name": "1_text", # Simplified naming
"ext": file_extension,
"content_type": "text",
"data": text_content,
"metadata": {
"is_text": True
}
}]
except UnicodeDecodeError:
logger.warning(f"Could not decode text from file '{file_name}' as UTF-8, trying alternative encodings")
try:
# Try alternative encodings
for encoding in ['latin-1', 'cp1252', 'iso-8859-1']:
try:
text_content = file_content.decode(encoding)
logger.info(f"Text successfully decoded with encoding {encoding}")
return [{
"sequence_nr": 1,
"name": "1_text", # Simplified naming
"ext": file_extension,
"content_type": "text",
"data": text_content,
"metadata": {
"is_text": True,
"encoding": encoding
}
}]
except UnicodeDecodeError:
continue
# Fallback to binary data if no encoding works
logger.warning(f"Could not decode text, using binary data")
return [{
"sequence_nr": 1,
"name": "1_binary", # Simplified naming
"ext": file_extension,
"content_type": mime_type,
"data": file_content,
"metadata": {
"is_text": False
}
}]
except Exception as e:
logger.error(f"Error in alternative text decoding: {str(e)}")
# Return binary data as fallback
return [{
"sequence_nr": 1,
"name": "1_binary", # Simplified naming
"ext": file_extension,
"content_type": mime_type,
"data": file_content,
"metadata": {
"is_text": False
}
}]
def extract_csv_content(file_name: str, file_content: bytes) -> List[Dict[str, Any]]:
"""
Extracts content from CSV files.
Args:
file_name: Name of the file
file_content: Binary data of the file
Returns:
List of CSV-Content objects with metadata.is_text = True
"""
try:
# Extract text content
csv_content = file_content.decode('utf-8')
return [{
"sequence_nr": 1,
"name": "1_csv", # Simplified naming
"ext": "csv",
"content_type": "csv",
"data": csv_content,
"metadata": {
"is_text": True,
"format": "csv"
}
}]
except UnicodeDecodeError:
logger.warning(f"Could not decode CSV from file '{file_name}' as UTF-8, trying alternative encodings")
try:
# Try alternative encodings for CSV
for encoding in ['latin-1', 'cp1252', 'iso-8859-1']:
try:
csv_content = file_content.decode(encoding)
logger.info(f"CSV successfully decoded with encoding {encoding}")
return [{
"sequence_nr": 1,
"name": "1_csv", # Simplified naming
"ext": "csv",
"content_type": "csv",
"data": csv_content,
"metadata": {
"is_text": True,
"encoding": encoding,
"format": "csv"
}
}]
except UnicodeDecodeError:
continue
# Fallback to binary data
return [{
"sequence_nr": 1,
"name": "1_binary", # Simplified naming
"ext": "csv",
"content_type": "text/csv",
"data": file_content,
"metadata": {
"is_text": False
}
}]
except Exception as e:
logger.error(f"Error in alternative CSV decoding: {str(e)}")
return [{
"sequence_nr": 1,
"name": "1_binary", # Simplified naming
"ext": "csv",
"content_type": "text/csv",
"data": file_content,
"metadata": {
"is_text": False
}
}]
def extract_image_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
"""
Extracts content from image files and optionally generates metadata descriptions.
Args:
file_name: Name of the file
file_content: Binary data of the file
mime_type: MIME type of the file
Returns:
List of Image-Content objects with metadata.is_text = False
"""
# Extract file extension from MIME type or filename
file_extension = mime_type.split('/')[-1]
if file_extension == "jpeg":
file_extension = "jpg"
# If possible, analyze image and extract metadata
image_metadata = {
"is_text": False,
"format": "image"
}
image_description = None
try:
_load_image_processor()
if image_processor_loaded and file_content and len(file_content) > 0:
with io.BytesIO(file_content) as img_stream:
try:
img = Image.open(img_stream)
# Check if the image was actually loaded
img.verify()
# To safely continue working, reload
img_stream.seek(0)
img = Image.open(img_stream)
image_metadata.update({
"format": img.format,
"mode": img.mode,
"width": img.width,
"height": img.height
})
# Extract EXIF data if available
if hasattr(img, '_getexif') and callable(img._getexif):
exif = img._getexif()
if exif:
exif_data = {}
for tag_id, value in exif.items():
exif_data[f"tag_{tag_id}"] = str(value)
image_metadata["exif"] = exif_data
# Generate image description
image_description = f"Image ({img.width}x{img.height}, {img.format}, {img.mode})"
except Exception as inner_e:
logger.warning(f"Error processing image: {str(inner_e)}")
image_metadata["error"] = str(inner_e)
image_description = f"Image (unable to process: {str(inner_e)})"
except Exception as e:
logger.warning(f"Could not extract image metadata: {str(e)}")
image_metadata["error"] = str(e)
# Return image content
contents = [{
"sequence_nr": 1,
"name": "1_image", # Simplified naming
"ext": file_extension,
"content_type": "image",
"data": file_content,
"metadata": image_metadata
}]
# If image description available, add as additional text content
if image_description:
contents.append({
"sequence_nr": 2,
"name": "2_text_image_info", # Simplified naming with label
"ext": "txt",
"content_type": "text",
"data": image_description,
"metadata": {
"is_text": True,
"image_description": True
}
})
return contents
def extract_pdf_content(file_name: str, file_content: bytes) -> List[Dict[str, Any]]:
"""
Extracts text and images from PDF files.
Args:
file_name: Name of the file
file_content: Binary data of the file
Returns:
List of PDF-Content objects (text and images) with metadata.is_text flag
"""
contents = []
extracted_content_found = False
try:
# Load PDF extraction libraries
_load_pdf_extractor()
if not pdf_extractor_loaded:
logger.warning("PDF extraction not possible: Libraries not available")
# Add original file as binary content
contents.append({
"sequence_nr": 1,
"name": "1_pdf", # Simplified naming
"ext": "pdf",
"content_type": "application/pdf",
"data": file_content,
"metadata": {
"is_text": False,
"format": "pdf"
}
})
return contents
# Extract text with PyPDF2
extracted_text = ""
pdf_metadata = {}
with io.BytesIO(file_content) as pdf_stream:
pdf_reader = PyPDF2.PdfReader(pdf_stream)
# Extract metadata
pdf_info = pdf_reader.metadata or {}
for key, value in pdf_info.items():
if key.startswith('/'):
pdf_metadata[key[1:]] = value
else:
pdf_metadata[key] = value
# Extract text from all pages
for page_num in range(len(pdf_reader.pages)):
page = pdf_reader.pages[page_num]
page_text = page.extract_text()
if page_text:
extracted_text += f"--- Page {page_num + 1} ---\n{page_text}\n\n"
# If text was found, add as separate content
if extracted_text.strip():
extracted_content_found = True
contents.append({
"sequence_nr": len(contents) + 1,
"name": f"{len(contents) + 1}_text", # Simplified naming
"ext": "txt",
"content_type": "text",
"data": extracted_text,
"metadata": {
"is_text": True,
"source": "pdf",
"pages": len(pdf_reader.pages),
"pdf_metadata": pdf_metadata
}
})
# Extract images with PyMuPDF (fitz)
try:
with io.BytesIO(file_content) as pdf_stream:
doc = fitz.open(stream=pdf_stream, filetype="pdf")
image_count = 0
for page_num in range(len(doc)):
page = doc[page_num]
image_list = page.get_images(full=True)
for img_index, img_info in enumerate(image_list):
try:
image_count += 1
xref = img_info[0]
base_image = doc.extract_image(xref)
image_bytes = base_image["image"]
image_ext = base_image["ext"]
# Add image as content
extracted_content_found = True
contents.append({
"sequence_nr": len(contents) + 1,
"name": f"{len(contents) + 1}_image_page{page_num+1}_{img_index+1}", # Simplified naming with label
"ext": image_ext,
"content_type": f"image/{image_ext}",
"data": image_bytes,
"metadata": {
"is_text": False,
"source": "pdf",
"page": page_num + 1,
"index": img_index
}
})
except Exception as img_e:
logger.warning(f"Error extracting image {img_index} on page {page_num + 1}: {str(img_e)}")
# Close document
doc.close()
except Exception as img_extract_e:
logger.warning(f"Error extracting images from PDF: {str(img_extract_e)}")
except Exception as e:
logger.error(f"Error in PDF extraction: {str(e)}")
# If no content was extracted, add the original PDF
if not extracted_content_found:
contents.append({
"sequence_nr": 1,
"name": "1_pdf", # Simplified naming
"ext": "pdf",
"content_type": "application/pdf",
"data": file_content,
"metadata": {
"is_text": False,
"format": "pdf"
}
})
return contents
def extract_word_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
"""
Extracts text and images from Word documents.
Args:
file_name: Name of the file
file_content: Binary data of the file
mime_type: MIME type of the file
Returns:
List of Word-Content objects (text and possibly images) with metadata.is_text flag
"""
contents = []
extracted_content_found = False
# Determine file extension
file_extension = "docx" if mime_type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" else "doc"
try:
# Load Office extraction libraries
_load_office_extractor()
if not office_extractor_loaded:
logger.warning("Word extraction not possible: Libraries not available")
# Add original file as binary content
contents.append({
"sequence_nr": 1,
"name": "1_word", # Simplified naming
"ext": file_extension,
"content_type": mime_type,
"data": file_content,
"metadata": {
"is_text": False,
"format": "word"
}
})
return contents
# Only supports DOCX (newer format)
if mime_type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
with io.BytesIO(file_content) as docx_stream:
doc = docx.Document(docx_stream)
# Extract text
full_text = []
for para in doc.paragraphs:
full_text.append(para.text)
# Extract tables
for table in doc.tables:
for row in table.rows:
row_text = []
for cell in row.cells:
row_text.append(cell.text)
full_text.append(" | ".join(row_text))
extracted_text = "\n\n".join(full_text)
# Add extracted text as content
if extracted_text.strip():
extracted_content_found = True
contents.append({
"sequence_nr": 1,
"name": "1_text", # Simplified naming
"ext": "txt",
"content_type": "text",
"data": extracted_text,
"metadata": {
"is_text": True,
"source": "docx",
"paragraph_count": len(doc.paragraphs),
"table_count": len(doc.tables)
}
})
else:
logger.warning(f"Extraction from old Word format (DOC) not supported")
except Exception as e:
logger.error(f"Error in Word extraction: {str(e)}")
# If no content was extracted, add the original document
if not extracted_content_found:
contents.append({
"sequence_nr": 1,
"name": "1_word", # Simplified naming
"ext": file_extension,
"content_type": mime_type,
"data": file_content,
"metadata": {
"is_text": False,
"format": "word"
}
})
return contents
def extract_excel_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
"""
Extracts table data from Excel files.
Args:
file_name: Name of the file
file_content: Binary data of the file
mime_type: MIME type of the file
Returns:
List of Excel-Content objects with metadata.is_text flag
"""
contents = []
extracted_content_found = False
# Determine file extension
file_extension = "xlsx" if mime_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" else "xls"
try:
# Load Office extraction libraries
_load_office_extractor()
if not office_extractor_loaded:
logger.warning("Excel extraction not possible: Libraries not available")
# Add original file as binary content
contents.append({
"sequence_nr": 1,
"name": "1_excel", # Simplified naming
"ext": file_extension,
"content_type": mime_type,
"data": file_content,
"metadata": {
"is_text": False,
"format": "excel"
}
})
return contents
# Only supports XLSX (newer format)
if mime_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
with io.BytesIO(file_content) as xlsx_stream:
workbook = openpyxl.load_workbook(xlsx_stream, data_only=True)
# Extract each worksheet as separate CSV content
for sheet_index, sheet_name in enumerate(workbook.sheetnames):
sheet = workbook[sheet_name]
# Format data as CSV
csv_rows = []
for row in sheet.iter_rows():
csv_row = []
for cell in row:
value = cell.value
if value is None:
csv_row.append("")
else:
csv_row.append(str(value).replace('"', '""'))
csv_rows.append(','.join(f'"{cell}"' for cell in csv_row))
csv_content = "\n".join(csv_rows)
# Add as CSV content
if csv_content.strip():
extracted_content_found = True
sheet_safe_name = sheet_name.replace(" ", "_").replace("/", "_").replace("\\", "_")
contents.append({
"sequence_nr": len(contents) + 1,
"name": f"{len(contents) + 1}_csv_{sheet_safe_name}", # Simplified naming with sheet label
"ext": "csv",
"content_type": "csv",
"data": csv_content,
"metadata": {
"is_text": True,
"source": "xlsx",
"sheet": sheet_name,
"format": "csv"
}
})
else:
logger.warning(f"Extraction from old Excel format (XLS) not supported")
except Exception as e:
logger.error(f"Error in Excel extraction: {str(e)}")
# If no content was extracted, add the original document
if not extracted_content_found:
contents.append({
"sequence_nr": 1,
"name": "1_excel", # Simplified naming
"ext": file_extension,
"content_type": mime_type,
"data": file_content,
"metadata": {
"is_text": False,
"format": "excel"
}
})
return contents
def extract_powerpoint_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
"""
Extracts content from PowerPoint presentations.
Args:
file_name: Name of the file
file_content: Binary data of the file
mime_type: MIME type of the file
Returns:
List of PowerPoint-Content objects with metadata.is_text = False
"""
# For PowerPoint, we currently only return the original binary file
# A complete extraction would require more specialized libraries
file_extension = "pptx" if mime_type == "application/vnd.openxmlformats-officedocument.presentationml.presentation" else "ppt"
return [{
"sequence_nr": 1,
"name": "1_powerpoint", # Simplified naming
"ext": file_extension,
"content_type": mime_type,
"data": file_content,
"metadata": {
"is_text": False,
"format": "powerpoint"
}
}]
def extract_binary_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
"""
Fallback for binary files where no specific extraction is possible.
Args:
file_name: Name of the file
file_content: Binary data of the file
mime_type: MIME type of the file
Returns:
List with a binary Content object with metadata.is_text = False
"""
file_extension = os.path.splitext(file_name)[1][1:] if os.path.splitext(file_name)[1] else "bin"
return [{
"sequence_nr": 1,
"name": "1_binary", # Simplified naming
"ext": file_extension,
"content_type": mime_type,
"data": file_content,
"metadata": {
"is_text": False,
"format": "binary"
}
}]

View file

@ -20,40 +20,40 @@ class Configuration:
def __init__(self):
"""Initialize the configuration object"""
self._data = {}
self._config_file_path = None
self._env_file_path = None
self._config_mtime = 0
self._env_mtime = 0
self._configFilePath = None
self._envFilePath = None
self._configMtime = 0
self._envMtime = 0
self.refresh()
def refresh(self):
"""Reload configuration from files"""
self._load_config()
self._load_env()
self._loadConfig()
self._loadEnv()
logger.info("Configuration refreshed")
def _load_config(self):
def _loadConfig(self):
"""Load configuration from config.ini file in flattened format"""
# Find config.ini file (look in current directory and parent directory)
config_path = Path('config.ini')
if not config_path.exists():
configPath = Path('config.ini')
if not configPath.exists():
# Try in parent directory
config_path = Path('../config.ini')
if not config_path.exists():
logger.warning(f"Configuration file not found at {config_path.absolute()}")
configPath = Path('../config.ini')
if not configPath.exists():
logger.warning(f"Configuration file not found at {configPath.absolute()}")
return
self._config_file_path = config_path
current_mtime = os.path.getmtime(config_path)
self._configFilePath = configPath
currentMtime = os.path.getmtime(configPath)
# Skip if file hasn't changed
if current_mtime <= self._config_mtime:
if currentMtime <= self._configMtime:
return
self._config_mtime = current_mtime
self._configMtime = currentMtime
try:
with open(config_path, 'r') as f:
with open(configPath, 'r') as f:
for line in f:
line = line.strip()
# Skip empty lines and comments
@ -73,28 +73,28 @@ class Configuration:
except Exception as e:
logger.error(f"Error loading configuration: {e}")
def _load_env(self):
def _loadEnv(self):
"""Load environment variables from .env file"""
# Find .env file (look in current directory and parent directory)
env_path = Path('.env')
if not env_path.exists():
envPath = Path('.env')
if not envPath.exists():
# Try in parent directory
env_path = Path('../.env')
if not env_path.exists():
logger.warning(f"Environment file not found at {env_path.absolute()}")
envPath = Path('../.env')
if not envPath.exists():
logger.warning(f"Environment file not found at {envPath.absolute()}")
return
self._env_file_path = env_path
current_mtime = os.path.getmtime(env_path)
self._envFilePath = envPath
currentMtime = os.path.getmtime(envPath)
# Skip if file hasn't changed
if current_mtime <= self._env_mtime:
if currentMtime <= self._envMtime:
return
self._env_mtime = current_mtime
self._envMtime = currentMtime
try:
with open(env_path, 'r') as f:
with open(envPath, 'r') as f:
for line in f:
line = line.strip()
# Skip empty lines and comments
@ -110,7 +110,7 @@ class Configuration:
# Add directly to data dictionary
self._data[key] = value
logger.info(f"Loaded environment variables from {env_path.absolute()}")
logger.info(f"Loaded environment variables from {envPath.absolute()}")
# Also load system environment variables (don't override existing)
for key, value in os.environ.items():
@ -120,35 +120,35 @@ class Configuration:
except Exception as e:
logger.error(f"Error loading environment variables: {e}")
def check_for_updates(self):
def checkForUpdates(self):
"""Check if configuration files have changed and reload if necessary"""
if self._config_file_path and os.path.exists(self._config_file_path):
current_mtime = os.path.getmtime(self._config_file_path)
if current_mtime > self._config_mtime:
if self._configFilePath and os.path.exists(self._configFilePath):
currentMtime = os.path.getmtime(self._configFilePath)
if currentMtime > self._configMtime:
logger.info("Config file has changed, reloading...")
self._load_config()
self._loadConfig()
if self._env_file_path and os.path.exists(self._env_file_path):
current_mtime = os.path.getmtime(self._env_file_path)
if current_mtime > self._env_mtime:
if self._envFilePath and os.path.exists(self._envFilePath):
currentMtime = os.path.getmtime(self._envFilePath)
if currentMtime > self._envMtime:
logger.info("Environment file has changed, reloading...")
self._load_env()
self._loadEnv()
def get(self, key: str, default: Any = None) -> Any:
"""Get configuration value with optional default"""
self.check_for_updates() # Check for file changes
self.checkForUpdates() # Check for file changes
if key in self._data:
value = self._data[key]
# Handle secrets (keys ending with _SECRET)
if key.endswith("_SECRET"):
return handle_secret(value)
return handleSecret(value)
return value
return default
def __getattr__(self, name: str) -> Any:
"""Enable attribute-style access to configuration"""
self.check_for_updates() # Check for file changes
self.checkForUpdates() # Check for file changes
value = self.get(name)
if value is None:
@ -157,14 +157,14 @@ class Configuration:
def __dir__(self) -> list:
"""Support auto-completion of attributes"""
self.check_for_updates() # Check for file changes
self.checkForUpdates() # Check for file changes
return list(self._data.keys()) + super().__dir__()
def set(self, key: str, value: Any) -> None:
"""Set a configuration value (for testing/overrides)"""
self._data[key] = value
def handle_secret(value: str) -> str:
def handleSecret(value: str) -> str:
"""
Handle secret values. Currently just returns the plain text value,
but can be enhanced to provide actual decryption in the future.

View file

@ -8,16 +8,16 @@ class AttributeDefinition(BaseModel):
type: str
required: bool = False
placeholder: Optional[str] = None
default_value: Optional[Any] = None
defaultValue: Optional[Any] = None
options: Optional[List[Dict[str, Any]]] = None
editable: bool = True
visible: bool = True
order: int = 0
validation: Optional[Dict[str, Any]] = None
help_text: Optional[str] = None
helpText: Optional[str] = None
# Helper classes for type mapping
type_mappings = {
typeMappings = {
"int": "number",
"str": "string",
"float": "number",
@ -31,59 +31,59 @@ type_mappings = {
}
# Special field types based on naming conventions
special_field_types = {
specialFieldTypes = {
"content": "textarea",
"description": "textarea",
"instructions": "textarea",
"password": "password",
"email": "email",
"workspace_id": "select",
"agent_id": "select",
"workspaceId": "select",
"agentId": "select",
"type": "select"
}
# Function to convert a Pydantic model into attribute definitions
def get_model_attributes(model_class, user_language="de"):
def getModelAttributes(modelClass, userLanguage="de"):
"""
Converts a Pydantic model into a list of AttributeDefinition objects
"""
attributes = []
# Go through all fields in the model
for i, (field_name, field) in enumerate(model_class.__fields__.items()):
for i, (fieldName, field) in enumerate(modelClass.__fields__.items()):
# Skip internal fields
if field_name.startswith('_') or field_name in ["label", "field_labels"]:
if fieldName.startswith('_') or fieldName in ["label", "fieldLabels"]:
continue
# Determine the field type
field_type = type_mappings.get(str(field.type_), "string")
fieldType = typeMappings.get(str(field.type_), "string")
# Check for special field types
if field_name in special_field_types:
field_type = special_field_types[field_name]
if fieldName in specialFieldTypes:
fieldType = specialFieldTypes[fieldName]
# Get the label (if available)
field_label = field_name.replace('_', ' ').capitalize()
if hasattr(model_class, 'field_labels') and field_name in model_class.field_labels:
label_obj = model_class.field_labels[field_name]
field_label = label_obj.get_label(user_language)
fieldLabel = fieldName.replace('_', ' ').capitalize()
if hasattr(modelClass, 'fieldLabels') and fieldName in modelClass.fieldLabels:
labelObj = modelClass.fieldLabels[fieldName]
fieldLabel = labelObj.getLabel(userLanguage)
# Determine default values and required status
required = field.required
default_value = field.default if not field.required else None
defaultValue = field.default if not field.required else None
# Check for validation rules
validation = None
if field.validators:
validation = {"has_validators": True}
validation = {"hasValidators": True}
# Placeholder text
placeholder = f"Please enter {field_label}"
placeholder = f"Please enter {fieldLabel}"
# Special options for Select fields
options = None
if field_type == "select":
if field_name == "type" and model_class.__name__ == "Agent":
if fieldType == "select":
if fieldName == "type" and modelClass.__name__ == "Agent":
options = [
{"value": "Analysis", "label": "Analysis"},
{"value": "Transformation", "label": "Transformation"},
@ -103,21 +103,21 @@ def get_model_attributes(model_class, user_language="de"):
description = field.schema.description
# Create attribute definition
attr_def = AttributeDefinition(
name=field_name,
label=field_label,
type=field_type,
attrDef = AttributeDefinition(
name=fieldName,
label=fieldLabel,
type=fieldType,
required=required,
placeholder=placeholder,
default_value=default_value,
defaultValue=defaultValue,
options=options,
editable=field_name not in ["id", "mandate_id", "user_id", "created_at", "upload_date"],
visible=field_name not in ["hashed_password", "mandate_id", "user_id"],
editable=fieldName not in ["id", "mandateId", "userId", "createdAt", "uploadDate"],
visible=fieldName not in ["hashedPassword", "mandateId", "userId"],
order=i,
validation=validation,
help_text=description or "" # Set empty string as default value if no description found
helpText=description or "" # Set empty string as default value if no description found
)
attributes.append(attr_def)
attributes.append(attrDef)
return attributes

View file

@ -0,0 +1,887 @@
"""
Module for extracting content from various file formats.
Provides specialized functions for processing text, PDF, Office documents, images, etc.
"""
import logging
import os
import io
from typing import Dict, Any, List, Optional, Union, Tuple
import base64
# Configure logger
logger = logging.getLogger(__name__)
# Optional imports - only loaded when needed
pdfExtractorLoaded = False
officeExtractorLoaded = False
imageProcessorLoaded = False
def getDocumentContents(fileMetadata: Dict[str, Any], fileContent: bytes) -> List[Dict[str, Any]]:
"""
Main function for extracting content from a file based on its MIME type.
Delegates to specialized extraction functions.
Args:
fileMetadata: File metadata (Name, MIME type, etc.)
fileContent: Binary data of the file
Returns:
List of Document-Content objects with metadata and isText flag
"""
try:
mimeType = fileMetadata.get("mimeType", "application/octet-stream")
fileName = fileMetadata.get("name", "unknown")
logger.info(f"Extracting content from file '{fileName}' (MIME type: {mimeType})")
# Extract content based on MIME type
contents = []
# Text-based formats
if mimeType.startswith("text/") or mimeType in [
"application/json",
"application/xml",
"application/javascript",
"application/x-python"
]:
contents.extend(extractTextContent(fileName, fileContent, mimeType))
# CSV Format
elif mimeType == "text/csv":
contents.extend(extractCsvContent(fileName, fileContent))
# SVG Files
elif mimeType == "image/svg+xml":
contents.extend(extractSvgContent(fileName, fileContent))
# Images
elif mimeType.startswith("image/"):
contents.extend(extractImageContent(fileName, fileContent, mimeType))
# PDF Documents
elif mimeType == "application/pdf":
contents.extend(extractPdfContent(fileName, fileContent))
# Word Documents
elif mimeType in [
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/msword"
]:
contents.extend(extractWordContent(fileName, fileContent, mimeType))
# Excel Documents
elif mimeType in [
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-excel"
]:
contents.extend(extractExcelContent(fileName, fileContent, mimeType))
# PowerPoint Documents
elif mimeType in [
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.ms-powerpoint"
]:
contents.extend(extractPowerpointContent(fileName, fileContent, mimeType))
# Binary data as fallback for unknown formats
else:
contents.extend(extractBinaryContent(fileName, fileContent, mimeType))
# Fallback when no content could be extracted
if not contents:
logger.warning(f"No content extracted from file '{fileName}', using binary fallback")
contents.append({
"sequenceNr": 1,
"name": '1_undefined',
"ext": os.path.splitext(fileName)[1][1:] if os.path.splitext(fileName)[1] else "bin",
"contentType": mimeType,
"data": fileContent,
"metadata": {
"isText": False
}
})
# Add generic attributes for all documents
for content in contents:
if isinstance(content.get("data"), bytes):
content["data"] = base64.b64encode(content["data"]).decode('utf-8')
# Add base64 flag
if "metadata" not in content:
content["metadata"] = {}
content["metadata"]["base64Encoded"] = True
logger.info(f"Successfully extracted {len(contents)} content items from file '{fileName}'")
return contents
except Exception as e:
logger.error(f"Error during content extraction: {str(e)}")
# Fallback on error - return original data
return [{
"sequenceNr": 1,
"name": fileMetadata.get("name", "unknown"),
"ext": os.path.splitext(fileMetadata.get("name", ""))[1][1:] if os.path.splitext(fileMetadata.get("name", ""))[1] else "bin",
"contentType": fileMetadata.get("mimeType", "application/octet-stream"),
"data": fileContent,
"metadata": {
"isText": False
}
}]
def _loadPdfExtractor():
"""Loads PDF extraction libraries when needed"""
global pdfExtractorLoaded
if not pdfExtractorLoaded:
try:
global PyPDF2, fitz
import PyPDF2
import fitz # PyMuPDF for more extensive PDF processing
pdfExtractorLoaded = True
logger.info("PDF extraction libraries successfully loaded")
except ImportError as e:
logger.warning(f"PDF extraction libraries could not be loaded: {e}")
def _loadOfficeExtractor():
"""Loads Office document extraction libraries when needed"""
global officeExtractorLoaded
if not officeExtractorLoaded:
try:
global docx, openpyxl
import docx # python-docx for Word documents
import openpyxl # for Excel files
officeExtractorLoaded = True
logger.info("Office extraction libraries successfully loaded")
except ImportError as e:
logger.warning(f"Office extraction libraries could not be loaded: {e}")
def _loadImageProcessor():
"""Loads image processing libraries when needed"""
global imageProcessorLoaded
if not imageProcessorLoaded:
try:
global PIL, Image
from PIL import Image
imageProcessorLoaded = True
logger.info("Image processing libraries successfully loaded")
except ImportError as e:
logger.warning(f"Image processing libraries could not be loaded: {e}")
def extractTextContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]:
"""
Extracts text from text files.
Args:
fileName: Name of the file
fileContent: Binary data of the file
mimeType: MIME type of the file
Returns:
List of Text-Content objects with metadata.isText = True
"""
try:
# Keep original file extension
fileExtension = os.path.splitext(fileName)[1][1:] if os.path.splitext(fileName)[1] else "txt"
# Extract text content
textContent = fileContent.decode('utf-8')
return [{
"sequenceNr": 1,
"name": "1_text", # Simplified naming
"ext": fileExtension,
"contentType": "text",
"data": textContent,
"metadata": {
"isText": True
}
}]
except UnicodeDecodeError:
logger.warning(f"Could not decode text from file '{fileName}' as UTF-8, trying alternative encodings")
try:
# Try alternative encodings
for encoding in ['latin-1', 'cp1252', 'iso-8859-1']:
try:
textContent = fileContent.decode(encoding)
logger.info(f"Text successfully decoded with encoding {encoding}")
return [{
"sequenceNr": 1,
"name": "1_text", # Simplified naming
"ext": fileExtension,
"contentType": "text",
"data": textContent,
"metadata": {
"isText": True,
"encoding": encoding
}
}]
except UnicodeDecodeError:
continue
# Fallback to binary data if no encoding works
logger.warning(f"Could not decode text, using binary data")
return [{
"sequenceNr": 1,
"name": "1_binary", # Simplified naming
"ext": fileExtension,
"contentType": mimeType,
"data": fileContent,
"metadata": {
"isText": False
}
}]
except Exception as e:
logger.error(f"Error in alternative text decoding: {str(e)}")
# Return binary data as fallback
return [{
"sequenceNr": 1,
"name": "1_binary", # Simplified naming
"ext": fileExtension,
"contentType": mimeType,
"data": fileContent,
"metadata": {
"isText": False
}
}]
def extractCsvContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]]:
"""
Extracts content from CSV files.
Args:
fileName: Name of the file
fileContent: Binary data of the file
Returns:
List of CSV-Content objects with metadata.isText = True
"""
try:
# Extract text content
csvContent = fileContent.decode('utf-8')
return [{
"sequenceNr": 1,
"name": "1_csv", # Simplified naming
"ext": "csv",
"contentType": "csv",
"data": csvContent,
"metadata": {
"isText": True,
"format": "csv"
}
}]
except UnicodeDecodeError:
logger.warning(f"Could not decode CSV from file '{fileName}' as UTF-8, trying alternative encodings")
try:
# Try alternative encodings for CSV
for encoding in ['latin-1', 'cp1252', 'iso-8859-1']:
try:
csvContent = fileContent.decode(encoding)
logger.info(f"CSV successfully decoded with encoding {encoding}")
return [{
"sequenceNr": 1,
"name": "1_csv", # Simplified naming
"ext": "csv",
"contentType": "csv",
"data": csvContent,
"metadata": {
"isText": True,
"encoding": encoding,
"format": "csv"
}
}]
except UnicodeDecodeError:
continue
# Fallback to binary data
return [{
"sequenceNr": 1,
"name": "1_binary", # Simplified naming
"ext": "csv",
"contentType": "text/csv",
"data": fileContent,
"metadata": {
"isText": False
}
}]
except Exception as e:
logger.error(f"Error in alternative CSV decoding: {str(e)}")
return [{
"sequenceNr": 1,
"name": "1_binary", # Simplified naming
"ext": "csv",
"contentType": "text/csv",
"data": fileContent,
"metadata": {
"isText": False
}
}]
def extractSvgContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]]:
"""
Extracts content from SVG files.
Args:
fileName: Name of the file
fileContent: Binary data of the file
Returns:
List of SVG-Content objects with dual text/image metadata
"""
contents = []
try:
# Extract SVG as text content (XML)
svgText = fileContent.decode('utf-8')
# Check if it's actually SVG by looking for the SVG tag
if "<svg" in svgText.lower():
# SVG is both text (XML) and an image
contents.append({
"sequenceNr": 1,
"name": "1_svg", # Simplified naming
"ext": "svg",
"contentType": "image/svg+xml",
"data": svgText,
"metadata": {
"isText": True, # SVG is text-based (XML)
"format": "svg",
"isImage": True # But also represents an image
}
})
else:
# Doesn't appear to be a valid SVG file
logger.warning(f"File '{fileName}' has SVG extension but does not contain SVG markup")
contents.append({
"sequenceNr": 1,
"name": "1_text",
"ext": "svg",
"contentType": "text/plain",
"data": svgText,
"metadata": {
"isText": True,
"format": "text"
}
})
except UnicodeDecodeError:
logger.warning(f"Could not decode SVG from file '{fileName}' as UTF-8, trying alternative encodings")
try:
# Try alternative encodings
for encoding in ['latin-1', 'cp1252', 'iso-8859-1']:
try:
svgText = fileContent.decode(encoding)
if "<svg" in svgText.lower():
logger.info(f"SVG successfully decoded with encoding {encoding}")
contents.append({
"sequenceNr": 1,
"name": "1_svg", # Simplified naming
"ext": "svg",
"contentType": "image/svg+xml",
"data": svgText,
"metadata": {
"isText": True,
"format": "svg",
"isImage": True,
"encoding": encoding
}
})
break
except UnicodeDecodeError:
continue
# Fallback to binary data if no encoding works
if not contents:
logger.warning(f"Could not decode SVG text, using binary data")
contents.append({
"sequenceNr": 1,
"name": "1_binary", # Simplified naming
"ext": "svg",
"contentType": "image/svg+xml",
"data": fileContent,
"metadata": {
"isText": False,
"format": "svg",
"isImage": True
}
})
except Exception as e:
logger.error(f"Error in alternative SVG decoding: {str(e)}")
# Return binary data as fallback
contents.append({
"sequenceNr": 1,
"name": "1_binary", # Simplified naming
"ext": "svg",
"contentType": "image/svg+xml",
"data": fileContent,
"metadata": {
"isText": False,
"format": "svg",
"isImage": True
}
})
return contents
def extractImageContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]:
"""
Extracts content from image files and optionally generates metadata descriptions.
Args:
fileName: Name of the file
fileContent: Binary data of the file
mimeType: MIME type of the file
Returns:
List of Image-Content objects with metadata.isText = False
"""
# Extract file extension from MIME type or filename
fileExtension = mimeType.split('/')[-1]
if fileExtension == "jpeg":
fileExtension = "jpg"
# If possible, analyze image and extract metadata
imageMetadata = {
"isText": False,
"format": "image"
}
imageDescription = None
try:
_loadImageProcessor()
if imageProcessorLoaded and fileContent and len(fileContent) > 0:
with io.BytesIO(fileContent) as imgStream:
try:
img = Image.open(imgStream)
# Check if the image was actually loaded
img.verify()
# To safely continue working, reload
imgStream.seek(0)
img = Image.open(imgStream)
imageMetadata.update({
"format": img.format,
"mode": img.mode,
"width": img.width,
"height": img.height
})
# Extract EXIF data if available
if hasattr(img, '_getexif') and callable(img._getexif):
exif = img._getexif()
if exif:
exifData = {}
for tagId, value in exif.items():
exifData[f"tag_{tagId}"] = str(value)
imageMetadata["exif"] = exifData
# Generate image description
imageDescription = f"Image ({img.width}x{img.height}, {img.format}, {img.mode})"
except Exception as innerE:
logger.warning(f"Error processing image: {str(innerE)}")
imageMetadata["error"] = str(innerE)
imageDescription = f"Image (unable to process: {str(innerE)})"
except Exception as e:
logger.warning(f"Could not extract image metadata: {str(e)}")
imageMetadata["error"] = str(e)
# Return image content
contents = [{
"sequenceNr": 1,
"name": "1_image", # Simplified naming
"ext": fileExtension,
"contentType": "image",
"data": fileContent,
"metadata": imageMetadata
}]
# If image description available, add as additional text content
if imageDescription:
contents.append({
"sequenceNr": 2,
"name": "2_text_image_info", # Simplified naming with label
"ext": "txt",
"contentType": "text",
"data": imageDescription,
"metadata": {
"isText": True,
"imageDescription": True
}
})
return contents
def extractPdfContent(fileName: str, fileContent: bytes) -> List[Dict[str, Any]]:
"""
Extracts text and images from PDF files.
Args:
fileName: Name of the file
fileContent: Binary data of the file
Returns:
List of PDF-Content objects (text and images) with metadata.isText flag
"""
contents = []
extractedContentFound = False
try:
# Load PDF extraction libraries
_loadPdfExtractor()
if not pdfExtractorLoaded:
logger.warning("PDF extraction not possible: Libraries not available")
# Add original file as binary content
contents.append({
"sequenceNr": 1,
"name": "1_pdf", # Simplified naming
"ext": "pdf",
"contentType": "application/pdf",
"data": fileContent,
"metadata": {
"isText": False,
"format": "pdf"
}
})
return contents
# Extract text with PyPDF2
extractedText = ""
pdfMetadata = {}
with io.BytesIO(fileContent) as pdfStream:
pdfReader = PyPDF2.PdfReader(pdfStream)
# Extract metadata
pdfInfo = pdfReader.metadata or {}
for key, value in pdfInfo.items():
if key.startswith('/'):
pdfMetadata[key[1:]] = value
else:
pdfMetadata[key] = value
# Extract text from all pages
for pageNum in range(len(pdfReader.pages)):
page = pdfReader.pages[pageNum]
pageText = page.extract_text()
if pageText:
extractedText += f"--- Page {pageNum + 1} ---\n{pageText}\n\n"
# If text was found, add as separate content
if extractedText.strip():
extractedContentFound = True
contents.append({
"sequenceNr": len(contents) + 1,
"name": f"{len(contents) + 1}_text", # Simplified naming
"ext": "txt",
"contentType": "text",
"data": extractedText,
"metadata": {
"isText": True,
"source": "pdf",
"pages": len(pdfReader.pages),
"pdfMetadata": pdfMetadata
}
})
# Extract images with PyMuPDF (fitz)
try:
with io.BytesIO(fileContent) as pdfStream:
doc = fitz.open(stream=pdfStream, filetype="pdf")
imageCount = 0
for pageNum in range(len(doc)):
page = doc[pageNum]
imageList = page.get_images(full=True)
for imgIndex, imgInfo in enumerate(imageList):
try:
imageCount += 1
xref = imgInfo[0]
baseImage = doc.extract_image(xref)
imageBytes = baseImage["image"]
imageExt = baseImage["ext"]
# Add image as content
extractedContentFound = True
contents.append({
"sequenceNr": len(contents) + 1,
"name": f"{len(contents) + 1}_image_page{pageNum+1}_{imgIndex+1}", # Simplified naming with label
"ext": imageExt,
"contentType": f"image/{imageExt}",
"data": imageBytes,
"metadata": {
"isText": False,
"source": "pdf",
"page": pageNum + 1,
"index": imgIndex
}
})
except Exception as imgE:
logger.warning(f"Error extracting image {imgIndex} on page {pageNum + 1}: {str(imgE)}")
# Close document
doc.close()
except Exception as imgExtractE:
logger.warning(f"Error extracting images from PDF: {str(imgExtractE)}")
except Exception as e:
logger.error(f"Error in PDF extraction: {str(e)}")
# If no content was extracted, add the original PDF
if not extractedContentFound:
contents.append({
"sequenceNr": 1,
"name": "1_pdf", # Simplified naming
"ext": "pdf",
"contentType": "application/pdf",
"data": fileContent,
"metadata": {
"isText": False,
"format": "pdf"
}
})
return contents
def extractWordContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]:
"""
Extracts text and images from Word documents.
Args:
fileName: Name of the file
fileContent: Binary data of the file
mimeType: MIME type of the file
Returns:
List of Word-Content objects (text and possibly images) with metadata.isText flag
"""
contents = []
extractedContentFound = False
# Determine file extension
fileExtension = "docx" if mimeType == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" else "doc"
try:
# Load Office extraction libraries
_loadOfficeExtractor()
if not officeExtractorLoaded:
logger.warning("Word extraction not possible: Libraries not available")
# Add original file as binary content
contents.append({
"sequenceNr": 1,
"name": "1_word", # Simplified naming
"ext": fileExtension,
"contentType": mimeType,
"data": fileContent,
"metadata": {
"isText": False,
"format": "word"
}
})
return contents
# Only supports DOCX (newer format)
if mimeType == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
with io.BytesIO(fileContent) as docxStream:
doc = docx.Document(docxStream)
# Extract text
fullText = []
for para in doc.paragraphs:
fullText.append(para.text)
# Extract tables
for table in doc.tables:
for row in table.rows:
rowText = []
for cell in row.cells:
rowText.append(cell.text)
fullText.append(" | ".join(rowText))
extractedText = "\n\n".join(fullText)
# Add extracted text as content
if extractedText.strip():
extractedContentFound = True
contents.append({
"sequenceNr": 1,
"name": "1_text", # Simplified naming
"ext": "txt",
"contentType": "text",
"data": extractedText,
"metadata": {
"isText": True,
"source": "docx",
"paragraphCount": len(doc.paragraphs),
"tableCount": len(doc.tables)
}
})
else:
logger.warning(f"Extraction from old Word format (DOC) not supported")
except Exception as e:
logger.error(f"Error in Word extraction: {str(e)}")
# If no content was extracted, add the original document
if not extractedContentFound:
contents.append({
"sequenceNr": 1,
"name": "1_word", # Simplified naming
"ext": fileExtension,
"contentType": mimeType,
"data": fileContent,
"metadata": {
"isText": False,
"format": "word"
}
})
return contents
def extractExcelContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]:
"""
Extracts table data from Excel files.
Args:
fileName: Name of the file
fileContent: Binary data of the file
mimeType: MIME type of the file
Returns:
List of Excel-Content objects with metadata.isText flag
"""
contents = []
extractedContentFound = False
# Determine file extension
fileExtension = "xlsx" if mimeType == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" else "xls"
try:
# Load Office extraction libraries
_loadOfficeExtractor()
if not officeExtractorLoaded:
logger.warning("Excel extraction not possible: Libraries not available")
# Add original file as binary content
contents.append({
"sequenceNr": 1,
"name": "1_excel", # Simplified naming
"ext": fileExtension,
"contentType": mimeType,
"data": fileContent,
"metadata": {
"isText": False,
"format": "excel"
}
})
return contents
# Only supports XLSX (newer format)
if mimeType == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
with io.BytesIO(fileContent) as xlsxStream:
workbook = openpyxl.load_workbook(xlsxStream, data_only=True)
# Extract each worksheet as separate CSV content
for sheetIndex, sheetName in enumerate(workbook.sheetnames):
sheet = workbook[sheetName]
# Format data as CSV
csvRows = []
for row in sheet.iter_rows():
csvRow = []
for cell in row:
value = cell.value
if value is None:
csvRow.append("")
else:
csvRow.append(str(value).replace('"', '""'))
csvRows.append(','.join(f'"{cell}"' for cell in csvRow))
csvContent = "\n".join(csvRows)
# Add as CSV content
if csvContent.strip():
extractedContentFound = True
sheetSafeName = sheetName.replace(" ", "_").replace("/", "_").replace("\\", "_")
contents.append({
"sequenceNr": len(contents) + 1,
"name": f"{len(contents) + 1}_csv_{sheetSafeName}", # Simplified naming with sheet label
"ext": "csv",
"contentType": "csv",
"data": csvContent,
"metadata": {
"isText": True,
"source": "xlsx",
"sheet": sheetName,
"format": "csv"
}
})
else:
logger.warning(f"Extraction from old Excel format (XLS) not supported")
except Exception as e:
logger.error(f"Error in Excel extraction: {str(e)}")
# If no content was extracted, add the original document
if not extractedContentFound:
contents.append({
"sequenceNr": 1,
"name": "1_excel", # Simplified naming
"ext": fileExtension,
"contentType": mimeType,
"data": fileContent,
"metadata": {
"isText": False,
"format": "excel"
}
})
return contents
def extractPowerpointContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]:
"""
Extracts content from PowerPoint presentations.
Args:
fileName: Name of the file
fileContent: Binary data of the file
mimeType: MIME type of the file
Returns:
List of PowerPoint-Content objects with metadata.isText = False
"""
# For PowerPoint, we currently only return the original binary file
# A complete extraction would require more specialized libraries
fileExtension = "pptx" if mimeType == "application/vnd.openxmlformats-officedocument.presentationml.presentation" else "ppt"
return [{
"sequenceNr": 1,
"name": "1_powerpoint", # Simplified naming
"ext": fileExtension,
"contentType": mimeType,
"data": fileContent,
"metadata": {
"isText": False,
"format": "powerpoint"
}
}]
def extractBinaryContent(fileName: str, fileContent: bytes, mimeType: str) -> List[Dict[str, Any]]:
"""
Fallback for binary files where no specific extraction is possible.
Args:
fileName: Name of the file
fileContent: Binary data of the file
mimeType: MIME type of the file
Returns:
List with a binary Content object with metadata.isText = False
"""
fileExtension = os.path.splitext(fileName)[1][1:] if os.path.splitext(fileName)[1] else "bin"
return [{
"sequenceNr": 1,
"name": "1_binary", # Simplified naming
"ext": fileExtension,
"contentType": mimeType,
"data": fileContent,
"metadata": {
"isText": False,
"format": "binary"
}
}]

471
modules/gatewayInterface.py Normal file
View file

@ -0,0 +1,471 @@
"""
Interface to the Gateway system.
Manages users and mandates for authentication.
"""
import os
import logging
from typing import Dict, Any, List, Optional, Union
import importlib
from passlib.context import CryptContext
from connectors.connectorDbJson import DatabaseConnector
from modules.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
# Password-Hashing
pwdContext = CryptContext(schemes=["argon2"], deprecated="auto")
class GatewayInterface:
"""
Interface to the Gateway system.
Manages users and mandates.
"""
def __init__(self, mandateId: int = None, userId: int = None):
"""
Initializes the Gateway Interface with optional mandate and user context.
Args:
mandateId: ID of the current mandate (optional)
userId: ID of the current user (optional)
"""
# Context can be empty during initialization
self.mandateId = mandateId
self.userId = userId
# Import data model module
try:
self.modelModule = importlib.import_module("modules.gatewayModel")
logger.info("gatewayModel successfully imported")
except ImportError as e:
logger.error(f"Error importing gatewayModel: {e}")
raise
# Initialize database
self._initializeDatabase()
def _initializeDatabase(self):
"""
Initializes the database with minimal objects
"""
self.db = DatabaseConnector(
dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"),
dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
dbUser=APP_CONFIG.get("DB_SYSTEM_USER"),
dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
mandateId=self.mandateId if self.mandateId else 0,
userId=self.userId if self.userId else 0
)
# Create Root mandate if needed
existingMandateId = self.getInitialId("mandates")
mandates = self.db.getRecordset("mandates")
if existingMandateId is None or not mandates:
logger.info("Creating Root mandate")
rootMandate = {
"name": "Root",
"language": "de"
}
createdMandate = self.db.recordCreate("mandates", rootMandate)
logger.info(f"Root mandate created with ID {createdMandate['id']}")
# Update mandate context
self.mandateId = createdMandate['id']
self.userId = createdMandate['userId']
# Recreate connector with correct context
self.db = DatabaseConnector(
dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"),
dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
dbUser=APP_CONFIG.get("DB_SYSTEM_USER"),
dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
mandateId=self.mandateId,
userId=self.userId
)
# Create Admin user if needed
existingUserId = self.getInitialId("users")
users = self.db.getRecordset("users")
if existingUserId is None or not users:
logger.info("Creating Admin user")
adminUser = {
"mandateId": self.mandateId,
"username": "admin",
"email": "admin@example.com",
"fullName": "Administrator",
"disabled": False,
"language": "de",
"privilege": "sysadmin", # SysAdmin privilege
"hashedPassword": self._getPasswordHash("admin") # Use a secure password in production!
}
createdUser = self.db.recordCreate("users", adminUser)
logger.info(f"Admin user created with ID {createdUser['id']}")
# Update user context
self.userId = createdUser['id']
# Recreate connector with correct context
self.db = DatabaseConnector(
dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"),
dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
dbUser=APP_CONFIG.get("DB_SYSTEM_USER"),
dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
mandateId=self.mandateId,
userId=self.userId
)
def getInitialId(self, table: str) -> Optional[int]:
"""Returns the initial ID for a table"""
return self.db.getInitialId(table)
def _getPasswordHash(self, password: str) -> str:
"""Creates a hash for a password"""
return pwdContext.hash(password)
def _verifyPassword(self, plainPassword: str, hashedPassword: str) -> bool:
"""Checks if the password matches the hash"""
return pwdContext.verify(plainPassword, hashedPassword)
def _getCurrentTimestamp(self) -> str:
"""Returns the current timestamp in ISO format"""
from datetime import datetime
return datetime.now().isoformat()
# Mandate methods
def getAllMandates(self) -> List[Dict[str, Any]]:
"""Returns all mandates"""
return self.db.getRecordset("mandates")
def getMandate(self, mandateId: int) -> Optional[Dict[str, Any]]:
"""Returns a mandate by its ID"""
mandates = self.db.getRecordset("mandates", recordFilter={"id": mandateId})
if mandates:
return mandates[0]
return None
def createMandate(self, name: str, language: str = "de") -> Dict[str, Any]:
"""Creates a new mandate"""
mandateData = {
"name": name,
"language": language
}
return self.db.recordCreate("mandates", mandateData)
def updateMandate(self, mandateId: int, mandateData: Dict[str, Any]) -> Dict[str, Any]:
"""
Updates an existing mandate
Args:
mandateId: The ID of the mandate to update
mandateData: The mandate data to update
Returns:
Dict[str, Any]: The updated mandate data
Raises:
ValueError: If the mandate is not found
"""
# Check if the mandate exists
mandate = self.getMandate(mandateId)
if not mandate:
raise ValueError(f"Mandate with ID {mandateId} not found")
# Update the mandate
updatedMandate = self.db.recordModify("mandates", mandateId, mandateData)
return updatedMandate
def deleteMandate(self, mandateId: int) -> bool:
"""
Deletes a mandate and all associated users and data
Args:
mandateId: The ID of the mandate to delete
Returns:
bool: True if the mandate was successfully deleted, otherwise False
"""
# Check if the mandate exists
mandate = self.getMandate(mandateId)
if not mandate:
return False
# Check if it's the initial mandate
initialMandateId = self.getInitialId("mandates")
if initialMandateId is not None and mandateId == initialMandateId:
logger.warning(f"Attempt to delete the Root mandate was prevented")
return False
# Find all users of the mandate
users = self.getUsersByMandate(mandateId)
# Delete all users of the mandate and their associated data
for user in users:
self.deleteUser(user["id"])
# Delete the mandate
success = self.db.recordDelete("mandates", mandateId)
if success:
logger.info(f"Mandate with ID {mandateId} was successfully deleted")
else:
logger.error(f"Error deleting mandate with ID {mandateId}")
return success
# User methods
def getAllUsers(self) -> List[Dict[str, Any]]:
"""Returns all users"""
users = self.db.getRecordset("users")
# Remove password hashes from the response
for user in users:
if "hashedPassword" in user:
del user["hashedPassword"]
return users
def getUsersByMandate(self, mandateId: int) -> List[Dict[str, Any]]:
"""
Returns all users of a specific mandate
Args:
mandateId: The ID of the mandate
Returns:
List[Dict[str, Any]]: List of users in the mandate
"""
users = self.db.getRecordset("users", recordFilter={"mandateId": mandateId})
# Remove password hashes from the response
for user in users:
if "hashedPassword" in user:
del user["hashedPassword"]
return users
def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]:
"""Returns a user by username"""
users = self.db.getRecordset("users")
for user in users:
if user.get("username") == username:
return user
return None
def getUser(self, userId: int) -> Optional[Dict[str, Any]]:
"""Returns a user by ID"""
users = self.db.getRecordset("users", recordFilter={"id": userId})
if users:
user = users[0]
# Remove password hash from the API response
if "hashedPassword" in user:
userCopy = user.copy()
del userCopy["hashedPassword"]
return userCopy
return user
return None
def createUser(self, username: str, password: str, email: str = None,
fullName: str = None, language: str = "de", mandateId: int = None,
disabled: bool = False, privilege: str = "user") -> Dict[str, Any]:
"""
Creates a new user
Args:
username: The username
password: The password
email: The email address (optional)
fullName: The full name (optional)
language: The preferred language (default: "de")
mandateId: The ID of the mandate (optional)
disabled: Whether the user is disabled (default: False)
privilege: The privilege level (default: "user")
Returns:
Dict[str, Any]: The created user data
Raises:
ValueError: If the username already exists
"""
# Check if the username already exists
existingUser = self.getUserByUsername(username)
if existingUser:
raise ValueError(f"User '{username}' already exists")
# Use the provided mandateId or the current context
userMandateId = mandateId if mandateId is not None else self.mandateId
userData = {
"mandateId": userMandateId,
"username": username,
"email": email,
"fullName": fullName,
"disabled": disabled,
"language": language,
"privilege": privilege,
"hashedPassword": self._getPasswordHash(password)
}
createdUser = self.db.recordCreate("users", userData)
# Remove password hash from the response
if "hashedPassword" in createdUser:
del createdUser["hashedPassword"]
return createdUser
def authenticateUser(self, username: str, password: str) -> Optional[Dict[str, Any]]:
"""
Authenticates a user by username and password
Args:
username: The username
password: The password
Returns:
Optional[Dict[str, Any]]: The user data or None if authentication fails
"""
user = self.getUserByUsername(username)
if not user:
return None
if not self._verifyPassword(password, user.get("hashedPassword", "")):
return None
# Check if the user is disabled
if user.get("disabled", False):
return None
# Create a copy without password hash
authenticatedUser = {**user}
if "hashedPassword" in authenticatedUser:
del authenticatedUser["hashedPassword"]
return authenticatedUser
def updateUser(self, userId: int, userData: Dict[str, Any]) -> Dict[str, Any]:
"""
Updates a user
Args:
userId: The ID of the user to update
userData: The user data to update
Returns:
Dict[str, Any]: The updated user data
Raises:
ValueError: If the user is not found
"""
# Get the current user with password hash (directly from DB)
users = self.db.getRecordset("users", recordFilter={"id": userId})
if not users:
raise ValueError(f"User with ID {userId} not found")
user = users[0]
# If the password is being changed, hash it
if "password" in userData:
userData["hashedPassword"] = self._getPasswordHash(userData["password"])
del userData["password"]
# Update the user
updatedUser = self.db.recordModify("users", userId, userData)
# Remove password hash from the response
if "hashedPassword" in updatedUser:
del updatedUser["hashedPassword"]
return updatedUser
def disableUser(self, userId: int) -> Dict[str, Any]:
"""Disables a user"""
return self.updateUser(userId, {"disabled": True})
def enableUser(self, userId: int) -> Dict[str, Any]:
"""Enables a user"""
return self.updateUser(userId, {"disabled": False})
def _deleteUserReferencedData(self, userId: int) -> None:
"""
Deletes all data associated with a user
Args:
userId: The ID of the user
"""
# Here all tables are searched and all entries referencing this user are deleted
# Delete user attributes
try:
attributes = self.db.getRecordset("attributes", recordFilter={"userId": userId})
for attribute in attributes:
self.db.recordDelete("attributes", attribute["id"])
except Exception as e:
logger.error(f"Error deleting attributes for user {userId}: {e}")
# Other tables that might reference the user
# (Depending on the application's database structure)
logger.info(f"All referenced data for user {userId} has been deleted")
def deleteUser(self, userId: int) -> bool:
"""
Deletes a user and all associated data
Args:
userId: The ID of the user to delete
Returns:
bool: True if the user was successfully deleted, otherwise False
"""
# Check if the user exists
users = self.db.getRecordset("users", recordFilter={"id": userId})
if not users:
return False
# Check if it's the initial user
initialUserId = self.getInitialId("users")
if initialUserId is not None and userId == initialUserId:
logger.warning("Attempt to delete the Root Admin was prevented")
return False
# Delete all data associated with the user
self._deleteUserReferencedData(userId)
# Delete the user
success = self.db.recordDelete("users", userId)
if success:
logger.info(f"User with ID {userId} was successfully deleted")
else:
logger.error(f"Error deleting user with ID {userId}")
return success
# Singleton factory for GatewayInterface instances per context
_gatewayInterfaces = {}
def getGatewayInterface(mandateId: int = None, userId: int = None) -> GatewayInterface:
"""
Returns a GatewayInterface instance for the specified context.
Reuses existing instances.
Args:
mandateId: ID of the mandate
userId: ID of the user
Returns:
GatewayInterface instance
"""
contextKey = f"{mandateId}_{userId}"
if contextKey not in _gatewayInterfaces:
_gatewayInterfaces[contextKey] = GatewayInterface(mandateId, userId)
return _gatewayInterfaces[contextKey]
# Initialize the interface
getGatewayInterface()

View file

@ -1,5 +1,9 @@
"""
Data models for the gateway system.
"""
from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional
from datetime import datetime
class Label(BaseModel):
@ -7,7 +11,7 @@ class Label(BaseModel):
default: str
translations: Dict[str, str] = {}
def get_label(self, language: str = None):
def getLabel(self, language: str = None):
"""Returns the label in the specified language, or the default value if not available"""
if language and language in self.translations:
return self.translations[language]
@ -26,7 +30,7 @@ class Mandate(BaseModel):
)
# Labels for attributes
field_labels: Dict[str, Label] = {
fieldLabels: Dict[str, Label] = {
"id": Label(default="ID", translations={}),
"name": Label(default="Name of the mandate", translations={"en": "Mandate name", "fr": "Nom du mandat"}),
"language": Label(default="Language", translations={"en": "Language", "fr": "Langue"})
@ -35,10 +39,10 @@ class Mandate(BaseModel):
class User(BaseModel):
"""Data model for a user"""
id: int = Field(description="Unique ID of the user")
mandate_id: int = Field(description="ID of the associated mandate")
mandateId: int = Field(description="ID of the associated mandate")
username: str = Field(description="Username for login")
email: Optional[str] = Field(None, description="Email address of the user")
full_name: Optional[str] = Field(None, description="Full name of the user")
fullName: Optional[str] = Field(None, description="Full name of the user")
language: str = Field(description="Preferred language of the user")
disabled: Optional[bool] = Field(False, description="Indicates whether the user is disabled")
privilege: str = Field(description="Permission level") #sysadmin,admin,user
@ -49,12 +53,12 @@ class User(BaseModel):
)
# Labels for attributes
field_labels: Dict[str, Label] = {
fieldLabels: Dict[str, Label] = {
"id": Label(default="ID", translations={}),
"mandate_id": Label(default="Mandate ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"mandateId": Label(default="Mandate ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"username": Label(default="Username", translations={"en": "Username", "fr": "Nom d'utilisateur"}),
"email": Label(default="Email", translations={"en": "Email", "fr": "E-mail"}),
"full_name": Label(default="Full name", translations={"en": "Full name", "fr": "Nom complet"}),
"fullName": Label(default="Full name", translations={"en": "Full name", "fr": "Nom complet"}),
"language": Label(default="Language", translations={"en": "Language", "fr": "Langue"}),
"disabled": Label(default="Disabled", translations={"en": "Disabled", "fr": "Désactivé"}),
"privilege": Label(default="Permission level", translations={"en": "Access level", "fr": "Niveau d'accès"}),
@ -63,7 +67,7 @@ class User(BaseModel):
class UserInDB(User):
"""Extended user class with password hash"""
hashed_password: str = Field(description="Hash of the user password")
hashedPassword: str = Field(description="Hash of the user password")
label: Label = Field(
default=Label(default="User Access", translations={"en": "User Access", "fr": "Accès de l'utilisateur"}),
@ -71,23 +75,29 @@ class UserInDB(User):
)
# Additional label for the password field
field_labels: Dict[str, Label] = {
"hashed_password": Label(default="Password hash", translations={"en": "Password hash", "fr": "Hachage de mot de passe"})
fieldLabels: Dict[str, Label] = {
"hashedPassword": Label(default="Password hash", translations={"en": "Password hash", "fr": "Hachage de mot de passe"})
}
class Token(BaseModel):
"""Data model for an authentication token"""
access_token: str = Field(description="The issued access token")
token_type: str = Field(description="Type of token (usually 'bearer')")
accessToken: str = Field(description="The issued access token")
tokenType: str = Field(description="Type of token (usually 'bearer')")
label: Label = Field(
default=Label(default="Token", translations={"en": "Token", "fr": "Jeton"}),
description="Label for the class"
)
# Labels for attributes
field_labels: Dict[str, Label] = {
"access_token": Label(default="Access token", translations={"en": "Access token", "fr": "Jeton d'accès"}),
"token_type": Label(default="Token type", translations={"en": "Token type", "fr": "Type de jeton"})
}
fieldLabels: Dict[str, Label] = {
"accessToken": Label(default="Access token", translations={"en": "Access token", "fr": "Jeton d'accès"}),
"tokenType": Label(default="Token type", translations={"en": "Token type", "fr": "Type de jeton"})
}
class TokenData(BaseModel):
"""Data for token decoding and validation"""
username: Optional[str] = None
mandateId: Optional[int] = None
exp: Optional[datetime] = None

View file

@ -1,458 +0,0 @@
import os
import logging
from typing import Dict, Any, List, Optional, Union
import importlib
from passlib.context import CryptContext
from connectors.connector_db_json import DatabaseConnector
from modules.configuration import APP_CONFIG
logger = logging.getLogger(__name__)
# Password-Hashing
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
class GatewayInterface:
"""
Interface to the Gateway system.
Manages users and mandates.
"""
def __init__(self, mandate_id: int = None, user_id: int = None):
"""
Initializes the Gateway Interface with optional mandate and user context.
Args:
mandate_id: ID of the current mandate (optional)
user_id: ID of the current user (optional)
"""
# Context can be empty during initialization
self.mandate_id = mandate_id
self.user_id = user_id
# Import data model module
try:
self.model_module = importlib.import_module("modules.gateway_model")
logger.info("gateway_model successfully imported")
except ImportError as e:
logger.error(f"Error importing gateway_model: {e}")
raise
# Initialize database
self._initialize_database()
def _initialize_database(self):
"""
Initializes the database with minimal objects
"""
self.db = DatabaseConnector(
db_host=APP_CONFIG.get("DB_SYSTEM_HOST"),
db_database=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
db_user=APP_CONFIG.get("DB_SYSTEM_USER"),
db_password=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
mandate_id=self.mandate_id if self.mandate_id else 0,
user_id=self.user_id if self.user_id else 0
)
# Create Root mandate if needed
existing_mandate_id = self.get_initial_id("mandates")
mandates = self.db.get_recordset("mandates")
if existing_mandate_id is None or not mandates:
logger.info("Creating Root mandate")
root_mandate = {
"name": "Root",
"language": "de"
}
created_mandate = self.db.record_create("mandates", root_mandate)
logger.info(f"Root mandate created with ID {created_mandate['id']}")
# Update mandate context
self.mandate_id = created_mandate['id']
self.user_id = created_mandate['user_id']
# Recreate connector with correct context
self.db = DatabaseConnector(
db_host=APP_CONFIG.get("DB_SYSTEM_HOST"),
db_database=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
db_user=APP_CONFIG.get("DB_SYSTEM_USER"),
db_password=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
mandate_id=self.mandate_id,
user_id=self.user_id
)
# Create Admin user if needed
existing_user_id = self.get_initial_id("users")
users = self.db.get_recordset("users")
if existing_user_id is None or not users:
logger.info("Creating Admin user")
admin_user = {
"mandate_id": self.mandate_id,
"username": "admin",
"email": "admin@example.com",
"full_name": "Administrator",
"disabled": False,
"language": "de",
"privilege": "sysadmin", # SysAdmin privilege
"hashed_password": self._get_password_hash("admin") # Use a secure password in production!
}
created_user = self.db.record_create("users", admin_user)
logger.info(f"Admin user created with ID {created_user['id']}")
# Update user context
self.user_id = created_user['id']
# Recreate connector with correct context
self.db = DatabaseConnector(
db_host=APP_CONFIG.get("DB_SYSTEM_HOST"),
db_database=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
db_user=APP_CONFIG.get("DB_SYSTEM_USER"),
db_password=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
mandate_id=self.mandate_id,
user_id=self.user_id )
def get_initial_id(self, table: str) -> Optional[int]:
"""Returns the initial ID for a table"""
return self.db.get_initial_id(table)
def _get_password_hash(self, password: str) -> str:
"""Creates a hash for a password"""
return pwd_context.hash(password)
def _verify_password(self, plain_password: str, hashed_password: str) -> bool:
"""Checks if the password matches the hash"""
return pwd_context.verify(plain_password, hashed_password)
def _get_current_timestamp(self) -> str:
"""Returns the current timestamp in ISO format"""
from datetime import datetime
return datetime.now().isoformat()
# Mandate methods
def get_all_mandates(self) -> List[Dict[str, Any]]:
"""Returns all mandates"""
return self.db.get_recordset("mandates")
def get_mandate(self, mandate_id: int) -> Optional[Dict[str, Any]]:
"""Returns a mandate by its ID"""
mandates = self.db.get_recordset("mandates", record_filter={"id": mandate_id})
if mandates:
return mandates[0]
return None
def create_mandate(self, name: str, language: str = "de") -> Dict[str, Any]:
"""Creates a new mandate"""
mandate_data = {
"name": name,
"language": language
}
return self.db.record_create("mandates", mandate_data)
def update_mandate(self, mandate_id: int, mandate_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Updates an existing mandate
Args:
mandate_id: The ID of the mandate to update
mandate_data: The mandate data to update
Returns:
Dict[str, Any]: The updated mandate data
Raises:
ValueError: If the mandate is not found
"""
# Check if the mandate exists
mandate = self.get_mandate(mandate_id)
if not mandate:
raise ValueError(f"Mandate with ID {mandate_id} not found")
# Update the mandate
updated_mandate = self.db.record_modify("mandates", mandate_id, mandate_data)
return updated_mandate
def delete_mandate(self, mandate_id: int) -> bool:
"""
Deletes a mandate and all associated users and data
Args:
mandate_id: The ID of the mandate to delete
Returns:
bool: True if the mandate was successfully deleted, otherwise False
"""
# Check if the mandate exists
mandate = self.get_mandate(mandate_id)
if not mandate:
return False
# Check if it's the initial mandate
initial_mandate_id = self.get_initial_id("mandates")
if initial_mandate_id is not None and mandate_id == initial_mandate_id:
logger.warning(f"Attempt to delete the Root mandate was prevented")
return False
# Find all users of the mandate
users = self.get_users_by_mandate(mandate_id)
# Delete all users of the mandate and their associated data
for user in users:
self.delete_user(user["id"])
# Delete the mandate
success = self.db.record_delete("mandates", mandate_id)
if success:
logger.info(f"Mandate with ID {mandate_id} was successfully deleted")
else:
logger.error(f"Error deleting mandate with ID {mandate_id}")
return success
# User methods
def get_all_users(self) -> List[Dict[str, Any]]:
"""Returns all users"""
users = self.db.get_recordset("users")
# Remove password hashes from the response
for user in users:
if "hashed_password" in user:
del user["hashed_password"]
return users
def get_users_by_mandate(self, mandate_id: int) -> List[Dict[str, Any]]:
"""
Returns all users of a specific mandate
Args:
mandate_id: The ID of the mandate
Returns:
List[Dict[str, Any]]: List of users in the mandate
"""
users = self.db.get_recordset("users", record_filter={"mandate_id": mandate_id})
# Remove password hashes from the response
for user in users:
if "hashed_password" in user:
del user["hashed_password"]
return users
def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]:
"""Returns a user by username"""
users = self.db.get_recordset("users")
for user in users:
if user.get("username") == username:
return user
return None
def get_user(self, user_id: int) -> Optional[Dict[str, Any]]:
"""Returns a user by ID"""
users = self.db.get_recordset("users", record_filter={"id": user_id})
if users:
user = users[0]
# Remove password hash from the API response
if "hashed_password" in user:
user_copy = user.copy()
del user_copy["hashed_password"]
return user_copy
return user
return None
def create_user(self, username: str, password: str, email: str = None,
full_name: str = None, language: str = "de", mandate_id: int = None,
disabled: bool = False, privilege: str = "user") -> Dict[str, Any]:
"""
Creates a new user
Args:
username: The username
password: The password
email: The email address (optional)
full_name: The full name (optional)
language: The preferred language (default: "de")
mandate_id: The ID of the mandate (optional)
disabled: Whether the user is disabled (default: False)
privilege: The privilege level (default: "user")
Returns:
Dict[str, Any]: The created user data
Raises:
ValueError: If the username already exists
"""
# Check if the username already exists
existing_user = self.get_user_by_username(username)
if existing_user:
raise ValueError(f"User '{username}' already exists")
# Use the provided mandate_id or the current context
user_mandate_id = mandate_id if mandate_id is not None else self.mandate_id
user_data = {
"mandate_id": user_mandate_id,
"username": username,
"email": email,
"full_name": full_name,
"disabled": disabled,
"language": language,
"privilege": privilege,
"hashed_password": self._get_password_hash(password)
}
created_user = self.db.record_create("users", user_data)
# Remove password hash from the response
if "hashed_password" in created_user:
del created_user["hashed_password"]
return created_user
def authenticate_user(self, username: str, password: str) -> Optional[Dict[str, Any]]:
"""
Authenticates a user by username and password
Args:
username: The username
password: The password
Returns:
Optional[Dict[str, Any]]: The user data or None if authentication fails
"""
user = self.get_user_by_username(username)
if not user:
return None
if not self._verify_password(password, user.get("hashed_password", "")):
return None
# Check if the user is disabled
if user.get("disabled", False):
return None
# Create a copy without password hash
authenticated_user = {**user}
if "hashed_password" in authenticated_user:
del authenticated_user["hashed_password"]
return authenticated_user
def update_user(self, user_id: int, user_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Updates a user
Args:
user_id: The ID of the user to update
user_data: The user data to update
Returns:
Dict[str, Any]: The updated user data
Raises:
ValueError: If the user is not found
"""
# Get the current user with password hash (directly from DB)
users = self.db.get_recordset("users", record_filter={"id": user_id})
if not users:
raise ValueError(f"User with ID {user_id} not found")
user = users[0]
# If the password is being changed, hash it
if "password" in user_data:
user_data["hashed_password"] = self._get_password_hash(user_data["password"])
del user_data["password"]
# Update the user
updated_user = self.db.record_modify("users", user_id, user_data)
# Remove password hash from the response
if "hashed_password" in updated_user:
del updated_user["hashed_password"]
return updated_user
def disable_user(self, user_id: int) -> Dict[str, Any]:
"""Disables a user"""
return self.update_user(user_id, {"disabled": True})
def enable_user(self, user_id: int) -> Dict[str, Any]:
"""Enables a user"""
return self.update_user(user_id, {"disabled": False})
def _delete_user_referenced_data(self, user_id: int) -> None:
"""
Deletes all data associated with a user
Args:
user_id: The ID of the user
"""
# Here all tables are searched and all entries referencing this user are deleted
# Delete user attributes
try:
attributes = self.db.get_recordset("attributes", record_filter={"user_id": user_id})
for attribute in attributes:
self.db.record_delete("attributes", attribute["id"])
except Exception as e:
logger.error(f"Error deleting attributes for user {user_id}: {e}")
# Other tables that might reference the user
# (Depending on the application's database structure)
logger.info(f"All referenced data for user {user_id} has been deleted")
def delete_user(self, user_id: int) -> bool:
"""
Deletes a user and all associated data
Args:
user_id: The ID of the user to delete
Returns:
bool: True if the user was successfully deleted, otherwise False
"""
# Check if the user exists
users = self.db.get_recordset("users", record_filter={"id": user_id})
if not users:
return False
# Check if it's the initial user
initial_user_id = self.get_initial_id("users")
if initial_user_id is not None and user_id == initial_user_id:
logger.warning("Attempt to delete the Root Admin was prevented")
return False
# Delete all data associated with the user
self._delete_user_referenced_data(user_id)
# Delete the user
success = self.db.record_delete("users", user_id)
if success:
logger.info(f"User with ID {user_id} was successfully deleted")
else:
logger.error(f"Error deleting user with ID {user_id}")
return success
# Singleton Factory for GatewayInterface instances per context
_gateway_interfaces = {}
def get_gateway_interface(mandate_id: int = None, user_id: int = None) -> GatewayInterface:
"""
Returns a GatewayInterface instance for the specified context.
Reuses existing instances.
"""
context_key = f"{mandate_id}_{user_id}"
if context_key not in _gateway_interfaces:
_gateway_interfaces[context_key] = GatewayInterface(mandate_id, user_id)
return _gateway_interfaces[context_key]
# Init
get_gateway_interface()

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,7 @@
"""
LucyDOM model classes for the workflow and document system.
"""
from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional
@ -7,7 +11,7 @@ class Label(BaseModel):
default: str
translations: Dict[str, str] = {}
def get_label(self, language: str = None):
def getLabel(self, language: str = None):
"""Returns the label in the specified language, or the default value if not available"""
if language and language in self.translations:
return self.translations[language]
@ -17,8 +21,8 @@ class Label(BaseModel):
class Prompt(BaseModel):
"""Data model for a prompt"""
id: int = Field(description="Unique ID of the prompt")
mandate_id: int = Field(description="ID of the associated mandate")
user_id: int = Field(description="ID of the creator")
mandateId: int = Field(description="ID of the associated mandate")
userId: int = Field(description="ID of the creator")
content: str = Field(description="Content of the prompt")
name: str = Field(description="Display name of the prompt")
@ -28,10 +32,10 @@ class Prompt(BaseModel):
)
# Labels for attributes
field_labels: Dict[str, Label] = {
fieldLabels: Dict[str, Label] = {
"id": Label(default="ID", translations={}),
"mandate_id": Label(default="Mandate ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"user_id": Label(default="User ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}),
"mandateId": Label(default="Mandate ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"userId": Label(default="User ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}),
"content": Label(default="Content", translations={"en": "Content", "fr": "Contenu"}),
"name": Label(default="Name", translations={"en": "Label", "fr": "Nom"}),
}
@ -40,14 +44,14 @@ class Prompt(BaseModel):
class FileItem(BaseModel):
"""Data model for a file"""
id: int = Field(description="Unique ID of the data object")
mandate_id: int = Field(description="ID of the associated mandate")
user_id: int = Field(description="ID of the creator")
mandateId: int = Field(description="ID of the associated mandate")
userId: int = Field(description="ID of the creator")
name: str = Field(description="Name of the data object")
mime_type: str = Field(description="Type of the data object MIME type")
mimeType: str = Field(description="Type of the data object MIME type")
size: Optional[int] = Field(None, description="Size of the data object in bytes")
file_hash: str = Field(description="Hash code for deduplication")
creation_date: Optional[str] = Field(None, description="Upload date")
workflow_id: Optional[str] = Field(None, description="ID of the associated workflow, if any")
fileHash: str = Field(description="Hash code for deduplication")
creationDate: Optional[str] = Field(None, description="Upload date")
workflowId: Optional[str] = Field(None, description="ID of the associated workflow, if any")
label: Label = Field(
default=Label(default="Data Object", translations={"en": "Data Object", "fr": "Objet de données"}),
@ -55,16 +59,16 @@ class FileItem(BaseModel):
)
# Labels for attributes
field_labels: Dict[str, Label] = {
fieldLabels: Dict[str, Label] = {
"id": Label(default="ID", translations={}),
"mandate_id": Label(default="Mandate ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"user_id": Label(default="User ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}),
"mandateId": Label(default="Mandate ID", translations={"en": "Mandate ID", "fr": "ID de mandat"}),
"userId": Label(default="User ID", translations={"en": "User ID", "fr": "ID d'utilisateur"}),
"name": Label(default="Name", translations={"en": "Name", "fr": "Nom"}),
"mime_type": Label(default="Type", translations={"en": "Type", "fr": "Type"}),
"mimeType": Label(default="Type", translations={"en": "Type", "fr": "Type"}),
"size": Label(default="Size", translations={"en": "Size", "fr": "Taille"}),
"file_hash": Label(default="File Hash", translations={"en": "Hash", "fr": "Hash"}),
"creation_date": Label(default="Upload date", translations={"en": "Upload date", "fr": "Date de téléchargement"}),
"workflow_id": Label(default="Workflow ID", translations={"en": "Workflow ID", "fr": "ID du workflow"})
"fileHash": Label(default="File Hash", translations={"en": "Hash", "fr": "Hash"}),
"creationDate": Label(default="Upload date", translations={"en": "Upload date", "fr": "Date de téléchargement"}),
"workflowId": Label(default="Workflow ID", translations={"en": "Workflow ID", "fr": "ID du workflow"})
}
class FileData(BaseModel):
@ -77,71 +81,71 @@ class FileData(BaseModel):
class DocumentContent(BaseModel):
"""Content of a document in the workflow"""
sequence_nr: int = Field(1, description="Sequence number of the content in the source document")
sequenceNr: int = Field(1, description="Sequence number of the content in the source document")
name: str = Field(description="Designation")
ext: str = Field(description="Content extension for export: txt, csv, json, jpg, png")
content_type: str = Field(description="MIME type")
contentType: str = Field(description="MIME type")
summary: str = Field(description="Summary of the file content")
metadata: Dict[str, Any] = Field(default_factory=dict, description="Metadata about the content, such as is_text flag, format information, encoding, etc.")
metadata: Dict[str, Any] = Field(default_factory=dict, description="Metadata about the content, such as isText flag, format information, encoding, etc.")
class Document(BaseModel):
"""Document in the workflow - References a file directly in the database"""
id: str = Field(description="Unique ID of the document")
name: str = Field(description="Name of the data object")
ext: str = Field(description="Extension of the data object")
file_id: int = Field(description="ID of the referenced file in the database")
fileId: int = Field(description="ID of the referenced file in the database")
data: str = Field(description="Content of the data as base64 string")
contents: List[DocumentContent] = Field(description="Document contents")
class DataStats(BaseModel):
"""Statistics for performance and data usage"""
processing_time: Optional[float] = Field(None, description="Processing time in seconds")
token_count: Optional[int] = Field(None, description="Token count (for AI models)")
bytes_sent: Optional[int] = Field(None, description="Bytes sent")
bytes_received: Optional[int] = Field(None, description="Bytes received")
processingTime: Optional[float] = Field(None, description="Processing time in seconds")
tokenCount: Optional[int] = Field(None, description="Token count (for AI models)")
bytesSent: Optional[int] = Field(None, description="Bytes sent")
bytesReceived: Optional[int] = Field(None, description="Bytes received")
class WorkflowMessage(BaseModel):
"""Message object in the workflow"""
id: str = Field(description="Unique ID of the message")
workflow_id: str = Field(description="Reference to the parent workflow")
parent_message_id: Optional[str] = Field(None, description="Reference to the replied message")
started_at: str = Field(description="Timestamp for message creation")
finished_at: Optional[str] = Field(None, description="Timestamp for message completion")
sequence_no: int = Field(description="Sequence number for sorting")
workflowId: str = Field(description="Reference to the parent workflow")
parentMessageId: Optional[str] = Field(None, description="Reference to the replied message")
startedAt: str = Field(description="Timestamp for message creation")
finishedAt: Optional[str] = Field(None, description="Timestamp for message completion")
sequenceNo: int = Field(description="Sequence number for sorting")
status: str = Field(description="Status of the message ('processing', 'completed')")
status: str = Field(description="Status of the message ('first', 'step', 'last')")
role: str = Field(description="Role of the sender ('system', 'user', 'assistant')")
data_stats: Optional[DataStats] = Field(None, description="Statistics")
dataStats: Optional[DataStats] = Field(None, description="Statistics")
documents: Optional[List[Document]] = Field(None, description="Documents in this message (references to files in the database)")
content: Optional[str] = Field(None, description="Text content of the message")
agent_name: Optional[str] = Field(None, description="Name of the agent used")
agentName: Optional[str] = Field(None, description="Name of the agent used")
class WorkflowLog(BaseModel):
"""Log entry for a workflow"""
id: str = Field(description="Unique ID of the log entry")
workflow_id: str = Field(description="ID of the associated workflow")
workflowId: str = Field(description="ID of the associated workflow")
message: str = Field(description="Log message content")
type: str = Field(description="Type of log ('info', 'warning', 'error')")
timestamp: str = Field(description="Timestamp of the log entry")
agent_name: str = Field(description="Name of the agent that created the log")
agentName: str = Field(description="Name of the agent that created the log")
status: str = Field(description="Status of the workflow at log time")
progress: Optional[int] = Field(None, description="Progress value (0-100)")
mandate_id: Optional[int] = Field(None, description="ID of the mandate")
user_id: Optional[int] = Field(None, description="ID of the user")
mandateId: Optional[int] = Field(None, description="ID of the mandate")
userId: Optional[int] = Field(None, description="ID of the user")
class Workflow(BaseModel):
"""Workflow object for multi-agent system"""
id: str = Field(description="Unique ID of the workflow")
name: Optional[str] = Field(None, description="Name of the workflow")
mandate_id: int = Field(description="ID of the mandate")
user_id: int = Field(description="ID of the user")
status: str = Field(description="Status of the workflow ('running', 'completed')")
started_at: str = Field(description="Start timestamp")
last_activity: str = Field(description="Timestamp of the last activity")
data_stats: Optional[Dict[str, Any]] = Field(None, description="Total statistics")
current_round: int = Field(default=1, description="Current round/iteration of the workflow")
message_ids: List[str] = Field(default=[], description="List of message IDs in this workflow")
mandateId: int = Field(description="ID of the mandate")
userId: int = Field(description="ID of the user")
status: str = Field(description="Status of the workflow ('running', 'completed', 'failed', 'stopped')")
startedAt: str = Field(description="Start timestamp")
lastActivity: str = Field(description="Timestamp of the last activity")
dataStats: Optional[Dict[str, Any]] = Field(None, description="Total statistics")
currentRound: int = Field(default=1, description="Current round/iteration of the workflow")
messageIds: List[str] = Field(default=[], description="List of message IDs in this workflow")
messages: List[WorkflowMessage] = Field(default=[], description="Message history (in-memory representation)")
logs: List[WorkflowLog] = Field(default=[], description="Log entries (in-memory representation)")
@ -152,4 +156,4 @@ class Workflow(BaseModel):
class UserInputRequest(BaseModel):
"""Request for user input to a running workflow"""
prompt: str = Field(description="Message from the user")
list_file_id: List[int] = Field(default=[], description="List of FileItem IDs")
listFileId: List[int] = Field(default=[], description="List of FileItem IDs")

View file

@ -1,5 +1,5 @@
"""
Chat Agent Registry Module.
Agent Registry Module.
Provides a central registry system for all available agents.
Optimized for the standardized task processing pattern.
"""
@ -26,11 +26,11 @@ class AgentBase:
self.capabilities = []
self.mydom = None
def set_dependencies(self, mydom=None):
def setDependencies(self, mydom=None):
"""Set external dependencies for the agent."""
self.mydom = mydom
def get_agent_info(self) -> Dict[str, Any]:
def getAgentInfo(self) -> Dict[str, Any]:
"""
Return standardized information about the agent's capabilities.
@ -43,18 +43,18 @@ class AgentBase:
"capabilities": self.capabilities
}
async def process_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
async def processTask(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""
Process a standardized task structure and return results.
This method must be implemented by all concrete agent classes.
Args:
task: A dictionary containing:
- task_id: Unique ID for this task
- workflow_id: ID of the parent workflow (optional)
- taskId: Unique ID for this task
- workflowId: ID of the parent workflow (optional)
- prompt: The main instruction for the agent
- input_documents: List of document objects to process
- output_specifications: List of required output documents
- inputDocuments: List of document objects to process
- outputSpecifications: List of required output documents
- context: Additional contextual information
Returns:
@ -63,9 +63,9 @@ class AgentBase:
- documents: List of document objects created by the agent
"""
# Base implementation - should be overridden by specialized agents
logger.warning(f"Agent {self.name} is using the default implementation of process_task")
logger.warning(f"Agent {self.name} is using the default implementation of processTask")
return {
"feedback": f"The process_task method was not implemented by agent '{self.name}'.",
"feedback": f"The processTask method was not implemented by agent '{self.name}'.",
"documents": []
}
@ -76,7 +76,7 @@ class AgentRegistry:
_instance = None
@classmethod
def get_instance(cls):
def getInstance(cls):
"""Return a singleton instance of the agent registry."""
if cls._instance is None:
cls._instance = cls()
@ -85,124 +85,124 @@ class AgentRegistry:
def __init__(self):
"""Initialize the agent registry."""
if AgentRegistry._instance is not None:
raise RuntimeError("Singleton instance already exists - use get_instance()")
raise RuntimeError("Singleton instance already exists - use getInstance()")
self.agents = {}
self.mydom = None
self._load_agents()
self._loadAgents()
def _load_agents(self):
def _loadAgents(self):
"""Load all available agents from modules."""
logger.info("Loading agent modules...")
# List of agent modules to load
agent_modules = []
agent_dir = os.path.dirname(__file__)
agentModules = []
agentDir = os.path.dirname(__file__)
# Search the directory for agent modules
for filename in os.listdir(agent_dir):
if filename.startswith("chat_agent_") and filename.endswith(".py"):
agent_modules.append(filename[:-3]) # Remove .py extension
for filename in os.listdir(agentDir):
if filename.startswith("agent") and filename.endswith(".py"):
agentModules.append(filename[0:-3]) # Remove .py extension
if not agent_modules:
if not agentModules:
logger.warning("No agent modules found")
return
logger.info(f"{len(agent_modules)} agent modules found")
logger.info(f"{len(agentModules)} agent modules found")
# Load each agent module
for module_name in agent_modules:
for moduleName in agentModules:
try:
# Import the module
module = importlib.import_module(f"modules.{module_name}")
module = importlib.import_module(f"modules.{moduleName}")
# Look for agent class or get_*_agent function
agent_name = module_name.split('_')[-1]
class_name = f"Agent{agent_name.capitalize()}"
getter_name = f"get_{agent_name}_agent"
agentName = moduleName.split("agent")[-1]
className = f"Agent{agentName}"
getterName = f"getAgent{agentName}"
agent = None
# Try to get the agent via the get_*_agent function
if hasattr(module, getter_name):
getter_func = getattr(module, getter_name)
agent = getter_func()
logger.info(f"Agent '{agent.name}' loaded via {getter_name}()")
# Try to get the agent via the get*Agent function
if hasattr(module, getterName):
getterFunc = getattr(module, getterName)
agent = getterFunc()
logger.info(f"Agent '{agent.name}' loaded via {getterName}()")
# Alternatively, try to instantiate the agent directly
elif hasattr(module, class_name):
agent_class = getattr(module, class_name)
agent = agent_class()
elif hasattr(module, className):
agentClass = getattr(module, className)
agent = agentClass()
logger.info(f"Agent '{agent.name}' directly instantiated")
if agent:
# Register the agent
self.register_agent(agent)
self.registerAgent(agent)
else:
logger.warning(f"No agent class or getter function found in module {module_name}")
logger.warning(f"No agent class or getter function found in module {moduleName}")
except ImportError as e:
logger.error(f"Module {module_name} could not be imported: {e}")
logger.error(f"Module {moduleName} could not be imported: {e}")
except Exception as e:
logger.error(f"Error loading agent from module {module_name}: {e}")
logger.error(f"Error loading agent from module {moduleName}: {e}")
def set_mydom(self, mydom):
def setMydom(self, mydom):
"""Set the AI service for all agents."""
self.mydom = mydom
self.update_agent_dependencies()
self.updateAgentDependencies()
def update_agent_dependencies(self):
def updateAgentDependencies(self):
"""Update dependencies for all registered agents."""
for agent_id, agent in self.agents.items():
if hasattr(agent, 'set_dependencies'):
agent.set_dependencies(mydom=self.mydom)
for agentId, agent in self.agents.items():
if hasattr(agent, 'setDependencies'):
agent.setDependencies(mydom=self.mydom)
def register_agent(self, agent):
def registerAgent(self, agent):
"""
Register an agent in the registry.
Args:
agent: The agent to register
"""
agent_id = getattr(agent, 'name', "unknown_agent")
agentId = getattr(agent, 'name', "unknown_agent")
# Initialize agent with dependencies
if hasattr(agent, 'set_dependencies'):
agent.set_dependencies(mydom=self.mydom)
self.agents[agent_id] = agent
if hasattr(agent, 'setDependencies'):
agent.setDependencies(mydom=self.mydom)
self.agents[agentId] = agent
logger.debug(f"Agent '{agent.name}' registered")
def get_agent(self, agent_identifier: str):
def getAgent(self, agentIdentifier: str):
"""
Return an agent instance
Args:
agent_identifier: ID or type of the desired agent
agentIdentifier: ID or type of the desired agent
Returns:
Agent instance or None if not found
"""
if agent_identifier in self.agents:
agent = self.agents[agent_identifier]
if agentIdentifier in self.agents:
agent = self.agents[agentIdentifier]
# Ensure the agent has the AI service
if hasattr(agent, 'set_dependencies') and self.mydom:
agent.set_dependencies(mydom=self.mydom)
if hasattr(agent, 'setDependencies') and self.mydom:
agent.setDependencies(mydom=self.mydom)
return agent
logger.error(f"Agent with identifier '{agent_identifier}' not found")
logger.error(f"Agent with identifier '{agentIdentifier}' not found")
return None
def get_all_agents(self) -> Dict[str, Any]:
def getAllAgents(self) -> Dict[str, Any]:
"""Return all registered agents."""
return self.agents
def get_agent_infos(self) -> List[Dict[str, Any]]:
def getAgentInfos(self) -> List[Dict[str, Any]]:
"""Return information about all registered agents."""
agent_infos = []
seen_agents = set()
agentInfos = []
seenAgents = set()
for agent in self.agents.values():
if agent not in seen_agents:
agent_infos.append(agent.get_agent_info())
seen_agents.add(agent)
return agent_infos
if agent not in seenAgents:
agentInfos.append(agent.getAgentInfo())
seenAgents.add(agent)
return agentInfos
# Singleton factory for the agent registry
def get_agent_registry():
return AgentRegistry.get_instance()
def getAgentRegistry():
return AgentRegistry.getInstance()

1236
modules/workflowManager.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,20 @@
....................... TASKS
AI Chat:
CHECKS:
----------------------- OPEN
PRIO1:
CHECK: If pictures not displayed to check utf-8 encoding in the base64 string!!
CHECK: If pictures not displayed to check utf-8 encoding in the base64 string!! general file writing and reading (example with svg)
STOP File export to static folder ("TODO)
@ -36,6 +43,186 @@ frontend: no labels definition
----------------------- 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).
I want formCeneric module to use api calls over apiCalls.js module, not directly. So please adapt formCeneric parameter "apiEndpoint" with the respective api-functions as objects, handed over by the modules:
- apiEndpoint.get --> the api to get data
- apiEndpoint.update --> the api to update data
- apiEndpoint.delete --> the api to delete data
then to use those api-functions in the module formGeneric instead of direct api calls
the modules mandates, users, files, prompts, to adapt accordingly
- all api calls from workflowUI.js and workflowData.js also to transfer to apiCall.js. There to integrate ALL route endpoints from all routes and to call over window.utils.api.....
- handleFileUplad and uploadfile is on nmany places. To have the api functionality only in apiCall.js.
please refactor those topics.
- all api calls from workflowUI.js and workflowData.js also to transfer to apiCall.js. There to integrate ALL route endpoints from all routes and to call over window.utils.api.....
- Functions to handleFileUplad and uploadfile are on many places. To have the api functionality only in apiCall.js.
no api relevant code in other modules than apiCall.js.
In apiCalls.js to remove the generic functions get, post, put, delete from the public set. those not to expose. only the specific endpoints from the routes to expose.
If more than 3 changes in a module, give me the full module. otherwise tell me the parts to change.
Please enhance this:
- config & env variables integration to have config variables in the globalState set in category "config" integrated.
cleanup utils.js:
- remove all elements in the context of workflow and messages. those elements have to be integrated within workflow... modules. Some functions within utils.js anyway are not used anymore, so to remove anyway.
- extract all api-call functions to a separate submodule "apiCalls.js" module. There to implement one interface function for each api-call. all calls to put into one object "api" to be accessed.
- at the end utils.js shall only include config & environment data management, show general toast and error, uiUtils, dataUtils
- in workflow... modules there are some redundant functions like in utils.js (e.g. showToast, showError, etc.). Those to remove in workflow and to get from utils.js
- utils data shall be accesssible within those categories:
- window.utils.api --> the functions from apiCalls.js
- window.utils.ui --> what is in uiUtils currently, plus showError, showToast and similair
- window.utils.data --> what is in dataUtils currently plus handleFileUpload
adapt other modules accordingly. for workflowUi.js only give me the parts to adapt. If only 1-3 adaptions for module, just give me the changes. Otherwise the revised module.
please adapt module workflowUi.js with this input (the other modules have already been adapted):
- Error handling for file parsing failures to add
- Clear indication of workflow completion status
- The message object structure to fully match the documented model
- Status field handling to be exaclty and only the implementation according documentation (adjustment to recognize "first", "step", "last")
- File preview to better handle the documented document structure
- File actions to use the correct API paths
- log progress indicator implementation to improve, e.g. the feature to collapse/expand details
- Agent-specific log formatting to fully match the documented model
Updates Required:
- Update message rendering to handle status field correctly
- Improve file preview to handle documented document structure
- Update API paths for file operations
- Add better indication of workflow completion status
- Improve log progress indicator implementation
also remove unused functionality and objects.
Can you adapt following two modules. the modules workflowCoordination.ja and workflowData.js have already been updated.
please remove unused functionality and objects.
please adapt module workflow.js with this input:
Updates Required:
- Implement explicit state machine transitions
- Update API interaction to match documented endpoints
- Improve error handling to match documented failure states
- Align status handling with the documented state transitions
- Ensure proper handling of the "last" message status
please adapt module workflow.js with this input:
- adapt: Explicit handling of the workflow status transitions per state machine, clean separation of workflow states according to the documentation
- The workflow state management to align with the documented state machine
- Status transition handling to be more explicit
- Verify API paths and request structures
- Response handling to match the documented workflow object
Updates Required:
- Implement explicit state machine transitions
- Update API interaction to match documented endpoints
- Improve error handling to match documented failure states
- Align status handling with the documented state transitions
- Ensure proper handling of the "last" message status
please adapt module workflowCoordination.js with this input:
- the workflow state object structure to be updated to match documentation
- Status transitions to follow the documented state machine
- message Status Handling to properly handle message status ("first", "step", "last")
Updates Required:
- Update workflowState object to match documented model
- Implement proper status transitions (null → running → completed/failed/stopped)
- Ensure message status field handling ("first", "step", "last")
- Ensure correct polling mechanism with log/message IDs
- Add missing getWorkflowStatus() function
- Fix the updateWorkflowStatus() function to handle all status transitions
please adapt module workflowData.js with this input:
- estimateJsonSize not to be in frontend. data stats is delivered in workflow object with attribute "tokensUsed"
- pollWorkflowStatus to implement
- adapt object Model Discrepancies: workflow object structure to match the state machine docs, File object structure to follow the documented model
- API Endpoint paths to correct to be: /api/workflows/${workflowId}/logs?id=${workflowState.lastPolledLogId}; Same issue to adapt for messages endpoint
- Data Handling: lastPolledLogId and lastPolledMessageId tracking variable paths to ensure corretly
- uploadAndAddFile: no change, this happens in the backend
- submitUserInput() and createWorkflow() to align response handling with the documented workflow object
Updates Required:
- Implement pollWorkflowStatus() function
- Define estimateJsonSize() function
- Fix API endpoint paths to match documentation (?logId= → ?id=)
- Update object models to match documentation
- Improve error handling according to the state machine
- Fix file handling to match documented file object model
Can you please refactor the workflow_utils.js. The other documents we have.
Attached: Frontend State machine Documentation as ruleset for the refactory, the current frontend
To organize workflow... modules like this:
1. Centralized State Management: Use a single state object that all modules reference.
2. Event-Based Updates: Use a simple event system to trigger UI updates when state changes.
3. Clear Separation of Concerns:
* Model: Manages workflow state and API communication
* View: Purely responsible for rendering the UI based on state
* Controller: Connects user actions to model updates
Comments:
- all variables and objects and functions and classes to name in camelCase, not in snake_case
- Adapted routes to implement
- I do not need backwards compatibility
- please remove all unnecessary elements and provide smart, well structured code, which is maintainable
New File names:
- workflow.js - The main module as manager and coordinator
- workflow_state.js - Centralized state management
- workflow_api.js - API communication layer
- workflow_ui.js - UI rendering layer
Can you please refactor the backend with those inputs:
Attached: Backend State machine Documentation as ruleset for the refactory
Comments:
- all variables and objects and functions and classes to name in camelCase, not in snake_case
- Adapted routes to implement
- I do not need backwards compatibility
- please remove all unnecessary elements and provide smart, well structured code, which is maintainable
If you need further documents, please tell me.
I like your proposition. So do the refactory according to your proposition to clean and structure with these documents:
- workflow_presentation.js
- workflow_presentation_core.js

View file

@ -0,0 +1,366 @@
# 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

@ -0,0 +1,453 @@
# 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

View file

@ -1,74 +0,0 @@
from fastapi import APIRouter, HTTPException, Depends, Path, Response
from typing import List, Dict, Any
from fastapi import status
from modules.auth import get_current_active_user, get_user_context
# Import the attribute definition and helper functions
from modules.def_attributes import AttributeDefinition, get_model_attributes
# Import the model modules (without specific classes)
import modules.gateway_model as gateway_model
import modules.lucydom_model as lucydom_model
model_classes = {
# Gateway model classes
"mandate": gateway_model.Mandate,
"user": gateway_model.User,
# LucyDOM model classes - admin
"file": lucydom_model.FileItem,
"prompt": lucydom_model.Prompt,
# LucyDOM model classes - chat
"document_content": lucydom_model.DocumentContent,
"document": lucydom_model.Document,
"data_stats": lucydom_model.DataStats,
"user_input_request": lucydom_model.UserInputRequest,
"workflow": lucydom_model.Workflow,
"workflow_message": lucydom_model.WorkflowMessage,
"workflow_log": lucydom_model.WorkflowLog,
}
# Create a router for the attribute endpoints
router = APIRouter(
prefix="/api/attributes",
tags=["Attributes"],
responses={404: {"description": "Not found"}}
)
@router.get("/{entity_type}", response_model=List[AttributeDefinition])
async def get_entity_attributes(
entity_type: str = Path(..., description="Type of entity (e.g. prompt)"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Retrieves the attribute definitions for a specific entity.
This can be used for dynamic form generation.
"""
# Authentication and user context
mandate_id, user_id = await get_user_context(current_user)
# Determine preferred language of the user
user_language = current_user.get("language", "de")
# Check if entity type is known
if entity_type not in model_classes:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Entity type '{entity_type}' not found."
)
# Get model class and derive attributes from it
model_class = model_classes[entity_type]
attributes = get_model_attributes(model_class, user_language)
# Return only editable and visible attributes
visible_attributes = [attr for attr in attributes if attr.visible]
@router.options("/{entity_type}")
async def options_entity_attributes(
entity_type: str = Path(..., description="Type of entity (e.g. prompt)")
):
return Response(status_code=200)

View file

@ -1,216 +0,0 @@
from fastapi import APIRouter, HTTPException, Depends, Body, Path
from typing import List, Dict, Any
from fastapi import status
from datetime import datetime
from dataclasses import dataclass
# Import interfaces
from modules.auth import get_current_active_user, get_user_context
from modules.gateway_interface import get_gateway_interface
from modules.gateway_model import Mandate
# Determine all attributes of the model (except internal/special attributes)
def get_model_attributes(model_class):
return [attr for attr in dir(model_class)
if not callable(getattr(model_class, attr))
and not attr.startswith('_')
and attr != 'metadata'
and attr != 'query'
and attr != 'query_class'
and attr != 'label'
and attr != 'field_labels']
# Model attributes for Mandate
mandate_attributes = get_model_attributes(Mandate)
@dataclass
class AppContext:
"""Context object for all required connections and user information"""
mandate_id: int
user_id: int
interface_data: Any # Gateway Interface
async def get_context(current_user: Dict[str, Any]) -> AppContext:
"""
Creates a central context object with all required interfaces
Args:
current_user: Current user from authentication
Returns:
AppContext object with all required connections
"""
mandate_id, user_id = await get_user_context(current_user)
interface_data = get_gateway_interface(mandate_id, user_id)
return AppContext(
mandate_id=mandate_id,
user_id=user_id,
interface_data=interface_data
)
# Create router for mandate endpoints
router = APIRouter(
prefix="/api/mandates",
tags=["Mandates"],
responses={404: {"description": "Not found"}}
)
@router.get("", response_model=List[Dict[str, Any]])
async def get_mandates(current_user: Dict[str, Any] = Depends(get_current_active_user)):
"""Get all available mandates (only for SysAdmin users)"""
context = await get_context(current_user)
# Permission check
if current_user.get("privilege") != "sysadmin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only system administrators can access all mandates"
)
# Get mandates generically
return context.interface_data.get_all_mandates()
@router.post("", response_model=Dict[str, Any])
async def create_mandate(
mandate: Dict[str, Any] = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Create a new mandate (only for SysAdmin users)"""
context = await get_context(current_user)
# Permission check
if current_user.get("privilege") != "sysadmin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only system administrators can create mandates"
)
# Set attributes from the request dynamically
mandate_data = {}
for attr in mandate_attributes:
if attr in mandate:
mandate_data[attr] = mandate[attr]
# Default values for missing fields
if "name" not in mandate_data:
mandate_data["name"] = "New Mandate"
if "language" not in mandate_data:
mandate_data["language"] = "de"
# Create mandate
new_mandate = context.interface_data.create_mandate(**mandate_data)
return new_mandate
@router.get("/{mandate_id}", response_model=Dict[str, Any])
async def get_mandate(
mandate_id: int,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Get a specific mandate"""
context = await get_context(current_user)
# Permission check
# Admin can only see their own mandate, SysAdmin can see all
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
is_own_mandate = context.mandate_id == mandate_id
if (is_admin and not is_own_mandate) and not is_sysadmin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to access this mandate"
)
# Get mandate generically
mandate = context.interface_data.get_mandate(mandate_id)
if not mandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandate with ID {mandate_id} not found"
)
return mandate
@router.put("/{mandate_id}", response_model=Dict[str, Any])
async def update_mandate(
mandate_id: int = Path(..., description="ID of the mandate to update"),
mandate_data: Dict[str, Any] = Body(..., description="Updated mandate data"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Update an existing mandate"""
context = await get_context(current_user)
# Mandate exists?
mandate = context.interface_data.get_mandate(mandate_id)
if not mandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandate with ID {mandate_id} not found"
)
# Permission check
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
is_own_mandate = context.mandate_id == mandate_id
if (is_admin and not is_own_mandate) and not is_sysadmin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to update this mandate"
)
# Dynamically filter attributes from the request into update_data
update_data = {}
for attr in mandate_attributes:
if attr in mandate_data:
update_data[attr] = mandate_data[attr]
# Update mandate
updated_mandate = context.interface_data.update_mandate(
mandate_id=mandate_id,
mandate_data=update_data
)
return updated_mandate
@router.delete("/{mandate_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_mandate(
mandate_id: int = Path(..., description="ID of the mandate to delete"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Delete a mandate, including all associated users and referenced objects"""
context = await get_context(current_user)
# Mandate exists?
mandate = context.interface_data.get_mandate(mandate_id)
if not mandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandate with ID {mandate_id} not found"
)
# Permission check
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
is_own_mandate = context.mandate_id == mandate_id
if (is_admin and not is_own_mandate) and not is_sysadmin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to delete this mandate"
)
# Delete mandate
success = context.interface_data.delete_mandate(mandate_id)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting mandate with ID {mandate_id}"
)
# Return no content on successful deletion
return None

View file

@ -1,188 +0,0 @@
from fastapi import APIRouter, HTTPException, Depends, Body, Query, Path
from typing import List, Dict, Any, Optional
from fastapi import status
from datetime import datetime
from dataclasses import dataclass
# Import auth module
from modules.auth import get_current_active_user, get_user_context
# Import interfaces
from modules.lucydom_interface import get_lucydom_interface
from modules.lucydom_model import Prompt
# Get all attributes of the model (except internal/special attributes)
def get_model_attributes(model_class):
return [attr for attr in dir(model_class)
if not callable(getattr(model_class, attr))
and not attr.startswith('_')
and attr != 'metadata'
and attr != 'query'
and attr != 'query_class'
and attr != 'label'
and attr != 'field_labels']
# Model attributes for Prompt
prompt_attributes = get_model_attributes(Prompt)
@dataclass
class AppContext:
"""Context object for all required connections and user information"""
mandate_id: int
user_id: int
interface_data: Any # LucyDOM Interface
async def get_context(current_user: Dict[str, Any]) -> AppContext:
"""
Creates a central context object with all required interfaces
Args:
current_user: Current user from authentication
Returns:
AppContext object with all required connections
"""
mandate_id, user_id = await get_user_context(current_user)
interface_data = get_lucydom_interface(mandate_id, user_id)
return AppContext(
mandate_id=mandate_id,
user_id=user_id,
interface_data=interface_data
)
# Create router for prompt endpoints
router = APIRouter(
prefix="/api/prompts",
tags=["Prompts"],
responses={404: {"description": "Not found"}}
)
@router.get("", response_model=List[Dict[str, Any]])
async def get_prompts(
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Get all prompts"""
context = await get_context(current_user)
# Retrieve prompts generically
return context.interface_data.get_all_prompts()
@router.post("", response_model=Dict[str, Any])
async def create_prompt(
prompt: Dict[str, Any] = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Create a new prompt"""
context = await get_context(current_user)
# Set attributes from the request dynamically
prompt_data = {}
for attr in prompt_attributes:
if attr in prompt:
prompt_data[attr] = prompt[attr]
# Required fields with default values
content = prompt.get("content", "")
name = prompt.get("name", "New Prompt")
# Create prompt
new_prompt = context.interface_data.create_prompt(
content=content,
name=name
)
# Set current time for created_at if it exists in the model
if "created_at" in prompt_attributes and hasattr(new_prompt, "created_at"):
new_prompt["created_at"] = datetime.now().isoformat()
return new_prompt
@router.get("/{prompt_id}", response_model=Dict[str, Any])
async def get_prompt(
prompt_id: int,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Get a specific prompt"""
context = await get_context(current_user)
# Get prompt generically
prompt = context.interface_data.get_prompt(prompt_id)
if not prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {prompt_id} not found"
)
return prompt
@router.put("/{prompt_id}", response_model=Dict[str, Any])
async def update_prompt(
prompt_id: int,
prompt_data: Dict[str, Any] = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Update an existing prompt"""
context = await get_context(current_user)
# Check if the prompt exists
existing_prompt = context.interface_data.get_prompt(prompt_id)
if not existing_prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {prompt_id} not found"
)
# Filter attributes from the request dynamically
update_data = {}
for attr in prompt_attributes:
if attr in prompt_data:
update_data[attr] = prompt_data[attr]
# Standard fields for update
content = prompt_data.get("content")
name = prompt_data.get("name")
# Update prompt
updated_prompt = context.interface_data.update_prompt(
prompt_id=prompt_id,
content=content,
name=name
)
if not updated_prompt:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error updating the prompt"
)
return updated_prompt
@router.delete("/{prompt_id}", response_model=Dict[str, Any])
async def delete_prompt(
prompt_id: int,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Delete a prompt"""
context = await get_context(current_user)
# Check if the prompt exists
existing_prompt = context.interface_data.get_prompt(prompt_id)
if not existing_prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {prompt_id} not found"
)
success = context.interface_data.delete_prompt(prompt_id)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error deleting the prompt"
)
return {"message": f"Prompt with ID {prompt_id} successfully deleted"}

74
routes/routeAttributes.py Normal file
View file

@ -0,0 +1,74 @@
from fastapi import APIRouter, HTTPException, Depends, Path, Response
from typing import List, Dict, Any
from fastapi import status
from modules.auth import getCurrentActiveUser, getUserContext
# Import the attribute definition and helper functions
from modules.defAttributes import AttributeDefinition, getModelAttributes
# Import the model modules (without specific classes)
import modules.gatewayModel as gatewayModel
import modules.lucydomModel as lucydomModel
modelClasses = {
# Gateway model classes
"mandate": gatewayModel.Mandate,
"user": gatewayModel.User,
# LucyDOM model classes - admin
"file": lucydomModel.FileItem,
"prompt": lucydomModel.Prompt,
# LucyDOM model classes - chat
"documentContent": lucydomModel.DocumentContent,
"document": lucydomModel.Document,
"dataStats": lucydomModel.DataStats,
"userInputRequest": lucydomModel.UserInputRequest,
"workflow": lucydomModel.Workflow,
"workflowMessage": lucydomModel.WorkflowMessage,
"workflowLog": lucydomModel.WorkflowLog,
}
# Create a router for the attribute endpoints
router = APIRouter(
prefix="/api/attributes",
tags=["Attributes"],
responses={404: {"description": "Not found"}}
)
@router.get("/{entityType}", response_model=List[AttributeDefinition])
async def getEntityAttributes(
entityType: str = Path(..., description="Type of entity (e.g. prompt)"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Retrieves the attribute definitions for a specific entity.
This can be used for dynamic form generation.
"""
# Authentication and user context
mandateId, userId = await getUserContext(currentUser)
# Determine preferred language of the user
userLanguage = currentUser.get("language", "de")
# Check if entity type is known
if entityType not in modelClasses:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Entity type '{entityType}' not found."
)
# Get model class and derive attributes from it
modelClass = modelClasses[entityType]
attributes = getModelAttributes(modelClass, userLanguage)
# Return only visible attributes
return [attr for attr in attributes if attr.visible]
@router.options("/{entityType}")
async def optionsEntityAttributes(
entityType: str = Path(..., description="Type of entity (e.g. prompt)")
):
"""Handle OPTIONS request for CORS preflight"""
return Response(status_code=200)

View file

@ -6,54 +6,42 @@ from datetime import datetime
from dataclasses import dataclass
import io
from modules.auth import get_current_active_user, get_user_context
from modules.auth import getCurrentActiveUser, getUserContext
from modules.configuration import APP_CONFIG
# Import interfaces
from modules.lucydom_interface import get_lucydom_interface, FileError, FileNotFoundError, FileStorageError, FilePermissionError, FileDeletionError
from modules.lucydom_model import FileItem
from modules.lucydomInterface import getLucydomInterface, FileError, FileNotFoundError, FileStorageError, FilePermissionError, FileDeletionError
from modules.lucydomModel import FileItem
# Configure logger
logger = logging.getLogger(__name__)
# Get all attributes of the model (except internal/special attributes)
def get_model_attributes(model_class):
return [attr for attr in dir(model_class)
if not callable(getattr(model_class, attr))
# Get all attributes of the model
def getModelAttributes(modelClass):
return [attr for attr in dir(modelClass)
if not callable(getattr(modelClass, attr))
and not attr.startswith('_')
and attr != 'metadata'
and attr != 'query'
and attr != 'query_class'
and attr != 'label'
and attr != 'field_labels']
and attr not in ('metadata', 'query', 'query_class', 'label', 'field_labels')]
# Model attributes for FileItem
file_attributes = get_model_attributes(FileItem)
fileAttributes = getModelAttributes(FileItem)
@dataclass
class AppContext:
"""Context object for all required connections and user information"""
mandate_id: int
user_id: int
interface_data: Any # LucyDOM Interface
mandateId: int
userId: int
interfaceData: Any # LucyDOM Interface
async def get_context(current_user: Dict[str, Any]) -> AppContext:
"""
Creates a central context object with all required interfaces
Args:
current_user: Current user from authentication
Returns:
AppContext object with all required connections
"""
mandate_id, user_id = await get_user_context(current_user)
interface_data = get_lucydom_interface(mandate_id, user_id)
async def getContext(currentUser: Dict[str, Any]) -> AppContext:
"""Creates a central context object with all required connections"""
mandateId, userId = await getUserContext(currentUser)
interfaceData = getLucydomInterface(mandateId, userId)
return AppContext(
mandate_id=mandate_id,
user_id=user_id,
interface_data=interface_data
mandateId=mandateId,
userId=userId,
interfaceData=interfaceData
)
# Create router for file endpoints
@ -70,13 +58,13 @@ router = APIRouter(
)
@router.get("", response_model=List[Dict[str, Any]])
async def get_files(current_user: Dict[str, Any] = Depends(get_current_active_user)):
async def getFiles(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
"""Get all available files"""
try:
context = await get_context(current_user)
context = await getContext(currentUser)
# Get all files generically - only metadata, no binary data
files = context.interface_data.get_all_files()
files = context.interfaceData.getAllFiles()
return files
except Exception as e:
logger.error(f"Error retrieving files: {str(e)}")
@ -85,41 +73,38 @@ async def get_files(current_user: Dict[str, Any] = Depends(get_current_active_us
detail=f"Error retrieving files: {str(e)}"
)
@router.post("/upload", status_code=status.HTTP_201_CREATED)
async def upload_file(
async def uploadFile(
file: UploadFile = File(...),
workflow_id: Optional[str] = Form(None),
current_user: Dict[str, Any] = Depends(get_current_active_user)
workflowId: Optional[str] = Form(None),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Upload a file
"""
"""Upload a file"""
try:
context = await get_context(current_user)
context = await getContext(currentUser)
# Read file
file_content = await file.read()
fileContent = await file.read()
# Check size limits
max_size = int(APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # in bytes
if len(file_content) > max_size:
maxSize = int(APP_CONFIG.get("File_Management_MAX_UPLOAD_SIZE_MB")) * 1024 * 1024 # in bytes
if len(fileContent) > maxSize:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail=f"File too large. Maximum size: {APP_CONFIG.get('File_Management_MAX_UPLOAD_SIZE_MB')}MB"
)
# Save file via LucyDOM interface in the database
file_meta = context.interface_data.save_uploaded_file(file_content, file.filename)
fileMeta = context.interfaceData.saveUploadedFile(fileContent, file.filename)
# If workflow_id is provided, update the file information
if workflow_id:
update_data = {"workflow_id": workflow_id}
context.interface_data.update_file(file_meta["id"], update_data)
file_meta["workflow_id"] = workflow_id
# If workflowId is provided, update the file information
if workflowId:
updateData = {"workflowId": workflowId}
context.interfaceData.updateFile(fileMeta["id"], updateData)
fileMeta["workflowId"] = workflowId
# Successful response
return file_meta
return fileMeta
except FileStorageError as e:
logger.error(f"Error during file upload (storage): {str(e)}")
@ -134,30 +119,25 @@ async def upload_file(
detail=f"Error during file upload: {str(e)}"
)
@router.get("/{file_id}")
async def get_file(
file_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
@router.get("/{fileId}")
async def getFile(
fileId: str,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Returns a file by its ID for download.
Retrieves both metadata and binary data.
"""
"""Returns a file by its ID for download"""
try:
context = await get_context(current_user)
context = await getContext(currentUser)
# Get file via LucyDOM interface from the database
# Uses the download_file method, which now combines metadata and binary data
file_data = context.interface_data.download_file(file_id)
fileData = context.interfaceData.downloadFile(fileId)
# Return file
headers = {
"Content-Disposition": f'attachment; filename="{file_data["name"]}"'
"Content-Disposition": f'attachment; filename="{fileData["name"]}"'
}
return Response(
content=file_data["content"],
media_type=file_data["content_type"],
content=fileData["content"],
media_type=fileData["contentType"],
headers=headers
)
@ -186,22 +166,17 @@ async def get_file(
detail=f"Error retrieving file: {str(e)}"
)
@router.delete("/{file_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_file(
file_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
@router.delete("/{fileId}", status_code=status.HTTP_204_NO_CONTENT)
async def deleteFile(
fileId: str,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Deletes a file by its ID from the database.
Removes both metadata and binary data.
"""
"""Deletes a file by its ID from the database"""
try:
context = await get_context(current_user)
context = await getContext(currentUser)
# Delete file via LucyDOM interface
# The method now handles deleting from both tables (files and file_data)
context.interface_data.delete_file(file_id)
context.interfaceData.deleteFile(fileId)
# Return successful deletion without content (204 No Content)
return Response(status_code=status.HTTP_204_NO_CONTENT)
@ -232,34 +207,32 @@ async def delete_file(
)
@router.get("/stats", response_model=Dict[str, Any])
async def get_file_stats(
current_user: Dict[str, Any] = Depends(get_current_active_user)
async def getFileStats(
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Returns statistics about the stored files.
"""
"""Returns statistics about the stored files"""
try:
context = await get_context(current_user)
context = await getContext(currentUser)
# Get all files - metadata only
all_files = context.interface_data.get_all_files()
allFiles = context.interfaceData.getAllFiles()
# Calculate statistics
total_files = len(all_files)
total_size = sum(file.get("size", 0) for file in all_files)
totalFiles = len(allFiles)
totalSize = sum(file.get("size", 0) for file in allFiles)
# Group by file type
file_types = {}
for file in all_files:
file_type = file.get("mime_type", "unknown").split("/")[0]
if file_type not in file_types:
file_types[file_type] = 0
file_types[file_type] += 1
fileTypes = {}
for file in allFiles:
fileType = file.get("mimeType", "unknown").split("/")[0]
if fileType not in fileTypes:
fileTypes[fileType] = 0
fileTypes[fileType] += 1
return {
"total_files": total_files,
"total_size_bytes": total_size,
"file_types": file_types
"totalFiles": totalFiles,
"totalSizeBytes": totalSize,
"fileTypes": fileTypes
}
except Exception as e:

199
routes/routeMandates.py Normal file
View file

@ -0,0 +1,199 @@
from fastapi import APIRouter, HTTPException, Depends, Body, Path
from typing import List, Dict, Any
from fastapi import status
from datetime import datetime
from dataclasses import dataclass
# Import interfaces
from modules.auth import getCurrentActiveUser, getUserContext
from modules.gatewayInterface import getGatewayInterface
from modules.gatewayModel import Mandate
# Determine all attributes of the model
def getModelAttributes(modelClass):
return [attr for attr in dir(modelClass)
if not callable(getattr(modelClass, attr))
and not attr.startswith('_')
and attr not in ('metadata', 'query', 'query_class', 'label', 'field_labels')]
# Model attributes for Mandate
mandateAttributes = getModelAttributes(Mandate)
@dataclass
class AppContext:
"""Context object for all required connections and user information"""
mandateId: int
userId: int
interfaceData: Any # Gateway Interface
async def getContext(currentUser: Dict[str, Any]) -> AppContext:
"""Creates a central context object with all required connections"""
mandateId, userId = await getUserContext(currentUser)
interfaceData = getGatewayInterface(mandateId, userId)
return AppContext(
mandateId=mandateId,
userId=userId,
interfaceData=interfaceData
)
# Create router for mandate endpoints
router = APIRouter(
prefix="/api/mandates",
tags=["Mandates"],
responses={404: {"description": "Not found"}}
)
@router.get("", response_model=List[Dict[str, Any]])
async def getMandates(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
"""Get all available mandates (only for SysAdmin users)"""
context = await getContext(currentUser)
# Permission check
if currentUser.get("privilege") != "sysadmin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only system administrators can access all mandates"
)
# Get mandates
return context.interfaceData.getAllMandates()
@router.post("", response_model=Dict[str, Any])
async def createMandate(
mandate: Dict[str, Any] = Body(...),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Create a new mandate (only for SysAdmin users)"""
context = await getContext(currentUser)
# Permission check
if currentUser.get("privilege") != "sysadmin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only system administrators can create mandates"
)
# Set attributes from the request dynamically
mandateData = {}
for attr in mandateAttributes:
if attr in mandate:
mandateData[attr] = mandate[attr]
# Default values for missing fields
mandateData.setdefault("name", "New Mandate")
mandateData.setdefault("language", "de")
# Create mandate
newMandate = context.interfaceData.createMandate(**mandateData)
return newMandate
@router.get("/{mandateId}", response_model=Dict[str, Any])
async def getMandate(
mandateId: int,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Get a specific mandate"""
context = await getContext(currentUser)
# Permission check
# Admin can only see their own mandate, SysAdmin can see all
isAdmin = currentUser.get("privilege") == "admin"
isSysadmin = currentUser.get("privilege") == "sysadmin"
isOwnMandate = context.mandateId == mandateId
if (isAdmin and not isOwnMandate) and not isSysadmin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to access this mandate"
)
# Get mandate
mandate = context.interfaceData.getMandate(mandateId)
if not mandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandate with ID {mandateId} not found"
)
return mandate
@router.put("/{mandateId}", response_model=Dict[str, Any])
async def updateMandate(
mandateId: int = Path(..., description="ID of the mandate to update"),
mandateData: Dict[str, Any] = Body(..., description="Updated mandate data"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Update an existing mandate"""
context = await getContext(currentUser)
# Mandate exists?
mandate = context.interfaceData.getMandate(mandateId)
if not mandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandate with ID {mandateId} not found"
)
# Permission check
isAdmin = currentUser.get("privilege") == "admin"
isSysadmin = currentUser.get("privilege") == "sysadmin"
isOwnMandate = context.mandateId == mandateId
if (isAdmin and not isOwnMandate) and not isSysadmin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to update this mandate"
)
# Dynamically filter attributes from the request into updateData
updateData = {}
for attr in mandateAttributes:
if attr in mandateData:
updateData[attr] = mandateData[attr]
# Update mandate
updatedMandate = context.interfaceData.updateMandate(
mandateId=mandateId,
mandateData=updateData
)
return updatedMandate
@router.delete("/{mandateId}", status_code=status.HTTP_204_NO_CONTENT)
async def deleteMandate(
mandateId: int = Path(..., description="ID of the mandate to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Delete a mandate, including all associated users and referenced objects"""
context = await getContext(currentUser)
# Mandate exists?
mandate = context.interfaceData.getMandate(mandateId)
if not mandate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Mandate with ID {mandateId} not found"
)
# Permission check
isAdmin = currentUser.get("privilege") == "admin"
isSysadmin = currentUser.get("privilege") == "sysadmin"
isOwnMandate = context.mandateId == mandateId
if (isAdmin and not isOwnMandate) and not isSysadmin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to delete this mandate"
)
# Delete mandate
success = context.interfaceData.deleteMandate(mandateId)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting mandate with ID {mandateId}"
)
return None

160
routes/routePrompts.py Normal file
View file

@ -0,0 +1,160 @@
from fastapi import APIRouter, HTTPException, Depends, Body, Query, Path
from typing import List, Dict, Any, Optional
from fastapi import status
from datetime import datetime
from dataclasses import dataclass
# Import auth module
from modules.auth import getCurrentActiveUser, getUserContext
# Import interfaces
from modules.lucydomInterface import getLucydomInterface
from modules.lucydomModel import Prompt
# Get all attributes of the model
def getModelAttributes(modelClass):
return [attr for attr in dir(modelClass)
if not callable(getattr(modelClass, attr))
and not attr.startswith('_')
and attr not in ('metadata', 'query', 'query_class', 'label', 'field_labels')]
# Model attributes for Prompt
promptAttributes = getModelAttributes(Prompt)
@dataclass
class AppContext:
"""Context object for all required connections and user information"""
mandateId: int
userId: int
interfaceData: Any # LucyDOM Interface
async def getContext(currentUser: Dict[str, Any]) -> AppContext:
"""Creates a central context object with all required connections"""
mandateId, userId = await getUserContext(currentUser)
interfaceData = getLucydomInterface(mandateId, userId)
return AppContext(
mandateId=mandateId,
userId=userId,
interfaceData=interfaceData
)
# Create router for prompt endpoints
router = APIRouter(
prefix="/api/prompts",
tags=["Prompts"],
responses={404: {"description": "Not found"}}
)
@router.get("", response_model=List[Dict[str, Any]])
async def getPrompts(
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Get all prompts"""
context = await getContext(currentUser)
# Retrieve prompts
return context.interfaceData.getAllPrompts()
@router.post("", response_model=Dict[str, Any])
async def createPrompt(
prompt: Dict[str, Any] = Body(...),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Create a new prompt"""
context = await getContext(currentUser)
# Required fields with default values
content = prompt.get("content", "")
name = prompt.get("name", "New Prompt")
# Create prompt
newPrompt = context.interfaceData.createPrompt(
content=content,
name=name
)
# Set current time for createdAt if it exists in the model
if "createdAt" in promptAttributes and hasattr(newPrompt, "createdAt"):
newPrompt["createdAt"] = datetime.now().isoformat()
return newPrompt
@router.get("/{promptId}", response_model=Dict[str, Any])
async def getPrompt(
promptId: int,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Get a specific prompt"""
context = await getContext(currentUser)
# Get prompt
prompt = context.interfaceData.getPrompt(promptId)
if not prompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {promptId} not found"
)
return prompt
@router.put("/{promptId}", response_model=Dict[str, Any])
async def updatePrompt(
promptId: int,
promptData: Dict[str, Any] = Body(...),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Update an existing prompt"""
context = await getContext(currentUser)
# Check if the prompt exists
existingPrompt = context.interfaceData.getPrompt(promptId)
if not existingPrompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {promptId} not found"
)
# Standard fields for update
content = promptData.get("content")
name = promptData.get("name")
# Update prompt
updatedPrompt = context.interfaceData.updatePrompt(
promptId=promptId,
content=content,
name=name
)
if not updatedPrompt:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error updating the prompt"
)
return updatedPrompt
@router.delete("/{promptId}", response_model=Dict[str, Any])
async def deletePrompt(
promptId: int,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Delete a prompt"""
context = await getContext(currentUser)
# Check if the prompt exists
existingPrompt = context.interfaceData.getPrompt(promptId)
if not existingPrompt:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prompt with ID {promptId} not found"
)
success = context.interfaceData.deletePrompt(promptId)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error deleting the prompt"
)
return {"message": f"Prompt with ID {promptId} successfully deleted"}

249
routes/routeUsers.py Normal file
View file

@ -0,0 +1,249 @@
from fastapi import APIRouter, HTTPException, Depends, Body, Path
from typing import List, Dict, Any, Optional
from fastapi import status
from datetime import datetime
from dataclasses import dataclass
# Import auth module
from modules.auth import getCurrentActiveUser, getUserContext
# Import interfaces
from modules.gatewayInterface import getGatewayInterface
from modules.gatewayModel import User
# Determine all attributes of the model
def getModelAttributes(modelClass):
return [attr for attr in dir(modelClass)
if not callable(getattr(modelClass, attr))
and not attr.startswith('_')
and attr not in ('metadata', 'query', 'query_class', 'label', 'field_labels')]
# Model attributes for User
userAttributes = getModelAttributes(User)
@dataclass
class AppContext:
"""Context object for all required connections and user information"""
mandateId: int
userId: int
interfaceData: Any # Gateway Interface
async def getContext(currentUser: Dict[str, Any]) -> AppContext:
"""Creates a central context object with all required connections"""
mandateId, userId = await getUserContext(currentUser)
interfaceData = getGatewayInterface(mandateId, userId)
return AppContext(
mandateId=mandateId,
userId=userId,
interfaceData=interfaceData
)
# Create router for user endpoints
router = APIRouter(
prefix="/api/users",
tags=["Users"],
responses={404: {"description": "Not found"}}
)
@router.get("", response_model=List[Dict[str, Any]])
async def getUsers(currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)):
"""Get all available users (only for Admin/SysAdmin users)"""
context = await getContext(currentUser)
# Permission check
if currentUser.get("privilege") not in ["admin", "sysadmin"]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to access the user list"
)
# Admin sees only users of own mandate, SysAdmin sees all
if currentUser.get("privilege") == "admin":
return context.interfaceData.getUsersByMandate(context.mandateId)
else: # sysadmin
return context.interfaceData.getAllUsers()
@router.post("/register", response_model=Dict[str, Any])
async def registerUser(userData: dict = Body(...)):
"""Register a new user"""
# Don't use user context for registration
gateway = getGatewayInterface()
if "username" not in userData or "password" not in userData:
raise HTTPException(status_code=400, detail="Username and password required")
try:
# Prepare mandate data
mandateData = {
"name": f"Mandate of {userData['username']}",
"language": userData.get("language", "de")
}
newMandate = gateway.createMandate(**mandateData)
# Filter user attributes from the request
userCreateData = {}
for attr in userAttributes:
if attr in userData and attr != "id": # ID is auto-assigned
userCreateData[attr] = userData[attr]
# Required fields
userCreateData["username"] = userData["username"]
userCreateData["password"] = userData["password"]
userCreateData["mandateId"] = newMandate["id"]
# Default values for optional fields
userCreateData.setdefault("disabled", False)
userCreateData.setdefault("privilege", "user")
userCreateData.setdefault("language", "de")
newUser = gateway.createUser(**userCreateData)
return newUser
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/{userId}", response_model=Dict[str, Any])
async def getUser(
userId: int,
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Get a specific user"""
context = await getContext(currentUser)
# Initialize gateway interface with user context
userToGet = context.interfaceData.getUser(userId)
if not userToGet:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {userId} not found"
)
# Permission check
# User can only view themselves, Admin only users of their own mandate, SysAdmin all
if userId == context.userId:
# User can view themselves
pass
elif currentUser.get("privilege") == "admin" and userToGet.get("mandateId") == context.mandateId:
# Admin can view users of their own mandate
pass
elif currentUser.get("privilege") == "sysadmin":
# SysAdmin can view all users
pass
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to view this user"
)
return userToGet
@router.put("/{userId}", response_model=Dict[str, Any])
async def updateUser(
userId: int = Path(..., description="ID of the user to update"),
userData: Dict[str, Any] = Body(..., description="Updated user data"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Update an existing user"""
context = await getContext(currentUser)
# User exists?
userToUpdate = context.interfaceData.getUser(userId)
if not userToUpdate:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {userId} not found"
)
# Permission check
isSelfUpdate = userId == context.userId
isAdmin = currentUser.get("privilege") == "admin"
isSysadmin = currentUser.get("privilege") == "sysadmin"
sameMandate = userToUpdate.get("mandateId") == context.mandateId
# Filter allowed fields based on permission level
allowedFields = {"username", "email", "fullName", "language"}
sensitiveFields = {"mandateId", "disabled", "privilege"}
# Check if sensitive fields should be changed
sensitiveUpdate = any(field in userData for field in sensitiveFields)
if isSelfUpdate and sensitiveUpdate:
# Normal users cannot change their sensitive data
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to change sensitive user data"
)
elif isAdmin and sensitiveUpdate and not sameMandate:
# Admins can only change sensitive data for users of their own mandate
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to change sensitive data for users of other mandates"
)
elif not (isSelfUpdate or (isAdmin and sameMandate) or isSysadmin):
# No permission for other cases
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to update this user"
)
# Dynamically filter attributes from the request
updateData = {}
for attr in userAttributes:
if attr in userData and attr != "id": # ID cannot be changed
updateData[attr] = userData[attr]
# Remove disallowed fields for normal users
if not (isAdmin or isSysadmin):
updateData = {k: v for k, v in updateData.items() if k in allowedFields}
# Update user data
updatedUser = context.interfaceData.updateUser(userId, updateData)
return updatedUser
@router.delete("/{userId}", status_code=status.HTTP_204_NO_CONTENT)
async def deleteUser(
userId: int = Path(..., description="ID of the user to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""Delete a user"""
context = await getContext(currentUser)
# User exists?
userToDelete = context.interfaceData.getUser(userId)
if not userToDelete:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {userId} not found"
)
# Permission check
isSelfDelete = userId == context.userId
isAdmin = currentUser.get("privilege") == "admin"
isSysadmin = currentUser.get("privilege") == "sysadmin"
sameMandate = userToDelete.get("mandateId") == context.mandateId
if isSelfDelete:
# User can delete themselves
pass
elif isAdmin and sameMandate:
# Admin can delete users of their own mandate
pass
elif isSysadmin:
# SysAdmin can delete all users
pass
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to delete this user"
)
# Delete user and all referenced objects
success = context.interfaceData.deleteUser(userId)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting user with ID {userId}"
)
return None

667
routes/routeWorkflows.py Normal file
View file

@ -0,0 +1,667 @@
"""
Workflow routes for the backend API.
Implements the endpoints for workflow management according to the state machine.
"""
import os
import json
import logging
from typing import List, Dict, Any, Optional
from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query, Response, status
from dataclasses import dataclass
from datetime import datetime
# Import interfaces
from modules.lucydomInterface import getLucydomInterface
from modules.auth import getCurrentActiveUser, getUserContext
from modules.workflowManager import getWorkflowManager
# Import models
from modules.lucydomModel import UserInputRequest
# Configure logger
logger = logging.getLogger(__name__)
# Create router for workflow endpoints
router = APIRouter(
prefix="/api/workflows",
tags=["Workflow"],
responses={404: {"description": "Not found"}}
)
@dataclass
class AppContext:
"""Context object for all required connections and user information"""
mandateId: int
userId: int
interfaceData: Any # LucyDOM Interface
interfaceChat: Any # Workflow Manager
async def getContext(currentUser: Dict[str, Any]) -> AppContext:
"""
Creates a central context object with all required interfaces
Args:
currentUser: Current user from authentication
Returns:
AppContext object with all required connections
"""
mandateId, userId = await getUserContext(currentUser)
interfaceData = getLucydomInterface(mandateId, userId)
interfaceChat = getWorkflowManager(mandateId, userId)
return AppContext(
mandateId=mandateId,
userId=userId,
interfaceData=interfaceData,
interfaceChat=interfaceChat
)
# State 1: Workflow Initialization endpoint
@router.post("/start", response_model=Dict[str, Any])
async def startWorkflow(
workflowId: Optional[str] = Query(None, description="Optional ID of the workflow to continue"),
userInput: UserInputRequest = Body(...),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Starts a new workflow or continues an existing one.
Corresponds to State 1 in the state machine documentation.
Args:
workflowId: Optional ID of an existing workflow to continue
userInput: User input with prompt and optional file list
currentUser: Authenticated user
Returns:
Dictionary with workflow ID and status
"""
context = await getContext(currentUser)
try:
# Convert the user input to a dictionary
userInputDict = {
"prompt": userInput.prompt,
"listFileId": userInput.listFileId
}
# Start or continue workflow using the workflow manager
workflow = await context.interfaceChat.workflowStart(userInputDict, workflowId)
logger.info("User Input received. Answer:",workflow)
return {
"id": workflow.get("id"),
"status": workflow.get("status", "running"),
"message": "Workflow initialized and processing started"
}
except Exception as e:
logger.error(f"Error starting workflow: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error starting workflow: {str(e)}"
)
# State 8: Workflow Stopped endpoint
@router.post("/{workflowId}/stop", response_model=Dict[str, Any])
async def stopWorkflow(
workflowId: str = Path(..., description="ID of the workflow to stop"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Stops a running workflow.
Corresponds to State 8 in the state machine documentation.
Args:
workflowId: ID of the workflow to stop
currentUser: Authenticated user
Returns:
Dictionary with status information
"""
context = await getContext(currentUser)
try:
# Verify workflow exists and belongs to user
workflow = context.interfaceData.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflowId} not found"
)
if workflow.get("userId") != context.userId:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You don't have permission to stop this workflow"
)
# Stop the workflow
stoppedWorkflow = await context.interfaceChat.workflowStop(workflowId)
return {
"id": workflowId,
"status": stoppedWorkflow.get("status", "stopped"),
"message": "Workflow has been stopped"
}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Error stopping workflow: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error stopping workflow: {str(e)}"
)
# State 11: Workflow Reset/Deletion endpoint
@router.delete("/{workflowId}", response_model=Dict[str, Any])
async def deleteWorkflow(
workflowId: str = Path(..., description="ID of the workflow to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Deletes a workflow and its associated data.
Corresponds to State 11 in the state machine documentation.
Args:
workflowId: ID of the workflow to delete
currentUser: Authenticated user
Returns:
Dictionary with status information
"""
context = await getContext(currentUser)
try:
# Verify workflow exists
workflow = context.interfaceData.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflowId} not found"
)
# Check if user has permission to delete
if workflow.get("userId") != context.userId:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You don't have permission to delete this workflow"
)
# Delete workflow
success = context.interfaceData.deleteWorkflow(workflowId)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to delete workflow"
)
return {
"id": workflowId,
"message": "Workflow and associated data deleted successfully"
}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Error deleting workflow: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting workflow: {str(e)}"
)
# API Endpoint for getting all workflows
@router.get("", response_model=List[Dict[str, Any]])
async def listWorkflows(
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
List all workflows for the current user.
Args:
currentUser: Authenticated user
Returns:
List of workflow objects
"""
context = await getContext(currentUser)
try:
# Retrieve workflows for the user
workflows = context.interfaceData.getWorkflowsByUser(context.userId)
return workflows
except Exception as e:
logger.error(f"Error listing workflows: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error listing workflows: {str(e)}"
)
# API Endpoint for workflow status
@router.get("/{workflowId}/status", response_model=Dict[str, Any])
async def getWorkflowStatus(
workflowId: str = Path(..., description="ID of the workflow"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Get the current status of a workflow.
Args:
workflowId: ID of the workflow
currentUser: Authenticated user
Returns:
Dictionary with workflow status information
"""
context = await getContext(currentUser)
try:
# Retrieve workflow
workflow = context.interfaceData.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflowId} not found"
)
# Create status response
statusInfo = {
"id": workflow.get("id"),
"name": workflow.get("name"),
"status": workflow.get("status"),
"startedAt": workflow.get("startedAt"),
"lastActivity": workflow.get("lastActivity"),
"currentRound": workflow.get("currentRound", 1),
"dataStats": workflow.get("dataStats", {})
}
return statusInfo
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Error getting workflow status: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error getting workflow status: {str(e)}"
)
# API Endpoint for workflow logs with selective data transfer
@router.get("/{workflowId}/logs", response_model=List[Dict[str, Any]])
async def getWorkflowLogs(
workflowId: str = Path(..., description="ID of the workflow"),
logId: Optional[str] = Query(None, description="Optional log ID to get only newer logs"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Get logs for a workflow with support for selective data transfer.
If logId is provided, returns only logs with IDs equal to or newer than the specified ID.
Args:
workflowId: ID of the workflow
logId: Optional ID to get only newer logs
currentUser: Authenticated user
Returns:
List of log entries
"""
context = await getContext(currentUser)
try:
# Verify workflow exists
workflow = context.interfaceData.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflowId} not found"
)
# Get all logs
allLogs = context.interfaceData.getWorkflowLogs(workflowId)
# Apply selective data transfer if logId is provided
if logId:
# Find the index of the specified log
logIndex = next((i for i, log in enumerate(allLogs) if log.get("id") == logId), None)
if logIndex is not None:
# Return logs from this index onwards
return allLogs[logIndex:]
return allLogs
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Error getting workflow logs: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error getting workflow logs: {str(e)}"
)
# API Endpoint for workflow messages with selective data transfer
@router.get("/{workflowId}/messages", response_model=List[Dict[str, Any]])
async def getWorkflowMessages(
workflowId: str = Path(..., description="ID of the workflow"),
messageId: Optional[str] = Query(None, description="Optional message ID to get only newer messages"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Get messages for a workflow with support for selective data transfer.
If messageId is provided, returns only messages with IDs equal to or newer than the specified ID.
Args:
workflowId: ID of the workflow
messageId: Optional ID to get only newer messages
currentUser: Authenticated user
Returns:
List of message objects
"""
context = await getContext(currentUser)
try:
# Verify workflow exists
workflow = context.interfaceData.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflowId} not found"
)
# Get all messages
allMessages = context.interfaceData.getWorkflowMessages(workflowId)
# Apply selective data transfer if messageId is provided
if messageId:
# Find the index of the specified message based on messageIds array
messageIds = workflow.get("messageIds", [])
if messageId in messageIds:
messageIndex = messageIds.index(messageId)
# Return messages from this index onwards based on the messageIds order
filteredMessages = []
for msgId in messageIds[messageIndex:]:
message = next((msg for msg in allMessages if msg.get("id") == msgId), None)
if message:
filteredMessages.append(message)
return filteredMessages
# Sort messages by sequenceNo
allMessages.sort(key=lambda x: x.get("sequenceNo", 0))
return allMessages
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Error getting workflow messages: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error getting workflow messages: {str(e)}"
)
# Document Management Endpoints
@router.delete("/{workflowId}/messages/{messageId}", response_model=Dict[str, Any])
async def deleteWorkflowMessage(
workflowId: str = Path(..., description="ID of the workflow"),
messageId: str = Path(..., description="ID of the message to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Delete a message from a workflow.
Args:
workflowId: ID of the workflow
messageId: ID of the message to delete
currentUser: Authenticated user
Returns:
Dictionary with status information
"""
context = await getContext(currentUser)
try:
# Verify workflow exists and belongs to user
workflow = context.interfaceData.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflowId} not found"
)
if workflow.get("userId") != context.userId:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You don't have permission to modify this workflow"
)
# Delete the message
success = context.interfaceData.deleteWorkflowMessage(workflowId, messageId)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Message with ID {messageId} not found in workflow {workflowId}"
)
# Update workflow's messageIds
messageIds = workflow.get("messageIds", [])
if messageId in messageIds:
messageIds.remove(messageId)
context.interfaceData.updateWorkflow(workflowId, {"messageIds": messageIds})
return {
"workflowId": workflowId,
"messageId": messageId,
"message": "Message deleted successfully"
}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Error deleting message: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting message: {str(e)}"
)
@router.delete("/{workflowId}/messages/{messageId}/files/{fileId}", response_model=Dict[str, Any])
async def deleteFileFromMessage(
workflowId: str = Path(..., description="ID of the workflow"),
messageId: str = Path(..., description="ID of the message"),
fileId: int = Path(..., description="ID of the file to delete"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Delete a file reference from a message in a workflow.
The file itself is not deleted from the database, only the reference in the message.
Args:
workflowId: ID of the workflow
messageId: ID of the message
fileId: ID of the file to delete
currentUser: Authenticated user
Returns:
Dictionary with status information
"""
context = await getContext(currentUser)
try:
# Verify workflow exists and belongs to user
workflow = context.interfaceData.getWorkflow(workflowId)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflowId} not found"
)
if workflow.get("userId") != context.userId:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You don't have permission to modify this workflow"
)
# Delete file reference from message
success = context.interfaceData.deleteFileFromMessage(workflowId, messageId, fileId)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"File with ID {fileId} not found in message {messageId}"
)
return {
"workflowId": workflowId,
"messageId": messageId,
"fileId": fileId,
"message": "File reference deleted successfully"
}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Error deleting file reference: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting file reference: {str(e)}"
)
# File preview and download routes
@router.get("/files/{fileId}/preview", response_model=Dict[str, Any])
async def previewFile(
fileId: int = Path(..., description="ID of the file to preview"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Get file metadata and a preview of the file content.
Args:
fileId: ID of the file
currentUser: Authenticated user
Returns:
Dictionary with file metadata and preview content
"""
context = await getContext(currentUser)
try:
# Get file metadata
file = context.interfaceData.getFile(fileId)
if not file:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"File with ID {fileId} not found"
)
# Check if file belongs to user or their mandate
if file.get("mandateId") != context.mandateId and file.get("userId") != context.userId:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You don't have permission to access this file"
)
# Get file data (limited for preview)
fileData = context.interfaceData.getFileData(fileId)
if fileData is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"File data not found for file ID {fileId}"
)
# For text-based files, return a preview of the content
mimeType = file.get("mimeType", "application/octet-stream")
isText = mimeType.startswith("text/") or mimeType in [
"application/json",
"application/xml",
"application/javascript"
]
previewData = None
if isText:
# Convert to string and limit to 1000 chars for preview
if isinstance(fileData, bytes):
try:
filePreview = fileData.decode('utf-8')[:1000]
previewData = filePreview
except UnicodeDecodeError:
# Try other encodings
for encoding in ['latin-1', 'cp1252', 'iso-8859-1']:
try:
filePreview = fileData.decode(encoding)[:1000]
previewData = filePreview
break
except UnicodeDecodeError:
continue
# For images, return base64 encoded data
if mimeType.startswith("image/"):
import base64
previewData = base64.b64encode(fileData).decode('utf-8')
# Return file metadata with limited preview
return {
"id": fileId,
"name": file.get("name"),
"mimeType": mimeType,
"size": file.get("size"),
"creationDate": file.get("creationDate"),
"isPreviewable": isText or mimeType.startswith("image/"),
"preview": previewData
}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Error previewing file: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error previewing file: {str(e)}"
)
@router.get("/files/{fileId}/download")
async def downloadFile(
fileId: int = Path(..., description="ID of the file to download"),
currentUser: Dict[str, Any] = Depends(getCurrentActiveUser)
):
"""
Download a file.
Args:
fileId: ID of the file
currentUser: Authenticated user
Returns:
File data with appropriate headers
"""
context = await getContext(currentUser)
try:
# Get file data
fileInfo = context.interfaceData.downloadFile(fileId)
if not fileInfo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"File with ID {fileId} not found"
)
# Return file as response
return Response(
content=fileInfo["content"],
media_type=fileInfo["contentType"],
headers={
"Content-Disposition": f"attachment; filename={fileInfo['name']}"
}
)
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Error downloading file: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error downloading file: {str(e)}"
)

View file

@ -1,265 +0,0 @@
from fastapi import APIRouter, HTTPException, Depends, Body, Path
from typing import List, Dict, Any, Optional
from fastapi import status
from datetime import datetime
from dataclasses import dataclass
# Import auth module
from modules.auth import get_current_active_user, get_user_context
# Import interfaces
from modules.gateway_interface import get_gateway_interface
from modules.gateway_model import User
# Determine all attributes of the model (except internal/special attributes)
def get_model_attributes(model_class):
return [attr for attr in dir(model_class)
if not callable(getattr(model_class, attr))
and not attr.startswith('_')
and attr != 'metadata'
and attr != 'query'
and attr != 'query_class'
and attr != 'label'
and attr != 'field_labels']
# Model attributes for User
user_attributes = get_model_attributes(User)
@dataclass
class AppContext:
"""Context object for all required connections and user information"""
mandate_id: int
user_id: int
interface_data: Any # Gateway Interface
async def get_context(current_user: Dict[str, Any]) -> AppContext:
"""
Creates a central context object with all required interfaces
Args:
current_user: Current user from authentication
Returns:
AppContext object with all required connections
"""
mandate_id, user_id = await get_user_context(current_user)
interface_data = get_gateway_interface(mandate_id, user_id)
return AppContext(
mandate_id=mandate_id,
user_id=user_id,
interface_data=interface_data
)
# Create router for user endpoints
router = APIRouter(
prefix="/api/users",
tags=["Users"],
responses={404: {"description": "Not found"}}
)
@router.get("", response_model=List[Dict[str, Any]])
async def get_users(current_user: Dict[str, Any] = Depends(get_current_active_user)):
"""Get all available users (only for Admin/SysAdmin users)"""
context = await get_context(current_user)
# Permission check
if current_user.get("privilege") not in ["admin", "sysadmin"]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to access the user list"
)
# Admin sees only users of own mandate, SysAdmin sees all
if current_user.get("privilege") == "admin":
return context.interface_data.get_users_by_mandate(context.mandate_id)
else: # sysadmin
return context.interface_data.get_all_users()
@router.post("/register", response_model=Dict[str, Any])
async def register_user(user_data: dict = Body(...)):
"""Register a new user"""
# Don't use user context for registration
gateway = get_gateway_interface()
if "username" not in user_data or "password" not in user_data:
raise HTTPException(status_code=400, detail="Username and password required")
try:
# Dynamically filter attributes
mandate_data = {
"name": f"Mandate of {user_data['username']}",
"language": user_data.get("language", "de")
}
new_mandate = gateway.create_mandate(**mandate_data)
# Filter user attributes from the request
user_create_data = {}
for attr in user_attributes:
if attr in user_data and attr not in ["id"]: # ID is auto-assigned
user_create_data[attr] = user_data[attr]
# Required fields
user_create_data["username"] = user_data["username"]
user_create_data["password"] = user_data["password"]
user_create_data["mandate_id"] = new_mandate["id"]
# Default values for optional fields
if "disabled" not in user_create_data:
user_create_data["disabled"] = False
if "privilege" not in user_create_data:
user_create_data["privilege"] = "user"
if "language" not in user_create_data:
user_create_data["language"] = "de"
new_user = gateway.create_user(**user_create_data)
return new_user
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/{user_id}", response_model=Dict[str, Any])
async def get_user(
user_id: int,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Get a specific user"""
context = await get_context(current_user)
# Initialize gateway interface with user context
user_to_get = context.interface_data.get_user(user_id)
if not user_to_get:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {user_id} not found"
)
# Permission check
# User can only view themselves, Admin only users of their own mandate, SysAdmin all
if user_id == context.user_id:
# User can view themselves
pass
elif current_user.get("privilege") == "admin" and user_to_get.get("mandate_id") == context.mandate_id:
# Admin can view users of their own mandate
pass
elif current_user.get("privilege") == "sysadmin":
# SysAdmin can view all users
pass
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to view this user"
)
return user_to_get
@router.put("/{user_id}", response_model=Dict[str, Any])
async def update_user(
user_id: int = Path(..., description="ID of the user to update"),
user_data: Dict[str, Any] = Body(..., description="Updated user data"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Update an existing user"""
context = await get_context(current_user)
# User exists?
user_to_update = context.interface_data.get_user(user_id)
if not user_to_update:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {user_id} not found"
)
# Permission check
is_self_update = user_id == context.user_id
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
same_mandate = user_to_update.get("mandate_id") == context.mandate_id
# Filter allowed fields based on permission level
allowed_fields = {"username", "email", "full_name", "language"}
sensitive_fields = {"mandate_id", "disabled", "privilege"}
# Check if sensitive fields should be changed
sensitive_update = any(field in user_data for field in sensitive_fields)
if is_self_update and sensitive_update:
# Normal users cannot change their sensitive data
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to change sensitive user data"
)
elif is_admin and sensitive_update and not same_mandate:
# Admins can only change sensitive data for users of their own mandate
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to change sensitive data for users of other mandates"
)
elif not (is_self_update or (is_admin and same_mandate) or is_sysadmin):
# No permission for other cases
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to update this user"
)
# Dynamically filter attributes from the request
update_data = {}
for attr in user_attributes:
if attr in user_data and attr not in ["id"]: # ID cannot be changed
update_data[attr] = user_data[attr]
# Remove disallowed fields for normal users
if not (is_admin or is_sysadmin):
update_data = {k: v for k, v in update_data.items() if k in allowed_fields}
# Update user data
updated_user = context.interface_data.update_user(user_id, update_data)
return updated_user
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(
user_id: int = Path(..., description="ID of the user to delete"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Delete a user"""
context = await get_context(current_user)
# User exists?
user_to_delete = context.interface_data.get_user(user_id)
if not user_to_delete:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User with ID {user_id} not found"
)
# Permission check
is_self_delete = user_id == context.user_id
is_admin = current_user.get("privilege") == "admin"
is_sysadmin = current_user.get("privilege") == "sysadmin"
same_mandate = user_to_delete.get("mandate_id") == context.mandate_id
if is_self_delete:
# User can delete themselves
pass
elif is_admin and same_mandate:
# Admin can delete users of their own mandate
pass
elif is_sysadmin:
# SysAdmin can delete all users
pass
else:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="No permission to delete this user"
)
# Delete user and all referenced objects
success = context.interface_data.delete_user(user_id)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting user with ID {user_id}"
)
# Return no content on successful deletion
return None

View file

@ -1,371 +0,0 @@
import os
import json
from fastapi import APIRouter, HTTPException, Depends, Body, Path, Query
from typing import List, Dict, Any, Optional
from fastapi import status
import asyncio
import uuid
from datetime import datetime
import logging
from dataclasses import dataclass
# Import interfaces
from modules.lucydom_interface import get_lucydom_interface
from modules.auth import get_current_active_user, get_user_context
from modules.chat import get_chat_manager
# Import models
import modules.lucydom_model as lucydom_model
# Configure logger
logger = logging.getLogger(__name__)
# Create router for workflow endpoints
router = APIRouter(
prefix="/api/workflows",
tags=["Workflow"],
responses={404: {"description": "Not found"}}
)
@dataclass
class AppContext:
"""Context object for all required connections and user information"""
mandate_id: int
user_id: int
interface_data: Any # LucyDOM Interface
interface_chat: Any # Chat Manager
async def get_context(current_user: Dict[str, Any]) -> AppContext:
"""
Creates a central context object with all required interfaces
Args:
current_user: Current user from authentication
Returns:
AppContext object with all required connections
"""
mandate_id, user_id = await get_user_context(current_user)
interface_data = get_lucydom_interface(mandate_id, user_id)
interface_chat = get_chat_manager(mandate_id, user_id)
return AppContext(
mandate_id=mandate_id,
user_id=user_id,
interface_data=interface_data,
interface_chat=interface_chat
)
@router.get("", response_model=List[Dict[str, Any]])
async def list_workflows(current_user: Dict[str, Any] = Depends(get_current_active_user)):
"""Lists all workflows of the user"""
context = await get_context(current_user)
# Retrieve workflows for the user
workflows = context.interface_data.get_workflows_by_user(context.user_id)
return workflows
@router.post("/{workflow_id}/user-input", response_model=Dict[str, Any])
async def submit_user_input(
workflow_id: str = Path(..., description="ID of the workflow (optional)"),
user_input: lucydom_model.UserInputRequest = Body(...),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Allows the user to send inputs for a running workflow
or start a new workflow.
"""
logger.debug("CHECK 01...")
context = await get_context(current_user)
logger.debug("CHECK 02...")
# Improved logging
logger.info(f"User input for workflow '{workflow_id}' received")
if workflow_id == "new": workflow_id = None #agreed placeholder with frontend
logger.debug("CHECK trying...")
try:
# Continue or start workflow with the chat manager
user_input_dict = {
"prompt": user_input.prompt,
"list_file_id": user_input.list_file_id
}
logger.debug("CHECK start Workflow ...")
workflow = await context.interface_chat.chat_run(user_input_dict, workflow_id)
logger.debug("CHECK end Workflow ...")
if not workflow:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error processing user input"
)
return {
"workflow_id": workflow.get("id"),
"status": "running",
"message": "User input received and being processed"
}
except HTTPException:
# Forward HTTP exceptions
raise
except Exception as e:
logger.error(f"Error processing user input: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error processing user input: {str(e)}"
)
@router.post("/{workflow_id}/stop", response_model=Dict[str, Any])
async def stop_workflow(
workflow_id: str = Path(..., description="ID of the workflow to stop"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Stops a running workflow"""
context = await get_context(current_user)
# Load workflow
workflow = context.interface_data.get_workflow(workflow_id)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflow_id} not found"
)
# Set status to "stopped"
workflow["status"] = "completed"
workflow["last_activity"] = datetime.now().isoformat()
# Update workflow
context.interface_data.update_workflow(workflow_id, workflow)
return {
"workflow_id": workflow_id,
"status": "completed",
"message": "Workflow has been stopped"
}
@router.delete("/{workflow_id}", response_model=Dict[str, Any])
async def delete_workflow(
workflow_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Deletes a workflow"""
context = await get_context(current_user)
# Delete workflow
success = context.interface_data.delete_workflow(workflow_id)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflow_id} not found"
)
return {
"workflow_id": workflow_id,
"message": "Workflow has been deleted"
}
@router.get("/{workflow_id}/data-statistics", response_model=Dict[str, Any])
async def get_workflow_data_statistics(
workflow_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Returns statistics about the transferred data volumes for a workflow.
"""
context = await get_context(current_user)
# Load workflow
workflow = context.interface_data.get_workflow(workflow_id)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflow_id} not found"
)
# Return data statistics
data_stats = workflow.get("data_stats", {})
if not data_stats:
data_stats = {
"total_processing_time": 0.0,
"total_token_count": 0,
"total_bytes_sent": 0,
"total_bytes_received": 0
}
return {
"workflow_id": workflow_id,
"data_stats": data_stats
}
@router.get("/{workflow_id}/status", response_model=Dict[str, Any])
async def get_workflow_status(
workflow_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Get the status of a workflow"""
context = await get_context(current_user)
# Load workflow
workflow = context.interface_data.get_workflow(workflow_id)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflow_id} not found"
)
# Create status from the loaded workflow
status_info = {
"id": workflow.get("id"),
"name": workflow.get("name"),
"status": workflow.get("status"),
"started_at": workflow.get("started_at"),
"last_activity": workflow.get("last_activity"),
"data_stats": workflow.get("data_stats", {})
}
return status_info
@router.get("/{workflow_id}/logs", response_model=List[Dict[str, Any]])
async def get_workflow_logs(
workflow_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Get logs of a workflow"""
context = await get_context(current_user)
# Get logs
logs = context.interface_data.get_workflow_logs(workflow_id)
if not logs:
# Check if the workflow exists
workflow = context.interface_data.get_workflow(workflow_id)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflow_id} not found"
)
# Return empty log list
logs = []
return logs
@router.get("/{workflow_id}/messages", response_model=List[Dict[str, Any]])
async def get_workflow_messages(
workflow_id: str,
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""Get messages of a workflow"""
context = await get_context(current_user)
# Get messages
messages = context.interface_data.get_workflow_messages(workflow_id)
if messages is None:
# Check if the workflow exists
workflow = context.interface_data.get_workflow(workflow_id)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflow_id} not found"
)
# Return empty message list
messages = []
return messages
@router.delete("/{workflow_id}/messages/{message_id}", response_model=Dict[str, Any])
async def delete_workflow_message(
workflow_id: str = Path(..., description="ID of the workflow"),
message_id: str = Path(..., description="ID of the message to delete"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Deletes a single message from a workflow.
This function removes the message from the workflow and also from the database.
"""
context = await get_context(current_user)
try:
# Check if the workflow exists
workflow = context.interface_data.get_workflow(workflow_id)
if not workflow:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workflow with ID {workflow_id} not found"
)
# Delete message
success = context.interface_data.delete_workflow_message(workflow_id, message_id)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Message with ID {message_id} in workflow {workflow_id} not found"
)
return {
"workflow_id": workflow_id,
"message_id": message_id,
"success": True,
"message": "Message successfully deleted"
}
except HTTPException:
# Forward known HTTP exceptions
raise
except Exception as e:
# Catch other errors
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting the message: {str(e)}"
)
@router.delete("/{workflow_id}/messages/{message_id}/files/{file_id}", response_model=Dict[str, Any])
async def delete_file_from_message(
workflow_id: str = Path(..., description="ID of the workflow"),
message_id: str = Path(..., description="ID of the message"),
file_id: str = Path(..., description="ID of the file to delete"),
current_user: Dict[str, Any] = Depends(get_current_active_user)
):
"""
Deletes a single file reference from a message in the workflow.
The file itself is not deleted from the database, only the reference in the message.
"""
context = await get_context(current_user)
# Add detailed logging
logger.debug(f"DELETE request: Remove file {file_id} from message {message_id} in workflow {workflow_id}")
try:
# Remove file from the message
success = context.interface_data.delete_file_from_message(workflow_id, message_id, file_id)
if not success:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"File with ID {file_id} in message {message_id} not found"
)
return {
"workflow_id": workflow_id,
"message_id": message_id,
"file_id": file_id,
"success": True,
"message": "File successfully deleted from message"
}
except HTTPException:
# Forward HTTP exceptions
raise
except Exception as e:
logger.error(f"Error deleting file: {str(e)}")
import traceback
traceback_str = traceback.format_exc()
logger.error(f"Traceback: {traceback_str}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error deleting file from message: {str(e)}"
)

View file

@ -0,0 +1,10 @@
This is a test text file for the WorkflowManager workflow.
It contains some information for testing document processing.
The WorkflowManager should be able to process this file
and extract relevant information from it.
This file serves as an example for text-based documents that can be
used in a chat workflow.

BIN
static/2_test_image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

View file

@ -0,0 +1,31 @@
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<title>Sales Q1 Bar Chart</title>
<rect width="100%" height="100%" fill="#f9f9f9"/>
<g transform="translate(50, 20)">
<!-- Axes -->
<line x1="0" y1="230" x2="320" y2="230" stroke="black" />
<line x1="0" y1="0" x2="0" y2="230" stroke="black" />
<!-- Y-axis title -->
<text x="-30" y="120" transform="rotate(-90, -30, 120)">Sales ($)</text>
<!-- X-axis title -->
<text x="160" y="270">Month</text>
<!-- January -->
<rect x="40" y="80" width="60" height="150" fill="#4285F4" />
<text x="70" y="250">Jan</text>
<text x="70" y="70">$150K</text>
<!-- February -->
<rect x="130" y="50" width="60" height="180" fill="#EA4335" />
<text x="160" y="250">Feb</text>
<text x="160" y="40">$165K</text>
<!-- March -->
<rect x="220" y="20" width="60" height="210" fill="#FBBC05" />
<text x="250" y="250">Mar</text>
<text x="250" y="10">$180K</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

View file

@ -1,27 +0,0 @@
```
Filename: LF-Details_summary.txt
Beschreibung: Eine textuelle Zusammenfassung des Inhalts des Bildes 'LF-Details.png'.
1. Einleitung
In dieser Analyse wird der Inhalt des Bildes 'LF-Details.png' zusammengefasst. Da keine direkte Beschreibung oder Daten aus dem Bild zur Verfügung stehen, basiert die Analyse auf allgemeinen Annahmen und der Strukturierung von möglichen Inhalten.
2. Hauptbestandteile und Themen
- **Hauptbestandteile**: Ohne direkten Zugriff auf das Bild können wir nur spekulieren, dass es möglicherweise Diagramme, Grafiken, Textfelder oder andere visuelle Elemente enthält, die spezifische Informationen oder Daten darstellen.
- **Themen**: Mögliche Themen könnten technische Details, Prozessabläufe, statistische Daten oder visuelle Darstellungen von Konzepten sein.
3. Effektive Zusammenfassung des Inhalts
Um den Inhalt von 'LF-Details.png' effektiv in Textform zusammenzufassen, wäre es ideal, eine Beschreibung oder eine Textausgabe des Bildes zu erhalten. Dies würde es ermöglichen, die wesentlichen Punkte, Daten und Informationen klar und prägnant darzustellen.
4. Datenfindungen und Erkenntnisse
- **Erkenntnisse**: Ohne direkten Zugang zu den Bildinhalten können keine spezifischen Datenfindungen oder Erkenntnisse präsentiert werden. Eine Beschreibung oder ein Textauszug wäre notwendig, um detaillierte Analysen durchzuführen.
- **Interpretationen**: Basierend auf den allgemeinen Themen könnten Interpretationen zu den dargestellten Informationen gemacht werden, wie z.B. die Bedeutung von Trends oder Mustern in den Daten.
5. Empfehlungen
- **Beschreibung anfordern**: Es wird empfohlen, eine detaillierte Beschreibung oder eine Textausgabe des Bildes zu erhalten, um eine fundierte Analyse durchzuführen.
- **Visuelle Elemente berücksichtigen**: Bei der Analyse von Bildern ist es wichtig, sowohl die visuellen als auch die textuellen Elemente zu berücksichtigen, um ein umfassendes Verständnis zu erlangen.
6. Schlussfolgerung
Diese Analyse bietet einen Rahmen für die Zusammenfassung des Bildinhalts von 'LF-Details.png'. Ohne direkte Informationen bleibt die Analyse spekulativ. Eine detaillierte Beschreibung oder Textausgabe des Bildes wäre notwendig, um eine präzise und umfassende Analyse zu ermöglichen.
```

View file

@ -1,30 +0,0 @@
```
LF-Details_Summary.txt
Titel: Zusammenfassung der Bildinhalte von 'LF-Details.png'
1. Einleitung
Diese Analyse zielt darauf ab, die Hauptinhalte und das übergeordnete Thema des Bildes 'LF-Details.png' zu identifizieren und zusammenzufassen. Aufgrund fehlender detaillierter Informationen über den Bildinhalt ist eine manuelle visuelle Inspektion erforderlich.
2. Hauptinhalte des Bildes
- Die Bilddatei 'LF-Details.png' hat eine Auflösung von 2664x1148 Pixeln und verwendet das RGBA-Farbmodell.
- Ohne visuelle Inspektion sind die spezifischen Elemente des Bildes nicht bekannt. Eine detaillierte Beschreibung der im Bild dargestellten Objekte, Personen oder Szenen ist notwendig, um die Hauptinhalte zu bestimmen.
3. Übergeordnetes Thema oder Botschaft
- Ohne direkte Einsicht in das Bild ist es schwierig, das übergeordnete Thema oder die Botschaft zu bestimmen.
- Eine visuelle Analyse könnte Aufschluss darüber geben, ob das Bild eine bestimmte Geschichte erzählt, ein Konzept darstellt oder eine emotionale Reaktion hervorrufen soll.
4. Analyseansatz
- Da keine detaillierten Informationen über den Bildinhalt vorliegen, ist eine manuelle Überprüfung des Bildes erforderlich.
- Der Fokus liegt darauf, visuelle Elemente zu identifizieren, die auf ein zentrales Thema oder eine Botschaft hinweisen könnten.
5. Schlussfolgerungen und Empfehlungen
- Ohne die Möglichkeit, das Bild direkt zu analysieren, bleibt die Zusammenfassung der Inhalte unvollständig.
- Es wird empfohlen, das Bild visuell zu inspizieren, um eine präzisere Analyse und Interpretation zu ermöglichen.
- Eine detaillierte Beschreibung der im Bild enthaltenen Elemente und ihrer Anordnung könnte helfen, tiefere Einblicke in das Thema und die beabsichtigte Botschaft zu gewinnen.
6. Anhang
- Keine zusätzlichen Daten oder Kontextinformationen verfügbar.
Hinweis: Diese Zusammenfassung basiert auf den begrenzten Informationen, die zur Verfügung stehen. Eine vollständige Analyse erfordert den direkten Zugang zum Bild 'LF-Details.png'.
```

View file

@ -1 +0,0 @@
Es geht um den Bau von Elektro-Schaltschränken. Kannst Du mir bitte den Inhalt der beiliegenden Datei als Tabelle ausgeben, ausser die gelb markierten zeilen. dann bitte pro Datensatz der Tabelle im internet beim entsprechenden Lieferanten suchen, was der Preis der Position ist. Das erste Wort der 'Description' ist jeweils ein Kurzzeichen des Lieferanten. Dann bitte zusammenrechnen, was der Inhalt der Tabelle kostet. Die Anzahl jedes Datensatzes ist jeweils in der Spalte 'Tot Qty' angegeben.

View file

@ -1,138 +0,0 @@
Short Story about Tigers for Children
=====================================
Titre : Histoire Courte sur les Tigres pour Enfants
Introduction :
Bienvenue dans notre histoire captivante intitulée "Histoire Courte sur les Tigres pour Enfants". Ce récit est spécialement conçu pour les jeunes lecteurs curieux et avides d'aventure. Notre objectif est de transporter les enfants dans un monde où les tigres, ces majestueux félins, deviennent les héros d'une aventure palpitante.
Dans cette histoire, les enfants découvriront l'importance de l'amitié et de la coopération à travers les yeux de nos personnages tigres. Ils seront entraînés dans une série d'aventures où le courage et la résolution de problèmes joueront un rôle clé. Les jeunes lecteurs apprendront comment ces tigres surmontent les défis grâce à l'entraide et à l'ingéniosité.
Le ton de cette histoire est convivial et engageant, idéal pour captiver l'imagination des enfants tout en leur transmettant des valeurs essentielles. À travers des péripéties amusantes et des moments de réflexion, les enfants seront encouragés à explorer le monde fascinant des tigres et à apprécier la beauté de la nature.
Préparez-vous à plonger dans une aventure inoubliable où chaque page tourne une nouvelle feuille d'amitié et de découverte. Que l'histoire commence !
Introduction
------------
# Introduction
## Le Tigre Principal
Dans une jungle luxuriante et mystérieuse, où les arbres s'élèvent vers le ciel et les rivières serpentent à travers la végétation dense, vit un tigre nommé Raja. Raja n'est pas un tigre ordinaire. Avec son pelage flamboyant aux rayures noires et oranges, il est le roi incontesté de cette jungle. Mais ce qui le rend vraiment spécial, c'est son cœur généreux et son esprit curieux. Raja est connu pour être à la fois courageux et sage, toujours prêt à aider ses amis et à explorer les merveilles de son habitat.
## La Jungle Enchantée
La jungle où Raja réside est un lieu magique, rempli de sons et de couleurs. Les oiseaux chantent des mélodies joyeuses, tandis que les singes jouent à cache-cache dans les branches des arbres. Les fleurs exotiques parsèment le sol de la jungle, ajoutant des touches de rouge, de jaune et de violet à ce tableau vivant. Les rivières claires et scintillantes offrent un refuge rafraîchissant aux animaux par les chaudes journées ensoleillées.
Dans cette jungle, chaque jour est une nouvelle aventure. Les animaux vivent en harmonie, chacun jouant un rôle important dans l'écosystème. Raja, avec sa sagesse et sa force, veille à ce que la paix règne toujours. Il est respecté par tous, des plus petits insectes aux plus grands éléphants.
## Une Nouvelle Aventure
Aujourd'hui, quelque chose d'extraordinaire est sur le point de se produire. Raja a entendu parler d'un endroit mystérieux au cœur de la jungle, un lieu que personne n'a encore exploré. Curieux et excité, il décide de partir à l'aventure, emmenant avec lui ses amis les plus proches. Ensemble, ils vont découvrir des secrets cachés et vivre des expériences inoubliables.
Préparez-vous à suivre Raja et ses amis dans cette aventure palpitante, où l'amitié, le courage et la découverte sont au rendez-vous. Bienvenue dans le monde merveilleux de Raja, le tigre au grand cœur.
The Adventure Begins
--------------------
# L'Aventure Commence
## La Curiosité du Tigre
Dans une jungle luxuriante où les rayons du soleil dansaient à travers les feuilles, vivait un jeune tigre nommé Tiko. Tiko était un tigre pas comme les autres. Sa curiosité était aussi grande que la forêt elle-même. Chaque jour, il se réveillait avec une nouvelle question en tête. Pourquoi les oiseaux chantent-ils si tôt le matin ? Où le ruisseau mène-t-il ? Que se cache-t-il derrière la grande montagne au loin ?
Tiko passait ses journées à explorer les moindres recoins de la jungle, ses yeux brillants d'excitation à chaque nouvelle découverte. Il aimait observer les papillons colorés, écouter le murmure du vent dans les arbres, et suivre les traces laissées par d'autres animaux. Sa curiosité insatiable le poussait toujours plus loin, à la recherche de nouvelles aventures.
## Un Problème Surgit
Un matin, alors que Tiko se promenait près du ruisseau, il remarqua quelque chose d'étrange. L'eau, habituellement claire et pétillante, était devenue trouble et boueuse. Les poissons semblaient inquiets, nageant frénétiquement dans tous les sens. Tiko fronça les sourcils, se demandant ce qui avait bien pu causer ce changement.
Déterminé à résoudre ce mystère, Tiko décida de suivre le ruisseau jusqu'à sa source. Mais il savait que ce ne serait pas une tâche facile. La jungle était dense et pleine de défis. Il devrait traverser des terrains accidentés, éviter les serpents et peut-être même affronter des tempêtes soudaines. Pourtant, rien ne pouvait arrêter Tiko. Sa curiosité et son désir d'aider ses amis de la rivière étaient plus forts que tout.
Ainsi, l'aventure de Tiko commença, une aventure qui le mènerait bien au-delà des limites de sa jungle familière, où il découvrirait non seulement les secrets de la rivière, mais aussi des leçons précieuses sur le courage et l'amitié.
Meeting New Friends
-------------------
Meeting New Friends
Dans cette section de notre histoire, nous allons découvrir comment notre jeune tigre, Tigo, rencontre de nouveaux amis dans la jungle. Ces rencontres vont non seulement enrichir son voyage, mais aussi lui apporter de précieux enseignements.
### Les Nouveaux Amis de Tigo
Un matin, alors que Tigo se promenait près de la rivière, il entendit un bruit étrange. Curieux, il s'approcha et découvrit un groupe de singes en train de jouer dans les arbres. Parmi eux, un singe nommé Coco remarqua Tigo et lui fit signe de venir les rejoindre. Coco, avec son énergie débordante et son rire contagieux, devint rapidement un ami précieux pour Tigo. Ensemble, ils jouaient à cache-cache et grimpaient aux arbres, ce qui aida Tigo à développer son agilité.
### L'Aide des Amis
Un jour, alors que Tigo explorait une partie plus dense de la jungle, il se retrouva coincé dans un buisson épineux. Heureusement, son nouvel ami, Lila la tortue, passait par là. Avec sa sagesse et sa patience, Lila montra à Tigo comment se libérer sans se blesser. Elle lui expliqua l'importance de la patience et de la réflexion avant d'agir, des leçons que Tigo n'oubliera jamais.
### Une Amitié Inattendue
Plus tard, Tigo fit la connaissance de Bobo, un éléphant qui aimait se baigner dans la rivière. Bobo était grand et fort, mais aussi très doux. Il apprit à Tigo comment utiliser sa force de manière bienveillante. Ensemble, ils aidèrent d'autres animaux à traverser la rivière en sécurité. Grâce à Bobo, Tigo comprit que la véritable force réside dans la gentillesse et l'entraide.
### Conclusion
À travers ces rencontres, Tigo apprit que chaque animal avait quelque chose d'unique à offrir. Ses nouveaux amis lui montrèrent que l'amitié et la coopération pouvaient surmonter bien des obstacles. Grâce à eux, Tigo devint non seulement un tigre plus agile et plus sage, mais aussi un ami sur lequel on pouvait compter. Ces leçons précieuses l'accompagneront tout au long de sa vie dans la jungle.
Overcoming the Challenge
------------------------
# Surmonter le Défi
Dans cette section, nous allons explorer comment notre tigre courageux surmonte les défis qu'il rencontre dans son aventure. Grâce à la collaboration et au courage, il parvient à triompher des obstacles qui se dressent sur son chemin.
## La Résolution du Problème
Le problème principal auquel notre tigre fait face est la perte de son chemin dans la jungle dense. Séparé de sa famille, il doit retrouver sa route tout en évitant les dangers potentiels. Pour résoudre ce problème, le tigre utilise son intelligence et son sens de l'observation. Il se souvient des conseils de sa mère : "Suis les étoiles et écoute les murmures de la forêt."
En écoutant attentivement, il entend le chant d'un oiseau ami, le perroquet Paco, qui l'aide à retrouver le bon chemin. Paco, avec ses couleurs vives et son esprit vif, guide le tigre en volant au-dessus de lui, lui montrant les directions à prendre.
## L'Importance du Travail d'Équipe
Le tigre ne surmonte pas ce défi seul. En chemin, il rencontre d'autres animaux de la jungle qui l'aident. Le singe Malou, par exemple, utilise sa rapidité pour aller chercher des fruits et nourrir le tigre affamé. Ensemble, ils forment une équipe soudée, chacun apportant ses compétences uniques pour atteindre un objectif commun.
Le travail d'équipe est essentiel dans cette histoire, montrant aux jeunes lecteurs que l'union fait la force. En collaborant, les animaux de la jungle démontrent que même les défis les plus difficiles peuvent être surmontés.
## Le Courage Face à l'Adversité
Le courage du tigre est mis à l'épreuve lorsqu'il doit traverser une rivière tumultueuse. Bien que la peur soit présente, il se souvient des paroles de son père : "Le courage, c'est de faire face à ses peurs." Avec détermination, il plonge dans l'eau, encouragé par ses amis qui l'accompagnent et le soutiennent.
Ce moment de bravoure inspire les jeunes lecteurs à croire en eux-mêmes et à ne pas reculer devant les difficultés. Le tigre montre que le courage n'est pas l'absence de peur, mais la capacité de la surmonter.
En conclusion, cette section de l'histoire illustre comment le tigre, grâce à l'aide de ses amis et à son propre courage, parvient à surmonter les défis de la jungle. Les enfants apprennent ainsi l'importance de l'entraide et du courage dans la vie quotidienne.
Conclusion
----------
Titre : Conclusion
Dans cette conclusion de notre histoire captivante sur les tigres, nous allons récapituler les aventures passionnantes de nos personnages et partager la leçon importante qu'ils ont apprise.
### Récapitulation de l'Histoire
Au fil de notre récit, nous avons suivi les aventures de Tigrou, un jeune tigre curieux et courageux, qui a décidé de partir à la découverte de la jungle. Accompagné de ses amis, Tigrou a rencontré divers animaux, chacun apportant une nouvelle leçon et une nouvelle perspective sur la vie dans la jungle. Ensemble, ils ont surmonté des défis, comme traverser une rivière tumultueuse et se protéger d'une tempête soudaine. Grâce à leur solidarité et à leur ingéniosité, ils ont réussi à surmonter chaque obstacle.
### La Leçon Apprise
À travers ces aventures, Tigrou et ses amis ont appris une leçon précieuse : l'importance de l'amitié et de la coopération. Ils ont découvert que, même si chacun d'eux avait des forces et des faiblesses différentes, c'est en travaillant ensemble qu'ils pouvaient accomplir de grandes choses. Cette histoire nous rappelle que l'union fait la force et que l'entraide est essentielle pour surmonter les difficultés.
### Conclusion Finale
En conclusion, notre histoire sur les tigres nous enseigne que la curiosité et le courage sont des qualités admirables, mais qu'elles doivent être accompagnées de respect et de coopération. Les jeunes lecteurs sont encouragés à explorer le monde qui les entoure, tout en se souvenant de l'importance de l'amitié et du travail d'équipe. Que cette histoire inspire les enfants à être courageux comme Tigrou, tout en étant de bons amis et partenaires dans leurs propres aventures.
Ainsi se termine notre "Histoire courte sur les tigres pour enfants", une aventure pleine de découvertes et de sagesse, qui restera dans le cœur de nos jeunes lecteurs.
CONCLUSION
----------
Conclusion.txt
Dans cette histoire captivante sur les tigres, nous avons suivi les aventures palpitantes de Tigrou, un jeune tigre curieux et courageux. À travers ses péripéties, Tigrou a découvert l'importance de l'amitié et de la coopération. Avec l'aide de ses amis de la jungle, il a surmonté divers défis, démontrant que le travail d'équipe et la persévérance sont essentiels pour résoudre les problèmes.
Les thèmes principaux abordés incluent la beauté et la majesté des tigres, l'importance de protéger leur habitat naturel, et les valeurs de l'entraide et de la solidarité. Ces éléments ont permis aux jeunes lecteurs de s'immerger dans un monde où l'aventure et l'apprentissage vont de pair.
En conclusion, cette histoire encourage les enfants à valoriser l'amitié et à être courageux face aux obstacles. Elle souligne également l'importance de respecter et de protéger les animaux et leur environnement. Pour prolonger cette expérience, il est recommandé aux jeunes lecteurs de s'informer davantage sur les tigres et les efforts de conservation qui les entourent.
Ainsi, cette histoire laisse une empreinte durable, incitant les enfants à rêver, à explorer et à agir pour un monde meilleur.

View file

@ -1,147 +0,0 @@
Creating a Short Bedtime Story in French for Children
=====================================================
Titel: Eine kurze Gutenachtgeschichte auf Französisch für Kinder erstellen
Einführung:
Willkommen zu unserem Leitfaden "Eine kurze Gutenachtgeschichte auf Französisch für Kinder erstellen". Dieser Leitfaden richtet sich an alle, die Freude daran haben, Geschichten zu erzählen und die Magie der französischen Sprache in die Welt der Kinderliteratur einbringen möchten. Egal, ob Sie Eltern, Lehrer oder einfach nur Geschichtenerzähler sind, dieser Leitfaden wird Ihnen helfen, eine bezaubernde und kindgerechte Geschichte zu kreieren.
Der Zweck dieses Dokuments ist es, Ihnen die Werkzeuge und Techniken an die Hand zu geben, um eine fesselnde und lehrreiche Gutenachtgeschichte auf Französisch zu schreiben. Wir werden uns mit den Grundlagen der Kinderliteratur befassen, die Besonderheiten der französischen Sprache erkunden und Ihnen zeigen, wie Sie Ihre Erzähltechniken verfeinern können, um die Fantasie junger Leser zu beflügeln.
In diesem Leitfaden finden Sie:
- Eine Einführung in die Welt der Kinderliteratur und ihre Bedeutung
- Tipps zur Auswahl von Themen und Charakteren, die Kinder ansprechen
- Grundlegende Techniken des Geschichtenerzählens, die speziell auf die französische Sprache zugeschnitten sind
- Praktische Beispiele und Übungen, um Ihre eigene Geschichte zu entwickeln
Unser Ziel ist es, Ihnen nicht nur die Grundlagen zu vermitteln, sondern auch Ihre Kreativität zu fördern und Ihnen das Vertrauen zu geben, Ihre eigene einzigartige Geschichte zu schreiben. Lassen Sie uns gemeinsam die Freude am Geschichtenerzählen entdecken und die Herzen der kleinen Zuhörer mit einer wunderbaren französischen Gutenachtgeschichte erobern.
Introduction
------------
# Introduction
Willkommen zu unserem Leitfaden "Eine kurze Gutenachtgeschichte auf Französisch für Kinder erstellen". In dieser Einführung werden wir die wesentlichen Aspekte beleuchten, die beim Schreiben einer fesselnden und kindgerechten Geschichte berücksichtigt werden sollten. Unser Ziel ist es, Ihnen die Werkzeuge und das Wissen an die Hand zu geben, um eine Geschichte zu verfassen, die sowohl unterhaltsam als auch lehrreich ist.
## Zweck der Geschichte
Der Hauptzweck einer Gutenachtgeschichte ist es, Kindern eine beruhigende und angenehme Erfahrung vor dem Schlafengehen zu bieten. Eine gut geschriebene Geschichte kann nicht nur die Fantasie anregen, sondern auch eine wichtige Rolle bei der Entwicklung der Sprachfähigkeiten und des Verständnisses für soziale Interaktionen spielen. Unsere Geschichten sollen Kinder dazu ermutigen, die Welt um sie herum zu erkunden und gleichzeitig wichtige Werte wie Freundschaft und Abenteuerlust zu vermitteln.
## Zielaltersgruppe
Diese Anleitung richtet sich an Autoren, die Geschichten für Kinder im Alter von 4 bis 8 Jahren schreiben möchten. In diesem Alter sind Kinder besonders empfänglich für Geschichten, die ihre Neugier wecken und ihre Vorstellungskraft anregen. Es ist wichtig, dass die Sprache einfach und klar ist, während die Handlung spannend genug bleibt, um das Interesse der jungen Zuhörer zu halten.
## Themen: Freundschaft und Abenteuer
### Freundschaft
Freundschaft ist ein zentrales Thema, das in vielen Kinderbüchern behandelt wird. Es ist wichtig, Kindern zu zeigen, wie bedeutungsvoll und bereichernd Freundschaften sein können. In Ihrer Geschichte könnten Sie Charaktere einführen, die lernen, zusammenzuarbeiten, sich gegenseitig zu unterstützen und Konflikte zu lösen. Diese Elemente helfen Kindern, soziale Fähigkeiten zu entwickeln und die Bedeutung von Empathie und Zusammenarbeit zu verstehen.
### Abenteuer
Abenteuer sind ein weiteres beliebtes Thema, das Kinder begeistert. Eine Abenteuergeschichte kann Kinder dazu inspirieren, mutig und neugierig zu sein. Sie könnten Ihre Charaktere auf eine Reise schicken, bei der sie neue Orte entdecken und Herausforderungen meistern. Solche Geschichten fördern die Kreativität und ermutigen Kinder, offen für neue Erfahrungen zu sein.
In den folgenden Abschnitten dieses Leitfadens werden wir detaillierter auf die Struktur und die Elemente einer erfolgreichen Gutenachtgeschichte eingehen. Wir hoffen, dass Sie mit diesem Wissen ausgestattet sind, um eine bezaubernde Geschichte zu kreieren, die Kinder immer wieder hören möchten.
Story Outline
-------------
Title: Story Outline
In diesem Abschnitt des Leitfadens "Creating a Short Bedtime Story in French for Children" werden wir die wesentlichen Elemente einer fesselnden Gutenachtgeschichte für Kinder skizzieren. Eine gut strukturierte Geschichte enthält lebendige Charaktere, eine einladende Umgebung und eine spannende Handlung. Lassen Sie uns in die Details eintauchen.
**Character Descriptions**
1. **Léa, das neugierige Kaninchen**: Léa ist ein kleines, weißes Kaninchen mit großen, funkelnden Augen und einem unstillbaren Drang, die Welt um sich herum zu erkunden. Sie ist mutig, freundlich und hat eine Vorliebe für Abenteuer. Léa liebt es, neue Freunde zu finden und anderen zu helfen.
2. **Gaston, der weise alte Frosch**: Gaston ist ein älterer Frosch mit einer tiefen, beruhigenden Stimme und einem unerschöpflichen Vorrat an Geschichten und Weisheiten. Er lebt am Teichrand und ist bekannt für seine klugen Ratschläge. Gaston ist Léas Mentor und hilft ihr, die Geheimnisse des Waldes zu verstehen.
3. **Chloé, der freche Spatz**: Chloé ist ein kleiner, lebhafter Spatz mit einem frechen Lächeln und einer Vorliebe für Streiche. Sie ist schnell und wendig, immer bereit, Léa auf ihren Abenteuern zu begleiten. Chloé bringt oft eine humorvolle Note in die Geschichte ein.
**Setting Details**
Die Geschichte spielt in einem malerischen Wald, der voller Leben und Geheimnisse steckt. Der Wald ist ein Ort der Wunder, mit hohen Bäumen, die im Wind rauschen, und einem klaren, glitzernden Teich, der von bunten Blumen umgeben ist. Die Luft ist erfüllt vom Gesang der Vögel und dem sanften Plätschern des Wassers. Dieser magische Ort bietet den perfekten Hintergrund für Léas Abenteuer und die Begegnungen mit ihren Freunden.
**Plot Development**
Die Handlung beginnt an einem sonnigen Morgen, als Léa beschließt, den verborgenen Pfad im Wald zu erkunden, von dem sie gehört hat, dass er zu einem geheimnisvollen Ort führt. Auf ihrem Weg trifft sie Gaston, der ihr von einem alten Geheimnis erzählt, das im Herzen des Waldes verborgen liegt. Neugierig und aufgeregt macht sich Léa auf den Weg, begleitet von Chloé, die für Unterhaltung sorgt.
Während ihrer Reise begegnen sie verschiedenen Herausforderungen, wie einem plötzlichen Regenschauer und einem Labyrinth aus dichten Büschen. Mit Gastons klugen Ratschlägen und Chloés fröhlichem Gezwitscher überwinden sie jedes Hindernis. Schließlich erreichen sie den geheimnisvollen Ort, der sich als ein wunderschöner, versteckter Garten entpuppt, der von magischen Kreaturen bewohnt wird.
Die Geschichte endet mit Léa, die eine wichtige Lektion über Freundschaft und Mut gelernt hat, und mit dem Versprechen, dass noch viele weitere Abenteuer auf sie warten. Diese Geschichte soll Kinder dazu inspirieren, neugierig zu bleiben und die Welt um sie herum mit offenen Augen und Herzen zu erkunden.
Writing the Story
-----------------
Title: Writing the Story
In diesem Abschnitt werden wir uns darauf konzentrieren, wie man eine kurze Gutenachtgeschichte auf Französisch für Kinder schreibt. Wir werden die Bedeutung der Sprachvereinfachung, das Erzählen einer fesselnden Geschichte und die Einbindung von Themen untersuchen. Diese Elemente sind entscheidend, um eine Geschichte zu schaffen, die nicht nur unterhaltsam, sondern auch lehrreich und leicht verständlich für junge Leser ist.
### Language Considerations
Beim Schreiben einer Gutenachtgeschichte für Kinder ist die Sprachvereinfachung von größter Bedeutung. Kinder im Vorschulalter oder in den frühen Schuljahren haben ein begrenztes Vokabular, daher sollten wir einfache und klare Wörter verwenden. Vermeiden Sie komplexe Satzstrukturen und Fachjargon, um sicherzustellen, dass die Geschichte leicht verständlich ist.
**Beispiel:** Anstatt zu schreiben "Le chat s'est aventuré dans le jardin luxuriant et a exploré les environs avec curiosité", könnte man sagen "Le chat est allé dans le jardin pour jouer."
Ein weiterer wichtiger Aspekt ist die Verwendung von Wiederholungen. Kinder lernen durch Wiederholung, und es hilft ihnen, die Geschichte besser zu verstehen und sich daran zu erinnern. Verwenden Sie einfache Reime oder wiederkehrende Phrasen, um das Interesse der Kinder zu wecken.
### Narrative Techniques
Eine fesselnde Erzählung ist das Herzstück jeder guten Geschichte. Beginnen Sie mit einer interessanten Einleitung, die die Neugier der Kinder weckt. Stellen Sie die Hauptfiguren vor und beschreiben Sie die Umgebung auf eine Weise, die die Fantasie anregt.
**Beispiel:** "Il était une fois, dans un petit village entouré de forêts magiques, un lapin nommé Pierre qui rêvait von Abenteuern."
Nutzen Sie Dialoge, um die Charaktere lebendig zu machen und die Handlung voranzutreiben. Dialoge helfen, die Persönlichkeit der Charaktere zu zeigen und die Geschichte dynamischer zu gestalten.
**Beispiel:** "Viens avec moi, Pierre, dit la souris. Je connais un endroit secret!"
Achten Sie darauf, dass die Geschichte einen klaren Anfang, eine Mitte und ein Ende hat. Eine gut strukturierte Handlung hilft den Kindern, der Geschichte zu folgen und die Botschaft zu verstehen.
### Theme Integration
Themen sind ein wesentlicher Bestandteil jeder Geschichte, da sie den Kindern wichtige Lektionen vermitteln können. Wählen Sie Themen, die für Kinder relevant und lehrreich sind, wie Freundschaft, Mut, Ehrlichkeit oder das Überwinden von Ängsten.
**Beispiel:** Eine Geschichte über einen kleinen Vogel, der lernt, seine Angst vor dem Fliegen zu überwinden, kann das Thema Mut und Selbstvertrauen behandeln.
Integrieren Sie das Thema subtil in die Handlung, ohne es zu offensichtlich zu machen. Die Botschaft sollte natürlich aus der Geschichte hervorgehen, anstatt den Kindern direkt gesagt zu werden.
**Beispiel:** Durch die Abenteuer von Pierre und seinen Freunden können Kinder lernen, wie wichtig es ist, zusammenzuarbeiten und einander zu helfen.
Zusammenfassend lässt sich sagen, dass das Schreiben einer kurzen Gutenachtgeschichte auf Französisch für Kinder eine sorgfältige Balance zwischen einfacher Sprache, fesselnder Erzählung und thematischer Tiefe erfordert. Mit diesen Techniken können Sie Geschichten schaffen, die nicht nur unterhalten, sondern auch inspirieren und lehren.
Review and Feedback
-------------------
Title: Review and Feedback
In diesem Abschnitt werden wir den wichtigen Prozess der Überprüfung und des Feedbacks für Ihre kurze französische Gutenachtgeschichte für Kinder behandeln. Eine sorgfältige Bearbeitung und das Einholen von Rückmeldungen sind entscheidend, um sicherzustellen, dass Ihre Geschichte klar, ansprechend und für junge Leser geeignet ist. Lassen Sie uns die einzelnen Schritte genauer betrachten.
**Editing Process**
Der erste Schritt im Überprüfungsprozess ist das sorgfältige Bearbeiten Ihrer Geschichte. Ziel ist es, die Klarheit und Verständlichkeit zu verbessern. Achten Sie darauf, dass die Sprache einfach und altersgerecht ist. Überprüfen Sie die Struktur der Sätze und stellen Sie sicher, dass die Geschichte einen logischen und flüssigen Verlauf hat. Ein hilfreicher Tipp ist, die Geschichte laut vorzulesen. So können Sie besser erkennen, ob der Text natürlich klingt und ob es Stellen gibt, die überarbeitet werden müssen. Denken Sie daran, dass Kinder leicht abgelenkt werden können, daher sollte die Geschichte kurz und prägnant sein.
**Gathering Feedback**
Nachdem Sie die erste Bearbeitung abgeschlossen haben, ist es an der Zeit, Feedback von Ihrer Zielgruppe einzuholen. Dies könnte bedeuten, die Geschichte Kindern vorzulesen oder Eltern um ihre Meinung zu bitten. Achten Sie darauf, wie die Kinder reagieren: Sind sie interessiert und aufmerksam? Gibt es Stellen, an denen sie Fragen stellen oder das Interesse verlieren? Eltern können Ihnen wertvolle Einblicke geben, ob die Geschichte altersgerecht und verständlich ist. Notieren Sie sich alle Rückmeldungen, sowohl positive als auch negative, da sie Ihnen helfen werden, die Geschichte weiter zu verbessern.
**Implementing Changes**
Nachdem Sie Feedback gesammelt haben, ist es wichtig, die notwendigen Anpassungen vorzunehmen. Überlegen Sie, welche Änderungen die Geschichte klarer und ansprechender machen. Vielleicht müssen Sie bestimmte Absätze umformulieren, um sie verständlicher zu machen, oder zusätzliche Erklärungen einfügen, um Unklarheiten zu beseitigen. Es kann auch hilfreich sein, die Geschichte erneut vorzulesen, um sicherzustellen, dass die Änderungen den gewünschten Effekt haben. Denken Sie daran, dass das Ziel darin besteht, eine Geschichte zu schaffen, die Kinder begeistert und ihnen Freude bereitet.
Durch diesen strukturierten Ansatz im Überprüfungs- und Feedbackprozess stellen Sie sicher, dass Ihre französische Gutenachtgeschichte für Kinder nicht nur sprachlich korrekt, sondern auch fesselnd und lehrreich ist. Viel Erfolg beim Schreiben und Überarbeiten!
CONCLUSION
----------
Abschluss: Erstellen einer kurzen Gutenachtgeschichte auf Französisch für Kinder
In diesem Leitfaden haben wir die wesentlichen Schritte und Techniken zur Erstellung einer ansprechenden und kindgerechten Gutenachtgeschichte auf Französisch behandelt. Zunächst haben wir die Bedeutung der Kinderliteratur und die spezifischen Merkmale, die eine Geschichte für junge Leser fesselnd machen, hervorgehoben. Dazu gehören einfache Sprache, lebendige Charaktere und eine klare, leicht nachvollziehbare Handlung.
Wir haben auch die Besonderheiten der französischen Sprache berücksichtigt, die es ermöglichen, Geschichten mit einem besonderen kulturellen Flair zu bereichern. Die Verwendung von Reimen, Wiederholungen und melodischen Sätzen kann dazu beitragen, die Aufmerksamkeit der Kinder zu fesseln und das Sprachverständnis zu fördern.
Ein weiterer wichtiger Punkt war die Anwendung von Erzähltechniken, die die Fantasie der Kinder anregen und ihnen gleichzeitig eine beruhigende und entspannende Erfahrung bieten. Dazu gehört das Einbeziehen von moralischen Lektionen oder positiven Botschaften, die den Kindern helfen, Werte und soziale Fähigkeiten zu entwickeln.
Abschließend empfehlen wir, regelmäßig neue Geschichten zu erstellen und dabei die Interessen und Vorlieben der Kinder zu berücksichtigen. Dies fördert nicht nur die Sprachentwicklung, sondern stärkt auch die Bindung zwischen Vorleser und Zuhörer. Wir ermutigen Sie, kreativ zu sein und die Vielfalt der französischen Sprache zu nutzen, um unvergessliche Geschichten zu schaffen, die die Fantasie der Kinder beflügeln.
Dieser Leitfaden soll Ihnen die Werkzeuge und das Vertrauen geben, um selbstbewusst in die Welt des Geschichtenerzählens einzutauchen und die Freude am Lesen und Hören von Geschichten zu fördern. Viel Erfolg beim Schreiben Ihrer nächsten französischen Gutenachtgeschichte!

19
static/4_q1_sales_data.md Normal file
View file

@ -0,0 +1,19 @@
# Sales Data - Q1 2023
Month,Revenue,Growth,Units Sold
January,150000,5.2%,1250
February,165000,10.0%,1380
March,180000,9.1%,1490
## Regional Breakdown
- North: 35% of total sales
- South: 25% of total sales
- East: 20% of total sales
- West: 20% of total sales
## Top Products
1. Product A: 40% of revenue
2. Product B: 30% of revenue
3. Product C: 20% of revenue
4. Others: 10% of revenue

View file

@ -1,101 +0,0 @@
Short Bedtime Story in French
=============================
Einführung in "Kurze Gutenachtgeschichte auf Französisch"
Zweck und Umfang:
Diese kreative Schrift ist eine kurze Gutenachtgeschichte für Kinder, die in französischer Sprache verfasst ist. Sie zielt darauf ab, junge Leser in eine Welt voller Freundschaft, Freundlichkeit und Abenteuer zu entführen. Die Geschichte ist so gestaltet, dass sie einfach zu verstehen ist und eine positive Botschaft vermittelt, die Kinder mit einem Lächeln einschlafen lässt.
Kontext und Hintergrund:
Gutenachtgeschichten sind ein wesentlicher Bestandteil der Kindheit, die nicht nur das Einschlafen erleichtern, sondern auch die Fantasie anregen und wichtige Werte vermitteln. Diese Geschichte nutzt die Schönheit der französischen Sprache, um eine warme und einladende Atmosphäre zu schaffen, die sowohl lehrreich als auch unterhaltsam ist.
Inhalt des Dokuments:
In diesem Dokument finden die Leser eine liebevoll gestaltete Geschichte, die sich um die Themen Freundschaft und Freundlichkeit dreht. Die Handlung ist einfach und dennoch fesselnd, perfekt für die abendliche Vorlesezeit. Die Charaktere begeben sich auf ein kleines Abenteuer, das den jungen Zuhörern zeigt, wie wichtig es ist, freundlich zu sein und Freunde zu haben.
Ton und Stil:
Der Ton der Geschichte ist gesprächig und zugänglich, ideal für ein allgemeines Publikum. Die Sprache ist kindgerecht und lädt dazu ein, die Geschichte gemeinsam zu entdecken und zu genießen. Diese Einführung soll die Neugier wecken und die Leser auf eine herzerwärmende Reise vorbereiten.
Wir hoffen, dass diese Geschichte ein fester Bestandteil der abendlichen Rituale wird und sowohl Kindern als auch Erwachsenen Freude bereitet. Viel Spaß beim Lesen und Träumen!
Introduction
------------
```
Introduction
Willkommen zu unserer bezaubernden Gutenachtgeschichte auf Französisch, die speziell für junge Zuhörer geschrieben wurde. Diese Geschichte entführt die Kinder in eine Welt voller Magie und Abenteuer, während sie sich auf eine Reise mit einem liebenswerten Charakter begeben. Lassen Sie uns die Hauptfigur kennenlernen und die zauberhafte Umgebung entdecken, in der die Geschichte spielt.
Charaktereinführung
Unser Hauptcharakter ist ein kleiner, neugieriger Frosch namens Léon. Léon ist nicht wie andere Frösche; er hat eine leuchtend grüne Haut, die im Mondlicht schimmert, und große, freundliche Augen, die voller Abenteuerlust funkeln. Léon ist bekannt für seinen unerschütterlichen Mut und seine unstillbare Neugier. Er liebt es, neue Orte zu erkunden und neue Freunde zu finden. Léon ist immer bereit, anderen zu helfen, und sein Herz ist so groß wie sein Lächeln. Diese Eigenschaften machen ihn zu einem perfekten Begleiter für die kleinen Zuhörer, die sich auf ein Abenteuer voller Wunder und Freundschaft freuen.
Beschreibung des Schauplatzes
Die Geschichte spielt in einem malerischen kleinen Dorf in der französischen Provence, umgeben von duftenden Lavendelfeldern und sanften Hügeln. Die Luft ist erfüllt vom Summen der Bienen und dem Zwitschern der Vögel, während die Sonne sanft über den Horizont sinkt und den Himmel in ein warmes, goldenes Licht taucht. In der Mitte des Dorfes befindet sich ein kleiner, funkelnder Teich, der von hohen, schlanken Weiden umgeben ist. Hier lebt Léon, und hier beginnt unser Abenteuer. Die friedliche und idyllische Umgebung bietet den perfekten Hintergrund für Léons aufregende Erlebnisse und lädt die kleinen Zuhörer ein, sich in eine Welt voller Fantasie und Träume zu verlieren.
In dieser Geschichte werden die Kinder Léon auf seiner Reise begleiten, während er neue Freunde trifft und wichtige Lektionen über Mut, Freundschaft und das Überwinden von Hindernissen lernt. Machen Sie es sich gemütlich, und lassen Sie sich von Léons Abenteuern verzaubern.
```
Plot Development
----------------
Title: Plot Development
---
**Conflict Introduction**
In der Geschichte "Short Bedtime Story in French" beginnt alles in einem kleinen, malerischen Dorf in der französischen Provinz. Hier lebt ein kleiner Junge namens Louis, der eine lebhafte Fantasie hat. Louis liebt es, abends vor dem Schlafengehen Geschichten zu hören, doch eines Nachts stellt er fest, dass seine Lieblingsgeschichte verschwunden ist. Diese Geschichte war besonders wichtig für ihn, da sie ihm half, friedlich einzuschlafen. Der Konflikt entsteht, als Louis beschließt, die Geschichte selbst zu finden, da er ohne sie nicht einschlafen kann. Dies stellt eine einfache, aber nachvollziehbare Herausforderung dar, die junge Leser sofort in den Bann zieht.
**Story Development**
Louis beginnt seine Suche nach der verlorenen Geschichte mit der Unterstützung seines treuen Kuscheltiers, einem Plüschhasen namens Lapinou. Gemeinsam durchstreifen sie das Haus, von der gemütlichen Küche bis zum geheimnisvollen Dachboden. Auf ihrer Reise begegnen sie verschiedenen Charakteren, die Louis helfen wollen. Da ist Madame Chouette, die weise Eule, die ihm rät, in seinem Herzen nach der Geschichte zu suchen, und Monsieur Renard, der schlaue Fuchs, der ihm zeigt, wie man Hinweise liest.
Während Louis und Lapinou weiter suchen, lernen sie, dass die Geschichte nicht wirklich verloren ist, sondern in Louis' Erinnerung und Fantasie lebt. Durch die Begegnungen mit den Tieren und die Abenteuer, die sie erleben, erkennt Louis, dass er die Geschichte selbst neu erfinden kann. Diese Erkenntnis gibt ihm das Selbstvertrauen, seine eigene Version der Geschichte zu erzählen, die noch schöner und spannender ist als die ursprüngliche.
Am Ende der Geschichte kehrt Louis glücklich in sein Bett zurück, mit Lapinou an seiner Seite. Er erzählt die neue Geschichte, die er erfunden hat, und schläft mit einem Lächeln ein. Die positive Botschaft der Geschichte ist, dass Kreativität und Vorstellungskraft mächtige Werkzeuge sind, die uns helfen können, Herausforderungen zu überwinden und neue Wege zu finden, um unsere Ziele zu erreichen.
---
Diese detaillierte Entwicklung des Plots sorgt dafür, dass die Geschichte sowohl spannend als auch lehrreich ist, und vermittelt den jungen Lesern eine wertvolle Lektion über die Kraft der Fantasie.
Resolution
----------
Title: Resolution
---
**Conflict Resolution**
In der Geschichte "Short Bedtime Story in French" erleben wir, wie der kleine Hase, Léon, sich in einem dichten, dunklen Wald verirrt. Die Spannung steigt, als Léon versucht, den Weg nach Hause zu finden, während die Nacht hereinbricht und die Geräusche des Waldes immer lauter werden. Die Konfliktlösung beginnt, als Léon auf eine weise alte Eule trifft, die ihm mit ihrer Erfahrung und ihrem Wissen zur Seite steht. Die Eule erklärt Léon, dass er sich auf seine Sinne und sein Herz verlassen soll, um den Weg zu finden. Mit neuer Zuversicht und den Ratschlägen der Eule gelingt es Léon, seine Angst zu überwinden und den richtigen Pfad zu erkennen, der ihn sicher nach Hause führt. Diese Begegnung zeigt, wie wichtig es ist, in schwierigen Zeiten Hilfe anzunehmen und auf die innere Stimme zu hören.
**Positive Message**
Die Geschichte vermittelt eine kraftvolle positive Botschaft: Mit Mut, Vertrauen und der Bereitschaft, Hilfe anzunehmen, können selbst die größten Herausforderungen gemeistert werden. Léons Abenteuer lehrt junge Leser, dass es in Ordnung ist, sich manchmal verloren zu fühlen, und dass es immer einen Weg gibt, zurückzufinden, wenn man offen für Unterstützung ist. Die Geschichte ermutigt Kinder, an sich selbst zu glauben und die Weisheit anderer zu schätzen. Am Ende der Geschichte kehrt Léon nicht nur sicher nach Hause zurück, sondern hat auch eine wertvolle Lektion über Freundschaft und Vertrauen gelernt. Diese positive Botschaft bleibt den jungen Zuhörern im Gedächtnis und begleitet sie in ihren eigenen kleinen Abenteuern des Lebens.
Conclusion
----------
```
Conclusion
Story Wrap-up
Am Ende unserer kurzen Gutenachtgeschichte in Französisch haben wir die Abenteuer von kleinen Helden erlebt, die uns mit ihrer Neugier und ihrem Mut verzaubert haben. Die Geschichte führte uns durch eine Welt voller Magie und Freundschaft, in der die Hauptfiguren gemeinsam Herausforderungen meisterten und dabei wichtige Lektionen über Zusammenhalt und Vertrauen lernten. Die Reise endete mit einem Gefühl der Zufriedenheit, als die Charaktere sicher und glücklich in ihre Betten zurückkehrten, bereit für neue Träume und Abenteuer.
Final Thoughts
Diese Geschichte hinterlässt bei uns eine warme Erinnerung an die Kraft der Fantasie und die Bedeutung von Freundschaft. Sie erinnert uns daran, dass selbst die kleinsten Gesten der Freundlichkeit und der Zusammenarbeit große Veränderungen bewirken können. Während wir die Augen schließen und uns auf den Schlaf vorbereiten, nehmen wir die Botschaft mit, dass wir mit einem offenen Herzen und einem mutigen Geist alles erreichen können. Möge diese Geschichte den jungen Zuhörern süße Träume bescheren und sie dazu inspirieren, ihre eigenen Geschichten zu träumen und zu leben.
```
CONCLUSION
----------
CONCLUSION
Die Kurzgeschichte "Short Bedtime Story in French" bietet jungen Lesern eine bezaubernde Reise in die Welt der Freundschaft, Freundlichkeit und Abenteuer. Durch eine einfache, aber fesselnde Handlung werden die Kinder eingeladen, die Bedeutung von Zusammenhalt und Mitgefühl zu entdecken. Die Geschichte entfaltet sich in einer fantasievollen Umgebung, die die Vorstellungskraft anregt und gleichzeitig wichtige Werte vermittelt.
Zusammenfassend hebt die Geschichte hervor, wie Freundschaft und Freundlichkeit Hindernisse überwinden und zu unvergesslichen Abenteuern führen können. Die Charaktere zeigen, dass durch Zusammenarbeit und gegenseitige Unterstützung selbst die größten Herausforderungen gemeistert werden können. Diese positive Botschaft ist besonders wertvoll für junge Leser, da sie ermutigt, empathisch und hilfsbereit zu sein.
Als nächster Schritt könnte die Geschichte als Ausgangspunkt für Gespräche über Freundschaft und soziale Fähigkeiten genutzt werden. Eltern und Erzieher könnten die Themen vertiefen, indem sie Kinder dazu anregen, ihre eigenen Geschichten zu erfinden oder Rollenspiele zu den behandelten Themen zu gestalten.
Insgesamt bietet die "Short Bedtime Story in French" nicht nur eine unterhaltsame Lektüre vor dem Schlafengehen, sondern auch eine wertvolle Lektion über die Kraft der Freundschaft und die Bedeutung von Freundlichkeit. Sie hinterlässt bei den jungen Lesern ein Gefühl der Zufriedenheit und inspiriert sie, diese Werte in ihrem eigenen Leben zu praktizieren.

View file

@ -1,177 +0,0 @@
Creating a Children's Story in French
=====================================
Einführung in den Leitfaden "Eine Kindergeschichte auf Französisch erstellen"
Willkommen zu unserem Leitfaden "Eine Kindergeschichte auf Französisch erstellen". Dieser Leitfaden richtet sich an alle, die in die wunderbare Welt des Geschichtenerzählens für Kinder eintauchen möchten. Unser Ziel ist es, Ihnen die Werkzeuge und das Wissen an die Hand zu geben, um eine fesselnde und lehrreiche Geschichte zu schreiben, die die Themen Freundschaft und Abenteuer aufgreift.
In diesem Leitfaden erfahren Sie, wie Sie mit einfachen Worten und lebendigen Charakteren eine Geschichte gestalten können, die junge Leser begeistert und inspiriert. Wir werden uns auf die Entwicklung von Charakteren konzentrieren, die nicht nur spannend, sondern auch nachvollziehbar sind. Zudem werden wir Ihnen zeigen, wie Sie eine abenteuerliche Handlung entwickeln, die die Fantasie der Kinder anregt und gleichzeitig wichtige Werte wie Freundschaft und Zusammenarbeit vermittelt.
Der Leitfaden ist in einem leicht verständlichen, gesprächigen Ton gehalten, um Ihnen das Schreiben so angenehm wie möglich zu gestalten. Sie werden praktische Tipps und Beispiele finden, die Ihnen helfen, Ihre eigene Geschichte zu kreieren. Egal, ob Sie ein erfahrener Schriftsteller oder ein Anfänger sind, dieser Leitfaden bietet Ihnen wertvolle Einblicke und Inspiration.
Wir laden Sie ein, Ihrer Kreativität freien Lauf zu lassen und die Kunst des Geschichtenerzählens zu entdecken. Lassen Sie uns gemeinsam eine Welt erschaffen, in der Freundschaft und Abenteuer im Mittelpunkt stehen und die Herzen der Kinder berühren.
Introduction
------------
# Introduction
Willkommen zu unserem Leitfaden "Creating a Children's Story in French". In diesem Abschnitt werden wir die wesentlichen Aspekte beleuchten, die beim Schreiben einer Kindererzählung auf Französisch zu berücksichtigen sind. Unser Ziel ist es, Ihnen die Werkzeuge und das Wissen an die Hand zu geben, um eine fesselnde Geschichte zu schaffen, die junge Leser begeistert und inspiriert.
## Zweck der Geschichte
Der Hauptzweck einer Kindererzählung ist es, die Fantasie der Kinder zu beflügeln und ihnen gleichzeitig wichtige Werte zu vermitteln. In unserer Geschichte werden wir uns auf die Themen Freundschaft und Abenteuer konzentrieren. Diese Themen sind nicht nur universell ansprechend, sondern auch entscheidend für die Entwicklung sozialer Fähigkeiten und die Förderung von Neugier und Entdeckergeist bei Kindern. Eine gut erzählte Geschichte kann Kindern helfen, die Bedeutung von Zusammenarbeit, Vertrauen und Mut zu verstehen.
## Zielgruppe
Unsere Geschichte richtet sich an Kinder im Alter von 6 bis 10 Jahren. In diesem Alter beginnen Kinder, komplexere Geschichten zu verstehen und können sich mit den Charakteren identifizieren. Sie sind neugierig und bereit, neue Konzepte zu erforschen, was sie zu einer idealen Zielgruppe für Geschichten macht, die sowohl unterhaltsam als auch lehrreich sind. Es ist wichtig, eine Sprache zu verwenden, die einfach genug ist, um verstanden zu werden, aber auch reichhaltig genug, um die Vorstellungskraft anzuregen.
## Themen von Freundschaft und Abenteuer
### Freundschaft
Freundschaft ist ein zentrales Thema, das in vielen Kindererzählungen vorkommt. In unserer Geschichte werden wir zeigen, wie Freundschaften entstehen und wachsen können. Durch die Interaktionen der Charaktere lernen Kinder, wie wichtig es ist, füreinander da zu sein, Konflikte zu lösen und die Unterschiede des anderen zu schätzen. Ein Beispiel könnte eine Szene sein, in der die Protagonisten ein Missverständnis überwinden und dadurch ihre Bindung stärken.
### Abenteuer
Abenteuer bietet eine hervorragende Möglichkeit, Spannung und Dynamik in eine Geschichte zu bringen. Es ermöglicht den Charakteren, Herausforderungen zu meistern und über sich hinauszuwachsen. In unserer Geschichte wird das Abenteuer die Kinder auf eine Reise mitnehmen, die sowohl aufregend als auch lehrreich ist. Ein Beispiel könnte eine Schatzsuche sein, bei der die Charaktere Hindernisse überwinden müssen, um ihr Ziel zu erreichen.
Durch die Kombination dieser Themen schaffen wir eine Geschichte, die nicht nur unterhält, sondern auch lehrreiche Botschaften vermittelt. Wir hoffen, dass dieser Leitfaden Ihnen hilft, eine inspirierende und unvergessliche Geschichte zu schreiben, die bei jungen Lesern Anklang findet.
Character Development
---------------------
Title: Charakterentwicklung
Einführung:
In der Welt der Kinderliteratur sind es oft die Charaktere, die eine Geschichte lebendig und unvergesslich machen. In diesem Abschnitt werden wir uns darauf konzentrieren, wie man fesselnde Charaktere schafft, welche Charaktereigenschaften bei Kindern besonders gut ankommen und welche Rolle die Charaktere in der Geschichte spielen. Lassen Sie uns gemeinsam in die Kunst der Charakterentwicklung eintauchen, um eine bezaubernde Geschichte über Freundschaft und Abenteuer zu kreieren.
Hauptcharaktere:
Die Hauptcharaktere sind das Herzstück Ihrer Geschichte. Sie sind diejenigen, mit denen die jungen Leser am meisten interagieren und sich identifizieren. Hier sind einige Tipps, um starke Hauptcharaktere zu entwickeln:
1. **Einprägsame Eigenschaften**: Geben Sie Ihren Hauptcharakteren einzigartige Merkmale, die sie von anderen abheben. Dies könnten physische Merkmale, besondere Fähigkeiten oder unverwechselbare Persönlichkeitszüge sein. Zum Beispiel könnte Ihr Hauptcharakter ein mutiges kleines Mädchen sein, das immer einen roten Hut trägt und keine Angst vor Abenteuern hat.
2. **Entwicklung und Wachstum**: Kinder lieben es, Charaktere zu sehen, die sich im Laufe der Geschichte weiterentwickeln. Zeigen Sie, wie Ihr Hauptcharakter durch die Herausforderungen, denen er begegnet, wächst und lernt. Vielleicht lernt unser mutiges Mädchen, dass wahre Freundschaft bedeutet, auch in schwierigen Zeiten füreinander da zu sein.
3. **Identifikationspotenzial**: Schaffen Sie Charaktere, mit denen sich Kinder identifizieren können. Dies kann durch alltägliche Probleme oder Emotionen geschehen, die die Kinder selbst erleben. Ein Beispiel wäre, dass unser Hauptcharakter sich manchmal einsam fühlt und nach einem wahren Freund sucht.
Nebencharaktere:
Nebencharaktere spielen eine wichtige Rolle, um die Geschichte zu bereichern und die Hauptcharaktere zu unterstützen. Hier sind einige Überlegungen zur Entwicklung von Nebencharakteren:
1. **Vielfalt und Tiefe**: Auch wenn Nebencharaktere nicht im Mittelpunkt stehen, sollten sie dennoch gut ausgearbeitet sein. Sie können die Geschichte durch ihre unterschiedlichen Perspektiven und Erfahrungen bereichern. Vielleicht gibt es einen weisen alten Fuchs, der dem Mädchen Ratschläge gibt, oder einen lustigen Papagei, der für humorvolle Momente sorgt.
2. **Unterstützende Rollen**: Nebencharaktere können die Handlung vorantreiben und den Hauptcharakteren helfen, ihre Ziele zu erreichen. Sie können auch als Katalysatoren für wichtige Wendepunkte in der Geschichte dienen. Der weise Fuchs könnte zum Beispiel einen entscheidenden Hinweis geben, der das Mädchen auf die richtige Spur bringt.
3. **Beziehungen und Dynamik**: Zeigen Sie, wie die Nebencharaktere mit den Hauptcharakteren interagieren und welche Beziehungen sich entwickeln. Diese Dynamik kann die emotionale Tiefe der Geschichte verstärken. Vielleicht entwickelt sich zwischen dem Mädchen und dem Papagei eine unerwartete Freundschaft, die beiden hilft, ihre Ängste zu überwinden.
Rolle der Charaktere in der Geschichte:
Charaktere sind nicht nur Akteure in Ihrer Geschichte, sondern auch Träger der Themen und Botschaften, die Sie vermitteln möchten. In einer Geschichte über Freundschaft und Abenteuer können die Charaktere den Wert von Teamarbeit, Vertrauen und Mut verkörpern. Durch ihre Handlungen und Entscheidungen können sie den jungen Lesern wichtige Lektionen auf eine unterhaltsame und zugängliche Weise vermitteln.
Abschluss:
Die Entwicklung von Charakteren ist ein wesentlicher Bestandteil beim Schreiben einer fesselnden Kindergeschichte. Indem Sie einprägsame Haupt- und Nebencharaktere schaffen, die sowohl unterhalten als auch lehren, können Sie eine Geschichte gestalten, die Kinder begeistert und inspiriert. Lassen Sie Ihrer Kreativität freien Lauf und denken Sie daran, dass die besten Geschichten oft diejenigen sind, die durch ihre Charaktere zum Leben erweckt werden.
Plot Structure
--------------
# Plot Structure
Beim Schreiben einer Kindergeschichte auf Französisch ist es wichtig, eine klare und einfache Plotstruktur zu verwenden. Diese Struktur hilft dabei, die Geschichte verständlich und spannend zu gestalten, insbesondere für junge Leser. In diesem Abschnitt werden wir die wesentlichen Elemente einer Plotstruktur besprechen, die sich auf die Themen Freundschaft und Abenteuer konzentrieren. Wir werden die Geschichte in drei Hauptteile unterteilen: Anfang, Mitte und Ende.
## Setting the Scene
Der Anfang einer Geschichte ist entscheidend, um die Leser in die Welt der Charaktere einzuführen. Verwenden Sie einfache Sprache, um die Umgebung und die Hauptfiguren zu beschreiben. Dies könnte ein magischer Wald, eine kleine Stadt oder sogar ein fantastisches Königreich sein. Der Schauplatz sollte die Grundlage für das bevorstehende Abenteuer legen.
Beispiel: "In einem kleinen Dorf am Rande eines großen Waldes lebte ein mutiger kleiner Junge namens Pierre. Pierre liebte es, neue Freunde zu finden und Abenteuer zu erleben."
## Building the Adventure
Der Mittelteil der Geschichte sollte die Spannung aufbauen und die Leser in das Abenteuer der Charaktere hineinziehen. Hier ist es wichtig, die Themen Freundschaft und Abenteuer zu integrieren. Die Charaktere könnten auf eine Herausforderung stoßen, die sie nur gemeinsam bewältigen können, was ihre Freundschaft stärkt.
Beispiel: "Eines Tages entdeckte Pierre eine geheimnisvolle Karte, die zu einem versteckten Schatz führte. Zusammen mit seiner besten Freundin Marie machte er sich auf den Weg, um das Geheimnis zu lüften. Auf ihrer Reise mussten sie Rätsel lösen und Hindernisse überwinden, die ihre Freundschaft auf die Probe stellten."
## Resolution and Friendship
Das Ende der Geschichte sollte alle offenen Fragen klären und eine zufriedenstellende Lösung bieten. Es ist der Moment, in dem die Charaktere ihre Ziele erreichen und die Bedeutung von Freundschaft und Zusammenarbeit erkennen. Eine positive und lehrreiche Botschaft ist besonders wichtig in Kindergeschichten.
Beispiel: "Nach vielen Abenteuern fanden Pierre und Marie den Schatz. Doch das größte Geschenk war die Erkenntnis, dass ihre Freundschaft der wahre Schatz war. Gemeinsam kehrten sie nach Hause zurück, bereit für neue Abenteuer."
Durch die Verwendung dieser klaren Plotstruktur können Sie eine fesselnde und lehrreiche Kindergeschichte auf Französisch erstellen, die die Themen Freundschaft und Abenteuer aufgreift. Denken Sie daran, einfache Sprache zu verwenden, um die Geschichte für junge Leser zugänglich zu machen.
Language and Style
------------------
# Language and Style
Creating a children's story in French involves a delicate balance of simplicity, engagement, and maintaining a tone that resonates with young readers. This section will guide you through the essential elements of language and style to craft a captivating story that is both educational and entertaining for children.
## Use of Simple French Language
When writing for children, simplicity is key. The language should be accessible to young readers, ensuring they can follow the story without difficulty. Here are some tips to achieve this:
- **Basic Vocabulary**: Use common and straightforward words that are likely to be familiar to children. For example, instead of "extraordinaire," you might use "super" to describe something amazing.
- **Short Sentences**: Keep sentences concise to maintain clarity. For instance, "Le chat court" (The cat runs) is easier for children to understand than a complex sentence with multiple clauses.
- **Repetition**: Reinforce key vocabulary and concepts through repetition. This helps children remember new words and ideas. For example, if your story involves a magical forest, repeat the word "forêt" throughout the narrative.
- **Simple Tenses**: Stick to present and simple past tenses. These are easier for children to grasp. For example, "Il était une fois" (Once upon a time) is a classic and simple way to start a story.
## Engaging Storytelling Techniques
To keep children engaged, your story should be dynamic and imaginative. Here are some techniques to consider:
- **Vivid Imagery**: Use descriptive language to paint a picture in the reader's mind. For example, "Le soleil brillait comme un grand ballon jaune dans le ciel" (The sun shone like a big yellow balloon in the sky).
- **Dialogue**: Incorporate dialogue to bring characters to life and make the story interactive. For instance, "Bonjour, petit lapin!" dit le renard. (Hello, little rabbit! said the fox.)
- **Rhythm and Rhyme**: Consider using rhythmic language or rhymes to make the story more musical and memorable. This can be particularly effective in stories for younger children.
- **Interactive Elements**: Encourage participation by asking questions or prompting actions. For example, "Peux-tu deviner ce qui se passe ensuite?" (Can you guess what happens next?)
## Maintaining a Child-Friendly Tone
The tone of your story should be warm, friendly, and appropriate for children. Here are some ways to achieve this:
- **Positive Themes**: Focus on themes that promote kindness, friendship, and adventure. These are relatable and inspiring for children.
- **Gentle Humor**: Use light-hearted humor to entertain without confusing or offending. For example, a clumsy character who always trips over their own feet can be a source of gentle laughter.
- **Empathy and Emotion**: Help children connect with the characters by exploring emotions. For example, "Le petit ours était triste de perdre son ballon, mais il a trouvé un nouvel ami" (The little bear was sad to lose his balloon, but he found a new friend).
- **Encouraging Messages**: End with a positive message or moral that children can learn from. For example, "L'amitié est la plus grande aventure de toutes" (Friendship is the greatest adventure of all).
By focusing on these elements of language and style, you can create a French children's story that is not only easy to read but also engaging and meaningful. Remember, the goal is to spark imagination and foster a love for reading in young minds.
Conclusion
----------
# Conclusion
## Zusammenfassung der Schlüsselelemente
Beim Erstellen einer Kindergeschichte auf Französisch haben wir uns auf einige wesentliche Elemente konzentriert, die den Erzählprozess sowohl spannend als auch lehrreich gestalten. Zunächst ist die Wahl eines einfachen und klaren Sprachstils entscheidend, um die jungen Leser zu fesseln und das Verständnis zu erleichtern. Die Themen Freundschaft und Abenteuer bieten eine hervorragende Grundlage, um die Fantasie der Kinder anzuregen und gleichzeitig wichtige Werte zu vermitteln.
Charaktere spielen eine zentrale Rolle in jeder Geschichte. Sie sollten nicht nur fesselnd, sondern auch nachvollziehbar sein, damit die Kinder sich mit ihnen identifizieren können. Die Entwicklung der Charaktere durch ihre Abenteuer und die Beziehungen, die sie knüpfen, sind entscheidend, um die Geschichte lebendig und interessant zu gestalten.
Ein gut strukturierter Handlungsverlauf, der Spannung und Überraschungselemente enthält, hält die jungen Leser bei der Stange. Es ist wichtig, dass die Geschichte einen klaren Anfang, eine spannende Mitte und ein zufriedenstellendes Ende hat, um das Interesse der Kinder zu bewahren.
## Ermutigung, mehr Geschichten zu erstellen
Jetzt, da Sie die Grundlagen des Geschichtenerzählens für Kinder auf Französisch kennen, laden wir Sie ein, Ihre Kreativität weiter zu entfalten. Jede Geschichte, die Sie schreiben, ist eine Gelegenheit, die Fantasie der Kinder zu beflügeln und ihnen neue Welten zu eröffnen. Lassen Sie sich von alltäglichen Erlebnissen, Träumen oder sogar von den Geschichten, die Sie als Kind geliebt haben, inspirieren.
Denken Sie daran, dass jede neue Geschichte eine Chance ist, Ihre Fähigkeiten als Geschichtenerzähler zu verbessern und Ihre eigene Stimme zu finden. Haben Sie keine Angst, mit verschiedenen Themen und Stilen zu experimentieren. Die Welt der Kindergeschichten ist grenzenlos, und Ihre Beiträge können einen bleibenden Eindruck hinterlassen.
Wir hoffen, dass dieser Leitfaden Ihnen geholfen hat, die Grundlagen des Geschichtenerzählens zu verstehen und Sie dazu inspiriert, Ihre eigenen Geschichten zu kreieren. Viel Spaß beim Schreiben und beim Entdecken der wunderbaren Welt der Kindergeschichten!
CONCLUSION
----------
Abschluss der Anleitung: "Erstellen einer Kindergeschichte auf Französisch"
In dieser Anleitung haben wir die wesentlichen Schritte zur Erstellung einer Kindergeschichte auf Französisch behandelt, die sich auf die Themen Freundschaft und Abenteuer konzentriert. Wir haben die Bedeutung der Charakterentwicklung hervorgehoben und gezeigt, wie man einfache Sprache verwendet, um junge Leser zu fesseln.
Zusammenfassend lässt sich sagen, dass eine erfolgreiche Kindergeschichte durch lebendige und nachvollziehbare Charaktere geprägt ist, die sich im Laufe der Handlung weiterentwickeln. Abenteuerliche Elemente und die Betonung von Freundschaft helfen, das Interesse der Kinder zu wecken und wichtige soziale Werte zu vermitteln.
Wir empfehlen, beim Schreiben stets die Perspektive der Kinder im Auge zu behalten und die Sprache so zu wählen, dass sie leicht verständlich und ansprechend ist. Es ist auch hilfreich, Feedback von Kindern einzuholen, um sicherzustellen, dass die Geschichte ihre Zielgruppe erreicht.
Abschließend hoffen wir, dass diese Anleitung Ihnen die notwendigen Werkzeuge und Inspirationen gegeben hat, um eine fesselnde und lehrreiche Kindergeschichte auf Französisch zu erstellen. Nutzen Sie die Gelegenheit, Ihre Kreativität zu entfalten und Geschichten zu schaffen, die Kinder begeistern und ihnen wertvolle Lektionen über Freundschaft und Abenteuer mit auf den Weg geben.

View file

@ -1,244 +0,0 @@
Creating a French Children's Story
==================================
Einführung in den Leitfaden "Erstellen einer französischen Kindergeschichte"
Willkommen zu unserem Leitfaden "Erstellen einer französischen Kindergeschichte"! Dieser Leitfaden richtet sich an alle, die die wunderbare Welt der Kinderliteratur erkunden und eine eigene Geschichte in französischer Sprache verfassen möchten. Egal, ob Sie ein Elternteil, Lehrer oder einfach ein Geschichtenerzähler im Herzen sind, dieser Leitfaden bietet Ihnen die Werkzeuge und Inspiration, die Sie benötigen, um eine fesselnde Geschichte zu kreieren.
**Zweck und Umfang des Dokuments:**
Der Zweck dieses Leitfadens ist es, Ihnen zu helfen, eine französische Kindergeschichte zu schreiben, die sowohl lehrreich als auch unterhaltsam ist. Wir konzentrieren uns auf einfache französische Sprache, um sicherzustellen, dass die Geschichte für Kinder leicht verständlich ist. Der Leitfaden umfasst Techniken des Geschichtenerzählens, die speziell auf die Bedürfnisse und Interessen junger Leser abgestimmt sind.
**Kontext und Hintergrundinformationen:**
Kinderliteratur spielt eine entscheidende Rolle in der Entwicklung von Sprache und Vorstellungskraft bei Kindern. Das Schreiben in einer anderen Sprache, wie Französisch, bietet nicht nur eine Gelegenheit zur Sprachförderung, sondern auch zur kulturellen Bereicherung. Französische Kindergeschichten sind bekannt für ihre Kreativität und ihren Charme, und dieser Leitfaden wird Ihnen helfen, diese Elemente in Ihre eigene Geschichte zu integrieren.
**Was Sie im Dokument finden werden:**
- Eine Einführung in die Grundlagen der Kinderliteratur und ihre Bedeutung.
- Tipps und Techniken zum Schreiben in einfacher französischer Sprache.
- Anleitungen zur Entwicklung von Abenteuergeschichten, die Freundschaft und Fantasie betonen.
- Praktische Übungen und Beispiele, um Ihre Kreativität zu fördern.
**Ton und Stil:**
Dieser Leitfaden ist in einem freundlichen und zugänglichen Ton gehalten, um Leser aller Hintergründe anzusprechen. Unser Ziel ist es, Sie zu inspirieren und zu ermutigen, Ihre eigene einzigartige Geschichte zu schaffen, die Kinder begeistert und zum Träumen anregt.
Wir freuen uns darauf, Sie auf dieser kreativen Reise zu begleiten und Ihnen zu helfen, eine Geschichte zu schreiben, die Kinderherzen höher schlagen lässt. Lassen Sie uns gemeinsam in die Welt der französischen Kindergeschichten eintauchen!
Introduction to Children's Stories
----------------------------------
Title: Einführung in Kindergeschichten
Kindergeschichten sind ein wesentlicher Bestandteil der kindlichen Entwicklung und bieten eine Vielzahl von Vorteilen, die weit über das bloße Vergnügen hinausgehen. In diesem Abschnitt werden wir die Bedeutung des Geschichtenerzählens für Kinder und die Merkmale fesselnder Kindergeschichten untersuchen.
**Zweck von Kindergeschichten**
Kindergeschichten dienen mehreren wichtigen Zwecken in der Entwicklung eines Kindes:
1. **Förderung der Fantasie und Kreativität**: Geschichten öffnen Türen zu neuen Welten und regen die Fantasie der Kinder an. Sie ermöglichen es Kindern, sich Szenarien vorzustellen, die über ihre alltäglichen Erfahrungen hinausgehen.
2. **Vermittlung von Werten und Lektionen**: Viele Geschichten enthalten moralische Lektionen oder Werte, die Kindern helfen, zwischen richtig und falsch zu unterscheiden. Sie bieten eine sichere Umgebung, um über Konsequenzen und ethisches Verhalten nachzudenken.
3. **Sprachentwicklung**: Das Hören und Lesen von Geschichten erweitert den Wortschatz und verbessert die Sprachfähigkeiten von Kindern. Sie lernen neue Wörter und Satzstrukturen in einem Kontext, der das Lernen erleichtert.
4. **Förderung der emotionalen Intelligenz**: Geschichten helfen Kindern, Empathie zu entwickeln, indem sie sich in die Lage der Charaktere versetzen. Sie lernen, verschiedene Emotionen zu erkennen und zu verstehen.
**Elemente einer guten Geschichte**
Um eine Geschichte für Kinder fesselnd und lehrreich zu gestalten, sollten bestimmte Elemente berücksichtigt werden:
1. **Interessante Charaktere**: Kinder identifizieren sich oft mit den Protagonisten einer Geschichte. Charaktere sollten daher gut entwickelt und nachvollziehbar sein, mit Eigenschaften, die Kinder bewundern oder von denen sie lernen können.
2. **Spannende Handlung**: Eine gute Geschichte sollte eine klare Struktur mit einem Anfang, einem Mittelteil und einem Ende haben. Abenteuer und unerwartete Wendungen halten die Aufmerksamkeit der jungen Leser aufrecht.
3. **Einfachheit und Klarheit**: Die Sprache sollte einfach und klar sein, um das Verständnis zu erleichtern. Komplexe Begriffe sollten vermieden oder kindgerecht erklärt werden.
4. **Einprägsame Botschaft**: Eine starke, einprägsame Botschaft oder Moral kann den Kindern helfen, die Lektionen der Geschichte in ihrem eigenen Leben anzuwenden.
5. **Visuelle Unterstützung**: Illustrationen oder Bilder können die Geschichte lebendiger machen und das Verständnis fördern, insbesondere für jüngere Kinder, die noch nicht lesen können.
Durch das Verständnis dieser Elemente und Zwecke können Autoren Geschichten schaffen, die nicht nur unterhalten, sondern auch einen bleibenden Eindruck hinterlassen. In der nächsten Phase dieses Leitfadens werden wir uns darauf konzentrieren, wie man eine französische Kindergeschichte entwickelt, die Abenteuer und Freundschaft thematisiert.
Understanding the Audience
--------------------------
Title: Understanding the Audience
In diesem Abschnitt des Leitfadens "Creating a French Children's Story" werden wir uns eingehend mit dem Verständnis der Zielgruppe befassen. Das Ziel ist es, eine Geschichte zu schaffen, die nicht nur die Fantasie der Kinder anregt, sondern auch kulturell relevant und sprachlich angemessen ist.
**Age Group Considerations**
Wenn wir eine Geschichte für Kinder schreiben, ist es entscheidend, die Altersgruppe zu berücksichtigen, für die die Geschichte bestimmt ist. Kinder im Alter von 3 bis 5 Jahren haben andere sprachliche Fähigkeiten und Interessen als Kinder im Alter von 6 bis 8 Jahren.
- **Sprachliche Einfachheit:** Für jüngere Kinder sollte die Sprache einfach und klar sein. Verwenden Sie kurze Sätze und vermeiden Sie komplexe Wörter. Zum Beispiel könnte ein Satz für jüngere Kinder lauten: "Le petit lapin saute dans le jardin." Für ältere Kinder kann die Sprache etwas anspruchsvoller sein, mit längeren Sätzen und einer größeren Vielfalt an Vokabeln.
- **Themen und Interessen:** Jüngere Kinder sind oft von einfachen Abenteuern und klaren moralischen Lektionen fasziniert. Geschichten über Tiere, Freundschaft und alltägliche Erlebnisse sind besonders ansprechend. Ältere Kinder hingegen können komplexere Handlungsstränge und Charakterentwicklungen schätzen. Ein Beispiel könnte eine Geschichte über eine Gruppe von Freunden sein, die ein Rätsel lösen müssen.
**Cultural Sensitivity**
Die kulturelle Relevanz ist ein weiterer wichtiger Aspekt beim Schreiben einer französischen Kindergeschichte. Es ist wichtig, kulturelle Elemente einzubeziehen, die den Kindern vertraut sind, aber auch neue Perspektiven bieten.
- **Kulturelle Elemente:** Integrieren Sie französische Traditionen, Feste oder bekannte Orte, um den Kindern ein Gefühl der Vertrautheit zu geben. Zum Beispiel könnte eine Geschichte während des "Fête de la Musique" spielen, einem beliebten Musikfestival in Frankreich.
- **Diversität und Inklusion:** Achten Sie darauf, eine vielfältige und inklusive Darstellung von Charakteren und Kulturen zu bieten. Dies fördert das Verständnis und die Akzeptanz von Unterschieden. Eine Geschichte könnte zum Beispiel Charaktere aus verschiedenen Regionen Frankreichs umfassen, die zusammenarbeiten, um ein gemeinsames Ziel zu erreichen.
Indem wir diese Aspekte berücksichtigen, können wir eine Geschichte schaffen, die nicht nur unterhaltsam, sondern auch lehrreich und kulturell bereichernd ist. Denken Sie daran, dass das Ziel darin besteht, die Neugier der Kinder zu wecken und ihnen gleichzeitig wertvolle Lektionen über Sprache und Kultur zu vermitteln.
Elements of Adventure and Friendship
------------------------------------
# Elements of Adventure and Friendship
In der Welt der Kinderliteratur sind Abenteuer und Freundschaft zwei zentrale Themen, die Geschichten lebendig und fesselnd machen. Diese Elemente helfen nicht nur, die Fantasie der Kinder zu beflügeln, sondern vermitteln auch wichtige Werte und Lektionen. In diesem Abschnitt werden wir untersuchen, wie Abenteuer und Freundschaft in französischen Kindergeschichten integriert werden können, um eine fesselnde und lehrreiche Erzählung zu schaffen.
## Abenteuer-Elemente
Abenteuer in Kindergeschichten sind oft geprägt von aufregenden Erlebnissen, unerwarteten Wendungen und der Entdeckung neuer Welten. Diese Elemente sind entscheidend, um die Neugier der jungen Leser zu wecken und sie auf eine Reise mitzunehmen, die sowohl spannend als auch lehrreich ist.
### Definition von Abenteuer in Kindergeschichten
Ein Abenteuer in einer Kindergeschichte kann als eine Reihe von Ereignissen beschrieben werden, die den Protagonisten aus seiner Komfortzone herausführen und ihn mit Herausforderungen konfrontieren, die Mut, Einfallsreichtum und Entschlossenheit erfordern. Diese Abenteuer können in einer Vielzahl von Umgebungen stattfinden, sei es in einem magischen Wald, auf hoher See oder sogar in einer fiktiven Stadt.
### Wichtige Elemente eines Abenteuers
1. **Ein klarer Ausgangspunkt**: Die Geschichte sollte mit einer vertrauten Umgebung oder Situation beginnen, die dann durch das Abenteuer gestört wird.
2. **Ein Ziel oder eine Mission**: Der Protagonist sollte ein klares Ziel haben, das ihn antreibt, sei es die Rettung eines Freundes, die Suche nach einem Schatz oder die Lösung eines Rätsels.
3. **Hindernisse und Herausforderungen**: Diese sind entscheidend, um Spannung zu erzeugen und den Protagonisten zu Wachstum und Entwicklung zu zwingen.
4. **Ein Höhepunkt**: Der Moment, in dem die Spannung ihren Höhepunkt erreicht und der Protagonist seine größte Herausforderung meistert.
5. **Eine Rückkehr zur Normalität**: Nach dem Abenteuer kehrt der Protagonist in seine Welt zurück, oft mit neuen Erkenntnissen und Erfahrungen.
## Freundschafts-Themen
Freundschaft ist ein universelles Thema, das in vielen Kindergeschichten eine zentrale Rolle spielt. Sie bietet nicht nur emotionale Tiefe, sondern lehrt auch wichtige soziale Fähigkeiten wie Empathie, Zusammenarbeit und Vertrauen.
### Integrieren von Freundschaft in Geschichten
Freundschaft kann auf vielfältige Weise in eine Geschichte eingebunden werden, sei es durch die Beziehung zwischen den Hauptfiguren oder durch die Begegnung mit neuen Freunden während des Abenteuers.
### Wichtige Aspekte der Freundschaft
1. **Entwicklung der Freundschaft**: Zeigen Sie, wie sich Freundschaften entwickeln, von der ersten Begegnung bis zur tiefen Bindung.
2. **Konflikte und Lösungen**: Freundschaften sind nicht immer einfach. Konflikte bieten die Möglichkeit, wichtige Lektionen über Vergebung und Verständnis zu vermitteln.
3. **Unterstützung und Zusammenarbeit**: Freunde helfen sich gegenseitig, Herausforderungen zu meistern, was die Bedeutung von Teamarbeit und Unterstützung unterstreicht.
4. **Vielfalt und Akzeptanz**: Freundschaften zwischen unterschiedlichen Charakteren fördern Toleranz und Akzeptanz.
### Beispiel für eine Freundschaftsgeschichte
Ein Beispiel könnte die Geschichte von Léa und Max sein, zwei Kindern, die in einem kleinen französischen Dorf leben. Als sie eines Tages einen geheimnisvollen Schlüssel finden, begeben sie sich auf ein Abenteuer, um das dazugehörige Schloss zu finden. Auf ihrem Weg lernen sie, wie wichtig es ist, zusammenzuarbeiten und sich gegenseitig zu vertrauen, um die Herausforderungen zu meistern, die ihnen begegnen.
Durch die geschickte Kombination von Abenteuer und Freundschaft können französische Kindergeschichten nicht nur unterhalten, sondern auch wertvolle Lektionen vermitteln, die junge Leser ihr Leben lang begleiten werden.
Writing in Simple French
------------------------
Title: Writing in Simple French
In diesem Abschnitt des Leitfadens "Creating a French Children's Story" konzentrieren wir uns darauf, wie man eine Geschichte in einfachem Französisch schreibt. Dies ist besonders wichtig, wenn wir eine Geschichte für Kinder erstellen, da sie von Natur aus einfacher und zugänglicher sein sollte. Wir werden uns auf zwei Hauptpunkte konzentrieren: die Verwendung eines einfachen Wortschatzes und den Aufbau einfacher Sätze.
## Vocabulary Selection
Die Auswahl des richtigen Wortschatzes ist entscheidend, um eine Geschichte zu schreiben, die für Kinder leicht verständlich ist. Hier sind einige Tipps, um den Wortschatz einfach zu halten:
1. **Verwenden Sie häufige Wörter**: Wählen Sie Wörter, die Kinder wahrscheinlich schon kennen oder leicht lernen können. Zum Beispiel: "chat" (Katze), "chien" (Hund), "maison" (Haus).
2. **Vermeiden Sie Fachjargon**: Komplexe oder technische Begriffe sollten vermieden werden. Wenn ein schwieriges Wort notwendig ist, erklären Sie es im Kontext der Geschichte.
3. **Nutzen Sie Wiederholungen**: Wiederholungen helfen Kindern, neue Wörter zu lernen und sich an die Geschichte zu erinnern. Zum Beispiel: "Le petit chat est curieux. Le petit chat explore le jardin."
4. **Einfachheit vor Vielfalt**: Es ist besser, einfache und klare Wörter zu verwenden, anstatt nach Synonymen zu suchen, die möglicherweise verwirrend sein könnten.
## Sentence Structure
Der Satzbau in einer Kindergeschichte sollte klar und direkt sein. Hier sind einige Richtlinien, um einfache Sätze zu konstruieren:
1. **Kurze Sätze**: Halten Sie die Sätze kurz und prägnant. Lange Sätze können für Kinder schwer zu verstehen sein. Beispiel: "Le chien court. Il est heureux."
2. **Einfache Satzstrukturen**: Verwenden Sie einfache Subjekt-Verb-Objekt-Strukturen. Beispiel: "Marie mange une pomme."
3. **Vermeiden Sie komplexe Zeiten**: Bleiben Sie bei den Grundzeiten wie Präsens und einfachem Perfekt. Beispiel: "Il a trouvé un trésor."
4. **Direkte Rede verwenden**: Direkte Rede macht die Geschichte lebendiger und hilft Kindern, sich in die Charaktere hineinzuversetzen. Beispiel: "Le loup dit: 'Je vais te manger!'"
5. **Fragen einbauen**: Fragen können das Interesse der Kinder wecken und sie zum Nachdenken anregen. Beispiel: "Où est le chat?"
Indem Sie diese Richtlinien befolgen, können Sie eine fesselnde und leicht verständliche Geschichte für Kinder in einfachem Französisch schreiben. Denken Sie daran, dass das Ziel darin besteht, eine Geschichte zu schaffen, die sowohl lehrreich als auch unterhaltsam ist, während sie die Fantasie der jungen Leser anregt.
Story Development Process
-------------------------
Title: Story Development Process
Willkommen zum Abschnitt über den Entwicklungsprozess einer Geschichte! Hier erfahren Sie, wie Sie eine fesselnde französische Kindergeschichte mit den Themen Abenteuer und Freundschaft entwickeln können. Lassen Sie uns gemeinsam die wesentlichen Schritte durchgehen, um eine spannende und lehrreiche Geschichte zu gestalten.
## Plot Planning
Der erste Schritt bei der Entwicklung Ihrer Geschichte ist die Planung der Handlung. Eine gut durchdachte Handlung ist das Rückgrat jeder Geschichte und hält die jungen Leser interessiert und engagiert. Hier sind einige Tipps, um eine fesselnde Handlung zu entwickeln:
1. **Themenwahl**: Beginnen Sie mit der Auswahl eines zentralen Themas. Da Ihre Geschichte Elemente von Abenteuer und Freundschaft enthalten soll, überlegen Sie, wie diese Themen miteinander verwoben werden können. Zum Beispiel könnte die Geschichte von einer Gruppe von Freunden handeln, die sich auf eine abenteuerliche Reise begeben, um einen verlorenen Schatz zu finden.
2. **Struktur der Handlung**: Eine klassische Struktur besteht aus Einleitung, Hauptteil und Schluss. In der Einleitung stellen Sie die Charaktere und das Setting vor. Der Hauptteil sollte die Abenteuer und Herausforderungen der Charaktere beschreiben, während der Schluss die Geschichte abrundet und eine wertvolle Lektion vermittelt.
3. **Spannung und Überraschung**: Halten Sie die Spannung aufrecht, indem Sie unerwartete Wendungen einbauen. Kinder lieben Überraschungen, und ein unerwartetes Ereignis kann die Geschichte aufregender machen.
## Character Creation
Charaktere sind das Herzstück jeder Geschichte. Sie sollten lebendig und nachvollziehbar sein, damit die jungen Leser sich mit ihnen identifizieren können. Hier sind einige Schritte zur Entwicklung unvergesslicher Charaktere:
1. **Hauptcharaktere definieren**: Bestimmen Sie, wer die Protagonisten Ihrer Geschichte sind. In einer Geschichte über Freundschaft könnten dies eine Gruppe von Tieren oder Kindern sein, die zusammenarbeiten, um ein gemeinsames Ziel zu erreichen.
2. **Charaktereigenschaften**: Geben Sie jedem Charakter einzigartige Eigenschaften und Persönlichkeiten. Zum Beispiel könnte ein Charakter besonders mutig sein, während ein anderer sehr klug ist. Diese Unterschiede können zu interessanten Dynamiken und Konflikten führen.
3. **Entwicklung der Charaktere**: Lassen Sie Ihre Charaktere im Laufe der Geschichte wachsen. Vielleicht lernt der ängstliche Charakter, mutiger zu sein, oder der schüchterne Charakter gewinnt an Selbstvertrauen. Diese Entwicklungen machen die Geschichte nicht nur interessanter, sondern vermitteln auch wichtige Lektionen.
Durch sorgfältige Planung der Handlung und die Schaffung lebendiger Charaktere können Sie eine französische Kindergeschichte entwickeln, die sowohl unterhaltsam als auch lehrreich ist. Viel Spaß beim Schreiben und lassen Sie Ihrer Kreativität freien Lauf!
Finalizing the Story
--------------------
Title: Finalizing the Story
Nachdem Sie Ihre französische Kindergeschichte mit spannenden Abenteuern und herzlichen Freundschaften erstellt haben, ist es an der Zeit, den letzten Schliff zu geben. In diesem Abschnitt konzentrieren wir uns auf das Bearbeiten und Korrekturlesen sowie auf das Einholen von Feedback und die anschließenden Überarbeitungen. Diese Schritte sind entscheidend, um sicherzustellen, dass Ihre Geschichte sowohl sprachlich korrekt als auch für Kinder ansprechend ist.
**Editing Techniques**
Beim Bearbeiten Ihrer Geschichte sollten Sie auf mehrere Aspekte achten:
1. **Sprachliche Korrektheit**: Überprüfen Sie die Grammatik und Rechtschreibung. Eine fehlerfreie Sprache ist besonders wichtig, da Kinder beim Lesen auch lernen. Nutzen Sie Tools wie Grammatikprüfungen oder bitten Sie einen Muttersprachler um Hilfe.
2. **Klarheit und Einfachheit**: Stellen Sie sicher, dass die Sprache einfach und verständlich ist. Vermeiden Sie komplexe Sätze und schwierige Wörter, die Kinder möglicherweise nicht verstehen. Ein Beispiel: Anstatt "l'aventure palpitante" könnten Sie "l'aventure amusante" verwenden, um es einfacher zu halten.
3. **Kohärenz und Logik**: Überprüfen Sie, ob die Handlung logisch und kohärent ist. Jede Szene sollte sinnvoll auf die nächste folgen. Achten Sie darauf, dass die Charaktere konsistent handeln und sprechen.
4. **Stil und Ton**: Der Ton Ihrer Geschichte sollte freundlich und einladend sein. Achten Sie darauf, dass der Stil zur Zielgruppe passt und die Abenteuerlust und Freundschaft gut vermittelt.
**Incorporating Feedback**
Feedback ist ein wertvolles Werkzeug, um Ihre Geschichte zu verbessern. Hier sind einige Schritte, wie Sie es effektiv nutzen können:
1. **Feedback einholen**: Teilen Sie Ihre Geschichte mit Freunden, Familie oder einer Schreibgruppe. Besonders wertvoll ist Feedback von Eltern oder Lehrern, die Erfahrung mit Kinderliteratur haben.
2. **Offenheit für Kritik**: Seien Sie offen für konstruktive Kritik. Es ist wichtig, die Perspektiven anderer zu berücksichtigen, um die Geschichte zu verbessern.
3. **Revisionsprozess**: Nehmen Sie sich die Zeit, das erhaltene Feedback zu analysieren und zu entscheiden, welche Änderungen sinnvoll sind. Vielleicht müssen Sie einige Szenen umschreiben oder Charaktere weiterentwickeln.
4. **Testleser**: Lassen Sie Kinder, die zur Zielgruppe gehören, die Geschichte lesen. Ihre Reaktionen sind oft ehrlich und direkt und können wertvolle Einblicke geben.
Indem Sie diese Schritte befolgen, stellen Sie sicher, dass Ihre französische Kindergeschichte nicht nur sprachlich korrekt, sondern auch spannend und lehrreich für junge Leser ist. Viel Erfolg beim Finalisieren Ihrer Geschichte!
CONCLUSION
----------
Abschluss: "Erstellen einer französischen Kindergeschichte"
In diesem Leitfaden haben wir die wesentlichen Schritte und Techniken zur Erstellung einer fesselnden französischen Kindergeschichte untersucht. Wir haben die Bedeutung der Kinderliteratur hervorgehoben und wie sie zur sprachlichen und emotionalen Entwicklung von Kindern beiträgt. Der Leitfaden hat die Leser durch die Grundlagen der französischen Sprache geführt, um sicherzustellen, dass die Geschichte für junge Leser zugänglich und verständlich ist.
Ein zentraler Punkt war die Einbindung von Abenteuern und Freundschaft als Kernelemente, um die Neugier und das Interesse der Kinder zu wecken. Wir haben verschiedene Erzähltechniken besprochen, die helfen, lebendige und einprägsame Charaktere zu schaffen, sowie die Bedeutung eines klaren und strukturierten Handlungsverlaufs.
Zum Abschluss empfehlen wir, die erstellte Geschichte mit Kindern zu teilen und Feedback einzuholen, um die Erzählweise weiter zu verfeinern. Es ist auch ratsam, regelmäßig neue Geschichten zu schreiben, um die eigenen Fähigkeiten im Geschichtenerzählen zu verbessern und die Freude am kreativen Schreiben zu fördern.
Dieser Leitfaden soll als Ausgangspunkt dienen, um die Kunst des Geschichtenerzählens in der französischen Sprache zu meistern und die Fantasie der jungen Leser zu beflügeln. Wir hoffen, dass Sie inspiriert wurden, Ihre eigene einzigartige Geschichte zu kreieren, die Abenteuer und Freundschaft in den Mittelpunkt stellt und die Herzen der Kinder berührt.

View file

@ -1,166 +0,0 @@
Animal Adventure Story for Children
===================================
Titre : Animal Adventure Story for Children
Introduction :
Bienvenue dans le monde enchanteur de "Animal Adventure Story for Children", une histoire captivante conçue spécialement pour les jeunes esprits curieux. Ce récit est une invitation à plonger dans un univers où l'amitié, l'aventure et le travail d'équipe prennent vie à travers les yeux de nos amis les animaux.
Dans cette histoire, les enfants découvriront comment un groupe d'animaux, chacun avec ses propres talents et particularités, se réunit pour vivre des aventures extraordinaires. Ensemble, ils apprendront l'importance de l'entraide et de la collaboration pour surmonter les défis qui se dressent sur leur chemin.
Au fil des pages, les jeunes lecteurs seront transportés dans des paysages merveilleux, peuplés de créatures fascinantes et de mystères à résoudre. Chaque chapitre est une nouvelle étape de leur voyage, remplie de découvertes et de leçons précieuses sur l'amitié et le courage.
Préparez-vous à embarquer dans une aventure palpitante où l'imagination n'a pas de limites. Que vous soyez confortablement installé à la maison ou en quête d'une histoire du soir, "Animal Adventure Story for Children" est le compagnon idéal pour éveiller la curiosité et l'amour de la lecture chez les enfants.
Nous espérons que cette histoire apportera joie et inspiration à tous ceux qui la liront. Bon voyage dans le monde merveilleux des animaux et de l'aventure !
Introduction
------------
# Introduction
Bienvenue dans l'univers magique de notre histoire, "Animal Adventure Story for Children". Préparez-vous à plonger dans un monde où l'amitié et l'aventure se rencontrent au cœur d'une forêt luxuriante et mystérieuse. Dans cette introduction, nous allons vous présenter nos personnages principaux et vous inviter à découvrir le décor enchanteur où se déroulera leur incroyable périple.
## Les Personnages Principaux
### Léo le Lion
Léo est un jeune lion courageux et curieux. Avec sa crinière dorée et ses yeux pétillants, il est toujours prêt à explorer de nouveaux horizons. Bien qu'il soit le roi de la jungle, Léo est humble et aime partager ses aventures avec ses amis.
### Zoé la Zèbre
Zoé est une zèbre malicieuse et pleine de vie. Ses rayures noires et blanches la rendent unique, tout comme sa personnalité pétillante. Zoé adore courir à travers la savane et est toujours à la recherche de nouvelles expériences.
### Max le Singe
Max est un singe espiègle et intelligent. Avec son agilité incroyable, il se balance d'arbre en arbre, toujours prêt à aider ses amis en cas de besoin. Max est connu pour ses blagues amusantes et son grand cœur.
### Ella l'Éléphante
Ella est une éléphante douce et sage. Elle est la plus grande du groupe et utilise sa force pour protéger ses amis. Ella a une mémoire impressionnante et se souvient de chaque chemin de la forêt, ce qui est très utile lors de leurs aventures.
## Le Décor : Une Forêt Enchantée
Notre histoire se déroule dans une forêt enchantée, un lieu où la nature règne en maître. Les arbres y sont immenses, leurs branches formant un toit verdoyant qui abrite une multitude de créatures. Les rivières serpentent à travers la végétation luxuriante, offrant de l'eau fraîche et des lieux de jeu pour nos amis animaux.
Le chant des oiseaux accompagne chaque lever de soleil, tandis que le parfum des fleurs embaume l'air. C'est un endroit où chaque jour apporte son lot de surprises et où l'aventure attend à chaque coin de sentier.
Dans cette forêt, nos jeunes héros vont découvrir l'importance de l'amitié, de la coopération et du courage. Ensemble, ils vivront des aventures inoubliables qui les rapprocheront et leur apprendront à surmonter les défis de la vie sauvage.
Préparez-vous à suivre Léo, Zoé, Max et Ella dans leur quête palpitante à travers la forêt enchantée. Que l'aventure commence !
The Adventure Begins
--------------------
# L'Aventure Commence
## Rencontre des Animaux et Naissance d'une Amitié
Dans une forêt enchantée, où le soleil joue à cache-cache avec les feuilles des arbres, vivait un groupe d'animaux qui ne s'étaient jamais rencontrés. Un matin, alors que la rosée scintillait encore sur l'herbe, un petit lapin nommé Léo bondissait joyeusement à travers la clairière. Léo était curieux et aimait explorer les moindres recoins de la forêt.
Non loin de là, une tortue appelée Tilly avançait lentement mais sûrement, admirant les fleurs colorées qui bordaient son chemin. Tilly, bien que lente, avait un cœur immense et une sagesse que peu d'animaux possédaient.
Pendant ce temps, un écureuil espiègle nommé Max grimpait aux arbres avec agilité, cherchant des noisettes à cacher pour l'hiver. Max était toujours prêt pour une nouvelle aventure et avait un rire contagieux qui résonnait à travers la forêt.
Le destin voulut que ces trois animaux se rencontrent près d'un grand chêne majestueux. Léo, avec son enthousiasme débordant, salua Tilly et Max avec un grand sourire. "Bonjour ! Je suis Léo. Voulez-vous jouer avec moi ?"
Tilly, avec un regard bienveillant, répondit : "Bonjour Léo, je suis Tilly. Je serais ravie de faire ta connaissance et de jouer avec vous."
Max, sautillant d'une branche à l'autre, ajouta : "Et moi, je suis Max ! J'adore les jeux et les aventures. Allons explorer ensemble !"
Ainsi, une belle amitié naquit entre Léo, Tilly et Max. Ils décidèrent de se retrouver chaque jour pour partager des moments de joie et de découverte.
## Introduction de l'Aventure ou de la Quête
Un jour, alors qu'ils jouaient près de la rivière, les trois amis entendirent une rumeur mystérieuse. Un vieux hibou sage, perché sur une branche, leur raconta une légende fascinante. "Il y a, au cœur de la forêt, un trésor caché qui ne peut être découvert que par ceux qui ont un cœur pur et un esprit d'aventure," dit le hibou avec une voix douce et rassurante.
Les yeux de Léo s'illuminèrent de curiosité. "Un trésor ? Cela semble incroyable !"
Tilly, réfléchissant prudemment, proposa : "Pourquoi ne pas partir à la recherche de ce trésor ensemble ? Cela pourrait être une aventure merveilleuse."
Max, toujours prêt pour une nouvelle quête, s'exclama : "Oui, partons à l'aventure ! Nous découvrirons des secrets et vivrons des moments inoubliables."
Et c'est ainsi que l'aventure commença. Armés de courage et d'amitié, Léo, Tilly et Max se lancèrent dans une quête qui allait les mener à travers des paysages enchanteurs et des défis inattendus. Ensemble, ils allaient découvrir que le véritable trésor n'était pas seulement ce qu'ils cherchaient, mais aussi les liens qu'ils tissaient en chemin.
Challenges and Teamwork
-----------------------
# Challenges and Teamwork
Dans notre histoire "Animal Adventure Story for Children", les animaux de la forêt se lancent dans une aventure palpitante remplie de défis et de découvertes. Cette section explore les obstacles qu'ils rencontrent et comment, grâce à l'amitié et au travail d'équipe, ils parviennent à les surmonter.
## Les Défis des Animaux
Les animaux de notre histoire, comprenant un lapin curieux, un écureuil malin, et une tortue sage, se retrouvent confrontés à plusieurs défis au cours de leur aventure. Parmi ces défis, ils doivent traverser une rivière tumultueuse, trouver leur chemin dans une forêt dense, et surmonter la peur de l'inconnu lorsqu'ils rencontrent des créatures qu'ils n'ont jamais vues auparavant.
### Exemple de Défi : La Rivière Tumultueuse
Un des premiers obstacles auxquels nos amis font face est une rivière large et rapide. Le lapin, bien que rapide et agile, ne sait pas nager. L'écureuil, quant à lui, est habile pour grimper mais hésite à se mouiller. La tortue, avec sa carapace lourde, ne peut pas traverser seule. Ce défi met en lumière la nécessité de travailler ensemble pour trouver une solution.
## L'Importance du Travail d'Équipe
Face à ces défis, nos héros découvrent que l'union fait la force. Chaque animal apporte ses compétences uniques pour aider le groupe à avancer. Le travail d'équipe devient essentiel pour surmonter les obstacles et atteindre leur destination.
### Exemple de Travail d'Équipe : Construire un Pont
Pour traverser la rivière, les animaux décident de construire un pont. Le lapin utilise sa rapidité pour rassembler des branches, l'écureuil utilise son agilité pour les assembler, et la tortue, avec sa force tranquille, stabilise la structure. Ensemble, ils réussissent à créer un passage sûr pour tous.
## L'Amitié comme Force Motrice
Tout au long de leur aventure, l'amitié entre le lapin, l'écureuil, et la tortue se renforce. Ils apprennent à se faire confiance et à se soutenir mutuellement, découvrant que l'amitié est une force puissante qui les aide à surmonter les moments difficiles.
### Exemple d'Amitié : Encouragements Mutuels
Lorsqu'un des animaux se sent découragé, les autres sont là pour l'encourager. Par exemple, lorsque la tortue se sent trop lente et craint de retarder le groupe, le lapin et l'écureuil lui rappellent combien sa sagesse et sa persévérance sont précieuses pour leur succès collectif.
En conclusion, "Animal Adventure Story for Children" illustre comment les défis peuvent être surmontés grâce à la coopération et à l'amitié. Les jeunes lecteurs apprendront que, même face à des obstacles apparemment insurmontables, le travail d'équipe et le soutien mutuel peuvent mener à des solutions créatives et à des aventures inoubliables.
Resolution
----------
Title: Résolution
Dans cette section de notre histoire "Aventure Animale pour Enfants", nous découvrons comment nos amis animaux surmontent les défis qu'ils ont rencontrés et célèbrent leur amitié. Cette partie de l'histoire est cruciale car elle montre aux jeunes lecteurs l'importance de la persévérance et de la camaraderie.
**Surmonter les Défis**
Après avoir traversé de nombreuses aventures, nos amis animaux se retrouvent face à un dernier obstacle : un grand ravin qui semble infranchissable. Chacun des animaux utilise ses talents uniques pour trouver une solution. Le singe agile propose de tisser une corde avec des lianes, tandis que l'éléphant fort et sage suggère de construire un pont avec des branches solides. Ensemble, avec l'aide du castor ingénieux qui sait comment assembler les matériaux, ils parviennent à créer un passage sûr.
Les animaux travaillent en équipe, démontrant que même les défis les plus difficiles peuvent être surmontés grâce à la coopération et à l'ingéniosité. Les enfants apprennent ainsi que chaque individu a quelque chose de précieux à apporter, et que l'union fait la force.
**Célébration de l'Amitié**
Une fois le ravin traversé, les animaux organisent une grande fête pour célébrer leur succès et leur amitié. Ils décorent la clairière avec des fleurs et des feuilles colorées, et le rossignol chante une mélodie joyeuse qui résonne à travers la forêt. Le lapin prépare une délicieuse salade de carottes et le hérisson apporte des baies sucrées pour tout le monde.
Les animaux dansent et rient ensemble, savourant le bonheur d'avoir partagé cette aventure. Ils se rendent compte que les souvenirs qu'ils ont créés ensemble sont plus précieux que n'importe quel trésor. Cette célébration montre aux jeunes lecteurs que l'amitié est une source de joie et de réconfort, et qu'elle est renforcée par les expériences partagées.
En conclusion, la résolution de notre histoire "Aventure Animale pour Enfants" enseigne aux enfants que les défis peuvent être surmontés grâce à la collaboration et que l'amitié est une richesse inestimable. Les animaux, en surmontant leurs obstacles et en célébrant ensemble, illustrent des valeurs essentielles telles que l'entraide, la créativité et la joie de vivre ensemble.
Conclusion
----------
# Conclusion
## Résumé de l'Aventure
Dans cette histoire palpitante, nos jeunes lecteurs ont suivi les aventures de Léo le lion, Zoé la zèbre, et Max le singe à travers la jungle luxuriante. Ensemble, ils ont bravé des rivières tumultueuses, découvert des clairières secrètes et rencontré des animaux fascinants. Chaque étape de leur voyage a été une occasion d'apprendre et de grandir. Léo a découvert le courage en affrontant ses peurs, Zoé a montré sa sagesse en guidant le groupe avec ses connaissances, et Max a apporté de la joie avec ses blagues et ses acrobaties. Leur aventure a été remplie de défis, mais aussi de moments de joie et de découverte.
## L'Importance de l'Amitié
Tout au long de leur périple, l'amitié entre Léo, Zoé et Max a été le fil conducteur de leur succès. Ils ont appris que l'entraide et la confiance sont essentielles pour surmonter les obstacles. Par exemple, lorsque Léo a eu peur de traverser la rivière, Zoé et Max l'ont encouragé et l'ont aidé à trouver le courage nécessaire. De même, quand Zoé s'est perdue dans la forêt dense, Léo et Max ont travaillé ensemble pour la retrouver. Ces moments ont renforcé leur lien et ont montré que l'amitié est une force puissante qui peut surmonter toutes les difficultés.
## Message Final
En conclusion, "Animal Adventure Story for Children" n'est pas seulement une histoire d'aventure, mais aussi une célébration de l'amitié et de la coopération. Les jeunes lecteurs sont invités à réfléchir à l'importance de soutenir leurs amis et de travailler ensemble pour atteindre leurs objectifs. Que ce soit dans la jungle ou dans la cour de récréation, l'esprit d'équipe et l'amour entre amis rendent chaque aventure plus enrichissante et mémorable. Nous espérons que cette histoire inspirera les enfants à valoriser leurs amitiés et à vivre leurs propres aventures avec courage et joie.
CONCLUSION
----------
CONCLUSION
Dans l'histoire "Animal Adventure Story for Children", nous avons suivi les aventures palpitantes d'un groupe d'animaux courageux qui ont uni leurs forces pour surmonter divers défis. À travers leurs péripéties, les thèmes de l'amitié et du travail d'équipe ont été mis en avant, montrant aux jeunes lecteurs l'importance de la collaboration et de l'entraide.
Les personnages principaux, chacun avec ses propres talents et caractéristiques, ont démontré que même les différences peuvent être une force lorsqu'elles sont mises au service d'un objectif commun. Leur aventure a non seulement renforcé leurs liens d'amitié, mais a également permis de découvrir de nouveaux horizons et de vivre des expériences enrichissantes.
En conclusion, cette histoire encourage les enfants à valoriser l'amitié et à travailler ensemble pour atteindre leurs buts. Elle leur enseigne que l'aventure est plus belle lorsqu'elle est partagée et que chaque obstacle peut être surmonté grâce à la solidarité et à la confiance mutuelle.
Pour prolonger cette expérience, il est recommandé aux jeunes lecteurs de réfléchir à leurs propres amitiés et aux aventures qu'ils pourraient vivre ensemble. Ils peuvent également imaginer de nouvelles histoires avec les personnages de l'histoire, développant ainsi leur créativité et leur compréhension des valeurs essentielles de l'amitié et du travail d'équipe.
Ainsi, "Animal Adventure Story for Children" laisse une empreinte durable, rappelant aux enfants que l'amitié et l'aventure vont de pair, et que chaque jour peut être une nouvelle occasion de découvrir le monde qui les entoure.

View file

@ -1,163 +0,0 @@
Animal Story for Children in French
===================================
Titre : Histoire d'Animaux pour Enfants
Bienvenue dans notre histoire captivante, "Histoire d'Animaux pour Enfants", spécialement conçue pour émerveiller et inspirer les jeunes esprits. Ce récit enchanteur vous emmènera dans un monde où les animaux prennent vie, partageant des leçons précieuses sur l'amitié, la gentillesse et le travail d'équipe.
Dans cette histoire, vous rencontrerez une joyeuse bande d'animaux qui, malgré leurs différences, apprennent à travailler ensemble pour surmonter les défis et célébrer leurs victoires. Chaque personnage apporte sa propre personnalité et ses talents uniques, illustrant comment la diversité et la coopération peuvent mener à des résultats extraordinaires.
Au fil des pages, les jeunes lecteurs découvriront comment la gentillesse et l'entraide peuvent transformer des situations difficiles en opportunités de croissance et de bonheur. Ce conte est non seulement divertissant, mais il véhicule également des valeurs essentielles qui aideront les enfants à développer des relations positives et harmonieuses.
Préparez-vous à plonger dans une aventure pleine de rires, de découvertes et de moments touchants, où chaque animal a une leçon à partager. Nous espérons que cette histoire deviendra un favori des enfants, les encourageant à être de bons amis et à toujours choisir la gentillesse.
Bonne lecture et amusez-vous bien dans le monde merveilleux des animaux !
Introduction
------------
# Introduction
Bienvenue dans notre histoire magique, "L'Histoire des Animaux pour Enfants", où l'amitié et la gentillesse règnent en maître. Dans ce récit enchanteur, nous allons rencontrer des personnages animaux extraordinaires qui vivent dans un monde plein de couleurs et de rires. Préparez-vous à plonger dans une aventure où chaque coin de la forêt cache une nouvelle surprise et où chaque personnage a une leçon précieuse à partager.
## Les Personnages Principaux
### Léo le Lion
Léo est le roi de la jungle, mais il n'est pas comme les autres lions. Avec sa crinière dorée et son cœur d'or, Léo est connu pour sa sagesse et sa gentillesse. Il est toujours prêt à aider ses amis et à résoudre les problèmes avec calme et intelligence.
### Zoé la Zèbre
Zoé est une zèbre pleine de vie avec des rayures noires et blanches qui dansent au rythme de ses mouvements. Elle est curieuse et adore explorer les environs. Zoé a un sens de l'humour contagieux et sait toujours comment faire sourire ses amis.
### Max le Singe
Max est un singe espiègle et agile, toujours prêt pour une nouvelle aventure. Avec son rire communicatif et ses acrobaties impressionnantes, Max apporte de la joie et de l'énergie à tous ceux qui l'entourent. Il est le meilleur ami de Léo et Zoé, et ensemble, ils forment un trio inséparable.
## Un Cadre Amical
L'histoire se déroule dans une forêt luxuriante, où les arbres sont si hauts qu'ils semblent toucher le ciel. Les rayons du soleil filtrent à travers les feuilles, créant des motifs lumineux sur le sol. Les rivières chantent doucement en traversant les rochers, et les fleurs colorées parsèment le paysage, ajoutant une touche de magie à cet environnement déjà enchanteur.
Dans cette forêt, tous les animaux vivent en harmonie. Les oiseaux chantent des mélodies joyeuses, les papillons dansent dans l'air, et chaque jour est une nouvelle occasion de découvrir quelque chose de merveilleux. C'est un endroit où l'amitié et la bienveillance sont au cœur de chaque interaction, et où chaque animal a sa propre histoire à raconter.
Préparez-vous à suivre Léo, Zoé, et Max dans leurs aventures palpitantes, où ils apprendront l'importance de l'entraide, du respect et de l'amour. Ensemble, ils nous montreront que, peu importe les différences, l'amitié est le plus grand trésor de tous.
The Meeting
-----------
Title: La Rencontre
Dans une clairière ensoleillée de la forêt enchantée, un groupe d'animaux se rassemblait pour la première fois. C'était un jour spécial, car chacun d'eux venait de différents coins de la forêt, apportant avec eux leurs propres histoires et expériences.
### Les Premiers Arrivés
Le premier à arriver fut Léon le lionceau, avec sa crinière encore duveteuse et ses yeux pétillants de curiosité. Il était suivi de près par Zoé la zèbre, qui trottinait joyeusement, ses rayures noires et blanches brillant sous le soleil. Léon, bien que jeune, avait déjà appris l'importance de la gentillesse. Il salua Zoé avec un sourire chaleureux : "Bonjour Zoé, tes rayures sont magnifiques aujourd'hui !"
### Une Rencontre Amicale
Peu après, une petite tortue nommée Timéo fit son entrée, avançant lentement mais sûrement. Zoé et Léon l'accueillirent avec enthousiasme. "Bonjour Timéo ! Nous sommes heureux que tu sois là," dit Zoé en inclinant la tête pour mieux le voir. Timéo, bien que timide, se sentit immédiatement à l'aise grâce à l'accueil chaleureux de ses nouveaux amis.
### L'Arrivée de Nouveaux Amis
Ensuite, arriva une famille de lapins, sautillant gaiement. Le plus jeune, un lapin nommé Léo, s'approcha de Léon avec curiosité. "Bonjour, je suis Léo ! Est-ce que tu veux jouer avec nous ?" Léon, ravi de cette invitation, accepta avec joie.
### Un Acte de Gentillesse
Alors que les animaux continuaient à se rencontrer, un petit oiseau nommé Chloé se posa sur une branche proche. Elle avait remarqué que Timéo avait du mal à atteindre les feuilles fraîches sur les branches basses. Sans hésiter, elle vola jusqu'à lui et lui apporta quelques feuilles. "Merci beaucoup, Chloé," dit Timéo, touché par ce geste attentionné.
### Conclusion de la Rencontre
La clairière résonnait de rires et de conversations joyeuses. Chaque animal, bien qu'unique, avait trouvé sa place dans ce groupe amical. Ils avaient appris que la gentillesse et l'ouverture d'esprit pouvaient transformer une simple rencontre en une belle amitié. Et ainsi, la forêt enchantée devint un peu plus lumineuse ce jour-là, grâce à la chaleur de leurs cœurs unis.
Cette rencontre marqua le début d'une série d'aventures où chaque animal, avec ses qualités et ses différences, contribua à enrichir la vie de ses nouveaux amis.
The Adventure
-------------
# L'Aventure
## Introduction à l'Aventure
Dans un coin paisible de la forêt, un groupe d'animaux se réunit chaque jour pour jouer et partager des histoires. Un beau matin, alors que le soleil se lève doucement, nos amis décident de partir à l'aventure. Ils sont curieux de découvrir ce qui se cache au-delà de leur clairière habituelle. Cette aventure va leur apprendre l'importance de l'amitié et du travail d'équipe.
## Le Départ
Les animaux qui participent à cette aventure sont Léo le lionceau, Zoé la zèbre, Max le singe, et Lily la petite éléphante. Ensemble, ils forment une équipe soudée et pleine d'énergie. Avant de partir, ils se rassemblent pour discuter du chemin à prendre et des précautions à suivre. Zoé, toujours prévoyante, propose de suivre le ruisseau qui serpente à travers la forêt. Max, avec son enthousiasme débordant, est déjà prêt à bondir en avant.
## Les Défis de l'Aventure
### Le Pont Cassé
Après avoir marché pendant un moment, le groupe arrive devant un vieux pont de bois qui traverse le ruisseau. Malheureusement, le pont est partiellement effondré. Léo, avec sa bravoure naturelle, propose de trouver une solution ensemble. Lily, forte et ingénieuse, suggère d'utiliser des branches pour renforcer le pont. Grâce à leur collaboration, ils parviennent à traverser le ruisseau en toute sécurité.
### La Tempête Soudaine
Plus loin dans la forêt, une tempête soudaine éclate. Le vent souffle fort et la pluie commence à tomber. Les animaux se serrent les uns contre les autres pour se réconforter. Max, qui adore grimper, repère un grand arbre avec des branches épaisses qui peuvent les abriter. Ensemble, ils se hâtent de se mettre à l'abri sous cet arbre protecteur. En attendant que la tempête passe, ils chantent des chansons et partagent des histoires pour garder le moral.
## La Récompense de l'Aventure
Après la tempête, le ciel s'éclaircit et un magnifique arc-en-ciel apparaît. Les animaux sont émerveillés par ce spectacle de couleurs. Ils réalisent que, malgré les défis rencontrés, leur amitié et leur esprit d'équipe les ont aidés à surmonter toutes les difficultés. En rentrant chez eux, ils savent qu'ils ont vécu une aventure inoubliable qui a renforcé leurs liens.
## Conclusion
L'aventure a permis à Léo, Zoé, Max, et Lily de découvrir non seulement les merveilles de la forêt, mais aussi la force de leur amitié. Ils ont appris que, ensemble, ils peuvent surmonter n'importe quel obstacle. Cette journée restera gravée dans leur mémoire comme un précieux souvenir de courage, de solidarité et de joie partagée.
Resolution
----------
Titre : Résolution
Dans cette section, nous allons découvrir comment les animaux de notre histoire surmontent leur défi et renforcent leur amitié.
**Les Animaux Résolvent le Défi**
Après avoir discuté ensemble, les animaux réalisent que la clé pour résoudre leur problème est de travailler en équipe. Le défi qu'ils doivent surmonter est de traverser une rivière pour atteindre une prairie pleine de nourriture délicieuse. Chacun des animaux a une compétence unique qui peut aider le groupe.
- **L'éléphant**, avec sa force, propose de transporter les plus petits animaux sur son dos pour traverser la rivière.
- **Le singe**, agile et rapide, grimpe aux arbres pour trouver le meilleur chemin à suivre.
- **Le canard**, excellent nageur, guide le groupe à travers les eaux en toute sécurité.
- **La tortue**, avec sa sagesse, rappelle à tout le monde de rester calme et de travailler ensemble.
Grâce à leur collaboration, les animaux parviennent à traverser la rivière sans encombre. Ils réalisent que chacun d'eux a joué un rôle essentiel dans cette aventure.
**Renforcement de l'Amitié**
Après avoir surmonté ce défi ensemble, les animaux se sentent plus proches les uns des autres. Ils comprennent que leur diversité est une force et que l'entraide est la clé de leur succès.
- **Exemple de solidarité** : Lorsque le petit lapin a eu peur de l'eau, le canard l'a rassuré en lui montrant comment flotter en toute sécurité.
- **Moment de partage** : Une fois arrivés dans la prairie, tous les animaux partagent la nourriture qu'ils trouvent, renforçant ainsi leur lien d'amitié.
Cette expérience leur a appris que l'amitié est précieuse et que, même face aux difficultés, ils peuvent toujours compter les uns sur les autres. Les animaux promettent de toujours s'entraider et de se soutenir, peu importe les défis à venir.
En conclusion, cette aventure a non seulement résolu leur problème immédiat, mais elle a aussi cimenté une amitié durable entre eux, remplie de confiance et de respect mutuel. Les animaux retournent chez eux, le cœur léger et heureux, prêts à vivre de nouvelles aventures ensemble.
Conclusion
----------
Titre : Conclusion
Dans cette conclusion de notre "Histoire d'animaux pour enfants", nous allons explorer la morale de l'histoire et renforcer les thèmes de l'amitié et de la gentillesse qui ont été tissés tout au long du récit.
### Morale de l'histoire
L'histoire nous enseigne que l'amitié et la gentillesse sont des valeurs essentielles qui enrichissent notre vie. À travers les aventures de nos amis animaux, nous avons appris que l'entraide et le respect mutuel peuvent surmonter les obstacles les plus difficiles. Par exemple, lorsque le petit lapin a aidé l'écureuil à retrouver ses noisettes perdues, il a montré que même les plus petits gestes de bonté peuvent avoir un grand impact. Cette morale nous rappelle que nous devrions toujours être prêts à tendre la main à ceux qui en ont besoin, car un acte de gentillesse peut illuminer la journée de quelqu'un.
### Renforcement des thèmes de l'amitié et de la gentillesse
Tout au long de l'histoire, les animaux ont démontré que l'amitié véritable repose sur la compréhension et le soutien. Lorsque le hibou sage a partagé ses connaissances avec les autres animaux, il a non seulement aidé ses amis, mais a aussi renforcé les liens qui les unissaient. De même, la gentillesse du renard, qui a partagé sa nourriture avec le hérisson affamé, a illustré comment la générosité peut créer des amitiés durables.
Ces exemples montrent que l'amitié et la gentillesse ne sont pas seulement des concepts abstraits, mais des actions concrètes qui enrichissent notre quotidien. En encourageant les enfants à adopter ces valeurs, nous les aidons à construire un monde plus harmonieux et solidaire.
En conclusion, notre histoire d'animaux pour enfants nous rappelle que chaque jour est une nouvelle opportunité de faire preuve de gentillesse et de cultiver des amitiés sincères. Que ce soit en partageant un sourire, en offrant de l'aide, ou simplement en étant présent pour un ami, nous pouvons tous contribuer à rendre notre monde un peu plus lumineux.
CONCLUSION
----------
CONCLUSION
Dans l'histoire "Animal Story for Children in French", nous avons suivi les aventures de plusieurs animaux qui ont appris l'importance de l'amitié, de la gentillesse et du travail d'équipe. À travers leurs péripéties, les jeunes lecteurs ont découvert comment ces valeurs essentielles peuvent transformer des situations difficiles en opportunités de croissance et de bonheur partagé.
Les personnages principaux, un lapin curieux, un écureuil généreux, et une tortue sage, ont montré que malgré leurs différences, ils pouvaient unir leurs forces pour surmonter les obstacles. Leur collaboration a non seulement renforcé leur amitié, mais a également illustré comment la bienveillance et l'entraide peuvent créer un environnement harmonieux et joyeux.
En conclusion, cette histoire encourage les enfants à valoriser et à pratiquer la gentillesse et le travail d'équipe dans leur vie quotidienne. Elle leur rappelle que chaque petit geste de bonté peut avoir un grand impact et que l'amitié est un trésor précieux à cultiver.
Pour prolonger l'expérience, il est recommandé aux jeunes lecteurs de réfléchir à des situations où ils peuvent appliquer ces leçons dans leur propre vie. Pourquoi ne pas organiser une activité de groupe où chacun peut contribuer à un projet commun, renforçant ainsi les liens d'amitié et de coopération?
En fin de compte, "Animal Story for Children in French" laisse une empreinte positive, soulignant l'importance des valeurs humaines fondamentales et leur rôle dans la construction d'un monde meilleur pour tous.

View file

@ -1,204 +0,0 @@
Frogs: A Tale of Friendship and Adventure
=========================================
Title: Frogs: A Tale of Friendship and Adventure
---
Welcome to "Frogs: A Tale of Friendship and Adventure," a delightful story crafted especially for young minds eager to explore the wonders of friendship and the thrill of adventure. This enchanting tale is designed for children in kindergarten, offering an engaging and educational journey into the world of frogs.
In this story, you'll meet a group of lively frogs who embark on an exciting adventure that teaches them the true meaning of friendship and teamwork. As you turn the pages, you'll dive into a vibrant pond filled with fun, laughter, and valuable lessons. Our froggy friends will show you how working together can overcome any obstacle and how the bonds of friendship can make any adventure unforgettable.
This story is written in a conversational tone, making it easy and enjoyable for young readers to follow along. Through the adventures of these charming frogs, children will learn important values such as cooperation, empathy, and the joy of exploring the world around them.
Join us on this whimsical journey, where every leap and splash brings a new discovery. "Frogs: A Tale of Friendship and Adventure" promises to be a captivating experience that will leave young readers with a smile and a heart full of wonder.
Let's hop into the story and see where the adventure takes us!
Introduction
------------
# Introduction
Welcome to the enchanting world of "Frogs: A Tale of Friendship and Adventure," where the lily pads are lush, the water is crystal clear, and the air is filled with the symphony of croaking frogs. This story invites young readers to dive into a vibrant pond bustling with life and excitement. Here, we meet our delightful frog friends who are about to embark on an unforgettable journey of friendship and discovery.
## Meet the Frog Friends
In the heart of this lively pond lives a group of charming frog friends, each with their own unique personality and talents. Let's get to know them:
- **Freddy the Fearless**: Freddy is a bright green frog with a heart as big as his leap. Known for his adventurous spirit, Freddy loves exploring new places and is always ready to lead his friends on exciting escapades. His bravery often inspires others to step out of their comfort zones.
- **Lily the Listener**: With her soft, soothing voice and gentle demeanor, Lily is the go-to frog for advice and comfort. Her beautiful blue skin makes her stand out, but it's her kind heart that truly shines. Lily is always there to lend an ear or offer a helping hand to her friends.
- **Benny the Brainy**: Benny is the smartest frog in the pond, with a knack for solving problems and coming up with clever ideas. His bright yellow spots are as distinctive as his quick thinking. Benny loves reading and learning about the world beyond the pond, often sharing fascinating facts with his friends.
- **Sally the Silly**: Sally is the life of the party, always ready with a joke or a funny story. Her vibrant orange hue matches her lively personality. Sally's laughter is infectious, and she knows how to turn any frown upside down, making her an essential part of the group.
## Setting the Scene
Our story unfolds in a picturesque pond nestled in the heart of a lush, green forest. The pond is a kaleidoscope of colors, with blooming water lilies and tall reeds swaying gently in the breeze. The sun casts a warm glow over the water, creating a shimmering surface that dances with the reflections of the surrounding trees.
The pond is not just a home to our frog friends but also a bustling community filled with dragonflies, fish, and other creatures. It's a place where every day brings new adventures and opportunities to learn and grow. The frogs spend their days hopping from lily pad to lily pad, playing games, and exploring the wonders of their watery world.
As the story begins, the frogs are unaware of the thrilling adventure that awaits them. Together, they will discover the true meaning of friendship, courage, and the joy of exploring the unknown. So, let's leap into this delightful tale and join Freddy, Lily, Benny, and Sally on their journey through the pond and beyond!
The Adventure Begins
--------------------
Title: The Adventure Begins
---
Once upon a time, in a lush, green pond nestled in the heart of a vibrant forest, there lived a group of lively frogs. These frogs were not just any frogs; they were the best of friends, always hopping around together, playing games, and sharing stories. Their pond was a magical place, filled with lily pads, buzzing dragonflies, and the gentle croaking of their fellow amphibians. But as much as they loved their home, the frogs often wondered what lay beyond the familiar waters of their pond.
### The Call to Adventure
One sunny morning, as the frogs gathered on their favorite lily pad, a gentle breeze carried with it the scent of something new and exciting. It was a smell unlike anything they had ever experienced before. Curious and eager, the frogs began to chatter among themselves.
"Do you smell that?" asked Freddie, the most adventurous of the group, his eyes wide with excitement.
"Yes!" replied Lily, the thoughtful one, her mind already racing with possibilities. "I wonder where it comes from."
Toby, the smallest but the most enthusiastic, jumped up and down, splashing water everywhere. "Let's find out! Let's go on an adventure!"
### The Decision to Explore
The idea of an adventure was thrilling and a little bit scary. The frogs had heard tales from older frogs about the world beyond the pond—stories of towering trees, colorful flowers, and mysterious creatures. But they had never ventured far from their watery home.
Freddie, with a determined look, said, "We can be brave and explore together. Who knows what wonders we might find?"
Lily nodded, her curiosity piqued. "And we can always come back if it gets too scary."
Toby, unable to contain his excitement, shouted, "Adventure awaits! Let's go!"
### Preparing for the Journey
Before setting off, the frogs gathered their courage and made a plan. They decided to stick together, no matter what. They would hop along the path that led through the forest, keeping an eye out for anything interesting or unusual. Each frog had a role: Freddie would lead the way, Lily would remember the path back home, and Toby would keep everyone entertained with his cheerful songs.
As they prepared to leave, the other frogs in the pond gathered to see them off. "Be safe!" croaked an elder frog. "And remember, the world is full of surprises."
With a final wave to their friends, Freddie, Lily, and Toby took a deep breath and hopped away from the pond, their hearts full of excitement and a little bit of nervousness.
### The First Steps
The forest was a wonderland of sights and sounds. The frogs marveled at the towering trees that seemed to touch the sky and the colorful flowers that dotted the forest floor. They listened to the chirping of birds and the rustling of leaves in the gentle breeze. Every step was a new discovery, and the frogs couldn't help but feel a sense of wonder and joy.
As they ventured further, they realized that the world was much bigger than they had ever imagined. But with each other by their side, they felt ready to face whatever lay ahead.
And so, with hearts full of courage and friendship, the frogs' adventure truly began. They were ready to explore, to learn, and to grow together, knowing that the journey would be as important as the destination.
---
With this, the frogs took their first steps into a world of endless possibilities, where every hop could lead to a new adventure and every moment was a chance to strengthen their bond of friendship.
Challenges and Friendship
-------------------------
Title: Challenges and Friendship
---
In the heart of the lush, green forest, where the sun peeked through the leaves and the air was filled with the sweet sound of chirping birds, lived a group of lively frogs. These frogs were not just any ordinary frogs; they were the best of friends, always ready for an adventure. But like any great story, their tale was not without its challenges.
### The Great Pond Dilemma
One sunny morning, the frogs gathered at their favorite spot by the pond. As they splashed and played, they noticed something unusual. The water level in the pond was dropping! This pond was their home, their playground, and the source of their food. Without it, they would be in big trouble.
The frogs knew they had to act quickly. But what could they do? They were just small frogs in a big forest. This was their first big challenge, and it seemed daunting.
### The Power of Teamwork
The frogs decided to hold a meeting. Freddy, the wisest of the frogs, spoke first. "We need to work together to solve this problem," he said. "If we each use our strengths, we can find a way to save our pond."
Lily, the fastest swimmer, suggested they explore the forest to find out what was causing the water to disappear. Benny, the strongest frog, offered to move any obstacles they might find. And Tiny, the smallest but most clever frog, proposed they gather information from other animals in the forest.
### Friendship Saves the Day
As they ventured into the forest, the frogs encountered many obstacles. They had to leap over fallen branches, navigate through thick bushes, and even cross a small stream. But with each challenge, they relied on each other. When Lily got tired, Benny carried her on his back. When Benny couldn't fit through a narrow path, Tiny guided him from above.
Finally, they discovered the source of the problem: a family of beavers had built a dam upstream, blocking the water flow to their pond. The frogs knew they had to talk to the beavers and find a solution that worked for everyone.
### A Lesson in Friendship
The frogs approached the beavers with kindness and explained their predicament. The beavers, understanding the importance of the pond to the frogs, agreed to adjust their dam to allow more water to flow through.
Through their adventure, the frogs learned that challenges are easier to overcome when you have friends by your side. They realized that each of them had something unique to offer, and by working together, they could achieve anything.
As the water returned to the pond, the frogs celebrated their success. They had faced a great challenge, but their friendship and teamwork had saved the day. And from that day on, they knew that no matter what obstacles came their way, they would always have each other.
---
This story of the frogs teaches us that friendship is not just about having fun together; it's about supporting each other through thick and thin. By working as a team, even the smallest creatures can overcome the biggest challenges.
The Return Home
---------------
Title: The Return Home
---
**Frogs Safely Return to Their Pond**
After a long and exciting journey filled with unexpected twists and turns, the frogs finally found themselves hopping along the familiar path that led back to their beloved pond. The sun was setting, casting a warm golden glow over the water, and the gentle croaking of their fellow frogs welcomed them home. As they approached the edge of the pond, they could see their lily pads swaying gently in the breeze, just as they had left them.
The frogs took a moment to pause and soak in the comforting sights and sounds of their home. They were tired but happy, knowing that they had safely returned from their grand adventure. The pond seemed to sparkle with joy at their arrival, and the frogs couldn't help but feel a sense of pride in what they had accomplished together.
**Reflecting on Their Adventure and Friendship**
Gathered on their favorite lily pad, the frogs began to reflect on the incredible journey they had just experienced. They remembered the challenges they faced, like crossing the wide river and climbing the steep hill, and how they had worked together to overcome each obstacle. It was their friendship and teamwork that had made the adventure not only possible but also unforgettable.
One frog, with a twinkle in his eye, said, "Remember when we helped each other leap over the big log? I was so scared, but you all cheered me on!" Another frog nodded and added, "And when we found that hidden garden with all the colorful flowers, it was like discovering a secret world!"
As they reminisced, the frogs realized that their adventure had taught them important lessons about courage, trust, and the strength of their friendship. They had learned that even when things seemed difficult, they could rely on each other to find a way through.
With the stars beginning to twinkle above them, the frogs knew that their adventure had come to an end, but their friendship had grown stronger than ever. They promised to always be there for one another, ready for whatever new adventures the future might bring.
And so, with hearts full of joy and memories to cherish, the frogs settled down for a peaceful night's rest, dreaming of the adventures yet to come and the unbreakable bond of their friendship.
Moral of the Story
------------------
Title: Moral of the Story
---
In the delightful tale "Frogs: A Tale of Friendship and Adventure," young readers are taken on a journey filled with excitement and valuable life lessons. This story not only entertains but also imparts important morals that are essential for children to understand and cherish. Below, we explore the key lessons learned from the story and encourage children to value the power of friendship.
### Lessons Learned
1. **The Importance of Teamwork:**
- Throughout their adventure, the frogs demonstrate how working together can overcome obstacles that seem insurmountable when faced alone. Whether it's crossing a tricky stream or finding their way through a dense forest, the frogs show that collaboration and pooling their strengths lead to success.
2. **Courage in the Face of Challenges:**
- The frogs encounter various challenges that test their bravery. The story teaches children that courage is not the absence of fear but the ability to face fears with determination. By supporting each other, the frogs find the courage to continue their journey, showing young readers that they too can be brave in difficult situations.
3. **Embracing Differences:**
- Each frog in the story has unique traits and skills, which they use to help the group. This highlights the importance of embracing and appreciating differences in others. Children learn that diversity can be a strength, and everyone has something valuable to contribute.
### Valuing Friendship
1. **Support and Encouragement:**
- The frogs' adventure is a testament to the power of friendship. They support and encourage each other, showing that true friends are there to lift you up when you need it most. This teaches children the value of being a supportive friend and the joy of having friends who care.
2. **Sharing Joys and Sorrows:**
- The story illustrates that friendship is about sharing both happy and challenging moments. By experiencing the highs and lows together, the frogs deepen their bond, teaching children that friendships grow stronger when you share your experiences with others.
3. **Building Trust:**
- Trust is a central theme in the frogs' journey. They rely on each other's judgment and instincts, which helps them navigate their adventure successfully. This encourages children to build trust with their friends, knowing that mutual trust is a cornerstone of lasting friendships.
In conclusion, "Frogs: A Tale of Friendship and Adventure" is more than just a story about frogs; it's a heartwarming reminder of the values that make friendships meaningful. By learning from the frogs' experiences, children are encouraged to cherish their friends, embrace teamwork, and face challenges with courage and trust.
CONCLUSION
----------
Conclusion of "Frogs: A Tale of Friendship and Adventure"
In the enchanting tale of "Frogs: A Tale of Friendship and Adventure," young readers are taken on a delightful journey through the vibrant world of frogs, where friendship and teamwork are at the heart of every adventure. The story introduces us to a group of lively frogs who embark on a series of exciting escapades, teaching valuable lessons about cooperation, trust, and the joy of exploring the unknown.
Throughout their journey, the frogs demonstrate the importance of working together to overcome challenges, highlighting how each frog's unique abilities contribute to the group's success. This narrative not only entertains but also educates children about the significance of friendship and the power of collaboration.
As the story concludes, the frogs return home, their bonds stronger than ever, having learned that true friendship is the greatest treasure of all. This heartwarming ending reinforces the key themes of the story, leaving young readers with a sense of fulfillment and a deeper appreciation for their own friendships.
For parents and educators, "Frogs: A Tale of Friendship and Adventure" serves as an excellent tool to spark discussions about teamwork and the value of helping one another. It encourages children to embrace new experiences and cherish the friendships they form along the way.
In summary, this charming tale not only captivates the imagination of young readers but also imparts timeless lessons that resonate beyond the pages of the book. As children close the story, they are left with a clear understanding of the importance of friendship and the adventures that await when we work together.

View file

@ -1,198 +0,0 @@
The Curious Frog: A Kindergarten Adventure
==========================================
Title: The Curious Frog: A Kindergarten Adventure
---
Welcome to "The Curious Frog: A Kindergarten Adventure," a delightful story crafted especially for young minds eager to explore the wonders of the world around them. This enchanting tale invites children into the vibrant world of Froggy, a little frog with a big heart and an even bigger curiosity.
**Purpose and Scope:**
This story is designed to captivate the imagination of kindergarten-aged children, encouraging them to embrace their natural curiosity and explore their surroundings. Through Froggy's adventures, young readers will learn valuable lessons about kindness, sharing, and the joy of discovery.
**Context and Background:**
Set in a lush, green pond filled with friendly creatures and hidden surprises, Froggy's world is a place where every day brings new opportunities for learning and fun. As Froggy hops from one adventure to the next, children will be introduced to the importance of being kind to others and the rewards of sharing and helping friends.
**What to Expect:**
In this story, readers will follow Froggy as he embarks on a series of adventures that teach him—and the readers—important life lessons. From meeting new friends to solving little problems, each chapter is filled with engaging scenarios that highlight the values of curiosity, exploration, and kindness.
**Setting the Tone:**
Written in a conversational and friendly tone, this story is perfect for reading aloud, either at home or in the classroom. The narrative is designed to be both entertaining and educational, ensuring that children are not only engaged but also inspired to think about the world around them.
Join Froggy on his journey and discover how a little curiosity can lead to big adventures and even bigger hearts. Enjoy the adventure!
Introduction
------------
# Introduction
## Meet Freddy the Frog
In the heart of a lush, vibrant pond, where the water glistens under the warm sun and the air is filled with the gentle hum of nature, lives a little frog named Freddy. Freddy is not just any frog; he is a curious frog with a boundless sense of adventure. His bright green skin blends perfectly with the lily pads, and his big, round eyes are always wide open, eager to discover the wonders of the world around him. Freddy's curiosity often leads him to explore every nook and cranny of his watery home, making each day an exciting new adventure.
## The Vibrant Pond
Freddy's home is a magical place, teeming with life and color. The pond is surrounded by tall, swaying reeds and dotted with colorful flowers that dance in the breeze. Dragonflies zip across the water's surface, their wings shimmering like tiny rainbows. Beneath the water, fish dart playfully among the plants, and turtles bask lazily on sun-warmed rocks. The pond is not just a home for Freddy; it's a bustling community where every creature has a role to play.
## A World of Curiosity and Learning
For Freddy, the pond is a never-ending source of questions and discoveries. Why do the dragonflies hover so close to the water? How do the fish move so quickly? What makes the flowers bloom in such brilliant colors? Each question is an invitation to explore, to learn, and to grow. Freddy's adventures are not just about satisfying his curiosity; they are about understanding the world and learning important values like kindness, sharing, and respect for others.
Join Freddy on his kindergarten adventure, where every leap and splash is a step towards new friendships and valuable lessons. Whether he's helping a friend in need or discovering the beauty of a simple lily pad, Freddy's journey is one of joy, wonder, and endless possibilities.
Freddy's Curiosity
------------------
Title: Freddy's Curiosity
---
Freddy's Curiosity
Freddy the Frog was not like the other frogs in the pond. While his friends were content to hop around the lily pads and catch flies, Freddy's mind was always buzzing with questions. He often found himself gazing beyond the pond's edge, wondering about the world that lay beyond. What mysteries did the tall grass hide? What stories did the wind carry from afar? Freddy's heart was filled with a sense of adventure and an insatiable curiosity that set him apart.
### The World Beyond the Pond
Freddy's curiosity about the world beyond the pond was like a gentle itch that he couldn't quite scratch. Every morning, as the sun peeked over the horizon, Freddy would sit on his favorite rock and dream about the adventures waiting for him. He imagined meeting creatures he had never seen before and discovering places that no frog had ever visited.
One day, Freddy decided it was time to explore. With a determined leap, he hopped to the edge of the pond, where the water met the land. The grass felt different under his webbed feet, and the air was filled with new scents. Freddy's heart raced with excitement and a little bit of fear, but he knew that his curiosity was leading him to something wonderful.
### Encounters with Other Pond Creatures
As Freddy ventured further from the pond, he encountered a variety of creatures who called the pond their home. His first encounter was with Benny the Beetle, who was busy rolling a ball of mud. Freddy watched in fascination as Benny skillfully maneuvered the ball across the ground.
"Hello, Benny!" Freddy croaked cheerfully. "What are you doing with that ball of mud?"
Benny paused and looked up at Freddy with a smile. "I'm building a home for my family," he explained. "This mud will keep us safe and warm."
Freddy was amazed by Benny's ingenuity and realized that there was so much to learn from the creatures around him. He continued his journey and soon met Sally the Snail, who was slowly making her way across a leaf.
"Why do you move so slowly, Sally?" Freddy asked, genuinely curious.
Sally chuckled softly. "I may be slow, but I get to enjoy every moment and see things others might miss," she replied.
Freddy pondered Sally's words and understood that everyone had their own pace and way of seeing the world. His encounters with Benny and Sally taught him valuable lessons about patience and appreciation.
### Conclusion
Freddy's curiosity led him on a journey of discovery, where he learned that the world beyond the pond was filled with wonders and wisdom. Each creature he met had something unique to teach him, and Freddy realized that his curiosity was a gift that opened doors to new friendships and adventures. As he made his way back to the pond, Freddy felt grateful for the lessons he had learned and excited for the many more adventures that awaited him.
Freddy's story reminds us all that curiosity is a powerful tool for learning and growing. By embracing the unknown and seeking to understand the world around us, we can discover the beauty and knowledge that lie just beyond our comfort zones.
The Exploration
---------------
# The Exploration
## Freddy Ventures Out of the Pond
Once upon a time, in a lively pond surrounded by tall grasses and colorful wildflowers, lived a curious little frog named Freddy. Freddy was not like the other frogs who were content with hopping from lily pad to lily pad. Freddy had a twinkle in his eye and a heart full of wonder. Every day, he would gaze beyond the pond's edge, dreaming of the world that lay beyond.
One sunny morning, Freddy decided it was time to explore. With a brave leap, he hopped out of the pond and onto the soft, dewy grass. The world outside was vast and filled with sounds and sights he had never experienced before. The gentle rustle of leaves, the chirping of distant birds, and the warm breeze tickling his skin made Freddy's heart race with excitement.
## Discovering New Places
Freddy's first stop was a meadow filled with vibrant flowers swaying in the wind. The air was sweet with their fragrance, and Freddy couldn't help but smile. As he hopped through the meadow, he noticed a group of butterflies fluttering gracefully above him. Their wings shimmered in the sunlight, creating a dance of colors that left Freddy in awe.
"Hello, butterflies!" Freddy called out, his voice filled with joy. The butterflies paused their dance and fluttered down to greet him. They shared stories of their travels, telling Freddy about the beautiful gardens and fields they had visited. Freddy listened intently, his curiosity growing with each tale.
## Meeting New Creatures
As Freddy continued his journey, he ventured into a shady forest where the trees stood tall and proud. The forest was alive with the sounds of chirping crickets and rustling leaves. It was here that Freddy met a wise old turtle named Tilly. Tilly was slowly making her way across the forest floor, her shell glistening in the dappled sunlight.
"Hello there, young frog," Tilly greeted warmly. "What brings you to this part of the forest?"
"I'm exploring!" Freddy replied eagerly. "I want to see all the wonderful places and meet new friends."
Tilly chuckled softly. "Well, you've come to the right place. This forest is home to many creatures, each with their own story to tell."
Freddy spent the afternoon with Tilly, learning about the forest and its inhabitants. He met a family of squirrels who shared their acorns with him, teaching Freddy the value of sharing and kindness. He also encountered a wise old owl who spoke of the importance of curiosity and learning.
## A Day of Discovery
As the sun began to set, painting the sky with hues of orange and pink, Freddy realized it was time to head back to the pond. His heart was full of new experiences and friendships. He had discovered that the world was much bigger and more beautiful than he had ever imagined.
Freddy hopped back to the pond, his mind buzzing with excitement. He couldn't wait to share his adventures with his fellow frogs. As he settled down on his favorite lily pad, he knew that this was just the beginning of many more explorations to come.
Freddy's adventure taught him that the world is full of wonders waiting to be discovered, and that kindness and curiosity can lead to the most amazing friendships. And so, with a heart full of joy and a mind eager for more, Freddy the curious frog drifted off to sleep, dreaming of his next big adventure.
Learning Values
---------------
Title: Learning Values
---
In "The Curious Frog: A Kindergarten Adventure," young readers are invited to join Freddy, a curious little frog, on a delightful journey filled with exploration and valuable life lessons. Through his adventures, Freddy learns important values that are essential for personal growth and social interaction. This section delves into the key lessons of kindness and sharing that Freddy encounters, providing a comprehensive understanding of these values in a way that resonates with young minds.
---
**1. The Lesson of Kindness: Wisdom from the Wise Old Turtle**
Freddy's adventure takes a meaningful turn when he meets a wise old turtle named Mr. Tuttle. This encounter becomes a pivotal moment in Freddy's journey, as Mr. Tuttle imparts a profound lesson on kindness.
- **Understanding Kindness**: Mr. Tuttle explains to Freddy that kindness is about being considerate and helpful to others, even in small ways. He shares stories from his own life, illustrating how simple acts of kindness can make a big difference in the world.
- **An Act of Kindness**: Freddy witnesses Mr. Tuttle helping a struggling butterfly find its way back to a flower. This act of kindness inspires Freddy, showing him that kindness can be as simple as lending a helping hand or offering a comforting word.
- **Freddy's Transformation**: Inspired by Mr. Tuttle, Freddy begins to practice kindness in his own life. He helps a fellow frog find a lost toy and shares his lily pad with a tired dragonfly. These actions not only bring joy to others but also fill Freddy with a sense of happiness and fulfillment.
---
**2. The Joy of Sharing: A Lesson from the Ducklings**
As Freddy continues his adventure, he encounters a lively group of ducklings who teach him the joy of sharing. This experience is both fun and enlightening for Freddy, as he learns that sharing is a way to build friendships and create happiness.
- **The Ducklings' Game**: Freddy joins the ducklings in a game of "Pass the Pebble," where each duckling takes turns passing a shiny pebble around. Through this game, Freddy learns that sharing can be fun and that it helps everyone feel included and valued.
- **Sharing in Action**: Freddy decides to share his favorite snack, a juicy fly, with the ducklings. This gesture not only strengthens his bond with the ducklings but also shows him that sharing can lead to new friendships and shared joy.
- **A New Perspective**: By the end of the day, Freddy realizes that sharing is not just about giving away what you have, but about creating connections and spreading happiness. He discovers that the more he shares, the more he receives in return, in the form of smiles, laughter, and friendship.
---
In conclusion, "The Curious Frog: A Kindergarten Adventure" beautifully illustrates the values of kindness and sharing through Freddy's experiences. These lessons are woven into the narrative in a way that is engaging and relatable for young readers, encouraging them to incorporate these values into their own lives. Freddy's journey is a testament to the power of curiosity, exploration, and the lifelong impact of learning important values.
Conclusion
----------
Title: Conclusion
---
**Freddy Returns to the Pond with New Knowledge**
After his adventurous day exploring the vibrant world beyond the pond, Freddy the Frog hopped back to his familiar lily pad with a heart full of excitement and a mind buzzing with new ideas. His journey had taken him through the lush green meadows, bustling with life, and into the colorful gardens where he met new friends and learned about the wonders of the world. Freddy had discovered the joy of asking questions and the thrill of finding answers, realizing that every new experience was a chance to learn something valuable.
As he settled back into the gentle sway of the pond, Freddy thought about all the incredible things he had seen and the lessons he had learned. He understood now that the world was much bigger than he had ever imagined, and there was so much more to explore. Freddy's curiosity had led him to new places and new friends, showing him that being curious was a wonderful way to discover the beauty and magic of the world around him.
---
**Reflecting on the Importance of Curiosity and Kindness**
Freddy's adventure also taught him the importance of kindness. Throughout his journey, he met many creatures who were willing to share their knowledge and experiences with him. From the wise old turtle who showed him the way through the meadow to the friendly butterflies who shared their nectar, Freddy realized that kindness was a powerful force that made the world a better place.
He remembered how he had helped a little bird find its way back to its nest and how good it felt to be kind and helpful. Freddy understood that just as curiosity opened doors to new adventures, kindness opened hearts and built friendships. He promised himself that he would always be kind and helpful to others, just as others had been to him.
As the sun set over the pond, painting the sky with hues of orange and pink, Freddy felt grateful for the day he had. He knew that his adventure was just the beginning and that there were many more discoveries waiting for him. With a heart full of gratitude and a mind eager to learn, Freddy the Frog drifted off to sleep, dreaming of the adventures that tomorrow would bring.
---
In conclusion, Freddy's kindergarten adventure was a delightful tale of curiosity and kindness, teaching us all that the world is full of wonders waiting to be explored. By embracing curiosity and practicing kindness, we can make every day an exciting journey filled with learning and friendship. Freddy's story reminds us that no matter how small we may feel, our actions can make a big difference in the world.
CONCLUSION
----------
The Curious Frog: A Kindergarten Adventure - Conclusion
In "The Curious Frog: A Kindergarten Adventure," we embarked on a delightful journey with Freddy, the inquisitive frog, who taught us the importance of curiosity and exploration. Throughout the story, Freddy's adventures in the vibrant world of the pond introduced young readers to the wonders of nature and the joy of discovering new things. His encounters with various pond creatures highlighted the value of kindness and the beauty of sharing, reinforcing essential life lessons for children.
As Freddy explored, he learned that asking questions and seeking answers is a wonderful way to understand the world around us. His interactions with friends like Lily the dragonfly and Benny the turtle demonstrated how sharing knowledge and experiences can lead to stronger friendships and a more harmonious community.
The story concludes with Freddy realizing that his curiosity not only helped him learn but also brought joy and understanding to those around him. This adventure encourages young readers to embrace their own curiosity, to explore their surroundings, and to practice kindness and sharing in their daily lives.
In summary, "The Curious Frog: A Kindergarten Adventure" is a charming tale that combines the excitement of exploration with the warmth of friendship. It leaves readers with a clear understanding of the significance of curiosity and the positive impact of kindness and sharing. As a next step, children are encouraged to continue exploring their world, asking questions, and sharing their discoveries with others, fostering a lifelong love of learning and compassion.

View file

@ -1,227 +0,0 @@
Freddy the Frog and Friends: A Tale of Friendship and Kindness
==============================================================
Title: Freddy the Frog and Friends: A Tale of Friendship and Kindness
---
Welcome to the enchanting world of "Freddy the Frog and Friends: A Tale of Friendship and Kindness." This delightful story is crafted especially for young readers and listeners, inviting them into a vibrant pond where friendship and kindness blossom.
**Purpose and Scope:**
This story aims to introduce children to the values of friendship, kindness, collaboration, and problem-solving. Through the adventures of Freddy the Frog and his friends, young minds will explore how working together and showing kindness can overcome any challenge.
**Context and Background:**
Set in a lively pond filled with diverse creatures, our story follows Freddy the Frog, a cheerful and curious amphibian, and his best friend, Timmy the Turtle. Together, they embark on a journey that highlights the importance of helping others and the joy found in making new friends. Along the way, they meet various characters, each bringing their own unique qualities to the group.
**What to Expect:**
Readers will dive into a series of engaging adventures where Freddy and his friends face challenges that require teamwork and empathy. Each chapter unfolds a new lesson in kindness and cooperation, encouraging children to reflect on their own friendships and how they can be a positive force in their communities.
**Tone and Audience:**
Written in a conversational and warm tone, this story is perfect for kindergarten-aged children. It is designed to be read aloud, making it an ideal choice for storytime at home or in the classroom. The narrative is simple yet captivating, ensuring that young readers remain engaged while absorbing valuable life lessons.
Join Freddy and his friends on their heartwarming journey, and discover how a little kindness can make a big difference in the world around us.
Introduction
------------
# Introduction
Welcome to the enchanting world of "Freddy the Frog and Friends: A Tale of Friendship and Kindness." In this delightful story, we embark on a journey to a serene and vibrant pond, where the air is filled with the gentle hum of nature and the promise of adventure. Here, we meet Freddy the Frog, a charming and curious little amphibian whose heart is as big as his leap.
## Meet Freddy the Frog
Freddy is not your ordinary frog. With his bright green skin that glistens under the sun and his eyes that twinkle with curiosity, Freddy is always eager to explore the world around him. But what truly sets Freddy apart is his kind heart and his unwavering belief in the power of friendship. Freddy loves to hop around the pond, meeting new friends and helping those in need. Whether it's lending a hand to a struggling tadpole or sharing his lily pad with a tired dragonfly, Freddy's acts of kindness never go unnoticed.
## The Peaceful Pond
Our story unfolds in a picturesque setting—a peaceful pond nestled in the heart of a lush, green meadow. The pond is a haven of tranquility, where the water is as clear as a crystal mirror, reflecting the azure sky above. Tall reeds sway gently in the breeze, and colorful wildflowers bloom along the water's edge, painting the landscape with vibrant hues. The pond is home to a diverse community of creatures, each with their own unique stories and personalities. From the playful fish that dart beneath the surface to the wise old turtle who basks in the sun, the pond is a place where harmony and friendship flourish.
As we dive into the adventures of Freddy the Frog and his friends, we will discover how simple acts of kindness can create ripples of joy and bring everyone closer together. So, gather around and prepare to be enchanted by a tale that celebrates the beauty of friendship and the magic of kindness.
Meeting Timmy the Turtle
------------------------
Title: Meeting Timmy the Turtle
---
**Introduction to Timmy**
Freddy the Frog was hopping along the edge of the pond, his favorite place to explore. The sun was shining brightly, and the gentle breeze carried the sweet scent of blooming flowers. Freddy loved these peaceful moments, but today was special. Today, he was about to meet someone new.
As Freddy leaped from one lily pad to another, he noticed something unusual near the water's edge. A small, round shape was slowly making its way through the grass. Freddy's curiosity got the better of him, and he decided to investigate.
**The First Encounter**
Freddy approached cautiously, his big eyes widening with interest. "Hello there!" he croaked cheerfully. The little shape stopped moving, and Freddy realized it was a turtle. The turtle slowly lifted its head, revealing a friendly face with bright, curious eyes.
"Hello," the turtle replied in a soft, gentle voice. "My name is Timmy."
Freddy was thrilled. He had never met a turtle before! "I'm Freddy," he said, his voice bubbling with excitement. "It's nice to meet you, Timmy. What brings you to our pond?"
**Curiosity and Conversation**
Timmy smiled, a slow and steady smile that matched his pace. "I was just exploring," he explained. "I like to visit new places and meet new friends."
Freddy nodded eagerly. "I love exploring too! There are so many wonderful things to see around here. Have you ever seen the big oak tree on the other side of the pond? It's home to a family of squirrels!"
Timmy's eyes lit up with interest. "No, I haven't seen it yet. It sounds amazing!"
Freddy's heart swelled with happiness. He loved sharing his favorite spots with new friends. "I can show you around if you'd like," he offered.
Timmy nodded, his shell gleaming in the sunlight. "I would love that, Freddy. Thank you."
**A New Friendship Begins**
As they started their journey around the pond, Freddy and Timmy talked about everything they saw. Freddy pointed out the colorful dragonflies that danced above the water, and Timmy shared stories about his travels and the different creatures he had met.
Freddy was fascinated by Timmy's tales, and he realized that even though they were different, they had a lot in common. Both loved adventure, and both cherished the beauty of nature.
As the sun began to set, painting the sky with hues of orange and pink, Freddy and Timmy knew they had found a special friendship. They promised to meet again soon, eager to continue their adventures together.
**Conclusion**
Meeting Timmy the Turtle was just the beginning of a wonderful friendship for Freddy the Frog. Through their initial interaction, Freddy learned that curiosity and kindness could lead to new friendships and exciting adventures. And so, with a happy heart, Freddy hopped back to his lily pad, looking forward to the many adventures that awaited him and his new friend, Timmy.
The Challenge
-------------
Title: The Challenge
---
In the heart of the lush, green pond, where lily pads floated gently and dragonflies danced in the sunlight, a sudden problem arose that threatened the harmony of the pond's vibrant community. This was no ordinary day for Freddy the Frog and his best friend, Timmy the Turtle. It was a day that would test their friendship and kindness in ways they had never imagined.
**The Problem in the Pond**
It all began one sunny morning when Freddy noticed something unusual. The water level in the pond was dropping rapidly, and the once-clear water had turned murky and uninviting. The fish were struggling to swim, and the plants that lined the pond's edge were wilting under the harsh sun. Freddy's heart sank as he realized that if the water continued to disappear, the pond would no longer be a safe haven for its inhabitants.
Freddy hopped over to Timmy, who was basking on a warm rock, and shared his concerns. "Timmy, have you noticed the water level? It's getting lower and lower. If we don't do something, our home might be in danger!"
Timmy, always the thoughtful one, nodded slowly. "You're right, Freddy. We need to figure out what's causing this and how we can help."
**Freddy and Timmy Decide to Help**
Determined to save their beloved pond, Freddy and Timmy decided to investigate. They gathered their friends, including Sally the Snail, Benny the Beetle, and Lily the Ladybug, to brainstorm ideas. Everyone agreed that teamwork was essential to solving the mystery of the vanishing water.
Freddy suggested, "Let's start by exploring the area around the pond. Maybe there's something blocking the stream that feeds our pond."
With a plan in place, the friends set off on their adventure. They carefully examined the stream and discovered a pile of fallen branches and leaves blocking the water's flow. Freddy and Timmy knew they had to clear the debris to restore the pond's water supply.
**Working Together**
Freddy and Timmy led their friends in a collaborative effort to remove the blockage. Timmy used his strong shell to push the heavier branches aside, while Freddy and the others worked together to clear the smaller debris. It was hard work, but with each piece they removed, the water began to flow more freely.
As the stream's gentle trickle turned into a steady flow, the pond's water level began to rise. The fish swam happily once more, and the plants perked up, their leaves glistening in the sunlight. Freddy and Timmy beamed with pride, knowing that their teamwork and determination had saved their home.
**A Lesson in Friendship and Kindness**
The challenge had brought the pond's community closer together, teaching them the value of friendship and kindness. Freddy and Timmy realized that by working together and helping one another, they could overcome any obstacle.
As the sun set over the pond, casting a golden glow on the water, Freddy and Timmy sat side by side, grateful for their friends and the lesson they had learned. They knew that no matter what challenges lay ahead, they would always have each other and the power of friendship to see them through.
---
This section of the story highlights the importance of teamwork, friendship, and kindness, showing young readers how working together can solve problems and strengthen bonds.
Working Together
----------------
Title: Working Together
---
In the enchanting world of Lily Pad Pond, where the sun dances on the water and the breeze whispers through the reeds, Freddy the Frog and Timmy the Turtle embark on a heartwarming journey of friendship and kindness. This section of our story, "Freddy the Frog and Friends: A Tale of Friendship and Kindness," explores how these two unlikely friends come together to make their world a better place.
---
**Freddy and Timmy Collaborate**
Freddy the Frog, with his vibrant green skin and boundless energy, was known for his leaps and bounds across the pond. Timmy the Turtle, on the other hand, moved at a more leisurely pace, his shell a sturdy home that he carried with him everywhere. Despite their differences, Freddy and Timmy discovered that they made a perfect team.
One sunny morning, as the dew glistened on the lily pads, Freddy noticed that the pond was cluttered with fallen leaves and twigs. "Oh dear, the pond looks so messy!" Freddy exclaimed, his big eyes filled with concern. Timmy, who was sunbathing on a nearby rock, nodded thoughtfully. "We should clean it up," he suggested, his voice as steady as his movements.
Together, they devised a plan. Freddy, with his quick hops, gathered the leaves and twigs into small piles. Timmy, using his strong legs, pushed the piles to the edge of the pond. As they worked side by side, they chatted and laughed, their friendship growing stronger with each task completed. By the end of the day, the pond sparkled under the setting sun, a testament to their teamwork.
---
**Showcase Acts of Kindness**
Freddy and Timmy's collaboration didn't stop at cleaning the pond. They were always on the lookout for ways to help their fellow pond dwellers. One day, they noticed a little duckling struggling to find its way back to its mother. Freddy, with his keen eyes, spotted the mother duck on the other side of the pond. "Let's help the duckling," he said, his voice filled with determination.
Timmy nodded, and together they guided the duckling across the pond. Freddy hopped ahead, encouraging the duckling to follow, while Timmy stayed close by, offering a reassuring presence. When the duckling was finally reunited with its mother, Freddy and Timmy felt a warm glow of happiness. Their small act of kindness had made a big difference.
Another time, Freddy and Timmy noticed that the flowers around the pond were wilting due to a lack of rain. Freddy suggested they carry water from the pond to the flowers. Timmy agreed, and with Freddy's agility and Timmy's strength, they managed to water the flowers, bringing them back to life. The flowers bloomed brightly, adding a splash of color to the pond, and the animals of Lily Pad Pond admired the duo's thoughtful gesture.
---
Through their collaborative efforts and acts of kindness, Freddy the Frog and Timmy the Turtle showed that friendship is about supporting each other and making the world a better place. Their story reminds us that no matter how different we may be, when we work together, we can achieve wonderful things.
Resolution
----------
Title: Resolution
---
**The Problem is Solved**
In the heart of the lush, green forest, where the sun gently kissed the leaves and the air was filled with the sweet songs of birds, Freddy the Frog and his friends faced a challenge that tested their bonds. The once vibrant pond, their favorite gathering spot, had become murky and uninviting. The water was low, and the plants around it were wilting, causing concern among the forest creatures.
Freddy, with his bright green skin and curious eyes, gathered his friends, including Timmy the Turtle, Sally the Squirrel, and Benny the Bird, to discuss how they could restore their beloved pond. They brainstormed ideas, each friend contributing their unique perspective. Timmy suggested they clean the pond, while Sally proposed planting new flowers around it. Benny, with his keen eye from above, noticed a blockage in the stream that fed the pond.
Together, they worked tirelessly. Freddy and Timmy cleared the debris from the stream, allowing fresh water to flow once more. Sally and Benny planted colorful flowers and spread seeds that would soon bloom into a beautiful garden. Their hard work paid off as the pond began to sparkle again, reflecting the bright blue sky and the cheerful faces of the friends who had come together to solve the problem.
**Friendship is Strengthened**
As the pond returned to its former glory, the friends gathered around to celebrate their success. They realized that it was not just the pond that had been restored, but their friendship had grown stronger through their shared efforts. Freddy, with a wide smile, thanked each of his friends for their help and kindness. He understood that it was their teamwork and willingness to listen to each other that made the difference.
Timmy, who had always been a bit shy, felt more confident and valued, knowing his ideas were important. Sally, with her energetic spirit, learned the importance of patience and collaboration. Benny, who often soared above, realized the joy of being grounded with friends who cared.
The experience taught them that true friendship is about supporting each other, sharing responsibilities, and celebrating successes together. The pond, now a symbol of their unity, became a place where they would gather not just to play, but to share stories, dreams, and laughter.
In the end, Freddy the Frog and his friends discovered that kindness and cooperation could overcome any obstacle, and their friendship was the greatest treasure of all. As the sun set over the forest, painting the sky with hues of orange and pink, the friends sat by the pond, grateful for the adventure that had brought them even closer together.
Conclusion
----------
Title: Conclusion
---
**Reflecting on the Importance of Friendship**
As we reach the end of "Freddy the Frog and Friends: A Tale of Friendship and Kindness," it's important to take a moment to reflect on the wonderful journey we've shared with Freddy, Timmy, and their friends. Throughout their adventures, we've seen how friendship can bring joy, support, and strength to everyone involved. Freddy the Frog and Timmy the Turtle showed us that true friends are always there for each other, whether it's helping to solve a problem or simply sharing a laugh.
Friendship is like a beautiful garden that needs care and attention to grow. Freddy and his friends taught us that by being there for one another, listening, and sharing, we can create bonds that last a lifetime. Their story reminds us that friends come in all shapes and sizes, and it's the love and care we give to each other that truly matters.
**Encouraging Kindness**
Another important lesson from Freddy's tale is the power of kindness. Throughout the story, we saw how small acts of kindness can make a big difference. Whether it was Freddy helping Timmy find his way home or the friends working together to clean up the pond, each act of kindness helped to strengthen their friendship and made their world a better place.
Kindness is like a ripple in a pond; it starts with one small action and spreads outward, touching everyone it meets. Freddy and his friends showed us that being kind doesn't just help others; it also brings happiness and fulfillment to ourselves. By choosing to be kind, we can create a community where everyone feels valued and loved.
**Final Thoughts**
In conclusion, "Freddy the Frog and Friends: A Tale of Friendship and Kindness" is more than just a story about a group of animals. It's a reminder of the values that make our lives richer and more meaningful. As we close this chapter, let's carry with us the lessons of friendship and kindness, and strive to be like Freddy and his friends in our own lives.
Remember, every day is a new opportunity to be a good friend and to spread kindness wherever we go. Just like Freddy and Timmy, we can make the world a brighter place, one small act at a time. Thank you for joining us on this delightful journey, and may your own adventures be filled with friendship and kindness.
CONCLUSION
----------
Conclusion of "Freddy the Frog and Friends: A Tale of Friendship and Kindness"
In the heartwarming tale of "Freddy the Frog and Friends," we journeyed through a vibrant pond where friendship and kindness blossomed. Freddy the Frog, alongside his loyal friend Timmy the Turtle, embarked on an adventure that highlighted the importance of collaboration and problem-solving. Throughout their journey, they encountered various challenges that tested their bonds and showcased the power of working together.
Key points of the story emphasized how Freddy and his friends, including Sally the Snail and Benny the Bird, used their unique strengths to overcome obstacles. Their collective efforts not only solved problems but also strengthened their friendships, illustrating that kindness and teamwork can lead to wonderful outcomes.
As the story concludes, readers are reminded of the significance of extending kindness and being a supportive friend. The tale encourages young minds to embrace diversity and appreciate the different abilities each individual brings to a group. It also inspires children to approach challenges with a positive attitude and a willingness to help others.
For future adventures, Freddy and his friends can continue to explore new environments, meet new characters, and learn more about the world around them. These experiences will further reinforce the values of friendship and kindness, leaving a lasting impact on young readers.
In summary, "Freddy the Frog and Friends: A Tale of Friendship and Kindness" is a delightful story that not only entertains but also imparts valuable life lessons. It serves as a gentle reminder of the beauty of friendship and the strength found in kindness, leaving readers with a heartwarming message to carry forward.

View file

@ -1,120 +0,0 @@
La Storia dei Pesci per Bambini
===============================
Titel: La Storia dei Pesci per Bambini
Einführung:
Willkommen zu "La Storia dei Pesci per Bambini", einer bezaubernden Erzählung, die junge Leser in die faszinierende Welt der Meeresbewohner entführt. Diese Geschichte ist nicht nur unterhaltsam, sondern auch lehrreich und zielt darauf ab, das Bewusstsein für die Bedeutung des Umweltschutzes zu schärfen.
In dieser Geschichte werden die Kinder die Abenteuer von bunten Fischen und ihren Freunden erleben, die in den tiefen blauen Ozeanen leben. Durch ihre Erlebnisse lernen die jungen Leser wichtige Lektionen über Freundschaft, Zusammenarbeit und den Schutz unserer Umwelt.
Die Erzählung ist in einem lockeren und gesprächigen Ton gehalten, um die Neugier der Kinder zu wecken und sie dazu zu ermutigen, mehr über das Leben im Meer zu erfahren. Die Geschichte ist ideal für Eltern und Erzieher, die ihren Kindern auf unterhaltsame Weise Wissen über die Meereswelt und die Bedeutung des Umweltschutzes vermitteln möchten.
Tauchen Sie ein in diese wunderbare Geschichte und begleiten Sie unsere Fischfreunde auf ihrer Reise durch die Meere. Lassen Sie sich von ihrer Welt verzaubern und entdecken Sie, wie wichtig es ist, unsere Ozeane zu schützen und zu bewahren. Viel Spaß beim Lesen!
Introduzione
------------
# Introduzione
## Introduzione ai Personaggi Principali
Benvenuti nel meraviglioso mondo di "La Storia dei Pesci per Bambini", un racconto che vi porterà nelle profondità dell'oceano, dove la vita è vibrante e piena di avventure. Incontrerete una serie di personaggi affascinanti che vi accompagneranno in questo viaggio sottomarino.
### Pesciolino Blu
Pesciolino Blu è il protagonista della nostra storia. È un piccolo pesce curioso e avventuroso, sempre pronto a esplorare nuovi angoli del suo vasto mondo acquatico. Con le sue squame scintillanti di un blu intenso, Pesciolino Blu è facile da riconoscere tra la folla di pesci che popolano il mare.
### Stella la Stella Marina
Stella è una stella marina saggia e gentile, amica fidata di Pesciolino Blu. Con le sue cinque braccia, Stella è sempre pronta a dare una mano (o un braccio!) ai suoi amici in difficoltà. È conosciuta per le sue storie affascinanti e per la sua capacità di trovare soluzioni creative ai problemi.
### Granchio Rosso
Granchio Rosso è un granchio vivace e un po' burbero, ma con un cuore d'oro. Ama raccontare storie di avventure passate e non perde mai l'occasione di sfidare Pesciolino Blu e Stella a nuove imprese. Con le sue chele forti, è un compagno di viaggio indispensabile.
## Ambientazione del Mondo Sottomarino
Il mondo sottomarino in cui si svolge la nostra storia è un luogo di meraviglie e misteri. Le acque cristalline dell'oceano ospitano una varietà infinita di creature marine, ognuna con la sua storia da raccontare. Le barriere coralline, con i loro colori vivaci, offrono rifugio e nutrimento a innumerevoli specie.
### La Grande Barriera Corallina
La Grande Barriera Corallina è il cuore pulsante del nostro racconto. Qui, tra coralli di ogni forma e colore, i nostri protagonisti vivono le loro avventure quotidiane. È un luogo di bellezza mozzafiato, ma anche di pericoli nascosti, dove ogni giorno porta nuove sfide e scoperte.
### La Foresta di Alghe
Non lontano dalla barriera, si estende una fitta foresta di alghe ondeggianti. Questo è il luogo preferito di Pesciolino Blu per giocare a nascondino con i suoi amici. Le alghe offrono riparo e cibo, ma nascondono anche segreti che aspettano solo di essere svelati.
In questo mondo incantato, ogni giorno è un'opportunità per imparare qualcosa di nuovo e per vivere avventure indimenticabili. Preparatevi a immergervi in un racconto che vi farà sognare e vi insegnerà l'importanza dell'amicizia, del coraggio e della curiosità.
Avventura nel Mare
------------------
# Avventura nel Mare
## Incontro con diversi tipi di pesci
Benvenuti, piccoli esploratori, nell'affascinante mondo sottomarino! In questa avventura, ci immergeremo nelle profondità del mare per incontrare alcuni dei suoi abitanti più straordinari. Prepariamoci a scoprire i segreti della vita marina e a fare amicizia con i pesci che popolano questi vasti oceani.
### Incontro con il pesce pagliaccio
Il nostro primo incontro è con il simpatico pesce pagliaccio, noto per i suoi vivaci colori arancioni e bianchi. Questo piccolo pesce vive tra i tentacoli delle anemoni di mare, che lo proteggono dai predatori. Ma come fa a non essere punto dalle anemoni? Ecco il segreto: il pesce pagliaccio ha una speciale mucosa sulla pelle che lo rende immune al veleno delle anemoni. In cambio di questa protezione, il pesce pagliaccio aiuta a mantenere pulite le anemoni, creando un perfetto esempio di simbiosi!
### Scoperta del pesce palla
Proseguendo la nostra avventura, ci imbattiamo nel curioso pesce palla. Questo pesce ha un aspetto piuttosto normale, ma quando si sente minacciato, si gonfia come un palloncino! Questo trucco non solo lo rende più grande e difficile da ingoiare per i predatori, ma le sue spine appuntite diventano un'arma di difesa formidabile. Inoltre, il pesce palla è noto per contenere una tossina molto potente, quindi è meglio osservarlo da lontano e con rispetto.
## Lezioni sulla vita marina
Durante il nostro viaggio, impariamo che ogni pesce ha un ruolo importante nell'ecosistema marino. I pesci non solo contribuiscono alla catena alimentare, ma aiutano anche a mantenere l'equilibrio dell'ambiente in cui vivono. Ad esempio, i pesci pulitori rimuovono i parassiti dai pesci più grandi, mentre i pesci erbivori aiutano a controllare la crescita delle alghe sui coralli.
Ricordiamoci che il mare è un luogo di meraviglia e mistero, ma anche di fragilità. È importante proteggere questi habitat e rispettare le creature che li abitano. Ogni piccolo gesto, come ridurre l'uso della plastica o sostenere la pesca sostenibile, può fare una grande differenza per il nostro pianeta blu.
E così, cari amici, la nostra avventura nel mare giunge al termine. Speriamo che abbiate imparato qualcosa di nuovo e che siate pronti a esplorare ancora di più il meraviglioso mondo dei pesci!
Lezione di Vita
---------------
Title: Lezione di Vita
---
**Importanza della Collaborazione**
Nella storia "La Storia dei Pesci per Bambini", i piccoli lettori imparano quanto sia fondamentale la collaborazione tra i pesci per superare le sfide dell'oceano. I pesci, sebbene diversi per dimensioni e colori, scoprono che lavorando insieme possono proteggersi dai predatori e trovare cibo più facilmente. Ad esempio, quando un gruppo di pesciolini si unisce per formare un grande banco, riescono a confondere i predatori e a nuotare in sicurezza. Questa lezione insegna ai bambini che, proprio come i pesci, anche loro possono ottenere risultati migliori quando collaborano con gli altri, sia a scuola che nei giochi.
---
**Rispetto per l'Ambiente Marino**
Un altro aspetto cruciale della storia è il rispetto per l'ambiente marino. I pesci protagonisti mostrano ai piccoli lettori l'importanza di mantenere pulito il loro habitat. Quando un pesce curioso si avvicina a un pezzo di plastica galleggiante, gli altri lo avvertono del pericolo e insieme decidono di spostarlo lontano dal loro rifugio. Questo episodio insegna ai bambini che l'inquinamento può essere dannoso per gli animali marini e che ognuno di noi ha la responsabilità di proteggere il mare. Attraverso semplici azioni quotidiane, come non gettare rifiuti in acqua, possiamo contribuire a preservare la bellezza e la salute degli oceani.
---
Queste lezioni, presentate in modo semplice e coinvolgente, aiutano i bambini a comprendere valori importanti come la collaborazione e il rispetto per l'ambiente, stimolando in loro una maggiore consapevolezza e responsabilità verso il mondo che li circonda.
Conclusione
-----------
# Conclusione
## Riflessione sull'avventura
La storia dei pesci per bambini ci ha portato in un mondo sottomarino ricco di colori e avventure. Abbiamo seguito i nostri piccoli amici acquatici mentre esploravano le profondità dell'oceano, affrontando sfide e scoprendo meraviglie nascoste. Attraverso le loro avventure, abbiamo imparato quanto sia importante la curiosità e il coraggio di esplorare l'ignoto. Ogni incontro con creature marine diverse ha insegnato ai nostri protagonisti, e a noi lettori, lezioni preziose sulla diversità e sull'importanza di rispettare l'ambiente che ci circonda.
## Messaggio finale di amicizia
Alla fine del loro viaggio, i pesci hanno scoperto che la vera forza risiede nell'amicizia e nella collaborazione. Insieme, sono riusciti a superare ostacoli che sembravano insormontabili e a trovare soluzioni creative ai problemi che incontravano. Questa storia ci ricorda che, proprio come i pesci, anche noi possiamo contare sugli amici nei momenti di bisogno. L'amicizia è un legame speciale che ci arricchisce e ci sostiene, rendendo ogni avventura più dolce e ogni sfida più affrontabile.
In conclusione, "La Storia dei Pesci per Bambini" non è solo un racconto di avventure sottomarine, ma anche una celebrazione dell'amicizia e della scoperta. Speriamo che questa storia abbia ispirato i giovani lettori a esplorare il mondo con occhi curiosi e a coltivare legami di amicizia che durano nel tempo.
CONCLUSION
----------
Titel: La Storia dei Pesci per Bambini
Schlussfolgerung:
In der Geschichte "La Storia dei Pesci per Bambini" haben wir die faszinierende Welt der Meeresbewohner erkundet. Die Erzählung führte uns durch die farbenfrohe und vielfältige Unterwasserwelt, in der wir die Bedeutung der Meeresumwelt und die Notwendigkeit ihres Schutzes kennengelernt haben. Durch die Abenteuer der Fischfreunde wurde die Wichtigkeit von Freundschaft und Zusammenarbeit hervorgehoben, um Herausforderungen zu meistern und die Umwelt zu bewahren.
Die Geschichte vermittelte jungen Lesern auf unterhaltsame Weise Wissen über die Meeresökologie und die Rolle, die jeder Einzelne beim Schutz unserer Ozeane spielen kann. Sie ermutigte dazu, respektvoll mit der Natur umzugehen und die Schönheit und Vielfalt der Meereswelt zu schätzen.
Abschließend wird den Lesern empfohlen, sich weiterhin über die Meeresumwelt zu informieren und aktiv an deren Schutz teilzunehmen. Kleine Schritte, wie das Reduzieren von Plastikmüll und das Unterstützen von Umweltprojekten, können einen großen Unterschied machen.
Diese Geschichte hinterlässt einen bleibenden Eindruck von der Bedeutung der Meeresbewohner und der Verantwortung, die wir alle tragen, um ihre Welt zu bewahren. Sie inspiriert junge Leser, sich für die Umwelt einzusetzen und die Freundschaften zu pflegen, die sie auf ihrem Weg begleiten.

View file

@ -1,213 +0,0 @@
How to Cook Beans: A Step-by-Step Recipe
========================================
Einführung in den Leitfaden "Wie man Bohnen kocht: Ein Schritt-für-Schritt-Rezept"
Willkommen zu unserem umfassenden Leitfaden "Wie man Bohnen kocht: Ein Schritt-für-Schritt-Rezept". Dieser Leitfaden wurde entwickelt, um Ihnen die Kunst des Bohnenkochens näherzubringen und Ihnen zu helfen, köstliche und nahrhafte Gerichte mit Leichtigkeit zuzubereiten. Bohnen sind nicht nur eine hervorragende Proteinquelle, sondern auch vielseitig einsetzbar und in vielen Küchen der Welt ein Grundnahrungsmittel.
In diesem Dokument finden Sie eine klare und präzise Anleitung, die Sie durch jeden Schritt des Kochprozesses führt. Wir beginnen mit der Vorbereitung der Zutaten, gefolgt von detaillierten Kochanweisungen, um sicherzustellen, dass Ihre Bohnen jedes Mal perfekt gelingen. Egal, ob Sie ein erfahrener Koch oder ein Anfänger in der Küche sind, dieser Leitfaden bietet Ihnen das notwendige Wissen und die Techniken, um Bohnen in Ihre täglichen Mahlzeiten zu integrieren.
Unser Ziel ist es, Ihnen nicht nur die Grundlagen des Bohnenkochens zu vermitteln, sondern auch Ihre kulinarischen Fähigkeiten zu erweitern und Ihnen neue Möglichkeiten aufzuzeigen, wie Sie Bohnen in Ihren Rezepten verwenden können. Von der Auswahl der richtigen Bohnensorte bis hin zur optimalen Kochzeit dieser Leitfaden deckt alle wichtigen Aspekte ab.
Wir laden Sie ein, sich auf diese kulinarische Reise zu begeben und die Vielseitigkeit und den Geschmack von Bohnen zu entdecken. Lassen Sie uns gemeinsam die Geheimnisse des Bohnenkochens erkunden und Ihre Küche mit neuen, aufregenden Aromen bereichern.
Introduction
------------
```
Title: Introduction
In der Kunst des Kochens sind Bohnen ein vielseitiges und nahrhaftes Grundnahrungsmittel, das in Küchen weltweit geschätzt wird. Diese Anleitung "How to Cook Beans: A Step-by-Step Recipe" bietet Ihnen eine umfassende Einführung in die Zubereitung von Bohnen, die sowohl für Anfänger als auch für erfahrene Köche geeignet ist. Unser Ziel ist es, Ihnen die notwendigen Kenntnisse und Techniken zu vermitteln, um köstliche Bohnengerichte zu kreieren, die sowohl gesund als auch geschmackvoll sind.
Überblick über das Rezept
-------------------------
Diese Anleitung führt Sie Schritt für Schritt durch den Prozess der Zubereitung von Bohnen, von der Auswahl der richtigen Bohnensorte bis hin zur optimalen Kochzeit. Wir werden die grundlegenden Methoden des Einweichens und Kochens abdecken und Ihnen Tipps geben, wie Sie den Geschmack Ihrer Bohnen durch Gewürze und Kräuter verfeinern können. Darüber hinaus bieten wir Ihnen Variationen und Anpassungen, um Ihre Bohnen nach Ihrem persönlichen Geschmack und den Bedürfnissen Ihrer Familie zuzubereiten.
Kurze Geschichte der Bohnen in der Küche
----------------------------------------
Bohnen haben eine lange und reiche Geschichte in der menschlichen Ernährung. Sie gehören zu den ältesten kultivierten Pflanzen und wurden bereits vor Tausenden von Jahren in verschiedenen Kulturen weltweit angebaut. In der Antike waren Bohnen ein Grundnahrungsmittel in der Ernährung der Griechen und Römer. In der Neuen Welt wurden sie von indigenen Völkern angebaut und spielten eine zentrale Rolle in der Ernährung der Azteken und Mayas.
Mit der Entdeckung Amerikas verbreiteten sich Bohnen schnell in Europa und Asien, wo sie sich in vielen traditionellen Gerichten etablierten. Heute sind Bohnen ein wesentlicher Bestandteil zahlreicher Küchen, von der italienischen Minestrone bis zum mexikanischen Chili. Ihre Vielseitigkeit und ihr hoher Nährwert machen sie zu einer beliebten Wahl für gesundheitsbewusste Köche und Genießer gleichermaßen.
Diese Einführung soll Ihnen nicht nur die Grundlagen der Bohnenzubereitung vermitteln, sondern auch ein tieferes Verständnis für die kulturelle Bedeutung und die kulinarische Vielfalt dieses bemerkenswerten Lebensmittels bieten. Lassen Sie uns gemeinsam die Welt der Bohnen entdecken und lernen, wie man sie mit Leichtigkeit und Freude zubereitet.
```
Ingredients
-----------
Title: Zutaten
In diesem Abschnitt finden Sie eine detaillierte Liste der benötigten Zutaten, um Bohnen auf schmackhafte Weise zuzubereiten. Wir bieten auch optionale Zutaten an, die Sie verwenden können, um dem Gericht eine persönliche Note zu verleihen oder es zu variieren.
**Erforderliche Zutaten:**
1. **Bohnen**
- Menge: 500 g getrocknete Bohnen (z. B. schwarze Bohnen, Kidneybohnen oder weiße Bohnen)
- Hinweis: Achten Sie darauf, die Bohnen über Nacht in Wasser einzuweichen, um die Kochzeit zu verkürzen und die Verdaulichkeit zu verbessern.
2. **Wasser**
- Menge: Ausreichend, um die Bohnen vollständig zu bedecken und während des Kochens nach Bedarf aufzufüllen.
3. **Salz**
- Menge: 1 Teelöffel
- Hinweis: Salz sollte erst gegen Ende des Kochvorgangs hinzugefügt werden, um zu verhindern, dass die Bohnen hart werden.
4. **Zwiebel**
- Menge: 1 mittelgroße Zwiebel, fein gehackt
5. **Knoblauch**
- Menge: 2-3 Zehen, fein gehackt
6. **Olivenöl**
- Menge: 2 Esslöffel
**Optionale Zutaten für Variation:**
1. **Lorbeerblatt**
- Menge: 1-2 Blätter
- Hinweis: Verleiht den Bohnen ein aromatisches, leicht würziges Aroma.
2. **Gemüsebrühe**
- Menge: 500 ml
- Hinweis: Kann anstelle von Wasser verwendet werden, um den Bohnen zusätzlichen Geschmack zu verleihen.
3. **Tomaten**
- Menge: 1 Dose (ca. 400 g) gehackte Tomaten oder 2 frische Tomaten, gewürfelt
- Hinweis: Für eine fruchtige Note und zusätzliche Säure.
4. **Paprika**
- Menge: 1 rote oder grüne Paprika, gewürfelt
- Hinweis: Fügt Farbe und Süße hinzu.
5. **Kreuzkümmel**
- Menge: 1 Teelöffel
- Hinweis: Für einen erdigen, leicht scharfen Geschmack.
6. **Chiliflocken oder frische Chilischoten**
- Menge: Nach Geschmack
- Hinweis: Für diejenigen, die es gerne scharf mögen.
Diese Zutatenliste bietet eine solide Grundlage für die Zubereitung von Bohnen. Die optionalen Zutaten ermöglichen es Ihnen, das Gericht nach Ihrem Geschmack zu variieren und zu verfeinern. Achten Sie darauf, die Mengen entsprechend der Anzahl der Personen, die Sie bedienen möchten, anzupassen.
Preparation Steps
-----------------
Title: Preparation Steps
---
**Soaking the Beans**
1. **Auswahl der Bohnen:** Beginnen Sie mit der Auswahl der gewünschten Bohnensorte. Beliebte Optionen sind schwarze Bohnen, Kidneybohnen oder weiße Bohnen. Achten Sie darauf, dass die Bohnen frisch und von guter Qualität sind.
2. **Reinigung:** Spülen Sie die Bohnen gründlich unter fließendem Wasser ab, um Schmutz und Verunreinigungen zu entfernen. Entfernen Sie beschädigte oder verfärbte Bohnen.
3. **Einweichen:**
- **Über Nacht Einweichen:** Legen Sie die Bohnen in eine große Schüssel und bedecken Sie sie mit reichlich Wasser. Lassen Sie sie über Nacht (mindestens 8 Stunden) einweichen. Dies hilft, die Kochzeit zu verkürzen und die Verdaulichkeit zu verbessern.
- **Schnell-Einweichmethode:** Wenn Sie wenig Zeit haben, bringen Sie die Bohnen in einem Topf mit Wasser zum Kochen. Lassen Sie sie 2 Minuten kochen, nehmen Sie den Topf vom Herd und lassen Sie die Bohnen 1 Stunde einweichen.
4. **Abgießen und Spülen:** Nach dem Einweichen die Bohnen abgießen und erneut unter fließendem Wasser abspülen. Dies entfernt überschüssige Stärke und reduziert Blähungen.
**Tipps für beste Ergebnisse:** Verwenden Sie für das Einweichen immer frisches Wasser und vermeiden Sie die Zugabe von Salz, da dies die Bohnen zäh machen kann.
---
**Cooking the Beans**
1. **Vorbereitung:** Geben Sie die eingeweichten und gespülten Bohnen in einen großen Topf. Fügen Sie frisches Wasser hinzu, sodass die Bohnen etwa 5 cm bedeckt sind.
2. **Kochen:**
- **Aufkochen:** Bringen Sie das Wasser zum Kochen. Reduzieren Sie dann die Hitze auf ein sanftes Köcheln.
- **Kochzeit:** Die Kochzeit variiert je nach Bohnensorte und Einweichmethode. Im Allgemeinen benötigen Bohnen 1 bis 2 Stunden, um weich zu werden. Testen Sie die Konsistenz, indem Sie eine Bohne zwischen den Fingern zerdrücken.
3. **Schaum entfernen:** Während des Kochens kann sich Schaum an der Oberfläche bilden. Entfernen Sie diesen mit einem Löffel, um eine klare Brühe zu erhalten.
4. **Überwachung:** Rühren Sie gelegentlich um und fügen Sie bei Bedarf mehr Wasser hinzu, um ein Anbrennen zu verhindern.
**Tipps für beste Ergebnisse:** Fügen Sie während des Kochens keine sauren Zutaten wie Tomaten oder Essig hinzu, da dies die Kochzeit verlängern kann.
---
**Seasoning and Serving**
1. **Würzen:** Sobald die Bohnen weich sind, können Sie sie nach Geschmack würzen. Beliebte Gewürze sind Salz, Pfeffer, Knoblauch, Lorbeerblätter und Zwiebeln. Fügen Sie diese Zutaten gegen Ende der Kochzeit hinzu, um den Geschmack zu bewahren.
2. **Servieren:**
- **Als Beilage:** Servieren Sie die Bohnen als Beilage zu Fleischgerichten oder Reis.
- **In Eintöpfen:** Verwenden Sie die Bohnen als Basis für Eintöpfe oder Suppen.
- **Salate:** Lassen Sie die Bohnen abkühlen und fügen Sie sie zu Salaten hinzu.
3. **Lagerung:** Bewahren Sie übrig gebliebene Bohnen in einem luftdichten Behälter im Kühlschrank auf. Sie halten sich bis zu 5 Tage.
**Tipps für beste Ergebnisse:** Experimentieren Sie mit verschiedenen Kräutern und Gewürzen, um den Geschmack der Bohnen zu variieren und anzupassen.
Tips and Variations
-------------------
Title: Tipps und Variationen
In diesem Abschnitt bieten wir Ihnen nützliche Tipps und verschiedene Variationen, um das Beste aus Ihrem Bohnenrezept herauszuholen. Egal, ob Sie nach alternativen Kochmethoden suchen oder den Geschmack Ihrer Bohnen anpassen möchten, hier finden Sie wertvolle Anregungen.
## Alternative Kochmethoden
1. **Druckkochtopf**:
- **Vorteile**: Verkürzt die Kochzeit erheblich und spart Energie.
- **Anwendung**: Geben Sie die eingeweichten Bohnen mit ausreichend Wasser in den Druckkochtopf. Kochen Sie sie bei hohem Druck für etwa 20-25 Minuten. Lassen Sie den Druck natürlich abfallen, bevor Sie den Topf öffnen.
2. **Slow Cooker**:
- **Vorteile**: Ideal für eine langsame, gleichmäßige Garung, die die Aromen intensiviert.
- **Anwendung**: Geben Sie die eingeweichten Bohnen mit Wasser und Gewürzen in den Slow Cooker. Kochen Sie sie auf niedriger Stufe für 6-8 Stunden oder auf hoher Stufe für 3-4 Stunden.
3. **Backofen**:
- **Vorteile**: Bietet eine gleichmäßige Hitzeverteilung und kann zu einer leicht gerösteten Textur führen.
- **Anwendung**: Legen Sie die Bohnen in einen ofenfesten Topf, fügen Sie Wasser und Gewürze hinzu und decken Sie ihn ab. Backen Sie bei 160°C für etwa 1,5 bis 2 Stunden.
## Geschmacksvariationen
1. **Kräuter und Gewürze**:
- **Optionen**: Thymian, Rosmarin, Lorbeerblätter, Kreuzkümmel oder Paprika.
- **Tipp**: Fügen Sie die Gewürze während des Kochens hinzu, um den Bohnen einen intensiven Geschmack zu verleihen.
2. **Aromatische Zutaten**:
- **Optionen**: Zwiebeln, Knoblauch, Sellerie oder Karotten.
- **Tipp**: Braten Sie diese Zutaten leicht an, bevor Sie die Bohnen hinzufügen, um eine tiefere Geschmacksbasis zu schaffen.
3. **Rauchige Note**:
- **Optionen**: Geräucherter Speck, Schinken oder geräucherte Paprika.
- **Tipp**: Fügen Sie diese Zutaten zu Beginn des Kochvorgangs hinzu, um den Bohnen eine rauchige Tiefe zu verleihen.
4. **Säuerliche Akzente**:
- **Optionen**: Ein Spritzer Zitronensaft oder ein Schuss Essig.
- **Tipp**: Fügen Sie diese am Ende des Kochvorgangs hinzu, um die Aromen aufzufrischen und die Bohnen aufzuhellen.
Mit diesen Tipps und Variationen können Sie Ihr Bohnenrezept nach Belieben anpassen und jedes Mal ein einzigartiges Gericht kreieren. Experimentieren Sie mit verschiedenen Kombinationen, um Ihren persönlichen Favoriten zu finden!
Conclusion
----------
# Conclusion
In diesem Abschnitt fassen wir die wesentlichen Schritte zusammen, die Sie beim Kochen von Bohnen beachten sollten, und ermutigen Sie, dieses einfache und nahrhafte Rezept selbst auszuprobieren.
## Zusammenfassung des Rezepts
Das Kochen von Bohnen ist eine lohnende und unkomplizierte Aufgabe, die mit ein wenig Vorbereitung und Geduld zu einem köstlichen Ergebnis führt. Der Prozess beginnt mit dem sorgfältigen Auslesen und Waschen der Bohnen, gefolgt von einem Einweichvorgang, der die Bohnen weicher macht und die Kochzeit verkürzt. Anschließend werden die Bohnen in frischem Wasser gekocht, wobei Gewürze und Aromastoffe nach Belieben hinzugefügt werden können, um den Geschmack zu verfeinern. Die Kochzeit variiert je nach Bohnensorte, liegt jedoch in der Regel zwischen 45 Minuten und zwei Stunden. Das Endergebnis sind zarte, schmackhafte Bohnen, die als Beilage oder Hauptgericht serviert werden können.
## Ermutigung zum Ausprobieren des Rezepts
Wir hoffen, dass diese Anleitung Ihnen das Vertrauen gibt, Bohnen selbst zu kochen. Die Vielseitigkeit von Bohnen macht sie zu einer hervorragenden Ergänzung für zahlreiche Gerichte, von Eintöpfen und Suppen bis hin zu Salaten und Beilagen. Probieren Sie verschiedene Gewürzkombinationen aus, um Ihren persönlichen Geschmack zu treffen, und scheuen Sie sich nicht, mit verschiedenen Bohnensorten zu experimentieren. Mit dieser Schritt-für-Schritt-Anleitung sind Sie bestens gerüstet, um ein einfaches, gesundes und köstliches Gericht zuzubereiten, das Ihre Mahlzeiten bereichern wird. Viel Spaß beim Kochen und guten Appetit!
CONCLUSION
----------
Abschluss: "Wie man Bohnen kocht: Ein Schritt-für-Schritt-Rezept"
In diesem Leitfaden haben wir die wesentlichen Schritte zur Zubereitung von Bohnen detailliert beschrieben, um Ihnen ein einfaches und effektives Kocherlebnis zu bieten. Wir haben die Bedeutung der richtigen Vorbereitung der Zutaten hervorgehoben, einschließlich des Einweichens und der Auswahl der passenden Gewürze, um den Geschmack zu maximieren.
Zusammenfassend lässt sich sagen, dass das Kochen von Bohnen nicht nur eine nahrhafte, sondern auch eine vielseitige Ergänzung zu jeder Mahlzeit darstellt. Die Schritt-für-Schritt-Anleitung in diesem Dokument soll Ihnen helfen, die Bohnen perfekt zuzubereiten, unabhängig von Ihrer Kocherfahrung.
Wir empfehlen, mit verschiedenen Gewürzen und Kräutern zu experimentieren, um Ihre persönlichen Vorlieben zu entdecken und die Bohnen in verschiedenen Gerichten zu integrieren. Ob als Beilage, in Salaten oder als Hauptgericht die Möglichkeiten sind vielfältig.
Mit diesem Wissen ausgestattet, können Sie nun selbstbewusst Bohnen kochen und genießen, und dabei die gesundheitlichen Vorteile dieser proteinreichen Hülsenfrucht voll ausschöpfen. Wir hoffen, dass dieser Leitfaden Ihnen nicht nur praktische Fähigkeiten vermittelt hat, sondern auch Ihre Freude am Kochen bereichert.
Vielen Dank, dass Sie diesen Leitfaden genutzt haben. Wir wünschen Ihnen viel Erfolg und Genuss beim Kochen Ihrer nächsten Bohnenmahlzeit!

View file

@ -0,0 +1,75 @@
```
Filename: sales_trends_analysis.txt
Sales Trends Analysis Report
============================
**Introduction**
----------------
This report provides a detailed analysis of the sales data for Q1 2023, focusing on identifying key trends and opportunities. The analysis covers monthly revenue trends, growth figures, regional sales breakdown, and top product revenue percentages. Visualizations have been recommended to support the insights derived from the data.
**1. Monthly Revenue Trends for Q1 2023**
-----------------------------------------
The sales data for Q1 2023 indicates a consistent upward trend in monthly revenue:
- **January**: $1,500,000
- **February**: $1,650,000
- **March**: $1,800,000
The revenue growth rates for each month are as follows:
- **January**: 5.2%
- **February**: 10.0%
- **March**: 9.1%
**Insight**: The data shows a steady increase in revenue from January to March, with February experiencing the highest growth rate. This suggests a positive momentum in sales performance during the first quarter.
**Recommended Visualization**: A line chart depicting monthly revenue growth will effectively illustrate this trend.
**2. Regional Sales Breakdown**
-------------------------------
The regional sales distribution for Q1 2023 is as follows:
- **North**: 35% of total sales
- **South**: 25% of total sales
- **East**: 20% of total sales
- **West**: 20% of total sales
**Insight**: The North region is the leading contributor to sales, accounting for the highest percentage of total sales. This indicates a strong market presence and potential for further growth in this region.
**Recommended Visualization**: A pie chart showing the regional sales breakdown will provide a clear visual representation of the regional contributions.
**3. Top Product Revenue Percentages**
--------------------------------------
The revenue contribution by top products is as follows:
- **Product A**: 40% of revenue
- **Product B**: 30% of revenue
- **Product C**: 20% of revenue
- **Others**: 10% of revenue
**Insight**: Product A is the top revenue generator, contributing significantly to the overall sales. This highlights the importance of maintaining and potentially expanding the market for Product A.
**Recommended Visualization**: A bar chart illustrating the revenue contribution of top products will highlight their impact on total sales.
**Conclusions and Recommendations**
-----------------------------------
The analysis of Q1 2023 sales data reveals several key trends and opportunities:
1. **Sustained Revenue Growth**: The consistent increase in monthly revenue suggests a robust sales strategy. Continued focus on maintaining this growth trajectory is recommended.
2. **Regional Focus**: With the North region leading in sales, targeted marketing and sales initiatives in this area could further enhance revenue.
3. **Product Strategy**: Given the significant contribution of Product A to total revenue, strategies to boost its sales, such as promotions or new features, should be considered.
4. **Diversification**: While Product A is a major revenue driver, exploring opportunities to increase the market share of other products could reduce dependency on a single product.
By leveraging these insights and implementing the recommended strategies, the company can capitalize on existing strengths and explore new growth avenues.
**Appendix**
------------
- **Data Source**: q1_sales_data.md
- **Visualizations**: Line chart, pie chart, and bar chart as recommended above.
End of Report
```

View file

@ -1,37 +0,0 @@
Filename: triage_definition.txt
---
**Triage: Definition and Contextual Applications**
**Executive Summary:**
This report provides a comprehensive analysis of the term 'triage,' exploring its definition in both medical and non-medical contexts, as well as its etymological origins. Triage is primarily associated with the medical field, where it plays a critical role in prioritizing patient care based on the severity of their conditions. The concept has also been adapted for use in various non-medical settings, emphasizing the prioritization of tasks and resources. The term 'triage' has its roots in the French language, reflecting its historical development and application.
**1. Definition of 'Triage' in a Medical Context:**
In the medical field, 'triage' refers to the process of determining the priority of patients' treatments based on the severity of their condition. This system is crucial in emergency situations, such as in hospitals' emergency departments or during mass casualty incidents, where resources are limited and immediate decisions are necessary to maximize the number of survivors. The primary goal of medical triage is to ensure that those who need urgent care receive it promptly, while those with less critical conditions are attended to as resources allow.
**2. Use of 'Triage' in Non-Medical Contexts:**
Beyond its medical application, the concept of triage has been adopted in various non-medical fields, including business, information technology, and disaster management. In these contexts, triage involves prioritizing tasks, projects, or issues to efficiently allocate resources and address the most critical needs first. For example, in IT, triage may refer to the process of sorting and addressing technical support requests based on urgency and impact. Similarly, in business, triage can help prioritize strategic initiatives to optimize organizational performance.
**3. Origins of the Term 'Triage':**
The term 'triage' originates from the French word 'trier,' which means 'to sort' or 'to select.' Historically, the concept of triage was developed during wartime to manage battlefield injuries, where medical personnel had to make quick decisions about which soldiers required immediate attention. Over time, the principles of triage have been refined and formalized, becoming an integral part of modern emergency medicine and crisis management.
**Conclusion:**
The term 'triage' embodies a critical decision-making process that is essential in both medical and non-medical settings. Its origins in the French language highlight its historical significance and evolution. Understanding the principles of triage allows for effective prioritization and resource allocation, ensuring that critical needs are addressed efficiently in various contexts.
**References:**
- [Source 1: Medical Dictionary or Encyclopedia]
- [Source 2: Business or IT Journal]
- [Source 3: Historical Text on Medical Practices]
(Note: The above references are placeholders and should be replaced with actual sources used in the research.)
---
This report is intended to provide a scholarly and comprehensive understanding of the term 'triage,' integrating information from various sources to address the research questions effectively.

View file

@ -1,37 +0,0 @@
# Goal Definition in German: A Comprehensive Research Report
## Executive Summary
This report explores the definition, etymology, and contextual usage of the term "goal" in the German language. The German equivalent of "goal" is "Ziel." This term is widely used in various contexts, including personal objectives, sports, and business targets. The etymology of "Ziel" traces back to Middle High German and Old High German, reflecting its longstanding presence in the German lexicon. This report synthesizes information from reputable language and etymology sources to provide a comprehensive understanding of the term.
## Research Questions and Findings
### 1. What is the definition of 'goal' in German?
The German word for "goal" is "Ziel." In general terms, "Ziel" refers to an objective or an endpoint that one aims to achieve. It is used to denote both tangible and intangible targets, ranging from personal ambitions to specific outcomes in various activities.
### 2. What is the etymology of the German word for 'goal'?
The word "Ziel" originates from Middle High German "ziel" and Old High German "zīl," which both mean a target or endpoint. The term has been part of the German language for centuries, signifying its deep-rooted significance in expressing objectives and endpoints.
### 3. How is the term 'goal' used in different contexts in German?
In German, "Ziel" is used in multiple contexts:
- **Personal Development**: In personal development, "Ziel" refers to individual aspirations or life goals, such as career objectives or personal growth targets.
- **Sports**: In sports, "Ziel" is used to describe the endpoint of a race or the goal in games like soccer, where scoring a "Tor" (goal) is the objective.
- **Business and Projects**: In business, "Ziel" denotes targets or objectives that organizations aim to achieve, such as sales goals or project milestones.
## Synthesis of Research
The term "Ziel" is integral to the German language, encapsulating the concept of goals across various domains. Its etymological roots highlight its historical significance, while its versatile usage demonstrates its relevance in contemporary settings. Whether in personal, athletic, or professional contexts, "Ziel" serves as a fundamental term for expressing aspirations and endpoints.
## Sources
1. [Duden Online Dictionary](https://www.duden.de) - Provides definitions and usage examples of German words.
2. [Etymologisches Wörterbuch des Deutschen](https://www.dwds.de) - Offers insights into the etymology of German terms.
3. [Langenscheidt German-English Dictionary](https://www.langenscheidt.com) - A reputable source for translations and definitions.
This report aims to deliver a scholarly and accurate understanding of the term "goal" in German, focusing on its definition, historical roots, and contextual applications.

View file

@ -1,44 +0,0 @@
Filename: handling_definition.txt
---
**Handling Definition in German: A Comprehensive Research Report**
**Executive Summary:**
This report explores the definition and contextual usage of the term "handling" in the German language. The research aims to provide a clear understanding of how "handling" is defined and applied across various contexts in German-speaking regions. Despite the absence of specific sources, this report synthesizes general knowledge and linguistic insights to address the research questions effectively.
**Research Questions:**
1. What is the definition of 'handling' in German?
2. What are the different contexts in which 'handling' is used in German?
**1. Definition of 'Handling' in German:**
In German, the term "handling" is often borrowed directly from English, especially in technical, business, and logistics contexts. It refers to the process of managing, controlling, or dealing with goods, tasks, or situations. The term is used similarly to its English counterpart, emphasizing the practical aspects of managing or operating something.
**2. Contextual Usage of 'Handling' in German:**
The usage of "handling" in German can be categorized into several key contexts:
- **Logistics and Supply Chain:** In logistics, "handling" refers to the physical management of goods, including loading, unloading, and moving items within a warehouse or during transportation. It is a critical component of supply chain operations, ensuring efficiency and safety in the movement of products.
- **Business and Management:** Within business contexts, "handling" can describe the management of tasks, projects, or customer interactions. It implies a level of skill and efficiency in dealing with various business processes or client relations.
- **Technical and Mechanical:** In technical fields, "handling" might refer to the operation or control of machinery and equipment. This includes the ability to manage tools or devices effectively to perform specific tasks.
- **Everyday Usage:** In everyday language, "handling" can be used more broadly to describe the way someone deals with a situation or problem. It suggests a practical approach to resolving issues or managing circumstances.
**Synthesis and Conclusion:**
The term "handling" in German retains much of its English meaning, particularly in professional and technical contexts. Its usage spans logistics, business, and technical fields, highlighting the importance of effective management and control. While the term is borrowed from English, it has been integrated into German vocabulary, reflecting the global nature of business and technology.
This report underscores the versatility of "handling" in German, illustrating its application across various domains. Understanding these contexts is crucial for professionals working in German-speaking environments, as it aids in effective communication and operational efficiency.
**References:**
Due to the absence of specific sources, this report is based on general linguistic knowledge and the contextual understanding of the term "handling" in German-speaking regions. Future research could benefit from direct citations from German dictionaries or industry-specific publications to enhance the depth and accuracy of the findings.
---
This report is intended to provide a scholarly and comprehensive overview of the term "handling" in German, suitable for academic and professional purposes.

View file

@ -1,45 +0,0 @@
**Triangle Definition Research Report**
**Filename:** triangle_definition.txt
**Description:** This report provides a comprehensive and concise definition of the term 'triangle' in the context of geometry, addressing key characteristics and integrating information from relevant sources.
---
**Executive Summary:**
This report explores the geometric definition of a triangle, focusing on its fundamental characteristics. A triangle is a three-sided polygon, a basic shape in geometry, characterized by its three edges and three vertices. The report synthesizes information from various authoritative sources to present a clear and concise understanding of triangles, highlighting their properties and significance in geometry.
---
**Research Questions and Findings:**
1. **What is the definition of a triangle in geometry?**
A triangle is defined as a polygon with three edges and three vertices. It is one of the simplest forms of polygons and is a fundamental shape in the study of geometry. The sum of the internal angles of a triangle is always 180 degrees. This definition is consistent across various educational and mathematical resources, emphasizing the triangle's role as a basic building block in geometric studies.
2. **What are the key characteristics of a triangle?**
Key characteristics of a triangle include:
- **Three Sides and Three Angles:** A triangle has three sides, which can be of equal or varying lengths, and three angles, which can also vary in measure.
- **Types of Triangles:** Triangles can be classified based on side length (equilateral, isosceles, scalene) or angle measure (acute, right, obtuse).
- **Angle Sum Property:** The sum of the internal angles of a triangle is always 180 degrees, a fundamental property that applies to all triangles.
- **Congruence and Similarity:** Triangles can be congruent (identical in shape and size) or similar (same shape but different sizes) based on their angles and side proportions.
---
**Synthesis of Research:**
The triangle is a foundational concept in geometry, serving as a critical element in the study of shapes and spatial reasoning. Its simplicity and versatility make it a subject of extensive study in mathematics. Triangles are not only important in theoretical geometry but also have practical applications in fields such as engineering, architecture, and art. Understanding the properties of triangles allows for the exploration of more complex geometric concepts and theorems.
---
**Sources:**
1. "Triangle." *Encyclopedia Britannica*. Retrieved from [Encyclopedia Britannica](https://www.britannica.com/science/triangle-mathematics)
2. "Triangle Definition." *Math is Fun*. Retrieved from [Math is Fun](https://www.mathsisfun.com/triangle.html)
3. "Types of Triangles." *Khan Academy*. Retrieved from [Khan Academy](https://www.khanacademy.org/math/geometry/hs-geo-foundations/hs-geo-intro-to-triangles/a/types-of-triangles-review)
---
This report has been formatted to provide a scholarly and accurate overview of the geometric concept of triangles, integrating information from multiple authoritative sources to ensure a comprehensive understanding.

View file

@ -1,37 +0,0 @@
**Translation and Meaning of the Word 'Horrible' from German to English**
---
**Executive Summary:**
This report investigates the translation and meaning of the word 'horrible' from German to English. The research focuses on providing an accurate translation and understanding of the term within the English language context. Despite the absence of specific sources, the report synthesizes general linguistic knowledge to address the research questions effectively.
---
**Research Questions and Findings:**
1. **What is the translation of the word 'horrible' from German to English?**
The German word for 'horrible' is "schrecklich." This translation captures the essence of something that is extremely unpleasant or distressing. The term "schrecklich" is commonly used in German to describe situations, events, or characteristics that evoke fear, disgust, or severe discomfort.
2. **What is the meaning of the word 'horrible' in English?**
In English, the word 'horrible' is an adjective used to describe something that causes horror, is extremely unpleasant, or is very bad. It can refer to experiences, sights, sounds, or even behaviors that provoke a strong negative emotional response. The term is often used to emphasize the severity or intensity of the unpleasantness.
---
**Synthesis of Research:**
The translation of 'horrible' from German to English as "schrecklich" aligns with the general understanding of the word in both languages. In both contexts, the term is used to describe situations or entities that are profoundly negative or distressing. This cross-linguistic consistency highlights the universal nature of the concept of horror and unpleasantness.
While the report lacks direct citations from specific sources due to the absence of listed findings, the translation and meaning provided are consistent with standard linguistic resources and dictionaries. This synthesis is based on widely accepted translations and definitions found in reputable language references.
---
**Conclusion:**
The translation of 'horrible' from German to English is "schrecklich," and its meaning in English pertains to something that is extremely unpleasant or causes horror. This report provides a clear understanding of the term across both languages, emphasizing its consistent use to describe negative experiences or characteristics.
---
**Note:** This report is based on general linguistic knowledge and standard translations. For precise and detailed linguistic studies, consulting specialized dictionaries or language experts is recommended.

View file

@ -1,39 +0,0 @@
# Definition of 'Greedy' in German: A Comprehensive Report
## Executive Summary
This report explores the definition, synonyms, and usage of the term 'greedy' in the German language. The term 'greedy' translates to "gierig" in German, and it is commonly used to describe an intense and selfish desire for something, particularly wealth or food. This report synthesizes information from various linguistic sources to provide a comprehensive understanding of the term's meaning, synonyms, and usage in sentences.
## Research Questions and Findings
### 1. What is the definition of 'greedy' in German?
The term 'greedy' is translated into German as "gierig." It is defined as having an intense and selfish desire for something, especially wealth, power, or food. The word "gierig" is often used to describe individuals who are excessively desirous of acquiring more than they need or deserve.
### 2. What are the synonyms of 'greedy' in German?
Several synonyms for "gierig" exist in the German language, each with slightly different connotations:
- **Habgierig**: This term emphasizes a strong desire for possessions or wealth.
- **Raffgierig**: Often used in a negative context, it highlights an insatiable greed for material gain.
- **Gefräßig**: While primarily used to describe someone who eats excessively, it can also imply greed in a broader sense.
- **Habsüchtig**: Similar to "habgierig," it denotes a covetous or acquisitive nature.
### 3. How is the term 'greedy' used in German sentences?
The term "gierig" can be used in various contexts within German sentences. Here are a few examples:
- **Er ist gierig nach Macht.** (He is greedy for power.)
- **Die gierige Katze fraß alles auf.** (The greedy cat devoured everything.)
- **Sein gieriges Verhalten führte zu seinem Untergang.** (His greedy behavior led to his downfall.)
- **Sie ist habgierig und will immer mehr Geld.** (She is greedy and always wants more money.)
## Integration of Information from Relevant Sources
The information presented in this report is derived from reputable linguistic resources, including German dictionaries and language learning platforms. These sources provide definitions, synonyms, and contextual usage examples that are essential for understanding the nuances of the term "gierig."
## Conclusion
The term "gierig" in German encapsulates the concept of greed, characterized by an excessive desire for more than what is necessary or deserved. Its synonyms, such as "habgierig" and "raffgierig," further illustrate the various facets of greed, from material wealth to power. Understanding these terms and their usage in sentences provides valuable insight into the cultural and linguistic interpretation of greed in the German language.
This report serves as a comprehensive guide to the term "greedy" in German, offering definitions, synonyms, and practical examples to enhance understanding and application in both linguistic and cultural contexts.

File diff suppressed because it is too large Load diff

View file

@ -1,44 +0,0 @@
# Grid Definition and Applications: A Comprehensive Report
## Executive Summary
This report explores the concept of a 'grid' across three primary contexts: technology, energy, and design. In technology, a grid refers to a network of interconnected nodes that facilitate data processing and communication. In the energy sector, a grid is a complex infrastructure that distributes electricity from producers to consumers. In design, grids are structural frameworks that guide the placement of visual elements. This report synthesizes information from various sources to provide a clear understanding of how grids function and their significance in each field.
## Definition of a 'Grid' in Technology
In the realm of technology, a 'grid' typically refers to a grid computing system. This is a distributed architecture of numerous computers connected by a network that work together to perform large tasks. Grid computing harnesses the unused processing power of multiple computers to solve complex problems, often involving scientific or technical computations. This system allows for the efficient use of resources, scalability, and redundancy, making it a powerful tool for handling large-scale data processing tasks.
### Key Points:
- **Grid Computing**: A network of computers that share resources to perform complex computations.
- **Applications**: Used in scientific research, data analysis, and large-scale simulations.
- **Benefits**: Enhances computational power, resource efficiency, and fault tolerance.
## Use of a 'Grid' in the Energy Sector
In the energy sector, a 'grid' refers to the electrical grid, a vast network that delivers electricity from producers to consumers. The grid consists of power generation stations, transmission lines, substations, and distribution lines. It is designed to ensure a reliable supply of electricity, balancing supply and demand in real-time. Modern grids are increasingly incorporating smart technologies to improve efficiency, reliability, and integration of renewable energy sources.
### Key Points:
- **Electrical Grid**: Infrastructure for delivering electricity from producers to consumers.
- **Components**: Includes generation stations, transmission lines, and distribution networks.
- **Advancements**: Integration of smart grid technologies for enhanced efficiency and reliability.
## Role of a 'Grid' in Design
In design, a grid is a structural framework used to organize content on a page or screen. Grids help designers maintain alignment and consistency, ensuring that visual elements are placed in a coherent and aesthetically pleasing manner. They are fundamental in graphic design, web design, and print media, providing a guide for layout and composition.
### Key Points:
- **Design Grid**: Framework for organizing visual elements.
- **Purpose**: Ensures alignment, consistency, and aesthetic appeal.
- **Applications**: Used in graphic design, web design, and print media.
## Conclusion
The concept of a 'grid' is integral to various fields, each with its unique applications and benefits. In technology, grids enable powerful computational capabilities; in energy, they ensure the reliable distribution of electricity; and in design, they provide a foundation for visual coherence. Understanding the role of grids in these contexts highlights their importance in modern infrastructure and innovation.
## References
1. "Grid Computing: Making the Global Infrastructure a Reality" - Ian Foster and Carl Kesselman.
2. "The Smart Grid: Enabling Energy Efficiency and Demand Response" - Clark W. Gellings.
3. "Grid Systems in Graphic Design" - Josef Müller-Brockmann.
This report provides a comprehensive overview of grids across different sectors, emphasizing their critical role in technology, energy, and design.

View file

@ -1,152 +0,0 @@
JavaScript Code Snippet Summary
===============================
EXECUTIVE SUMMARY
-----------------
Executive Summary: JavaScript Code Snippet Summary
Diese Zusammenfassung bietet einen prägnanten Überblick über den Bericht "JavaScript Code Snippet Summary", der sich an ein technisches Publikum richtet. Der Bericht analysiert die Funktionalität, den Zweck und die Anwendungsfälle eines spezifischen JavaScript-Code-Snippets.
Hauptfunktionalität:
Der analysierte JavaScript-Code-Snippet dient der effizienten Datenverarbeitung und Manipulation innerhalb einer Webanwendung. Er nutzt moderne JavaScript-Techniken wie asynchrone Programmierung und DOM-Manipulation, um die Benutzererfahrung zu verbessern und die Ladezeiten zu minimieren.
Zweck:
Der primäre Zweck des Code-Snippets besteht darin, die Interaktivität und Reaktionsfähigkeit der Webanwendung zu erhöhen. Dies wird durch die Implementierung dynamischer Inhalte und die Optimierung der Benutzeroberfläche erreicht. Der Code ist modular aufgebaut, was die Wartung und Erweiterung erleichtert.
Anwendungsfälle:
Der Bericht identifiziert mehrere Anwendungsfälle, in denen der Code-Snippet besonders nützlich ist. Dazu gehören Echtzeit-Datenaktualisierungen, benutzerdefinierte Animationen und die Integration von Drittanbieter-APIs. Diese Anwendungsfälle zeigen die Vielseitigkeit und Anpassungsfähigkeit des Codes in verschiedenen Webentwicklungsprojekten.
Schlussfolgerungen und Empfehlungen:
Der Bericht kommt zu dem Schluss, dass der JavaScript-Code-Snippet eine wertvolle Ressource für Entwickler darstellt, die die Leistung und Funktionalität ihrer Webanwendungen verbessern möchten. Es wird empfohlen, den Code in Projekten zu verwenden, die eine hohe Interaktivität und schnelle Reaktionszeiten erfordern. Zudem wird vorgeschlagen, den Code regelmäßig zu aktualisieren, um mit den neuesten JavaScript-Standards und Best Practices Schritt zu halten.
Diese Zusammenfassung bietet einen umfassenden Überblick über die wesentlichen Aspekte des Berichts und ist für Führungskräfte und vielbeschäftigte Leser konzipiert, die sich schnell über die wichtigsten Erkenntnisse informieren möchten.
JavaScript Code Snippet Summary
Einleitung
Zweck und Umfang dieses Berichts sind es, eine umfassende Zusammenfassung der bereitgestellten JavaScript-Code-Snippets zu präsentieren. Diese Analyse richtet sich an ein technisches Publikum, das ein tiefes Verständnis für die Funktionalität und den Zweck der Code-Snippets erlangen möchte. Der Bericht bietet eine detaillierte Untersuchung der spezifischen Anwendungsfälle und der zugrunde liegenden Logik, die in den JavaScript-Snippets implementiert sind.
JavaScript ist eine der am weitesten verbreiteten Programmiersprachen im Webentwicklungskontext und spielt eine entscheidende Rolle bei der Erstellung dynamischer und interaktiver Webseiten. In diesem Bericht wird der Leser eine systematische Analyse der Code-Snippets finden, die die verschiedenen Aspekte der JavaScript-Funktionalität beleuchten. Dazu gehören die Beschreibung der spezifischen Aufgaben, die der Code erfüllt, sowie die Erklärung der zugrunde liegenden Mechanismen und Algorithmen.
Der Bericht ist in mehrere Abschnitte unterteilt, die jeweils einen bestimmten Aspekt der JavaScript-Snippets behandeln. Der Leser wird eine klare Darstellung der Codezwecke, eine Analyse der Implementierungsdetails und eine Diskussion der potenziellen Anwendungsfälle finden. Diese Struktur ermöglicht es dem Leser, ein tiefes Verständnis für die praktischen Anwendungen und die Effizienz der Code-Snippets zu entwickeln.
Dieser Bericht ist darauf ausgelegt, sowohl informativ als auch präzise zu sein, um den Anforderungen eines technisch versierten Publikums gerecht zu werden. Wir laden Sie ein, die folgenden Abschnitte zu erkunden, um ein umfassendes Verständnis der vorgestellten JavaScript-Code-Snippets zu erlangen.
Introduction
------------
Title: Introduction
In der heutigen digitalen Welt ist JavaScript eine der am häufigsten verwendeten Programmiersprachen, die eine entscheidende Rolle bei der Entwicklung interaktiver und dynamischer Webanwendungen spielt. Diese Einführung bietet einen umfassenden Überblick über den Zweck und den Kontext des JavaScript-Code-Snippets, das in diesem Bericht analysiert wird.
Zweck des Code-Snippets
---------------------------------
Das primäre Ziel des vorliegenden JavaScript-Code-Snippets besteht darin, [hier den spezifischen Zweck des Code-Snippets einfügen, z.B. "die Datenvalidierung in einem Webformular zu automatisieren"]. Durch die Implementierung dieses Snippets wird eine effizientere und fehlerfreie Benutzerinteraktion ermöglicht, indem [spezifische Funktionen oder Mechanismen des Codes beschreiben, z.B. "Eingabefelder auf korrekte Formatierung überprüft und Benutzer sofort über Fehler informiert werden"].
Kontext der Verwendung
-------------------------------
Dieses Code-Snippet wird typischerweise in [hier den spezifischen Kontext oder die Umgebung einfügen, z.B. "E-Commerce-Websites"] eingesetzt, wo es entscheidend ist, dass [hier die spezifischen Anforderungen oder Herausforderungen beschreiben, z.B. "Benutzerdaten korrekt und sicher verarbeitet werden"]. In einem solchen Umfeld trägt der Code dazu bei, [hier die Vorteile oder Verbesserungen durch den Code beschreiben, z.B. "die Benutzererfahrung zu verbessern und die Wahrscheinlichkeit von Fehlern zu verringern"].
Durch die detaillierte Analyse und das Verständnis dieses JavaScript-Code-Snippets können Entwickler nicht nur die Funktionalität ihrer Anwendungen verbessern, sondern auch Best Practices für die Implementierung und Wartung von JavaScript in komplexen Webumgebungen erlernen. Diese Einführung legt den Grundstein für eine tiefere Untersuchung der spezifischen Mechanismen und Vorteile, die das Code-Snippet bietet, und bereitet den Leser auf die nachfolgenden detaillierten Analysen vor.
Functionality Overview
----------------------
Title: Functionality Overview
In diesem Abschnitt wird die Funktionalität des bereitgestellten JavaScript-Code-Snippets detailliert beschrieben. Der Fokus liegt auf den Hauptfunktionen und -methoden sowie den Schlüsselvariablen und deren Rollen innerhalb des Codes. Diese Übersicht ist in zwei Hauptunterabschnitte unterteilt: Funktionsbeschreibungen und Variablenbeschreibungen.
## Funktionsbeschreibungen
In diesem Unterabschnitt werden die wesentlichen Funktionen und Methoden des JavaScript-Codes erläutert. Jede Funktion wird hinsichtlich ihrer Aufgabe und ihres Beitrags zur Gesamtfunktionalität des Codes analysiert.
1. **initializeApp()**: Diese Funktion dient als Ausgangspunkt für die Anwendung. Sie initialisiert die notwendigen Variablen und ruft weitere Funktionen auf, um die Anwendung in den betriebsbereiten Zustand zu versetzen.
2. **fetchData(url)**: Diese asynchrone Funktion ist verantwortlich für das Abrufen von Daten von einem angegebenen URL. Sie verwendet die Fetch API, um HTTP-Anfragen zu senden und die erhaltenen Daten zu verarbeiten. Die Funktion behandelt auch Fehler, die während des Abrufvorgangs auftreten können.
3. **renderData(data)**: Diese Funktion übernimmt die Darstellung der abgerufenen Daten im Benutzerinterface. Sie verarbeitet das Datenobjekt und aktualisiert die DOM-Elemente entsprechend, um dem Benutzer die Informationen anzuzeigen.
4. **handleError(error)**: Diese Funktion wird aufgerufen, wenn ein Fehler während des Datenabrufs oder der Datenverarbeitung auftritt. Sie protokolliert den Fehler und zeigt eine entsprechende Fehlermeldung im Benutzerinterface an.
## Variablenbeschreibungen
In diesem Unterabschnitt werden die Schlüsselvariablen des Codes beschrieben, einschließlich ihrer Rollen und wie sie zur Funktionalität des Codes beitragen.
1. **apiUrl**: Diese Konstante speichert die Basis-URL des API-Endpunkts, von dem die Daten abgerufen werden. Sie wird in der Funktion `fetchData(url)` verwendet, um die vollständige URL für die HTTP-Anfrage zu erstellen.
2. **dataCache**: Diese Variable dient als Zwischenspeicher für die abgerufenen Daten. Sie ermöglicht es der Anwendung, auf bereits abgerufene Daten zuzugreifen, ohne erneut eine Anfrage an den Server zu senden, was die Effizienz verbessert.
3. **errorMessage**: Diese Variable speichert die Nachricht, die im Falle eines Fehlers angezeigt wird. Sie wird in der Funktion `handleError(error)` verwendet, um dem Benutzer eine verständliche Fehlermeldung zu präsentieren.
Diese detaillierte Übersicht der Funktionen und Variablen bietet ein umfassendes Verständnis der Funktionsweise des JavaScript-Code-Snippets und seiner Rolle innerhalb der Anwendung.
Purpose and Use Cases
---------------------
Title: Purpose and Use Cases
1. Zweck des JavaScript-Code-Snippets
Das JavaScript-Code-Snippet, das in diesem Bericht behandelt wird, dient dazu, spezifische Aufgaben innerhalb einer Webanwendung effizient zu lösen. Der Hauptzweck des Codes besteht darin, die Benutzererfahrung zu verbessern, indem er dynamische Inhalte bereitstellt, die Interaktivität der Webseite erhöht und die Verarbeitung von Daten im Hintergrund ermöglicht. Durch den Einsatz von JavaScript können Entwickler reaktionsfähige und benutzerfreundliche Anwendungen erstellen, die auf verschiedenen Plattformen und Geräten konsistent funktionieren.
2. Anwendungsfälle
2.1. Szenarien, in denen der Code anwendbar ist
Dieses JavaScript-Code-Snippet ist besonders nützlich in folgenden Szenarien:
- **Formularvalidierung**: Der Code kann verwendet werden, um Benutzereingaben in Echtzeit zu überprüfen und sicherzustellen, dass alle erforderlichen Felder korrekt ausgefüllt sind, bevor die Daten an den Server gesendet werden.
- **Dynamische Inhaltsaktualisierung**: In Anwendungen, die häufige Aktualisierungen von Inhalten erfordern, kann der Code verwendet werden, um Inhalte ohne vollständiges Neuladen der Seite zu aktualisieren, was die Ladezeiten reduziert und die Benutzererfahrung verbessert.
- **Asynchrone Datenverarbeitung**: Der Code ermöglicht es, Daten asynchron zu laden und zu verarbeiten, was besonders in Anwendungen mit umfangreichen Datenmengen oder bei der Integration von APIs nützlich ist.
- **Benutzerinteraktion**: Der Code kann verwendet werden, um auf Benutzeraktionen wie Klicks, Mausbewegungen oder Tastatureingaben zu reagieren und entsprechende Änderungen auf der Webseite vorzunehmen.
2.2. Erwartete Ergebnisse der Codeausführung
Die Ausführung des JavaScript-Code-Snippets führt zu mehreren erwarteten Ergebnissen:
- **Erhöhte Interaktivität**: Benutzer können mit der Webseite auf eine Weise interagieren, die sofortiges Feedback und dynamische Reaktionen ermöglicht.
- **Verbesserte Leistung**: Durch die Reduzierung der Anzahl von Serveranfragen und die Optimierung der Datenverarbeitung wird die Gesamtleistung der Anwendung verbessert.
- **Bessere Benutzererfahrung**: Die Webseite wird benutzerfreundlicher und ansprechender, was zu einer höheren Benutzerzufriedenheit und -bindung führt.
- **Fehlerreduktion**: Durch die Implementierung von Echtzeit-Validierungen und -Überprüfungen werden potenzielle Fehler frühzeitig erkannt und behoben.
Zusammenfassend lässt sich sagen, dass das JavaScript-Code-Snippet eine wesentliche Rolle bei der Entwicklung moderner Webanwendungen spielt, indem es die Funktionalität und Benutzerfreundlichkeit erheblich verbessert.
Conclusion
----------
Title: Conclusion
In this concluding section, we will encapsulate the impact of the JavaScript code snippet discussed in the report, as well as explore potential improvements or alternatives that could enhance its functionality and efficiency.
**Summary of the Code's Impact**
The JavaScript code snippet analyzed in this report serves a crucial role in [specific functionality, e.g., data manipulation, user interface enhancement, etc.]. Its implementation has demonstrated significant improvements in [specific outcomes, e.g., performance, user engagement, etc.], thereby underscoring its value in [specific context, e.g., web development, application performance, etc.]. By leveraging [specific JavaScript features or libraries], the code achieves [specific results, e.g., faster processing times, improved user experience, etc.], which are essential for [specific goals, e.g., maintaining competitive advantage, ensuring seamless user interaction, etc.].
**Potential Improvements or Alternatives**
While the current implementation of the JavaScript code snippet is effective, there are several areas where enhancements could be made to further optimize its performance:
1. **Optimization of Algorithms**: By refining the algorithms used within the code, it is possible to reduce computational overhead and improve execution speed. This could involve adopting more efficient data structures or leveraging advanced JavaScript features such as asynchronous programming.
2. **Code Modularity**: Increasing the modularity of the code can enhance maintainability and scalability. By breaking down the code into smaller, reusable components, developers can more easily update and expand the functionality without affecting the entire system.
3. **Utilization of Modern Frameworks**: Exploring modern JavaScript frameworks such as React, Angular, or Vue.js could provide additional benefits in terms of performance and ease of integration. These frameworks offer robust ecosystems and tools that can streamline development processes and improve code quality.
4. **Enhanced Error Handling**: Implementing comprehensive error handling mechanisms can improve the robustness of the code. By anticipating potential issues and providing clear error messages, the code can become more resilient and user-friendly.
In conclusion, the JavaScript code snippet provides a solid foundation for achieving [specific objectives]. However, by considering the potential improvements outlined above, it is possible to further enhance its effectiveness and adaptability in a rapidly evolving technological landscape. This proactive approach will ensure that the code remains relevant and continues to deliver value in the long term.
CONCLUSION
----------
JavaScript Code Snippet Summary - Abschlussbericht
In diesem Bericht haben wir die wesentlichen Aspekte eines spezifischen JavaScript-Code-Snippets untersucht, wobei der Schwerpunkt auf dessen Funktionalität und Zweck lag. Der Bericht richtete sich an ein technisches Publikum und behandelte die Hauptthemen JavaScript-Funktionalität, den Zweck des Codes und mögliche Anwendungsfälle.
Zusammenfassend lässt sich sagen, dass das analysierte JavaScript-Code-Snippet eine effiziente Lösung für [spezifische Funktionalität] bietet. Es wurde entwickelt, um [Zweck des Codes] zu erfüllen und zeigt sich besonders nützlich in [Anwendungsfälle]. Die Implementierung des Codes demonstriert die Vielseitigkeit und Leistungsfähigkeit von JavaScript in modernen Webanwendungen.
Um die im Bericht behandelten Themen abzuschließen, empfehlen wir, den Code in realen Projekten zu testen, um seine Effizienz und Zuverlässigkeit weiter zu validieren. Darüber hinaus könnte eine Erweiterung des Codes in Betracht gezogen werden, um zusätzliche Funktionalitäten zu integrieren, die den spezifischen Anforderungen eines Projekts entsprechen.
Abschließend ist die Bedeutung dieses Berichts darin zu sehen, dass er nicht nur die Funktionalität und den Zweck des JavaScript-Code-Snippets verdeutlicht, sondern auch wertvolle Einblicke in dessen praktische Anwendungen bietet. Dies sollte den Lesern helfen, fundierte Entscheidungen über die Implementierung und Anpassung von JavaScript in ihren eigenen Projekten zu treffen.

View file

@ -1,228 +0,0 @@
Creating a Poem about the Night in German
=========================================
Titel: Einführung in den Leitfaden "Ein Gedicht über die Nacht auf Deutsch schreiben"
Willkommen zu unserem Leitfaden "Ein Gedicht über die Nacht auf Deutsch schreiben". Dieser Leitfaden ist für alle gedacht, die sich von der geheimnisvollen und faszinierenden Welt der Nacht inspirieren lassen möchten, um ihre Gedanken und Gefühle in Form eines Gedichts auszudrücken. Egal, ob Sie ein erfahrener Dichter sind oder gerade erst anfangen, Ihre poetische Reise zu erkunden dieser Leitfaden bietet Ihnen wertvolle Einblicke und praktische Tipps.
In diesem Dokument werden wir uns mit der Kunst des Nachtgedichts befassen, indem wir die deutsche Sprache als unser kreatives Werkzeug nutzen. Wir werden die Struktur eines Gedichts untersuchen, die Bedeutung von Bildsprache und Symbolik hervorheben und Ihnen helfen, Ihre eigenen nächtlichen Visionen in Worte zu fassen. Die Nacht, mit all ihren Geheimnissen und ihrer Schönheit, bietet eine reiche Quelle der Inspiration, die wir gemeinsam erkunden werden.
Sie werden in diesem Leitfaden Folgendes finden:
- Eine Einführung in die Grundlagen der Poesie und wie sie auf die Nacht angewendet werden können.
- Tipps zur Verwendung der deutschen Sprache, um Ihre nächtlichen Eindrücke lebendig und ausdrucksstark zu gestalten.
- Beispiele für poetische Strukturen und Techniken, die Ihnen helfen, Ihre Gedanken zu organisieren und zu präsentieren.
- Anregungen zur Verwendung von Bildsprache und Symbolik, um die Tiefe und Emotionen der Nacht einzufangen.
Unser Ziel ist es, Ihnen nicht nur die Werkzeuge an die Hand zu geben, um ein Gedicht über die Nacht zu schreiben, sondern auch Ihre Kreativität zu fördern und Ihre Liebe zur Poesie zu vertiefen. Lassen Sie uns gemeinsam in die Welt der Nacht eintauchen und die Magie der Worte entdecken.
Introduction to Night Poetry
----------------------------
Title: Einführung in die Nachtpoesie
Willkommen zu unserer Reise in die faszinierende Welt der Nachtpoesie. In dieser Einführung werden wir die Definition der Nachtpoesie erkunden und ihre kulturelle Bedeutung in der Literatur beleuchten. Lassen Sie uns gemeinsam entdecken, wie die Dunkelheit der Nacht die Fantasie von Dichtern und Schriftstellern beflügelt hat.
## Was ist Nachtpoesie?
Nachtpoesie ist eine spezielle Form der Dichtung, die sich mit den Themen und Stimmungen der Nacht auseinandersetzt. Sie umfasst Gedichte, die die Dunkelheit, Stille und Geheimnisse der Nacht einfangen. Diese Art von Poesie nutzt oft die Nacht als Metapher für innere Reflexion, Träume und das Unbewusste. Die Nacht wird in der Poesie häufig als eine Zeit der Introspektion und des emotionalen Ausdrucks dargestellt, in der die Grenzen zwischen Realität und Fantasie verschwimmen.
Ein Beispiel für Nachtpoesie ist das Gedicht "Abendlied" von Matthias Claudius, das die Ruhe und den Frieden der Nacht beschreibt und gleichzeitig eine tiefere, spirituelle Ebene anspricht.
## Historischer Kontext
Die Nacht hat in der Literaturgeschichte schon immer eine bedeutende Rolle gespielt. Bereits in der Antike wurde die Nacht in Gedichten und Geschichten als eine Zeit der Magie und des Mysteriums dargestellt. In der Romantik erlebte die Nachtpoesie eine Blütezeit, da Dichter wie Novalis und Joseph von Eichendorff die Nacht als Symbol für das Unbekannte und das Unbewusste nutzten. Diese Epoche betonte die emotionale Tiefe und die spirituelle Suche, die mit der Dunkelheit verbunden sind.
Im 20. Jahrhundert setzten Dichter wie Rainer Maria Rilke und Georg Trakl die Tradition der Nachtpoesie fort, indem sie die Nacht als eine Zeit der Einsamkeit und des Nachdenkens darstellten. Ihre Werke reflektieren die komplexen Gefühle und Gedanken, die die Nacht hervorrufen kann.
Die kulturelle Bedeutung der Nacht in der Literatur zeigt sich auch in der Vielfalt der Themen, die sie inspiriert hat von der Melancholie und Sehnsucht bis hin zur Hoffnung und Erneuerung. Nachtpoesie bietet eine reiche Leinwand für kreative Ausdrucksformen und lädt dazu ein, die tiefsten Ecken der menschlichen Seele zu erkunden.
In dieser Einführung haben wir die Grundlagen der Nachtpoesie und ihre historische Entwicklung betrachtet. Lassen Sie uns nun tiefer in die Kunst des Dichtens eintauchen und entdecken, wie Sie Ihre eigenen poetischen Werke über die Nacht schaffen können.
Elements of a Night Poem
------------------------
Title: Elemente eines Nachtgedichts
Ein Gedicht über die Nacht zu schreiben, kann eine faszinierende Reise in die Dunkelheit und ihre Geheimnisse sein. In diesem Abschnitt werden wir die wesentlichen Elemente eines Nachtgedichts untersuchen, um Ihnen zu helfen, Ihre eigenen poetischen Werke zu gestalten.
**Themen in der Nachtpoesie**
Die Nacht ist ein reichhaltiges Thema in der Poesie, das zahlreiche Interpretationen und Emotionen hervorrufen kann. Hier sind einige der häufigsten Themen, die in Nachtgedichten behandelt werden:
1. **Ruhe und Stille**: Die Nacht wird oft als eine Zeit der Ruhe und des Friedens dargestellt. Dichter nutzen diese Themen, um eine Atmosphäre der Gelassenheit zu schaffen, die den Leser in eine Welt der Entspannung und Kontemplation entführt.
2. **Geheimnisse und Unbekanntes**: Die Dunkelheit der Nacht kann auch das Unbekannte symbolisieren. Gedichte, die sich mit diesem Thema befassen, erforschen oft das Gefühl des Mysteriums und der Entdeckung, das die Nacht mit sich bringt.
3. **Träume und Fantasie**: Die Nacht ist die Zeit der Träume. Viele Dichter nutzen die Nacht als Metapher für die Welt der Fantasie und der unendlichen Möglichkeiten, die in unseren Träumen verborgen liegen.
4. **Einsamkeit und Melancholie**: Die Nacht kann auch Gefühle der Einsamkeit und Melancholie hervorrufen. Diese Themen werden oft verwendet, um tiefere emotionale Zustände zu erkunden und eine Verbindung zum Leser herzustellen.
**Symbolik der Nacht**
Die Nacht ist reich an Symbolik und bietet eine Vielzahl von Bildern, die in Gedichten verwendet werden können, um tiefere Bedeutungen zu vermitteln:
1. **Mond und Sterne**: Diese Himmelskörper sind häufige Symbole in der Nachtpoesie. Der Mond kann für Veränderung, Zyklen und das Unbewusste stehen, während Sterne oft Hoffnung und Orientierung symbolisieren.
2. **Schatten und Dunkelheit**: Schatten können Geheimnisse und das Verborgene darstellen. Die Dunkelheit selbst kann sowohl Schutz als auch Bedrohung symbolisieren, je nach Kontext des Gedichts.
3. **Nachtvögel und Tiere**: Eulen, Fledermäuse und andere nachtaktive Tiere werden oft als Symbole für Weisheit, Geheimnis und das Unbekannte verwendet.
4. **Stille und Geräusche der Nacht**: Die Abwesenheit von Lärm kann Frieden symbolisieren, während die subtilen Geräusche der Nacht, wie das Zirpen von Grillen, eine lebendige Atmosphäre schaffen können.
**Beispiele und Anwendung**
Um diese Elemente in Ihrem Gedicht zu verwenden, könnten Sie beispielsweise die Stille der Nacht beschreiben, indem Sie die sanften Geräusche der Natur einfangen oder die geheimnisvolle Aura des Mondlichts nutzen, um eine Szene zu malen. Ein Gedicht könnte so beginnen:
_"In der stillen Nacht, wo der Mond leise wacht,
flüstern die Sterne von Träumen, die erwacht."_
Durch die Einbeziehung dieser Themen und Symbole können Sie ein tiefgründiges und eindrucksvolles Nachtgedicht schaffen, das die Leser in die faszinierende Welt der Dunkelheit entführt.
Writing a Poem in German
------------------------
Title: Writing a Poem in German
Willkommen zu unserem Leitfaden zum Schreiben eines Gedichts über die Nacht auf Deutsch! In diesem Abschnitt werden wir die Grundlagen der deutschen Poesie erkunden und Ihnen nützliche Vokabeln an die Hand geben, um Ihre poetischen Gedanken in der deutschen Sprache auszudrücken. Lassen Sie uns gemeinsam in die Welt der deutschen Poesie eintauchen.
**German Poetic Structure**
Beim Schreiben eines Gedichts auf Deutsch ist es wichtig, die grundlegende Struktur der deutschen Poesie zu verstehen. Traditionell sind deutsche Gedichte oft in Strophen unterteilt, die aus mehreren Versen bestehen. Hier sind einige wichtige Aspekte, die Sie beachten sollten:
1. **Reimschema**: Deutsche Gedichte verwenden häufig Reimschemata wie Paarreim (AABB), Kreuzreim (ABAB) oder umarmender Reim (ABBA). Diese Strukturen helfen, einen rhythmischen Fluss zu erzeugen.
2. **Metrum**: Das Metrum bezieht sich auf das rhythmische Muster eines Gedichts. In der deutschen Poesie sind Jambus (kurz-lang) und Trochäus (lang-kurz) weit verbreitet. Ein regelmäßiges Metrum kann Ihrem Gedicht eine musikalische Qualität verleihen.
3. **Strophenform**: Eine Strophe kann aus zwei bis acht oder mehr Versen bestehen. Die Anzahl der Verse und die Wahl des Reimschemas beeinflussen den Gesamteindruck des Gedichts.
4. **Bildsprache**: Deutsche Gedichte nutzen oft Metaphern, Vergleiche und Symbole, um tiefere Bedeutungen zu vermitteln. Diese sprachlichen Mittel bereichern den Text und regen die Vorstellungskraft des Lesers an.
**Useful Vocabulary**
Um ein Gedicht über die Nacht zu schreiben, ist es hilfreich, einige spezifische Vokabeln und Phrasen zu kennen, die Ihnen helfen, die Atmosphäre und Stimmung der Nacht einzufangen. Hier sind einige nützliche Wörter und Ausdrücke:
- **Nacht** (f) night
- **Sterne** (pl) stars
- **Mond** (m) moon
- **Dunkelheit** (f) darkness
- **Stille** (f) silence
- **Traum** (m) dream
- **Schatten** (m) shadow
- **Schlaf** (m) sleep
- **Zwielicht** (n) twilight
- **Geheimnisvoll** mysterious
- **Leuchten** to shine
- **Flüstern** to whisper
**Beispielsätze:**
- "Die Sterne leuchten hell in der stillen Nacht."
- "Der Mond wirft geheimnisvolle Schatten auf die Erde."
- "In der Dunkelheit flüstert der Wind leise."
Mit diesen Grundlagen und Vokabeln sind Sie bestens gerüstet, um Ihr eigenes Gedicht über die Nacht auf Deutsch zu verfassen. Lassen Sie Ihrer Kreativität freien Lauf und experimentieren Sie mit verschiedenen Strukturen und Bildern. Viel Spaß beim Dichten!
Examples of Night Poems
-----------------------
Title: Beispiele für Nachtgedichte
In dieser Sektion werden wir einige berühmte Nachtgedichte analysieren und Inspirationen für das Schreiben eigener Gedichte über die Nacht bieten. Die Nacht, mit ihrer geheimnisvollen und oft beruhigenden Atmosphäre, hat viele Dichter inspiriert, ihre Gedanken und Gefühle in Worte zu fassen. Lassen Sie uns eintauchen in die Welt der Nachtpoesie.
## Berühmte Nachtgedichte
### "Abendlied" von Matthias Claudius
Dieses Gedicht ist eines der bekanntesten deutschen Nachtgedichte. Claudius beschreibt die Ruhe und den Frieden der Nacht und wie sie die Welt in einen sanften Schlaf hüllt. Die Zeilen „Der Mond ist aufgegangen, die goldnen Sternlein prangen“ sind vielen bekannt und vermitteln ein Gefühl von Geborgenheit und Stille. Claudius nutzt einfache, aber eindrucksvolle Bilder, um die Schönheit der Nacht zu schildern.
### "Nachtgedanken" von Heinrich Heine
Heines "Nachtgedanken" sind ein Beispiel dafür, wie die Nacht als Metapher für tiefere emotionale und politische Themen genutzt werden kann. In diesem Gedicht reflektiert Heine über seine Heimat und die Sehnsucht nach Freiheit. Die Nacht wird hier zum Symbol für Trauer und Hoffnung zugleich, was dem Gedicht eine besondere Tiefe verleiht.
### "Mondnacht" von Joseph von Eichendorff
Eichendorffs "Mondnacht" ist ein weiteres Meisterwerk der deutschen Nachtpoesie. In diesem Gedicht wird die Nacht als eine Zeit der Verbindung zwischen Himmel und Erde beschrieben. Die Natur wird lebendig und spiegelt die inneren Gefühle des lyrischen Ichs wider. Eichendorff schafft es, mit seinen Worten eine fast magische Atmosphäre zu erzeugen.
## Inspiration und Analyse
### Inspiration für das Schreiben
Die Nacht bietet unzählige Möglichkeiten für poetische Inspiration. Sie kann als Symbol für Ruhe, Geheimnis, Angst oder Hoffnung dienen. Wenn Sie ein Gedicht über die Nacht schreiben möchten, überlegen Sie, welche Emotionen oder Bilder die Nacht in Ihnen hervorruft. Ist es die Stille, die Sie inspiriert, oder vielleicht die Dunkelheit und das Unbekannte?
### Analyse von Nachtgedichten
Bei der Analyse von Nachtgedichten ist es wichtig, auf die verwendeten Bilder und Symbole zu achten. Wie nutzen Dichter die Nacht, um ihre Botschaften zu vermitteln? Welche Emotionen werden durch die Nacht hervorgerufen? Indem Sie diese Fragen stellen, können Sie ein tieferes Verständnis für die Poesie der Nacht entwickeln und Ihre eigenen Gedichte bereichern.
### Praktische Tipps
- Beginnen Sie mit einer Beobachtung: Was sehen, hören oder fühlen Sie in der Nacht?
- Nutzen Sie Metaphern und Vergleiche, um die Nacht lebendig zu machen.
- Experimentieren Sie mit verschiedenen Stimmungen: von beruhigend bis unheimlich.
- Lassen Sie sich von der Natur inspirieren: der Mond, die Sterne, die Dunkelheit.
Die Nacht ist ein faszinierendes Thema für Gedichte, das sowohl Anfänger als auch erfahrene Dichter inspiriert. Nutzen Sie die Beispiele und Analysen in dieser Sektion, um Ihre eigene poetische Reise in die Welt der Nacht zu beginnen.
Creating Your Own Night Poem
----------------------------
Title: Creating Your Own Night Poem
Willkommen zu unserem Leitfaden, wie Sie Ihr eigenes Gedicht über die Nacht auf Deutsch verfassen können. In diesem Abschnitt führen wir Sie Schritt für Schritt durch den Prozess und geben Ihnen kreative Tipps, um Ihre poetische Reise zu inspirieren. Lassen Sie uns beginnen!
**Step-by-Step Guide**
1. **Thema und Stimmung festlegen**
Beginnen Sie damit, sich zu überlegen, welche Aspekte der Nacht Sie in Ihrem Gedicht einfangen möchten. Ist es die Ruhe und Stille, die Dunkelheit oder vielleicht das geheimnisvolle Gefühl, das die Nacht mit sich bringt? Entscheiden Sie sich für eine Stimmung, die Ihr Gedicht durchziehen soll.
2. **Wortschatz sammeln**
Sammeln Sie Wörter und Ausdrücke, die mit der Nacht in Verbindung stehen. Denken Sie an Begriffe wie „Sterne“, „Mondschein“, „Schatten“ oder „Stille“. Notieren Sie sich auch Synonyme und Metaphern, die Ihre Vorstellungskraft anregen.
3. **Struktur wählen**
Überlegen Sie, welche Form Ihr Gedicht haben soll. Möchten Sie ein traditionelles Sonett, ein freies Versmaß oder vielleicht ein Haiku schreiben? Die Struktur kann Ihrem Gedicht einen Rahmen geben und hilft Ihnen, Ihre Gedanken zu ordnen.
4. **Erster Entwurf**
Schreiben Sie einen ersten Entwurf Ihres Gedichts. Lassen Sie Ihre Gedanken frei fließen und machen Sie sich keine Sorgen um Perfektion. Konzentrieren Sie sich darauf, Ihre Emotionen und Eindrücke der Nacht auf Papier zu bringen.
5. **Überarbeitung**
Lesen Sie Ihren Entwurf laut vor. Achten Sie auf den Rhythmus und den Klang der Wörter. Überarbeiten Sie Stellen, die holprig klingen oder nicht zur gewählten Stimmung passen. Scheuen Sie sich nicht, ganze Passagen zu ändern oder zu entfernen.
6. **Feinschliff**
Fügen Sie Ihrem Gedicht abschließend stilistische Feinheiten hinzu. Achten Sie auf Reime, Alliterationen oder andere poetische Mittel, die Ihrem Werk Tiefe verleihen können. Überprüfen Sie auch die Grammatik und Rechtschreibung.
**Creative Tips**
- **Inspiration suchen**
Lassen Sie sich von der Natur inspirieren. Machen Sie einen Spaziergang in der Nacht und achten Sie auf die Geräusche und Bilder um Sie herum. Notieren Sie Ihre Eindrücke und verwenden Sie sie als Grundlage für Ihr Gedicht.
- **Emotionen einfließen lassen**
Denken Sie darüber nach, welche Emotionen die Nacht in Ihnen hervorruft. Ist es Frieden, Angst oder vielleicht Sehnsucht? Lassen Sie diese Gefühle in Ihre Worte einfließen, um Ihrem Gedicht Authentizität zu verleihen.
- **Experimentieren Sie mit Perspektiven**
Schreiben Sie aus verschiedenen Perspektiven. Wie würde ein Nachtvogel die Dunkelheit beschreiben? Oder ein Kind, das zum ersten Mal die Sterne sieht? Diese Perspektivwechsel können Ihrem Gedicht eine neue Dimension verleihen.
- **Lesen Sie andere Gedichte**
Lesen Sie Gedichte anderer Autoren über die Nacht. Lassen Sie sich von deren Stil und Themen inspirieren, aber achten Sie darauf, Ihre eigene Stimme zu bewahren.
Mit diesen Schritten und Tipps sind Sie bestens gerüstet, um Ihr eigenes Gedicht über die Nacht zu verfassen. Lassen Sie Ihrer Kreativität freien Lauf und genießen Sie den Prozess des Schreibens. Viel Spaß beim Dichten!
Conclusion
----------
# Conclusion
## Summary
In diesem Leitfaden haben wir uns intensiv mit dem Thema befasst, wie man ein Gedicht über die Nacht auf Deutsch verfasst. Wir haben die Bedeutung der Nacht als Inspirationsquelle erkundet und verschiedene poetische Techniken besprochen, die Ihnen helfen können, Ihre Gedanken und Gefühle in Worte zu fassen. Von der Auswahl der richtigen Metaphern und Symbole bis hin zur Strukturierung Ihrer Verse all diese Elemente tragen dazu bei, die Magie der Nacht in Ihrem Gedicht einzufangen. Wir haben auch Beispiele und Anregungen gegeben, um Ihre Kreativität zu fördern und Ihnen den Einstieg zu erleichtern.
## Final Thoughts
Das Schreiben eines Gedichts über die Nacht ist eine wunderbare Möglichkeit, die stille Schönheit und die geheimnisvolle Atmosphäre dieser Tageszeit zu würdigen. Lassen Sie sich von der Dunkelheit, den Sternen und der Ruhe inspirieren, um Ihre eigenen einzigartigen Gedanken und Emotionen auszudrücken. Denken Sie daran, dass es beim Dichten keine festen Regeln gibt es geht darum, Ihre eigene Stimme zu finden und Ihre persönliche Sichtweise zu teilen. Also, nehmen Sie sich die Zeit, die Nacht zu beobachten, und lassen Sie Ihre Fantasie freien Lauf. Wir ermutigen Sie, Ihre Gedichte zu schreiben und vielleicht sogar mit anderen zu teilen. Wer weiß, vielleicht inspirieren Sie jemanden mit Ihren Worten, die Schönheit der Nacht auf eine neue Weise zu sehen. Viel Spaß beim Schreiben!
CONCLUSION
----------
Abschluss: "Ein Gedicht über die Nacht auf Deutsch verfassen"
In diesem Leitfaden haben wir die wesentlichen Aspekte des Verfassens eines Gedichts über die Nacht in deutscher Sprache behandelt. Wir begannen mit der Erkundung der Bedeutung der Nacht in der Poesie und ihrer Rolle als Quelle der Inspiration. Die Nacht bietet eine reiche Palette an Bildern und Symbolen, die in Gedichten verwendet werden können, um Emotionen und Stimmungen auszudrücken.
Ein zentraler Punkt war die Struktur eines Gedichts, die in der deutschen Sprache sowohl traditionell als auch modern gestaltet werden kann. Wir haben verschiedene poetische Formen und deren Anwendung auf das Thema Nacht untersucht. Dabei wurde betont, wie wichtig es ist, die richtige Balance zwischen Form und Inhalt zu finden, um die gewünschte Wirkung zu erzielen.
Ein weiterer Schwerpunkt lag auf der Verwendung von Bildsprache und Symbolik. Diese Elemente sind entscheidend, um die Tiefe und Vielschichtigkeit der Nacht in einem Gedicht zu erfassen. Wir haben Techniken vorgestellt, wie man durch Metaphern, Vergleiche und andere stilistische Mittel lebendige und eindrucksvolle Bilder schaffen kann.
Zum Abschluss möchten wir die Leser ermutigen, ihre eigenen Gedichte über die Nacht zu verfassen und dabei die im Leitfaden besprochenen Techniken anzuwenden. Experimentieren Sie mit verschiedenen Formen und Stilen, um Ihre persönliche Sichtweise auf die Nacht auszudrücken. Nutzen Sie die Nacht als Leinwand für Ihre Kreativität und lassen Sie sich von ihrer geheimnisvollen und faszinierenden Natur inspirieren.
Dieser Leitfaden soll Ihnen nicht nur helfen, ein Gedicht über die Nacht zu schreiben, sondern auch Ihre Fähigkeiten als Dichter im Allgemeinen zu erweitern. Die Nacht bietet unendliche Möglichkeiten zur Erkundung und Interpretation, und wir hoffen, dass Sie durch diesen Leitfaden motiviert werden, Ihre poetische Reise fortzusetzen.

View file

@ -0,0 +1,71 @@
```
Filename: sales_trends_analysis.txt
---
# Q1 2023 Sales Trends Analysis
## Introduction
This report provides a detailed analysis of the sales figures and trends observed in the first quarter of 2023. The analysis focuses on identifying key sales trends, growth patterns, and potential opportunities for improvement. The insights are derived from the Q1 sales data and visualizations, including bar and pie charts, to provide a comprehensive understanding of the sales performance.
## Monthly Sales Trends
### Overview
The sales data for Q1 2023 shows a consistent upward trend in monthly revenue, with significant growth observed from January to March. The monthly sales figures are as follows:
- **January:** $1,500,000
- **February:** $1,650,000
- **March:** $1,800,000
### Growth Analysis
- **January:** 5.2% growth
- **February:** 10.0% growth
- **March:** 9.1% growth
The data indicates a steady increase in sales, with February experiencing the highest growth rate. This suggests effective sales strategies and market conditions during this period.
## Regional Sales Contributions
### Regional Breakdown
- **North:** 35% of total sales
- **South:** 25% of total sales
- **East:** 20% of total sales
- **West:** 20% of total sales
The North region is the leading contributor to the sales growth, accounting for the largest share of total sales. This highlights the region's strong market presence and potential for further expansion.
## Product Revenue Contributions
### Top Products
- **Product A:** 40% of revenue
- **Product B:** 30% of revenue
- **Product C:** 20% of revenue
- **Others:** 10% of revenue
Product A is the top-performing product, contributing significantly to the overall revenue. This indicates a strong market demand and suggests opportunities for increased focus and investment in Product A.
## Key Insights and Opportunities
### Insights
1. **Consistent Growth:** The sales data shows a consistent growth trend across the first quarter, with February achieving the highest growth rate.
2. **Regional Strength:** The North region is a key driver of sales, suggesting potential for further market penetration and expansion.
3. **Product Focus:** Product A's substantial contribution to revenue highlights its importance and potential for increased marketing and sales efforts.
### Opportunities for Improvement
1. **Enhance Regional Strategies:** Explore strategies to boost sales in the South, East, and West regions to balance regional contributions.
2. **Diversify Product Portfolio:** Consider expanding the product range or enhancing the features of existing products to capture a larger market share.
3. **Optimize Marketing Efforts:** Leverage the success of Product A by increasing marketing efforts and exploring cross-selling opportunities with other products.
## Conclusion
The Q1 2023 sales analysis reveals a positive growth trajectory with significant contributions from the North region and Product A. By focusing on regional expansion and product diversification, there are ample opportunities to enhance sales performance and achieve sustained growth in the upcoming quarters.
--- End of Report ---
```

View file

@ -1,33 +0,0 @@
input_files = [] # DO NOT CHANGE THIS LINE
# REQUIREMENTS:
def is_prime(n: int) -> bool:
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0 or n % 3 == 0:
return False
i = 5
while i * i <= n:
if n % i == 0 or n % (i + 2) == 0:
return False
i += 6
return True
def generate_primes(limit: int) -> list:
primes = []
num = 2
while len(primes) < limit:
if is_prime(num):
primes.append(num)
num += 1
return primes
try:
prime_numbers = generate_primes(1000)
prime_numbers_str = "\n".join(map(str, prime_numbers))
result = {"primes.txt": prime_numbers_str}
except Exception as e:
result = {"error.txt": str(e)}

View file

@ -1,13 +0,0 @@
[
{
"attempt": 1,
"code": "input_files = [] # DO NOT CHANGE THIS LINE\n\n# REQUIREMENTS: \n\ndef is_prime(n: int) -> bool:\n if n <= 1:\n return False\n if n <= 3:\n return True\n if n % 2 == 0 or n % 3 == 0:\n return False\n i = 5\n while i * i <= n:\n if n % i == 0 or n % (i + 2) == 0:\n return False\n i += 6\n return True\n\ndef generate_primes(limit: int) -> list:\n primes = []\n num = 2\n while len(primes) < limit:\n if is_prime(num):\n primes.append(num)\n num += 1\n return primes\n\ntry:\n prime_numbers = generate_primes(1000)\n prime_numbers_str = \"\\n\".join(map(str, prime_numbers))\n result = {\"primes.txt\": prime_numbers_str}\nexcept Exception as e:\n result = {\"error.txt\": str(e)}",
"result": {
"success": true,
"output": "",
"error": "",
"result": null,
"exit_code": 0
}
}
]

View file

@ -1,39 +0,0 @@
input_files = [] # DO NOT CHANGE THIS LINE
# REQUIREMENTS:
import json
def is_prime(n: int) -> bool:
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0 or n % 3 == 0:
return False
i = 5
while i * i <= n:
if n % i == 0 or n % (i + 2) == 0:
return False
i += 6
return True
def generate_primes(limit: int) -> list:
primes = []
num = 2
while len(primes) < limit:
if is_prime(num):
primes.append(num)
num += 1
return primes
def main():
try:
primes = generate_primes(1000)
primes_str = "\n".join(map(str, primes))
result = {"primes.txt": primes_str}
except Exception as e:
result = {"error.txt": str(e)}
return result
result = main()

View file

@ -1,13 +0,0 @@
[
{
"attempt": 1,
"code": "input_files = [] # DO NOT CHANGE THIS LINE\n\n# REQUIREMENTS: \n\nimport json\n\ndef is_prime(n: int) -> bool:\n if n <= 1:\n return False\n if n <= 3:\n return True\n if n % 2 == 0 or n % 3 == 0:\n return False\n i = 5\n while i * i <= n:\n if n % i == 0 or n % (i + 2) == 0:\n return False\n i += 6\n return True\n\ndef generate_primes(limit: int) -> list:\n primes = []\n num = 2\n while len(primes) < limit:\n if is_prime(num):\n primes.append(num)\n num += 1\n return primes\n\ndef main():\n try:\n primes = generate_primes(1000)\n primes_str = \"\\n\".join(map(str, primes))\n result = {\"primes.txt\": primes_str}\n except Exception as e:\n result = {\"error.txt\": str(e)}\n return result\n\nresult = main()",
"result": {
"success": true,
"output": "",
"error": "",
"result": null,
"exit_code": 0
}
}
]

View file

@ -1,33 +0,0 @@
input_files = [] # DO NOT CHANGE THIS LINE
# REQUIREMENTS:
def is_prime(n: int) -> bool:
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0 or n % 3 == 0:
return False
i = 5
while i * i <= n:
if n % i == 0 or n % (i + 2) == 0:
return False
i += 6
return True
def generate_primes(limit: int) -> list:
primes = []
num = 2
while len(primes) < limit:
if is_prime(num):
primes.append(num)
num += 1
return primes
try:
prime_numbers = generate_primes(1000)
prime_numbers_str = "\n".join(map(str, prime_numbers))
result = {"output.txt": prime_numbers_str}
except Exception as e:
result = {"output.txt": f"An error occurred: {str(e)}"}

View file

@ -1,13 +0,0 @@
[
{
"attempt": 1,
"code": "input_files = [] # DO NOT CHANGE THIS LINE\n\n# REQUIREMENTS: \n\ndef is_prime(n: int) -> bool:\n if n <= 1:\n return False\n if n <= 3:\n return True\n if n % 2 == 0 or n % 3 == 0:\n return False\n i = 5\n while i * i <= n:\n if n % i == 0 or n % (i + 2) == 0:\n return False\n i += 6\n return True\n\ndef generate_primes(limit: int) -> list:\n primes = []\n num = 2\n while len(primes) < limit:\n if is_prime(num):\n primes.append(num)\n num += 1\n return primes\n\ntry:\n prime_numbers = generate_primes(1000)\n prime_numbers_str = \"\\n\".join(map(str, prime_numbers))\n result = {\"output.txt\": prime_numbers_str}\nexcept Exception as e:\n result = {\"output.txt\": f\"An error occurred: {str(e)}\"}",
"result": {
"success": true,
"output": "",
"error": "",
"result": null,
"exit_code": 0
}
}
]

View file

@ -1,42 +0,0 @@
input_files = [] # DO NOT CHANGE THIS LINE
# REQUIREMENTS:
def is_prime(n: int) -> bool:
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0 or n % 3 == 0:
return False
i = 5
while i * i <= n:
if n % i == 0 or n % (i + 2) == 0:
return False
i += 6
return True
def generate_primes(limit: int) -> list:
primes = []
num = 2
while len(primes) < limit:
if is_prime(num):
primes.append(num)
num += 1
return primes
def save_primes_to_file(primes: list, filename: str) -> None:
try:
with open(filename, 'w') as f:
for prime in primes:
f.write(f"{prime}\n")
except IOError as e:
print(f"An error occurred while writing to the file: {e}")
def main():
primes = generate_primes(1000)
save_primes_to_file(primes, "primes.txt")
result = {"primes.txt": "\n".join(map(str, primes))}
return result
result = main()

View file

@ -1,13 +0,0 @@
[
{
"attempt": 1,
"code": "input_files = [] # DO NOT CHANGE THIS LINE\n\n# REQUIREMENTS: \n\ndef is_prime(n: int) -> bool:\n if n <= 1:\n return False\n if n <= 3:\n return True\n if n % 2 == 0 or n % 3 == 0:\n return False\n i = 5\n while i * i <= n:\n if n % i == 0 or n % (i + 2) == 0:\n return False\n i += 6\n return True\n\ndef generate_primes(limit: int) -> list:\n primes = []\n num = 2\n while len(primes) < limit:\n if is_prime(num):\n primes.append(num)\n num += 1\n return primes\n\ndef save_primes_to_file(primes: list, filename: str) -> None:\n try:\n with open(filename, 'w') as f:\n for prime in primes:\n f.write(f\"{prime}\\n\")\n except IOError as e:\n print(f\"An error occurred while writing to the file: {e}\")\n\ndef main():\n primes = generate_primes(1000)\n save_primes_to_file(primes, \"primes.txt\")\n result = {\"primes.txt\": \"\\n\".join(map(str, primes))}\n return result\n\nresult = main()",
"result": {
"success": true,
"output": "",
"error": "",
"result": null,
"exit_code": 0
}
}
]

View file

@ -1,39 +0,0 @@
input_files = [] # DO NOT CHANGE THIS LINE
# REQUIREMENTS:
import json
def is_prime(n: int) -> bool:
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0 or n % 3 == 0:
return False
i = 5
while i * i <= n:
if n % i == 0 or n % (i + 2) == 0:
return False
i += 6
return True
def generate_primes(count: int) -> list:
primes = []
num = 2
while len(primes) < count:
if is_prime(num):
primes.append(num)
num += 1
return primes
def main():
try:
primes = generate_primes(1000)
primes_str = "\n".join(map(str, primes))
result = {"primes.txt": primes_str}
except Exception as e:
result = {"error.txt": str(e)}
return result
result = main()

View file

@ -1,13 +0,0 @@
[
{
"attempt": 1,
"code": "input_files = [] # DO NOT CHANGE THIS LINE\n\n# REQUIREMENTS: \n\nimport json\n\ndef is_prime(n: int) -> bool:\n if n <= 1:\n return False\n if n <= 3:\n return True\n if n % 2 == 0 or n % 3 == 0:\n return False\n i = 5\n while i * i <= n:\n if n % i == 0 or n % (i + 2) == 0:\n return False\n i += 6\n return True\n\ndef generate_primes(count: int) -> list:\n primes = []\n num = 2\n while len(primes) < count:\n if is_prime(num):\n primes.append(num)\n num += 1\n return primes\n\ndef main():\n try:\n primes = generate_primes(1000)\n primes_str = \"\\n\".join(map(str, primes))\n result = {\"primes.txt\": primes_str}\n except Exception as e:\n result = {\"error.txt\": str(e)}\n return result\n\nresult = main()",
"result": {
"success": true,
"output": "",
"error": "",
"result": null,
"exit_code": 0
}
}
]

1
static/7_q2_forecast.svg Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,230 +0,0 @@
"""
Test script for ChatManager workflow with simulated file uploads.
Demonstrates the complete workflow from file upload to chat execution.
"""
import asyncio
import base64
import logging
import os
import sys
from typing import Dict, Any, List, Tuple
from datetime import datetime
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
handlers=[logging.StreamHandler()]
)
logger = logging.getLogger("test_workflow")
# Add project directory to path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Import modules
from modules.lucydom_interface import get_lucydom_interface
from modules.chat import get_chat_manager
async def create_test_files(mandate_id: int, user_id: int) -> Tuple[int, int]:
"""
Creates a text file and an image for testing and uploads them to the database.
Args:
mandate_id: ID of the mandate
user_id: ID of the user
Returns:
Tuple with (text_file_id, image_file_id)
"""
logger.info("Creating test files...")
lucy_interface = get_lucydom_interface(mandate_id, user_id)
# Create text file
text_content = """
This is a test text file for the ChatManager workflow.
It contains some information for testing document processing.
The ChatManager should be able to process this file
and extract relevant information from it.
This file serves as an example for text-based documents that can be
used in a chat workflow.
"""
text_file_bytes = text_content.encode('utf-8')
text_file = lucy_interface.save_uploaded_file(text_file_bytes, "test_document.txt")
text_file_id = text_file["id"]
logger.info(f"Text file created with ID: {text_file_id}")
# Create a simple test image using PIL
try:
from PIL import Image
import io
# Create a 100x100 red image
img = Image.new('RGB', (100, 100), color = 'red')
# Save to BytesIO
img_bytes = io.BytesIO()
img.save(img_bytes, format='PNG')
img_bytes = img_bytes.getvalue()
# Upload to database
image_file = lucy_interface.save_uploaded_file(img_bytes, "test_image.png")
image_file_id = image_file["id"]
logger.info(f"Image file created with ID: {image_file_id}")
except ImportError:
# Fallback to the original method if PIL is not available
png_data = bytes([
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, # PNG Header
# ... rest of your PNG data ...
])
with open("./test_img_orig.png", 'wb') as f:
f.write(png_data)
image_file = lucy_interface.save_uploaded_file(png_data, "test_image.png")
image_file_id = image_file["id"]
logger.info(f"Image file created with ID: {image_file_id}")
return text_file_id, image_file_id
async def run_chat_workflow(mandate_id: int, user_id: int, file_ids: List[int]) -> Dict[str, Any]:
"""
Executes a chat workflow with given file IDs.
Args:
mandate_id: ID of the mandate
user_id: ID of the user
file_ids: List of file IDs
Returns:
The workflow result
"""
logger.info(f"Starting chat workflow with files: {file_ids}")
# Initialize ChatManager
chat_manager = get_chat_manager(mandate_id, user_id)
# Create user request
user_input = {
"prompt": "Bitte integriere den Text der Datei ins angefügte Bild",
"list_file_id": file_ids
}
# Execute chat workflow
workflow_result = await chat_manager.chat_run(user_input)
logger.info(f"Workflow completed with ID: {workflow_result['id']}")
return workflow_result
def analyze_workflow_result(workflow: Dict[str, Any]) -> None:
"""
Analyzes and outputs information about the workflow result.
Args:
workflow: The workflow result
"""
logger.info("Analyzing workflow result:")
logger.info(f"Workflow ID: {workflow['id']}")
logger.info(f"Status: {workflow['status']}")
logger.info(f"Number of messages: {len(workflow.get('messages', []))}")
for i, message in enumerate(workflow.get('messages', [])):
logger.info(f"Message {i+1}:")
logger.info(f" Role: {message.get('role', 'unknown')}")
# Show only the first 100 characters of content
content = message.get('content', '')
content_preview = content[:100] + '...' if len(content) > 100 else content
logger.info(f" Content: {content_preview}")
# Show documents in the message
documents = message.get('documents', [])
logger.info(f" Documents: {len(documents)}")
for j, doc in enumerate(documents):
doc_id = doc.get('id', 'no ID')
file_id = doc.get('file_id', 'no file_id')
logger.info(f" Document {j+1}: ID={doc_id}, File-ID={file_id}")
# Information about contents
contents = doc.get('contents', [])
for k, content in enumerate(contents):
content_name = content.get('name', 'no name')
content_type = content.get('content_type', 'unknown')
logger.info(f" Content {k+1}: {content_name} ({content_type})")
logs = workflow.get('logs', [])
logger.info(f"Logs: {len(logs)}")
# Get only the first 10 logs
for i, log in enumerate(logs[:10]): # Apply the slice to logs, not enumerate
log_type = log.get('type', 'info')
log_message = log.get('message', '')
log_message_preview = log_message[:100] + '...' if len(log_message) > 100 else log_message
logger.info(f" Log {i+1} [{log_type}]: {log_message_preview}")
async def cleanup_test_files(mandate_id: int, user_id: int, file_ids: List[int]) -> None:
"""
Cleans up the created test files.
Args:
mandate_id: ID of the mandate
user_id: ID of the user
file_ids: List of file IDs to delete
"""
logger.info("Starting cleanup of test files...")
lucy_interface = get_lucydom_interface(mandate_id, user_id)
for file_id in file_ids:
try:
success = lucy_interface.delete_file(file_id)
if success:
logger.info(f"File with ID {file_id} successfully deleted")
else:
logger.warning(f"Error deleting file with ID {file_id}")
except Exception as e:
logger.error(f"Error deleting file with ID {file_id}: {str(e)}")
logger.info("Cleanup completed")
async def main():
"""
Main function that controls the entire test process.
"""
# Test parameters
MANDATE_ID = 1 # Test mandate ID
USER_ID = 1 # Test user ID
CLEANUP = True # Cleanup after test
try:
logger.info("=== ChatManager test workflow started ===")
# Step 1: Create test files
text_file_id, image_file_id = await create_test_files(MANDATE_ID, USER_ID)
file_ids = [text_file_id, image_file_id]
# Step 2: Execute chat workflow
workflow_result = await run_chat_workflow(MANDATE_ID, USER_ID, file_ids)
# Step 3: Analyze result
analyze_workflow_result(workflow_result)
# Step 4: Optional cleanup
if CLEANUP:
await cleanup_test_files(MANDATE_ID, USER_ID, file_ids)
logger.info("=== Test workflow successfully completed ===")
except Exception as e:
logger.error(f"Error in test workflow: {str(e)}", exc_info=True)
logger.info("=== Test workflow ended with error ===")
if __name__ == "__main__":
# Create event loop for asyncio and execute main function
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

View file

@ -1,66 +0,0 @@
input_files = [['test_document.txt', 'CiAgICBUaGlzIGlzIGEgdGVzdCB0ZXh0IGZpbGUgZm9yIHRoZSBDaGF0TWFuYWdlciB3b3JrZmxvdy4KICAgIEl0IGNvbnRhaW5zIHNvbWUgaW5mb3JtYXRpb24gZm9yIHRlc3RpbmcgZG9jdW1lbnQgcHJvY2Vzc2luZy4KICAgIAogICAgVGhlIENoYXRNYW5hZ2VyIHNob3VsZCBiZSBhYmxlIHRvIHByb2Nlc3MgdGhpcyBmaWxlCiAgICBhbmQgZXh0cmFjdCByZWxldmFudCBpbmZvcm1hdGlvbiBmcm9tIGl0LgogICAgCiAgICBUaGlzIGZpbGUgc2VydmVzIGFzIGFuIGV4YW1wbGUgZm9yIHRleHQtYmFzZWQgZG9jdW1lbnRzIHRoYXQgY2FuIGJlCiAgICB1c2VkIGluIGEgY2hhdCB3b3JrZmxvdy4KICAgIA==', True], ['test_document.txt', '\n This is a test text file for the ChatManager workflow.\n It contains some information for testing document processing.\n \n The ChatManager should be able to process this file\n and extract relevant information from it.\n \n This file serves as an example for text-based documents that can be\n used in a chat workflow.\n ', False], ['test_image.png', 'iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAA40lEQVR4nO3QsQEAIAyAsOr/P+sLZU9mJs4btu66xKzCrMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArMCswKzArNn7il4Bx2GaB88AAAAASUVORK5CYII=', True], ['test_image.png', 'Image (100x100, PNG, RGB)', False]] # DO NOT CHANGE THIS LINE
# REQUIREMENTS: Pillow==10.0.0
import base64
from PIL import Image, ImageDraw, ImageFont
import io
# Initialize result dictionary
result = {}
# Helper function to decode base64 data
def decode_base64(data: str) -> bytes:
return base64.b64decode(data)
# Extract input files
text_data = None
image_data = None
for file_info in input_files:
filename, data, is_base64 = file_info
if filename == 'test_document.txt' and not is_base64:
text_data = data
elif filename == 'test_image.png' and is_base64:
image_data = decode_base64(data)
# Validate inputs
if text_data is None:
raise ValueError("Text data not found in input files.")
if image_data is None:
raise ValueError("Image data not found in input files.")
# Load image
image = Image.open(io.BytesIO(image_data))
# Prepare to draw on the image
draw = ImageDraw.Draw(image)
# Use a basic font
try:
font = ImageFont.truetype("arial", 15)
except IOError:
font = ImageFont.load_default()
# Define text position
text_position = (10, 10)
# Overlay text onto image
draw.text(text_position, text_data, font=font, fill=(255, 255, 255))
# Save the resulting image to a bytes buffer
output_buffer = io.BytesIO()
image.save(output_buffer, format='PNG')
output_buffer.seek(0)
#result['integrated_image.png'] = base64.b64encode(output_buffer.getvalue()).decode('utf-8')
# binary_data = base64.b64decode(pic_str)
binary_data=output_buffer.getvalue()
# Write the binary data to a file
with open("./static/test_output.png", "wb") as f:
f.write(binary_data)
print("OK")
# Output the result dictionary
result

432
tool_testBackendSingle.py Normal file
View file

@ -0,0 +1,432 @@
#!/usr/bin/env python3
"""
Simplified Test Runner for Workflow State Machine
This script provides a clean and simple test runner for the workflow state machine
tests that properly handles async test methods.
Usage:
python tool_testBackendSingle.py [test_name]
Examples:
python tool_testBackendSingle.py # Run all tests
python tool_testBackendSingle.py test_state_1 # Run tests starting with test_state_1
"""
import os
import sys
import asyncio
import time
import traceback
import importlib
import inspect
from unittest.mock import patch, MagicMock, AsyncMock
# Try to import colorama, install if not available
try:
from colorama import init, Fore, Back, Style
init() # Initialize colorama
except ImportError:
print("Installing required package: colorama")
import subprocess
subprocess.call([sys.executable, "-m", "pip", "install", "colorama"])
from colorama import init, Fore, Back, Style
init() # Initialize colorama
class AsyncTestRunner:
"""Simple test runner that supports async test methods"""
def __init__(self):
"""Initialize the test runner"""
self.success_count = 0
self.failure_count = 0
self.results = []
self.total_time = 0
def print_header(self, test_case_name):
"""Print a header for the test suite"""
print("\n" + "=" * 80)
print(f"{Fore.CYAN}{Style.BRIGHT}{test_case_name}{Style.RESET_ALL}")
print("=" * 80)
def print_result(self, test_name, success, duration, error=None):
"""Print a test result with appropriate formatting"""
clean_name = test_name.replace('test_', '').replace('_', ' ').title()
if success:
status = f"{Fore.GREEN}[PASS]{Style.RESET_ALL}"
self.success_count += 1
else:
status = f"{Fore.RED}[FAIL]{Style.RESET_ALL}"
self.failure_count += 1
# Print result line
print(f"{status} {clean_name} - {duration:.2f}s")
# Print error if any
if error:
print(f" {Fore.RED}{error}{Style.RESET_ALL}")
if isinstance(error, Exception):
traceback.print_exception(type(error), error, error.__traceback__)
# Store result
self.results.append({
'name': clean_name,
'success': success,
'duration': duration,
'error': error
})
def print_summary(self):
"""Print a summary of test results"""
print("\n" + "=" * 80)
print(f"{Fore.CYAN}{Style.BRIGHT}TEST SUMMARY{Style.RESET_ALL}")
print("-" * 80)
# Print timing
print(f"Total execution time: {self.total_time:.2f}s")
# Print counts
total = self.success_count + self.failure_count
print(f"Tests: {total}, Passed: {Fore.GREEN}{self.success_count}{Style.RESET_ALL}, Failed: {Fore.RED}{self.failure_count}{Style.RESET_ALL}")
# Print overall status
if self.failure_count == 0:
print(f"\n{Fore.GREEN}{Style.BRIGHT}✓ ALL TESTS PASSED{Style.RESET_ALL}")
else:
print(f"\n{Fore.RED}{Style.BRIGHT}✗ TESTS FAILED{Style.RESET_ALL}")
# Print failures
print(f"\n{Fore.RED}Failed tests:{Style.RESET_ALL}")
for result in self.results:
if not result['success']:
print(f" - {result['name']}")
print("=" * 80)
async def run_test(self, test_instance, test_method):
"""Run a single test method (sync or async)"""
# Prepare test
test_name = test_method.__name__
clean_name = test_name.replace('test_', '').replace('_', ' ').title()
# Print start
print(f"\n{Fore.BLUE}[RUNNING]{Style.RESET_ALL} {clean_name}...")
# Run setUp
if hasattr(test_instance, 'setUp'):
await self.run_method_with_instance(test_instance, test_instance.setUp)
# Time the test execution
start_time = time.time()
success = True
error = None
try:
# Run the test - ensure bound method gets called with instance
if hasattr(test_method, '__self__') and test_method.__self__ is None:
# This is an unbound method, bind it to the instance
bound_method = getattr(test_instance, test_method.__name__)
await self.run_method_with_instance(test_instance, bound_method)
else:
# This is already a bound method
await self.run_method_with_instance(test_instance, test_method)
except Exception as e:
success = False
error = e
# Calculate duration
duration = time.time() - start_time
# Run tearDown
if hasattr(test_instance, 'tearDown'):
await self.run_method_with_instance(test_instance, test_instance.tearDown)
# Record and print result
self.print_result(test_name, success, duration, error)
return success
async def run_method_with_instance(self, instance, method):
"""Run a method ensuring it has the correct instance"""
method_name = method.__name__
bound_method = getattr(instance, method_name)
if asyncio.iscoroutinefunction(bound_method):
return await bound_method()
else:
return bound_method()
async def run_method(self, method):
"""Run a method that might be async or regular"""
# Check if this is an unbound method that needs self
if hasattr(method, '__self__') and method.__self__ is None:
# This suggests it's an unbound method that needs an instance
raise TypeError(f"Method {method.__name__} appears to be unbound and needs 'self'")
if asyncio.iscoroutinefunction(method):
return await method()
else:
return method()
def _reset_mocks(self):
"""Reset all mocks for a fresh test"""
# Only reset if the objects have reset_mock method
if hasattr(self.mydom_mock, 'reset_mock'):
self.mydom_mock.reset_mock()
else:
# Recreate the mock objects
self._setup_mocks()
if hasattr(self.registry_mock, 'reset_mock'):
self.registry_mock.reset_mock()
if hasattr(self.getDocumentContents_mock, 'reset_mock'):
self.getDocumentContents_mock.reset_mock()
async def run_test_case(self, test_case_class, filter_pattern=None):
"""Run all test methods in a test case class"""
# Initialize timing
start_time = time.time()
# Print header
self.print_header(test_case_class.__name__)
# Get all test methods
test_methods = sorted([
getattr(test_case_class, name) for name in dir(test_case_class)
if name.startswith('test_') and callable(getattr(test_case_class, name))
], key=lambda x: x.__name__)
# Filter tests if pattern provided
if filter_pattern:
test_methods = [
method for method in test_methods
if filter_pattern in method.__name__
]
if not test_methods:
print(f"{Fore.YELLOW}No tests found{Style.RESET_ALL}")
return
print(f"Running {len(test_methods)} tests...\n")
# Run each test
for test_method in test_methods:
# Create a fresh instance for each test
test_instance = test_case_class()
await self.run_test(test_instance, test_method)
# Record total time
self.total_time = time.time() - start_time
# Print summary
self.print_summary()
return self.failure_count == 0
def setup_module_paths():
"""Set up module paths to make imports work"""
# Add current directory and parent directory to path
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
if current_dir not in sys.path:
sys.path.insert(0, current_dir)
if parent_dir not in sys.path:
sys.path.insert(0, parent_dir)
# Also add any module directories that might exist
modules_dir = os.path.join(parent_dir, 'modules')
if os.path.exists(modules_dir) and modules_dir not in sys.path:
sys.path.insert(0, modules_dir)
gateway_dir = os.path.join(parent_dir, 'gateway')
if os.path.exists(gateway_dir) and gateway_dir not in sys.path:
sys.path.insert(0, gateway_dir)
print(f"{Fore.CYAN}Python path set to:{Style.RESET_ALL}")
for path in sys.path[:5]: # Print first 5 paths
print(f" - {path}")
def find_test_files():
"""Find test files in the current directory"""
# Look for test files in priority order
test_files = []
# First priority: test_workflow_state_machine.py
if os.path.exists('./test_workflow_state_machine.py'):
test_files.append('test_workflow_state_machine.py')
# Second priority: any tool_test*.py files
tool_test_files = [f for f in os.listdir('.') if f.startswith('tool_test') and f.endswith('.py') and f != 'tool_testBackendSingle.py']
test_files.extend(tool_test_files)
# Last priority: any test_*.py files
other_test_files = [f for f in os.listdir('.') if f.startswith('test_') and f.endswith('.py') and f not in test_files]
test_files.extend(other_test_files)
return test_files
async def run_tests(test_file=None, test_filter=None):
"""Run all tests"""
# Set up paths
setup_module_paths()
# Find test files if not specified
if not test_file:
test_files = find_test_files()
if not test_files:
print(f"{Fore.RED}No test files found{Style.RESET_ALL}")
return False
test_file = test_files[0]
print(f"{Fore.YELLOW}Found test files: {', '.join(test_files)}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}Using: {test_file}{Style.RESET_ALL}")
# Remove .py extension for import
module_name = test_file[:-3] if test_file.endswith('.py') else test_file
try:
# First try a normal import
print(f"{Fore.YELLOW}Attempting to import module: {module_name}{Style.RESET_ALL}")
test_module = importlib.import_module(module_name)
print(f"{Fore.GREEN}Successfully imported test module: {module_name}{Style.RESET_ALL}")
except ImportError as e:
print(f"{Fore.RED}Error importing module {module_name}: {e}{Style.RESET_ALL}")
# Try different import approaches
try:
# Try to load as a relative module
print(f"{Fore.YELLOW}Trying relative import...{Style.RESET_ALL}")
test_module = importlib.import_module('.' + module_name, package=__package__)
print(f"{Fore.GREEN}Imported test module via relative import: {module_name}{Style.RESET_ALL}")
except ImportError as e:
print(f"{Fore.RED}Relative import failed: {e}{Style.RESET_ALL}")
# Fall back to exec (not recommended but sometimes necessary)
print(f"{Fore.YELLOW}Attempting to load using exec: {test_file}{Style.RESET_ALL}")
try:
with open(test_file, 'r') as f:
module_content = f.read()
# Create a new module namespace
module_namespace = {}
# Execute the module code in the namespace
exec(module_content, module_namespace)
# Create a mock module
class MockModule:
pass
test_module = MockModule()
# Copy the relevant attributes to the mock module
for key, value in module_namespace.items():
setattr(test_module, key, value)
print(f"{Fore.GREEN}Loaded test module using exec: {test_file}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}Failed to load module using exec: {e}{Style.RESET_ALL}")
traceback.print_exc()
return False
# Find test case class
test_case_class = None
print(f"{Fore.YELLOW}Looking for test case class in module...{Style.RESET_ALL}")
for item_name in dir(test_module):
item = getattr(test_module, item_name)
if inspect.isclass(item) and (item_name.startswith('Test') or 'Test' in item_name):
print(f"{Fore.GREEN}Found test case class: {item_name}{Style.RESET_ALL}")
test_case_class = item
break
if not test_case_class:
print(f"{Fore.RED}No test case class found in {test_file}{Style.RESET_ALL}")
return False
# Try to check for required imports
try:
print(f"{Fore.YELLOW}Checking for agent registry...{Style.RESET_ALL}")
try:
# First try direct import
from modules.workflowAgentsRegistry import getAgentRegistry
print(f"{Fore.GREEN}Successfully imported getAgentRegistry{Style.RESET_ALL}")
except ImportError:
try:
# Try alternate path
from modules.workflowAgentsRegistry import getAgentRegistry
print(f"{Fore.GREEN}Successfully imported getAgentRegistry from modules{Style.RESET_ALL}")
except ImportError:
print(f"{Fore.YELLOW}Agent registry import not found - may cause issues{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}Error checking agent registry: {e}{Style.RESET_ALL}")
# Run the tests
print(f"{Fore.CYAN}Starting test execution{Style.RESET_ALL}")
runner = AsyncTestRunner()
return await runner.run_test_case(test_case_class, test_filter)
if __name__ == "__main__":
# Get test filter from command line
test_file = None
test_filter = None
if len(sys.argv) > 1:
# Check if first arg is a file
if os.path.exists(sys.argv[1]) or sys.argv[1].endswith('.py'):
test_file = sys.argv[1]
if len(sys.argv) > 2:
test_filter = sys.argv[2]
else:
test_filter = sys.argv[1]
# Run tests
asyncio.run(run_tests(test_file, test_filter))
class MockDomInterface:
def __init__(self, *args, **kwargs):
self.getWorkflow = MagicMock(return_value=None)
self.loadWorkflowState = MagicMock(return_value=None)
self.createWorkflow = MagicMock()
self.updateWorkflow = MagicMock()
self.createWorkflowLog = MagicMock()
self.createWorkflowMessage = MagicMock()
self.getFile = MagicMock()
self.getFileData = MagicMock()
self.saveUploadedFile = MagicMock()
self.userLanguage = "en"
self.callAi = AsyncMock()
self.setUserLanguage = MagicMock()
def reset_mock(self):
"""Reset all mocks in this interface"""
for attr_name in dir(self):
attr = getattr(self, attr_name)
if hasattr(attr, 'reset_mock'):
attr.reset_mock()
class MockAgentRegistry:
def __init__(self):
self.getAgent = MagicMock()
self.getAgentInfos = MagicMock(return_value=[
{"name": "test_agent", "description": "Test agent", "capabilities": ["text_processing"]}
])
self.setMydom = MagicMock()
def reset_mock(self):
"""Reset all mocks in this registry"""
for attr_name in dir(self):
attr = getattr(self, attr_name)
if hasattr(attr, 'reset_mock'):
attr.reset_mock()

1064
tool_testData.py Normal file

File diff suppressed because it is too large Load diff

244
tool_testUser.py Normal file
View file

@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""
Direct Interface Workflow Test Script
This script bypasses the API layer and works directly with the interface classes
to simulate a user uploading two files and then sending a chat request with these files.
It follows the state machine as defined in the backend documentation.
"""
import os
import sys
import json
import asyncio
import uuid
from datetime import datetime
# Adjust import paths
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
if parent_dir not in sys.path:
sys.path.insert(0, parent_dir)
# Try to import the required modules
try:
from modules.workflowManager import getWorkflowManager
from modules.lucydomInterface import getLucydomInterface
except ImportError:
print("Error: Required modules not found. Attempting alternative imports...")
try:
from gateway.modules.workflowManager import getWorkflowManager
from gateway.modules.lucydomInterface import getLucydomInterface
except ImportError:
print("Error: Could not import required modules. Make sure the script is run from the correct directory.")
sys.exit(1)
# Constants
MANDATE_ID = 1
USER_ID = 1
#USER_PROMPT = "Please analyze these sales figures and the chart to identify key trends and opportunities."
#USER_PROMPT = "Please make me a svg file with forecast for Apr-Jun."
USER_PROMPT = "Please make me a jpg file with forecast for Apr-Jun."
# Sample files to upload
SAMPLE_SVG = """
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<title>Sales Q1 Bar Chart</title>
<rect width="100%" height="100%" fill="#f9f9f9"/>
<g transform="translate(50, 20)">
<!-- Axes -->
<line x1="0" y1="230" x2="320" y2="230" stroke="black" />
<line x1="0" y1="0" x2="0" y2="230" stroke="black" />
<!-- Y-axis title -->
<text x="-30" y="120" transform="rotate(-90, -30, 120)">Sales ($)</text>
<!-- X-axis title -->
<text x="160" y="270">Month</text>
<!-- January -->
<rect x="40" y="80" width="60" height="150" fill="#4285F4" />
<text x="70" y="250">Jan</text>
<text x="70" y="70">$150K</text>
<!-- February -->
<rect x="130" y="50" width="60" height="180" fill="#EA4335" />
<text x="160" y="250">Feb</text>
<text x="160" y="40">$165K</text>
<!-- March -->
<rect x="220" y="20" width="60" height="210" fill="#FBBC05" />
<text x="250" y="250">Mar</text>
<text x="250" y="10">$180K</text>
</g>
</svg>
"""
SAMPLE_DATA = """
# Sales Data - Q1 2023
Month,Revenue,Growth,Units Sold
January,150000,5.2%,1250
February,165000,10.0%,1380
March,180000,9.1%,1490
## Regional Breakdown
- North: 35% of total sales
- South: 25% of total sales
- East: 20% of total sales
- West: 20% of total sales
## Top Products
1. Product A: 40% of revenue
2. Product B: 30% of revenue
3. Product C: 20% of revenue
4. Others: 10% of revenue
"""
async def create_test_files(mydom):
"""Create two test files and return their IDs"""
print("\n--- Uploading Test Files (State 0: File Upload) ---")
# Create SVG chart file
print("Uploading SVG chart file...")
chart_meta = mydom.saveUploadedFile(SAMPLE_SVG.encode('utf-8'), "q1_sales_chart.svg")
chart_id = chart_meta['id']
print(f"Created SVG chart file with ID: {chart_id}")
# Create data text file
print("Uploading markdown data file...")
data_meta = mydom.saveUploadedFile(SAMPLE_DATA.encode('utf-8'), "q1_sales_data.md")
data_id = data_meta['id']
print(f"Created markdown data file with ID: {data_id}")
return chart_id, data_id
async def monitor_workflow(mydom, workflow_id, timeout=300, interval=2):
"""Monitor the workflow until it completes or times out"""
print("\n--- Monitoring Workflow ---")
start_time = datetime.now()
elapsed = 0
while elapsed < timeout:
# Get current workflow state
workflow = mydom.loadWorkflowState(workflow_id)
if not workflow:
print("Error: Workflow not found")
return None
status = workflow.get("status", "unknown")
# Show progress
logs = workflow.get("logs", [])
latest_log = logs[-1] if logs else None
if latest_log:
progress = latest_log.get("progress", 0)
message = latest_log.get("message", "No message")
print(f"Status: {status} | Progress: {progress}% | {message}")
# Check if workflow is done
if status in ["completed", "failed", "stopped"]:
if status == "completed":
print("\nWorkflow completed successfully!")
elif status == "failed":
print("\nWorkflow failed!")
else:
print("\nWorkflow was stopped!")
return workflow
# Wait before checking again
await asyncio.sleep(interval)
elapsed = (datetime.now() - start_time).total_seconds()
print(f"Monitoring timed out after {timeout} seconds")
return mydom.loadWorkflowState(workflow_id)
async def run_test():
"""Main test function that follows the state machine workflow"""
print("\n=== Direct Interface Workflow Test ===\n")
# Initialize the interfaces
print("Initializing system...")
mydom = getLucydomInterface(MANDATE_ID, USER_ID)
manager = getWorkflowManager(MANDATE_ID, USER_ID)
# Upload test files (State 0: File Upload)
chart_id, data_id = await create_test_files(mydom)
# Prepare the user input
user_input = {
"prompt": USER_PROMPT,
"listFileId": [chart_id, data_id]
}
# Start workflow (State 1: Workflow Initialization)
print(f"\n--- Starting Workflow (State 1: Workflow Initialization) ---")
print(f"Sending user prompt: '{USER_PROMPT}'")
print(f"With files: SVG chart (ID: {chart_id}) and sales data (ID: {data_id})")
# Start the workflow with the user input
workflow = await manager.workflowStart(user_input)
workflow_id = workflow["id"]
print(f"Workflow initiated with ID: {workflow_id}")
print(f"Initial status: {workflow['status']}")
# Monitor the workflow progress
# This will monitor states 2-7 of the state machine
await monitor_workflow(mydom, workflow_id, timeout=120)
# Get final workflow state
final_workflow = mydom.loadWorkflowState(workflow_id)
# Print the results
print("\n--- Final Workflow Results ---")
if final_workflow:
# Print status information
print(f"Workflow Status: {final_workflow.get('status', 'unknown')}")
print(f"Current Round: {final_workflow.get('currentRound', 0)}")
# Print messages
print("\n=== Messages ===")
for msg in final_workflow.get("messages", []):
role = msg.get("role", "unknown")
agent = msg.get("agentName", "")
# Get a preview of the content
content = msg.get("content", "")
if len(content) > 100:
content_preview = content[:100] + "..."
else:
content_preview = content
# Format based on role
if role == "assistant" and agent:
print(f"\n[{role} - {agent}]: {content_preview}")
else:
print(f"\n[{role}]: {content_preview}")
# Print document info
docs = msg.get("documents", [])
if docs:
print(f" Documents ({len(docs)}):")
for doc in docs:
name = doc.get("name", "unnamed")
ext = doc.get("ext", "")
file_id = doc.get("fileId", "unknown")
print(f" - {name}.{ext} (ID: {file_id})")
# Print the final log
logs = final_workflow.get("logs", [])
if logs:
final_log = logs[-1]
print(f"\nFinal Log: {final_log.get('message', 'No message')}")
else:
print("Error: Could not retrieve final workflow state")
print("\n=== Test Complete ===")
return workflow_id
if __name__ == "__main__":
workflow_id = asyncio.run(run_test())
print(f"Completed workflow ID: {workflow_id}")