Merge branch 'release/v3.1.3-beta'

This commit is contained in:
Josako
2025-09-25 17:38:48 +02:00
36 changed files with 1595 additions and 937 deletions

1
.gitignore vendored
View File

@@ -55,3 +55,4 @@ scripts/__pycache__/run_eveai_app.cpython-312.pyc
/nginx/node_modules/ /nginx/node_modules/
/nginx/.parcel-cache/ /nginx/.parcel-cache/
/nginx/static/ /nginx/static/
/docker/build_logs/

View File

@@ -0,0 +1,45 @@
import json
import os
from functools import lru_cache
from typing import Dict
# Default manifest path inside app images; override with env
DEFAULT_MANIFEST_PATH = os.environ.get(
'EVEAI_STATIC_MANIFEST_PATH',
'/app/config/static-manifest/manifest.json'
)
@lru_cache(maxsize=1)
def _load_manifest(manifest_path: str = DEFAULT_MANIFEST_PATH) -> Dict[str, str]:
try:
with open(manifest_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return {}
def resolve_asset(logical_path: str, manifest_path: str = DEFAULT_MANIFEST_PATH) -> str:
"""
Map a logical asset path (e.g. 'dist/chat-client.js') to the hashed path
found in the Parcel manifest. If not found or manifest missing, return the
original logical path for graceful fallback.
"""
if not logical_path:
return logical_path
manifest = _load_manifest(manifest_path)
# Try several key variants as Parcel manifests may use different keys
candidates = [
logical_path,
logical_path.lstrip('/'),
logical_path.replace('static/', ''),
logical_path.replace('dist/', ''),
]
for key in candidates:
if key in manifest:
return manifest[key]
return logical_path

View File

@@ -107,6 +107,33 @@ def get_pagination_html(pagination, endpoint, **kwargs):
return Markup(''.join(html)) return Markup(''.join(html))
def asset_url(logical_path: str):
"""
Resolve an asset logical path to a hashed URL using Parcel manifest when available.
Fallback to the original logical path under /static/ if manifest is missing.
Examples:
- asset_url('dist/chat-client.js') -> '/static/dist/chat-client.abc123.js'
- asset_url('dist/chat-client.css') -> '/static/dist/chat-client.def456.css'
"""
if not logical_path:
return logical_path
try:
from common.utils.asset_manifest import resolve_asset
resolved = resolve_asset(logical_path)
if not resolved:
return f"/static/{logical_path.lstrip('/')}"
# If resolved is already an absolute URL starting with /static or http(s), return as is
if resolved.startswith('/static/') or resolved.startswith('http://') or resolved.startswith('https://'):
return resolved
# If it starts with 'dist/', prefix /static/
if resolved.startswith('dist/'):
return '/static/' + resolved
# Otherwise, best effort: ensure it lives under /static/
return '/static/' + resolved.lstrip('/')
except Exception:
return f"/static/{logical_path.lstrip('/')}"
def register_filters(app): def register_filters(app):
""" """
Registers custom filters with the Flask app. Registers custom filters with the Flask app.
@@ -123,4 +150,5 @@ def register_filters(app):
app.jinja_env.globals['prefixed_url_for'] = prefixed_url_for app.jinja_env.globals['prefixed_url_for'] = prefixed_url_for
app.jinja_env.globals['get_pagination_html'] = get_pagination_html app.jinja_env.globals['get_pagination_html'] = get_pagination_html
app.jinja_env.globals['get_base_background_color'] = get_base_background_color app.jinja_env.globals['get_base_background_color'] = get_base_background_color
app.jinja_env.globals['asset_url'] = asset_url

View File

@@ -424,7 +424,7 @@ class StagingConfig(Config):
CHAT_CLIENT_PREFIX = 'chat-client/chat/' CHAT_CLIENT_PREFIX = 'chat-client/chat/'
# Define the static path # Define the static path
STATIC_URL = 'https://evie-staging-static.askeveai.com' STATIC_URL = 'https://evie-staging-static.askeveai.com/'
# PATH settings # PATH settings
ffmpeg_path = '/usr/bin/ffmpeg' ffmpeg_path = '/usr/bin/ffmpeg'

View File

@@ -0,0 +1,6 @@
{
"dist/chat-client.js": "dist/chat-client.25888758.js",
"dist/chat-client.css": "dist/chat-client.eef0ef31.css",
"dist/main.js": "dist/main.f3dde0f6.js",
"dist/main.css": "dist/main.c40e57ad.css"
}

View File

@@ -5,12 +5,23 @@ All notable changes to EveAI will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.1.2-beta] ## 3.1.3-beta
### Added
- Cache busting for static files
### Changed
- Build process optimised for cache busting
### Fixed
- TRA-83 - numerous changes to the mobile version of the chat client
## 3.1.2-beta
### Changed ### Changed
- Several improvements to the mobile version of the chat client - Several improvements to the mobile version of the chat client
## [3.1.1-alfa] ## 3.1.1-alfa
### Fixed ### Fixed
- TRA-76 - Send Button color changes implemented - TRA-76 - Send Button color changes implemented
@@ -21,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security ### Security
- In case of vulnerabilities. - In case of vulnerabilities.
## [3.1.0-alfa] ## 3.1.0-alfa
### Added ### Added
- Configuration of the full k8s staging environment - Configuration of the full k8s staging environment
@@ -41,7 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Migration of dev and test with changes required for k8s - Migration of dev and test with changes required for k8s
- More comprehensive error communication system - More comprehensive error communication system
## [3.0.1-beta] ## 3.0.1-beta
### Changed ### Changed
- Podman now replaces Docker for building images - Podman now replaces Docker for building images
@@ -58,7 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security ### Security
- In case of vulnerabilities. - In case of vulnerabilities.
## [3.0.0-beta] ## 3.0.0-beta
### Added ### Added
- Mobile Support for the chat client. - Mobile Support for the chat client.
@@ -70,7 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Humanisation of cached interaction messages (random choice) - Humanisation of cached interaction messages (random choice)
- Adding specialist configuration information to be added as arguments for retrievers - Adding specialist configuration information to be added as arguments for retrievers
## [2.3.12-alfa] ## 2.3.12-alfa
### Added ### Added
- Modal display of privacy statement and terms & conditions documents in eveai_Chat_client - Modal display of privacy statement and terms & conditions documents in eveai_Chat_client
@@ -87,7 +98,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Error Messages for adding documents in 'alert' - Error Messages for adding documents in 'alert'
- Correction of error in Template variable replacement, resulting in missing template variable value - Correction of error in Template variable replacement, resulting in missing template variable value
## [2.3.11-alfa] ## 2.3.11-alfa
### Added ### Added
- RQC (Recruitment Qualified Candidate) export to EveAIDataCapsule - RQC (Recruitment Qualified Candidate) export to EveAIDataCapsule
@@ -99,7 +110,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Adapting TRAICIE_SELECTION_SPECIALIST to retrieve preferred contact times using a form iso free text - Adapting TRAICIE_SELECTION_SPECIALIST to retrieve preferred contact times using a form iso free text
- Improvement of DynamicForm en FormField to handle boolean values. - Improvement of DynamicForm en FormField to handle boolean values.
## [2.3.10-alfa] ## 2.3.10-alfa
### Added ### Added
- introduction of eveai-listview that is sortable and filterable (using tabulator), with client-side pagination - introduction of eveai-listview that is sortable and filterable (using tabulator), with client-side pagination
@@ -115,7 +126,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Chat-client converted to vue components and composables - Chat-client converted to vue components and composables
## [2.3.9-alfa] ## 2.3.9-alfa
### Added ### Added
- Translation functionality for Front-End, configs (e.g. Forms) and free text - Translation functionality for Front-End, configs (e.g. Forms) and free text
@@ -131,7 +142,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Catalogs & Retrievers now fully type-based, removing need for end-user definition of Tagging Fields - Catalogs & Retrievers now fully type-based, removing need for end-user definition of Tagging Fields
- RAG_SPECIALIST to support new possibilities - RAG_SPECIALIST to support new possibilities
## [2.3.8-alfa] ## 2.3.8-alfa
### Added ### Added
- Translation Service - Translation Service
@@ -155,7 +166,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security ### Security
- In case of vulnerabilities. - In case of vulnerabilities.
## [2.3.7-alfa] ## 2.3.7-alfa
### Added ### Added
- Basic Base Specialist additions for handling phases and transferring data between state and output - Basic Base Specialist additions for handling phases and transferring data between state and output
@@ -165,13 +176,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Logging improvement & simplification (remove Graylog) - Logging improvement & simplification (remove Graylog)
- Traicie Selection Specialist v1.3 - full roundtrip & full process - Traicie Selection Specialist v1.3 - full roundtrip & full process
## [2.3.6-alfa] ## 2.3.6-alfa
### Added ### Added
- Full Chat Client functionality, including Forms, ESS, theming - Full Chat Client functionality, including Forms, ESS, theming
- First Demo version of Traicie Selection Specialist - First Demo version of Traicie Selection Specialist
## [2.3.5-alfa] ## 2.3.5-alfa
### Added ### Added
- Chat Client Initialisation (based on SpecialistMagicLink code) - Chat Client Initialisation (based on SpecialistMagicLink code)
@@ -185,7 +196,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Several Bugfixes to administrative app - Several Bugfixes to administrative app
## [2.3.4-alfa] ## 2.3.4-alfa
### Added ### Added
- Introduction of Tenant Make - Introduction of Tenant Make
@@ -196,7 +207,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Enable Specialist 'activation' / 'deactivation' - Enable Specialist 'activation' / 'deactivation'
- Unique constraints introduced for Catalog Name (tenant level) and make name (public level) - Unique constraints introduced for Catalog Name (tenant level) and make name (public level)
## [2.3.3-alfa] ## 2.3.3-alfa
### Added ### Added
- Add Tenant Make - Add Tenant Make
@@ -209,7 +220,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure document version is selected in UI before trying to view it. - Ensure document version is selected in UI before trying to view it.
- Remove obsolete tab from tenant overview - Remove obsolete tab from tenant overview
## [2.3.2-alfa] ## 2.3.2-alfa
### Added ### Added
- Changelog display - Changelog display
@@ -222,7 +233,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- data-type dynamic field needs conversion to isoformat - data-type dynamic field needs conversion to isoformat
- Add public tables to env.py of tenant schema - Add public tables to env.py of tenant schema
## [2.3.1-alfa] ## 2.3.1-alfa
### Added ### Added
- Introduction of ordered_list dynamic field type (using tabulator) - Introduction of ordered_list dynamic field type (using tabulator)
@@ -233,7 +244,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Role Definition Specialist creates Selection Specialist from generated competencies - Role Definition Specialist creates Selection Specialist from generated competencies
- Improvements to Selection Specialist (Agent definition to be started) - Improvements to Selection Specialist (Agent definition to be started)
## [2.3.0-alfa] ## 2.3.0-alfa
### Added ### Added
- Introduction of Push Gateway for Prometheus - Introduction of Push Gateway for Prometheus
@@ -280,7 +291,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security ### Security
- In case of vulnerabilities. - In case of vulnerabilities.
## [2.2.0-alfa] ## 2.2.0-alfa
### Added ### Added
- Mistral AI as main provider for embeddings, chains and specialists - Mistral AI as main provider for embeddings, chains and specialists
@@ -309,7 +320,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Set default language when registering Documents or URLs. - Set default language when registering Documents or URLs.
## [2.1.0-alfa] ## 2.1.0-alfa
### Added ### Added
- Zapier Refresh Document - Zapier Refresh Document
@@ -332,7 +343,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Deprecated ### Deprecated
- eveai_chat - using sockets - will be replaced with new specialist_execution_api and SSE - eveai_chat - using sockets - will be replaced with new specialist_execution_api and SSE
## [2.0.1-alfa] ## 2.0.1-alfa
### Added ### Added
- Zapîer Integration (partial - only adding files). - Zapîer Integration (partial - only adding files).
@@ -357,7 +368,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security ### Security
- In case of vulnerabilities. - In case of vulnerabilities.
## [2.0.0-alfa] ## 2.0.0-alfa
### Added ### Added
- Introduction of dynamic Retrievers & Specialists - Introduction of dynamic Retrievers & Specialists
@@ -376,7 +387,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security ### Security
- Security improvements to Docker images - Security improvements to Docker images
## [1.0.14-alfa] ## 1.0.14-alfa
### Added ### Added
- New release script added to tag images with release number - New release script added to tag images with release number
@@ -401,7 +412,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security ### Security
- In case of vulnerabilities. - In case of vulnerabilities.
## [1.0.13-alfa] ## 1.0.13-alfa
### Added ### Added
- Finished Catalog introduction - Finished Catalog introduction
@@ -414,7 +425,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Overall bugfixes as result from the Catalog introduction - Overall bugfixes as result from the Catalog introduction
## [1.0.12-alfa] ## 1.0.12-alfa
### Added ### Added
- Added Catalog functionality - Added Catalog functionality
@@ -434,7 +445,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security ### Security
- In case of vulnerabilities. - In case of vulnerabilities.
## [1.0.11-alfa] ## 1.0.11-alfa
### Added ### Added
- License Usage Calculation realised - License Usage Calculation realised
@@ -449,7 +460,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Various fixes as consequence of changing file_location / file_name ==> bucket_name / object_name - Various fixes as consequence of changing file_location / file_name ==> bucket_name / object_name
- Celery Routing / Queuing updated - Celery Routing / Queuing updated
## [1.0.10-alfa] ## 1.0.10-alfa
### Added ### Added
- BusinessEventLog monitoring using Langchain native code - BusinessEventLog monitoring using Langchain native code
@@ -462,7 +473,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed ### Removed
- Portkey removed for monitoring usage - Portkey removed for monitoring usage
## [1.0.9-alfa] - 2024/10/01 ## 1.0.9-alfa - 2024/10/01
### Added ### Added
- Business Event tracing (eveai_workers & eveai_chat_workers) - Business Event tracing (eveai_workers & eveai_chat_workers)
@@ -481,7 +492,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Set default language when registering Documents or URLs. - Set default language when registering Documents or URLs.
## [1.0.8-alfa] - 2024-09-12 ## 1.0.8-alfa - 2024-09-12
### Added ### Added
- Tenant type defined to allow for active, inactive, demo ... tenants - Tenant type defined to allow for active, inactive, demo ... tenants
@@ -494,7 +505,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Refine audio_processor and srt_processor to reduce duplicate code and support larger files - Refine audio_processor and srt_processor to reduce duplicate code and support larger files
## [1.0.7-alfa] - 2024-09-12 ## 1.0.7-alfa - 2024-09-12
### Added ### Added
- Full Document API allowing for creation, updating and invalidation of documents. - Full Document API allowing for creation, updating and invalidation of documents.
@@ -504,14 +515,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Maximal deduplication of code between views and api in document_utils.py - Maximal deduplication of code between views and api in document_utils.py
## [1.0.6-alfa] - 2024-09-03 ## 1.0.6-alfa - 2024-09-03
### Fixed ### Fixed
- Problems with tenant scheme migrations - may have to be revisited - Problems with tenant scheme migrations - may have to be revisited
- Correction of default language settings when uploading docs or URLs - Correction of default language settings when uploading docs or URLs
- Addition of a CHANGELOG.md file - Addition of a CHANGELOG.md file
## [1.0.5-alfa] - 2024-09-02 ## 1.0.5-alfa - 2024-09-02
### Added ### Added
- Allow chatwidget to connect to multiple servers (e.g. development and production) - Allow chatwidget to connect to multiple servers (e.g. development and production)
@@ -525,10 +536,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed ### Removed
- Removed direct upload of Youtube URLs, due to continuous changes in Youtube website - Removed direct upload of Youtube URLs, due to continuous changes in Youtube website
## [1.0.4-alfa] - 2024-08-27 ## 1.0.4-alfa - 2024-08-27
Skipped Skipped
## [1.0.3-alfa] - 2024-08-27 ## 1.0.3-alfa - 2024-08-27
### Added ### Added
- Refinement of HTML processing - allow for excluded classes and elements. - Refinement of HTML processing - allow for excluded classes and elements.
@@ -538,12 +549,12 @@ Skipped
- PDF Processing extracted in new PDF Processor class. - PDF Processing extracted in new PDF Processor class.
- Allow for longer and more complex PDFs to be uploaded. - Allow for longer and more complex PDFs to be uploaded.
## [1.0.2-alfa] - 2024-08-22 ## 1.0.2-alfa - 2024-08-22
### Fixed ### Fixed
- Bugfix for ResetPasswordForm in config.py - Bugfix for ResetPasswordForm in config.py
## [1.0.1-alfa] - 2024-08-21 ## 1.0.1-alfa - 2024-08-21
### Added ### Added
- Full Document Version Overview - Full Document Version Overview
@@ -551,7 +562,7 @@ Skipped
### Changed ### Changed
- Improvements to user creation and registration, renewal of passwords, ... - Improvements to user creation and registration, renewal of passwords, ...
## [1.0.0-alfa] - 2024-08-16 ## 1.0.0-alfa - 2024-08-16
### Added ### Added
- Initial release of the project. - Initial release of the project.

View File

@@ -1,7 +1,49 @@
#!/bin/bash #!/bin/bash
# Exit on any error # Safer bash: we manage errors manually (no -e) but detect pipeline failures
set -e set -o pipefail
# Quiet mode default; enable verbose with --verbose
QUIET=${QUIET:-true}
# Parse --verbose early (we'll reparse fully later as well)
for arg in "$@"; do
if [[ "$arg" == "--verbose" ]]; then QUIET=false; fi
done
# Per-run logs directory
RUN_TS=$(date +%Y%m%d_%H%M%S)
LOG_DIR="./build_logs/$RUN_TS"
mkdir -p "$LOG_DIR"
# Error aggregation
ERRORS=()
ERROR_LINES=()
EXIT_CODE=0
# Helper: run_quiet SERVICE STEP -- CMD ARGS...
run_quiet() {
local SERVICE="$1"; shift
local STEP="$1"; shift
# Expect a literal "--" separator before the command
if [[ "$1" == "--" ]]; then shift; fi
local LOG_FILE="$LOG_DIR/${SERVICE}.${STEP}.log"
if [[ "$QUIET" == "true" ]]; then
"$@" > /dev/null 2> >(tee -a "$LOG_FILE" >&2)
else
"$@" > >(tee -a "$LOG_FILE") 2> >(tee -a "$LOG_FILE" >&2)
fi
local RC=$?
echo "$LOG_FILE" > "$LOG_DIR/.last_${SERVICE}_${STEP}.path"
return $RC
}
record_error() {
local SERVICE="$1"; local STEP="$2"; local MESSAGE="$3"; local LOG_FILE="$4"
ERRORS+=("$SERVICE|$STEP|$LOG_FILE|$MESSAGE")
ERROR_LINES+=("$MESSAGE")
EXIT_CODE=1
}
source ./podman_env_switch.sh dev source ./podman_env_switch.sh dev
@@ -39,7 +81,7 @@ BASE_ONLY=""
# Function to display usage information # Function to display usage information
usage() { usage() {
echo "Usage: $0 [-b|-p|-bb|--base-only] [--no-cache] [--progress=plain] [--debug] [service1 service2 ...]" echo "Usage: $0 [-b|-p|-bb|--base-only] [--no-cache] [--progress=plain] [--debug] [--verbose] [service1 service2 ...]"
echo " -b: Build only" echo " -b: Build only"
echo " -p: Push only" echo " -p: Push only"
echo " -bb: Build base image (in addition to services)" echo " -bb: Build base image (in addition to services)"
@@ -47,6 +89,7 @@ usage() {
echo " --no-cache: Perform a clean build without using cache" echo " --no-cache: Perform a clean build without using cache"
echo " --progress=plain: Show detailed progress of the build" echo " --progress=plain: Show detailed progress of the build"
echo " --debug: Enable debug mode for the build" echo " --debug: Enable debug mode for the build"
echo " --verbose: Show full output of build/push (default is quiet; logs always saved under ./build_logs/<timestamp>)"
echo " If no option is provided, both build and push will be performed." echo " If no option is provided, both build and push will be performed."
echo " If no services are specified, all eveai_ services and nginx will be processed." echo " If no services are specified, all eveai_ services and nginx will be processed."
echo " All images are built for AMD64 platform (compatible with both x86_64 and Apple Silicon via emulation)." echo " All images are built for AMD64 platform (compatible with both x86_64 and Apple Silicon via emulation)."
@@ -55,6 +98,10 @@ usage() {
# Parse command-line options # Parse command-line options
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
--verbose)
QUIET=false
shift
;;
-b) -b)
ACTION="build" ACTION="build"
shift shift
@@ -96,13 +143,13 @@ done
# Function to build base image # Function to build base image
build_base_image() { build_base_image() {
echo "🏗️ Building base image..." echo "🏗️ Building base image... =============================================================="
local BASE_IMAGE_NAME="$REGISTRY/$ACCOUNT/eveai-base:$TAG" local BASE_IMAGE_NAME="$REGISTRY/$ACCOUNT/eveai-base:$TAG"
echo "Building base image for platform: $PLATFORM" echo "Building base image for platform: $PLATFORM"
echo "Base image tag: $BASE_IMAGE_NAME" echo "Base image tag: $BASE_IMAGE_NAME"
podman build \ run_quiet base build -- podman build \
--platform "$PLATFORM" \ --platform "$PLATFORM" \
$NO_CACHE \ $NO_CACHE \
$PROGRESS \ $PROGRESS \
@@ -111,12 +158,24 @@ build_base_image() {
-t "$BASE_IMAGE_NAME" \ -t "$BASE_IMAGE_NAME" \
-f Dockerfile.base \ -f Dockerfile.base \
.. ..
if [ $? -ne 0 ]; then
LOG_FILE="$LOG_DIR/base.build.log"
echo "❌ Failed to build base image"
record_error base build "❌ Failed to build base image" "$LOG_FILE"
return 1
fi
if [ "$ACTION" = "push" ] || [ "$ACTION" = "both" ]; then if [ "$ACTION" = "push" ] || [ "$ACTION" = "both" ]; then
echo "Pushing base image to registry..." echo "Pushing base image to registry..."
podman push "$BASE_IMAGE_NAME" run_quiet base push -- podman push "$BASE_IMAGE_NAME"
if [ $? -ne 0 ]; then
LOG_FILE="$LOG_DIR/base.push.log"
echo "❌ Failed to push base image"
record_error base push "❌ Failed to push base image" "$LOG_FILE"
return 1
fi
fi fi
echo "✅ Base image built successfully" echo "✅ Base image built successfully"
} }
@@ -132,7 +191,7 @@ should_build_base() {
# Function to build and/or push a service # Function to build and/or push a service
process_service() { process_service() {
local SERVICE="$1" local SERVICE="$1"
echo "Processing $SERVICE..." echo "Processing $SERVICE... =================================================================="
# Extract the build context and dockerfile from the compose file # Extract the build context and dockerfile from the compose file
CONTEXT=$(yq e ".services.$SERVICE.build.context" compose_dev.yaml) CONTEXT=$(yq e ".services.$SERVICE.build.context" compose_dev.yaml)
@@ -160,8 +219,8 @@ process_service() {
# Build and/or push based on ACTION # Build and/or push based on ACTION
if [ "$ACTION" = "build" ]; then if [ "$ACTION" = "build" ]; then
echo "Building $SERVICE for $PLATFORM..." echo "🛠️ Building $SERVICE for $PLATFORM..."
podman build \ run_quiet "$SERVICE" build -- podman build \
--platform "$PLATFORM" \ --platform "$PLATFORM" \
$NO_CACHE \ $NO_CACHE \
$PROGRESS \ $PROGRESS \
@@ -170,26 +229,27 @@ process_service() {
-t "$REGISTRY_IMAGE_NAME" \ -t "$REGISTRY_IMAGE_NAME" \
-f "$CONTEXT/$DOCKERFILE" \ -f "$CONTEXT/$DOCKERFILE" \
"$CONTEXT" "$CONTEXT"
if [ $? -ne 0 ]; then
LOG_FILE="$LOG_DIR/${SERVICE}.build.log"
echo "❌ Failed to build $SERVICE"
record_error "$SERVICE" build "❌ Failed to build $SERVICE" "$LOG_FILE"
return 1
fi
elif [ "$ACTION" = "push" ]; then elif [ "$ACTION" = "push" ]; then
echo "Building and pushing $SERVICE for $PLATFORM..." echo "📤 Pushing $SERVICE to registry..."
podman build \ run_quiet "$SERVICE" push -- podman push "$REGISTRY_IMAGE_NAME"
--platform "$PLATFORM" \ if [ $? -ne 0 ]; then
$NO_CACHE \ LOG_FILE="$LOG_DIR/${SERVICE}.push.log"
$PROGRESS \ echo "❌ Failed to push $SERVICE"
$DEBUG \ record_error "$SERVICE" push "❌ Failed to push $SERVICE" "$LOG_FILE"
-t "$LOCAL_IMAGE_NAME" \ return 1
-t "$REGISTRY_IMAGE_NAME" \ fi
-f "$CONTEXT/$DOCKERFILE" \
"$CONTEXT"
echo "Pushing $SERVICE to registry..."
podman push "$REGISTRY_IMAGE_NAME"
else else
# Both build and push # Both build and push
echo "Building $SERVICE for $PLATFORM..." echo "🛠️ Building $SERVICE for $PLATFORM..."
podman build \ run_quiet "$SERVICE" build -- podman build \
--platform "$PLATFORM" \ --platform "$PLATFORM" \
$NO_CACHE \ $NO_CACHE \
$PROGRESS \ $PROGRESS \
@@ -198,9 +258,21 @@ process_service() {
-t "$REGISTRY_IMAGE_NAME" \ -t "$REGISTRY_IMAGE_NAME" \
-f "$CONTEXT/$DOCKERFILE" \ -f "$CONTEXT/$DOCKERFILE" \
"$CONTEXT" "$CONTEXT"
if [ $? -ne 0 ]; then
LOG_FILE="$LOG_DIR/${SERVICE}.build.log"
echo "❌ Failed to build $SERVICE"
record_error "$SERVICE" build "❌ Failed to build $SERVICE" "$LOG_FILE"
return 1
fi
echo "Pushing $SERVICE to registry..." echo "📤 Pushing $SERVICE to registry..."
podman push "$REGISTRY_IMAGE_NAME" run_quiet "$SERVICE" push -- podman push "$REGISTRY_IMAGE_NAME"
if [ $? -ne 0 ]; then
LOG_FILE="$LOG_DIR/${SERVICE}.push.log"
echo "❌ Failed to push $SERVICE"
record_error "$SERVICE" push "❌ Failed to push $SERVICE" "$LOG_FILE"
return 1
fi
fi fi
} }
@@ -231,22 +303,87 @@ fi
echo "Using simplified AMD64-only approach for maximum compatibility..." echo "Using simplified AMD64-only approach for maximum compatibility..."
echo "Images will be tagged as: $REGISTRY/$ACCOUNT/[service]:$TAG" echo "Images will be tagged as: $REGISTRY/$ACCOUNT/[service]:$TAG"
# Reorder to ensure nginx builds before eveai_* if both are present
HAS_NGINX=false
HAS_APPS=false
for S in "${SERVICES[@]}"; do
if [[ "$S" == "nginx" ]]; then HAS_NGINX=true; fi
if [[ "$S" == eveai_* ]]; then HAS_APPS=true; fi
done
if $HAS_NGINX && $HAS_APPS; then
ORDERED_SERVICES=("nginx")
for S in "${SERVICES[@]}"; do
if [[ "$S" != "nginx" ]]; then ORDERED_SERVICES+=("$S"); fi
done
SERVICES=("${ORDERED_SERVICES[@]}")
fi
# Loop through services # Loop through services
for SERVICE in "${SERVICES[@]}"; do for SERVICE in "${SERVICES[@]}"; do
if [[ "$SERVICE" == "nginx" ]]; then if [[ "$SERVICE" == "nginx" ]]; then
./copy_specialist_svgs.sh ../config ../nginx/static/assets 2>/dev/null || echo "Warning: copy_specialist_svgs.sh not found or failed" run_quiet nginx copy-specialist-svgs -- ./copy_specialist_svgs.sh ../config ../nginx/static/assets
if [ $? -ne 0 ]; then
LOG_FILE="$LOG_DIR/nginx.copy-specialist-svgs.log"
echo "⚠️ copy_specialist_svgs.sh not found or failed"
record_error nginx copy-specialist-svgs "⚠️ copy_specialist_svgs.sh not found or failed" "$LOG_FILE"
fi
run_quiet nginx rebuild-chat-client -- ./rebuild_chat_client.sh
if [ $? -ne 0 ]; then
LOG_FILE="$LOG_DIR/nginx.rebuild-chat-client.log"
echo "❌ rebuild_chat_client.sh failed"
record_error nginx rebuild-chat-client "❌ rebuild_chat_client.sh failed" "$LOG_FILE"
fi
MANIFEST_SRC="../nginx/static/dist/manifest.json"
MANIFEST_DST_DIR="../config/static-manifest"
MANIFEST_DST="$MANIFEST_DST_DIR/manifest.json"
if [ ! -f "$MANIFEST_SRC" ]; then
if $HAS_NGINX; then
echo "⚠️ manifest.json not found at $MANIFEST_SRC yet. nginx should be built first in this run."
else
echo "❌ manifest.json not found at $MANIFEST_SRC. Please build nginx (assets) first."
exit 1
fi
fi
mkdir -p "$MANIFEST_DST_DIR"
if [ -f "$MANIFEST_SRC" ]; then
cp -f "$MANIFEST_SRC" "$MANIFEST_DST"
echo "📄 Staged manifest at $MANIFEST_DST"
fi
fi fi
if [[ "$SERVICE" == "nginx" || "$SERVICE" == eveai_* || "$SERVICE" == "prometheus" || "$SERVICE" == "grafana" ]]; then if [[ "$SERVICE" == "nginx" || "$SERVICE" == eveai_* || "$SERVICE" == "prometheus" || "$SERVICE" == "grafana" ]]; then
if process_service "$SERVICE"; then if process_service "$SERVICE"; then
echo "✅ Successfully processed $SERVICE" echo "✅ Successfully processed $SERVICE"
else else
echo "❌ Failed to process $SERVICE" echo "❌ Failed to process $SERVICE"
ERROR_LINES+=("❌ Failed to process $SERVICE")
EXIT_CODE=1
fi fi
else else
echo "⏭️ Skipping $SERVICE as it's not nginx, prometheus, grafana or doesn't start with eveai_" echo "⏭️ Skipping $SERVICE as it's not nginx, prometheus, grafana or doesn't start with eveai_"
fi fi
done done
echo -e "\033[32m✅ All specified services processed successfully!\033[0m" if [ ${#ERRORS[@]} -eq 0 ]; then
echo -e "\033[32m📦 Images are available locally and in registry\033[0m" echo -e "\033[32m✅ All specified services processed successfully!\033[0m"
echo -e "\033[32m🕐 Finished at $(date +"%d/%m/%Y %H:%M:%S")\033[0m" echo -e "\033[32m📦 Images are available locally and in registry\033[0m"
else
echo -e "\033[31m❌ One or more errors occurred during build/push\033[0m"
# Reprint short failure lines (your concise messages)
for LINE in "${ERROR_LINES[@]}"; do
echo "$LINE"
done
echo ""
echo "Details (see logs for full output):"
for ITEM in "${ERRORS[@]}"; do
SERVICE_STEP_MSG_LOG=$(echo "$ITEM")
IFS='|' read -r SVC STEP LOGFILE MSG <<< "$SERVICE_STEP_MSG_LOG"
echo "- Service: $SVC | Step: $STEP"
echo " ↳ Log: $LOGFILE"
done
EXIT_CODE=1
fi
# Always print finished timestamp
echo -e "\033[32m🕐 Finished at $(date +"%d/%m/%Y %H:%M:%S")\033[0m"
exit $EXIT_CODE

View File

@@ -3,4 +3,3 @@ FROM registry.ask-eve-ai-local.com/josakola/eveai-base:latest
# Copy the service-specific source code into the container. # Copy the service-specific source code into the container.
COPY eveai_chat_client /app/eveai_chat_client COPY eveai_chat_client /app/eveai_chat_client
COPY content /app/content COPY content /app/content

View File

@@ -1,6 +1,9 @@
# Use the official Nginx image as the base image # Use the official Nginx image as the base image
FROM nginx:latest ARG TARGETPLATFORM
FROM --platform=$TARGETPLATFORM nginx:latest
# Ensure we use user root
USER root
# Copy the custom Nginx configuration file into the container # Copy the custom Nginx configuration file into the container
COPY ../../nginx/nginx.conf /etc/nginx/nginx.conf COPY ../../nginx/nginx.conf /etc/nginx/nginx.conf

View File

@@ -1,20 +1,12 @@
#!/bin/bash #!/bin/bash
cd /Volumes/OWC4M2_1/Development/Josako/EveAI/TBD/docker
source ./docker_env_switch.sh dev
echo "Copying client images" echo "Copying client images"
cp -fv ../eveai_chat_client/static/assets/img/* ../nginx/static/assets/img cp -fv ../eveai_chat_client/static/assets/img/* ../nginx/static/assets/img
pcdown
cd ../nginx cd ../nginx
npm run clean npm run clean
npm run build npm run build
cd ../docker cd ../docker/
./build_and_push_eveai.sh -b nginx
pcup

View File

@@ -130,6 +130,11 @@ helm install monitoring prometheus-community/kube-prometheus-stack \
--create-namespace \ --create-namespace \
--values scaleway/manifests/base/monitoring/prometheus-values.yaml --values scaleway/manifests/base/monitoring/prometheus-values.yaml
# Install pushgateway
helm install monitoring-pushgateway prometheus-community/prometheus-pushgateway \
-n monitoring --create-namespace \
--set serviceMonitor.enabled=true
# Monitor deployment progress # Monitor deployment progress
kubectl get pods -n monitoring -w kubectl get pods -n monitoring -w
# Wait until all pods show STATUS: Running # Wait until all pods show STATUS: Running

View File

@@ -0,0 +1,106 @@
# Cache busting, builds en distributie (Parcel v2 + manifest)
Dit document beschrijft hoe cache busting nu structureel is geïmplementeerd met Parcel v2 (filename hashing) en hoe je builds en distributie uitvoert in de verschillende omgevingen.
## Overzicht van de aanpak
- Parcel v2 bouwt de frontend bundles met content-hashes in de bestandsnamen (bijv. `/static/dist/chat-client.abcd123.js`).
- We bouwen via minimale HTML entries (frontend_src/entries/*.html) die de JS importeren; dit dwingt Parcel tot consistente hashing en CSS-extractie.
- Na de build schrijven we een `manifest.json` in `nginx/static/dist/` met een mapping van logische namen naar gehashte paden.
- Templates gebruiken nu `asset_url('dist/chat-client.js|css')` om automatisch naar de juiste gehashte bestanden te verwijzen.
- Nginx (DEV/TEST) is ingesteld om voor `/static/dist/` lange cache headers te sturen: `Cache-Control: public, max-age=31536000, immutable`.
- HTML krijgt geen agressieve caching via Nginx; de browser ontdekt bij volgende request de nieuwe gehashte asset-URLs.
- In STAGING/PROD (Ingress + Bunny) volg je dezelfde principes; CDN ziet unieke URLs en cachet langdurig, zonder purges te vereisen.
## Relevante bestanden die zijn aangepast/toegevoegd
- `nginx/package.json`
- DevDependency: `@parcel/reporter-bundle-manifest` toegevoegd.
- `postbuild` script toegevoegd (optioneel; puur informatief).
- `nginx/.parcelrc`
- Parcel reporter geconfigureerd zodat `manifest.json` wordt weggeschreven.
- `common/utils/template_filters.py`
- Nieuwe Jinja global `asset_url(logical_path)` geregistreerd.
- `eveai_chat_client/asset_manifest.py`
- Hulpfuncties om manifest in te lezen en paden te resolven met caching.
- `eveai_chat_client/templates/base.html` en `templates/scripts.html`
- CSS/JS referenties omgezet naar `{{ asset_url('dist/chat-client.css|js') }}`.
- `nginx/nginx.conf`
- Extra location voor `/static/dist/` met lange cache headers.
## Build uitvoeren (lokaal of in CI)
1. Ga naar de `nginx` directory.
2. Installeer dependencies (eenmalig of na wijzigingen):
- `npm install`
3. Run build:
- `npm run clean`
- `npm run build`
Resultaat:
- Bundles met content-hash in `nginx/static/dist/`
- `nginx/static/dist/manifest.json` aanwezig
Tip: De build wordt ook aangeroepen door het bestaande script `docker/rebuild_chat_client.sh`.
## rebuild_chat_client.sh
Script: `docker/rebuild_chat_client.sh`
- Kopieert client images naar `nginx/static/assets/img`.
- Voert vervolgens `npm run clean` en `npm run build` uit in `nginx/`.
- Bouwt en pusht daarna de `nginx` container.
Door de nieuwe Parcel configuratie zal de build automatisch `manifest.json` genereren en gehashte assets outputten. Er zijn geen extra stappen nodig in dit script.
## Hoe de templates de juiste bestanden vinden
- De Jinja global `asset_url()` zoekt in `manifest.json` de gehashte bestandsnaam op basis van een logische naam.
- Voorbeelden in templates:
- CSS: `{{ asset_url('dist/chat-client.css') }}`
- JS: `{{ asset_url('dist/chat-client.js') }}`
- Als het manifest onverhoopt ontbreekt (bijv. in een edge-case), valt `asset_url` automatisch terug naar het on-gehashte pad onder `/static/` zodat de pagina niet breekt.
## Nginx (DEV/TEST)
In `nginx/nginx.conf` is toegevoegd:
```
location ^~ /static/dist/ {
alias /etc/nginx/static/dist/;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
```
- Hiermee krijgen gehashte assets lange caching. Omdat de URL wijzigt bij iedere inhoudswijziging, halen browsers/CDN de nieuwe versie zonder problemen op.
- HTML wordt niet agressief gecachet door Nginx; wil je volledig no-cache afdwingen voor HTML, dan kun je dat ook applicatiezijde doen (Flask) of via extra Nginx rules voor specifieke HTML routes.
## Ingress (STAGING/PROD) en Bunny.net
- Ingress: Zet (indien nog niet aanwezig) voor het pad dat `/static/dist/` serveert een configuration snippet met dezelfde header:
- `add_header Cache-Control "public, max-age=31536000, immutable" always;`
- HTML/endpoints laat je kort/no-cache of standaard; belangrijk is dat templates met nieuwe gehashte URLs snel door de clients worden opgepakt.
- Bunny.net (pull zone): CDN cachet op basis van volledige URL. Door de content-hash in de bestandsnaam zijn purges normaliter niet nodig.
## Troubleshooting / Tips
- Controleer na build of `manifest.json` bestaat:
- `ls nginx/static/dist/manifest.json`
- Inspecteer headers in DEV:
- `curl -I http://<host>/static/dist/chat-client.<hash>.js`
- Moet `Cache-Control: public, max-age=31536000, immutable` tonen.
- Als een client toch oude CSS/JS toont:
- Check of de geleverde HTML naar de nieuwste gehashte bestandsnaam verwijst (View Source / Network tab).
- Zorg dat de HTML niet op CDN langdurig wordt gecachet.
## Samenvatting workflow per omgeving
- DEV/TEST (Nginx):
1) `docker/rebuild_chat_client.sh` draaien (of handmatig `npm run build` in `nginx/`).
2) Nginx container wordt vernieuwd met nieuwe `static/dist` inclusief manifest en gehashte bundles.
- STAGING/PROD (Ingress + Bunny):
1) Zelfde build, gehashte bundles en manifest worden gedeployed via container image.
2) Ingress dient `/static/dist/` met lange cache headers.
3) Bunny.net cachet de nieuwe URLs automatisch; purgen niet nodig.
Met deze setup combineren we maximale performance (lange caching) met directe updates (nieuwe URL bij wijziging).

View File

@@ -0,0 +1,208 @@
# Migratieplan: Standaardisatie van Startup Processen en Docker Builds
Dit document beschrijft de migratie-afspraken rond het opstarten van onze applicaties, inclusief het gebruik van een generiek startscript, het inzetten van `tini` als entrypoint, en de overstap naar een **shared base build** structuur.
Doel: **standaardisatie**, **betere betrouwbaarheid in Kubernetes**, en **snellere builds**.
---
## 1. Generiek startscript (`scripts/start.sh`)
### Doel
- Eén startscript voor **alle rollen**: web, worker, beat.
- Gedrag wordt bepaald via **environment variables** (`ROLE=web|worker|beat`).
- Geen “magische” verschillen meer tussen Podman en Kubernetes.
### Voorbeeld
```bash
#!/usr/bin/env bash
set -euo pipefail
ROLE="${ROLE:-web}"
case "$ROLE" in
web)
exec gunicorn -w "${WORKERS:-1}" -k "${WORKER_CLASS:-gevent}" -b "0.0.0.0:${PORT:-8080}" --worker-connections "${WORKER_CONN:-100}" --access-logfile - --error-logfile - --log-level "${LOGLEVEL:-info}" scripts.run_eveai_app:app
;;
worker)
exec celery -A scripts.run_eveai_workers worker --loglevel="${CELERY_LOGLEVEL:-INFO}" --concurrency="${CELERY_CONCURRENCY:-2}" --max-tasks-per-child="${CELERY_MAX_TASKS_PER_CHILD:-1000}" --prefetch-multiplier="${CELERY_PREFETCH:-1}" -O fair
;;
beat)
exec celery -A scripts.run_eveai_workers beat --loglevel="${CELERY_LOGLEVEL:-INFO}"
;;
*)
echo "Unknown ROLE=$ROLE" >&2
exit 1
;;
esac
```
### Belangrijk
- **Geen init/migraties** meer in het startscript (die draaien we via Jobs/CronJobs).
- **Geen `cd`, `chown`, of PYTHONPATH-hacks** in startscript → alles naar Dockerfile.
- **Altijd `exec`** gebruiken zodat processen signalen correct ontvangen.
---
## 2. Gebruik van `tini` als ENTRYPOINT
### Waarom?
- In containers draait het eerste proces als **PID 1**.
- Zonder init-proces worden signalen niet correct doorgegeven en ontstaan **zombieprocessen**.
- `tini` is een lichtgewicht init die dit oplost.
### Implementatie
```dockerfile
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["/usr/bin/tini","-g","--"]
CMD ["bash","-lc","scripts/start.sh"]
```
- `-g` = stuur signalen naar de hele process group (belangrijk voor Gunicorn en Celery).
- Hiermee is een apart `entrypoint.sh` niet meer nodig.
---
## 3. Verplaatsen van logica naar Dockerfile
### Wat naar de Dockerfile moet
- `WORKDIR /app`
- `ENV PYTHONPATH=/app:/app/patched_packages:$PYTHONPATH`
- `ENV FLASK_APP=/app/scripts/run_eveai_app.py` (alleen nodig voor CLI, niet voor gunicorn)
- User-aanmaak en permissies:
```dockerfile
ARG UID=10001
ARG GID=10001
RUN groupadd -g ${GID} appuser && useradd -u ${UID} -g ${GID} -M -d /nonexistent -s /usr/sbin/nologin appuser
RUN chown -R appuser:appuser /app
USER appuser
```
### Wat niet meer in startscript hoort
- `cd /app`
- `export PYTHONPATH=...`
- `chown -R ... /logs`
- DB-migraties of cache-invalidatie → deze verhuizen naar **Kubernetes Jobs/CronJobs**.
---
## 4. Kubernetes Jobs & CronJobs
### Use cases
- **Jobs** → eenmalige taken zoals DB-migraties of cache-invalidatie.
- **CronJobs** → geplande taken (bv. nachtelijke opschoningen).
### Waarom?
- Startup scripts blijven lean.
- Geen race conditions bij meerdere replicas.
- Flexibel los van deploys uit te voeren.
---
## 5. Docker builds: naar een shared base (Optie B)
### Nieuwe structuur
```
repo/
├─ Dockerfile.base
├─ requirements.txt
├─ common/
├─ config/
├─ scripts/
├─ patched_packages/
├─ docker/
│ ├─ eveai_app/Dockerfile
│ ├─ eveai_api/Dockerfile
│ └─ eveai_workers/Dockerfile
└─ compose_dev.yaml
```
### Dockerfile.base
```dockerfile
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
ARG UID=10001
ARG GID=10001
RUN groupadd -g ${GID} appuser && useradd -u ${UID} -g ${GID} -M -d /nonexistent -s /usr/sbin/nologin appuser
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY common /app/common
COPY config /app/config
COPY scripts /app/scripts
COPY patched_packages /app/patched_packages
RUN chown -R appuser:appuser /app && chmod +x /app/scripts/start.sh
ENV PYTHONPATH=/app:/app/patched_packages:${PYTHONPATH} ROLE=web PORT=8080
USER appuser
EXPOSE 8080
ENTRYPOINT ["/usr/bin/tini","-g","--"]
CMD ["bash","-lc","scripts/start.sh"]
```
### Service Dockerfiles
#### docker/eveai_app/Dockerfile
```dockerfile
FROM yourorg/eveai-base:py312-v1
WORKDIR /app
COPY eveai_app /app/eveai_app
COPY migrations /app/migrations
COPY content /app/content
ENV ROLE=web PORT=8080
```
#### docker/eveai_api/Dockerfile
```dockerfile
FROM yourorg/eveai-base:py312-v1
WORKDIR /app
COPY eveai_api /app/eveai_api
ENV ROLE=web PORT=8080
```
#### docker/eveai_workers/Dockerfile
```dockerfile
FROM yourorg/eveai-base:py312-v1
WORKDIR /app
COPY eveai_workers /app/eveai_workers
ENV ROLE=worker
```
---
## 6. Workflow afspraken
1. **Startscript** is generiek → gedrag via env (`ROLE=...`).
2. **Geen init-taken** meer in startscript → Jobs/CronJobs in k8s.
3. **`tini` als ENTRYPOINT** → correcte signalen en zombie-cleanup.
4. **Dockerfile** regelt PYTHONPATH, FLASK_APP, permissies en user.
5. **Base image** bevat Python, deps en common code.
6. **Service-images** kopiëren enkel hun eigen directories.
7. **Build flow**:
- Base builden/pushen bij gewijzigde deps/common.
- Services snel erbovenop rebuilden bij code-wijzigingen.
---
## 7. Samenvatting
Met deze migratie krijgen we:
- Eenvoudiger startscript (alleen proceskeuze).
- Betere betrouwbaarheid bij shutdowns (via tini).
- Strikte scheiding tussen **lifecycle taken** en **runtime**.
- Snellere builds via shared base image.
- Uniforme aanpak in zowel Podman/Compose als Kubernetes.

108
documentation/mobile-ux.md Normal file
View File

@@ -0,0 +1,108 @@
# Mobile UX Improvements for EveAI Chat Client
This document explains the changes we made to improve the mobile user experience and how to verify or tweak them.
## Summary of Issues Addressed
- Unwanted white margins and page scroll on mobile due to default body margin and mixed vh/dvh usage.
- Layout jitter and incorrect heights when the browser address bar or the on-screen keyboard appears.
- Chat input potentially being obscured by the keyboard.
- Differences between DevTools emulation and real devices.
## Key Changes
1. CSS base reset and layout fixes
- Remove default white margins and prevent double scroll.
- Ensure outer containers dont create conflicting scrollbars.
- Respect device safe-area insets (bottom notch, etc.).
Files:
- eveai_chat_client/templates/base.html
- Added a CSS custom property fallback: `--vvh: 1vh;` on `:root`.
- eveai_chat_client/static/assets/css/chat.css
- `.app-container` now uses `height: calc(var(--vvh, 1vh) * 100);`.
- Added base reset: `body { margin: 0; overflow: hidden; }` and global `box-sizing: border-box`.
- Mobile `.content-area` no longer uses `calc(100vh - header)`; we rely on flex with `min-height: 0`.
- eveai_chat_client/static/assets/css/chat-components.css
- Added `padding-bottom: env(safe-area-inset-bottom)` for the chat input area on mobile.
- When keyboard is open (detected via JS), we slightly increase bottom padding.
2. VisualViewport utility and debug overlay
- We introduced a small utility that:
- Sets a CSS variable `--vvh` equal to 1% of the current visual viewport height.
- Toggles a `keyboard-open` class on `<body>` when the keyboard is likely visible.
- Optionally shows a debug overlay with real-time viewport metrics.
Files:
- frontend_src/js/utils/viewport.js (new)
- Exports `initViewportFix({ enableDebug })`.
- Updates `--vvh` on resize/scroll/orientation changes.
- Optional debug overlay via `enableDebug`.
- frontend_src/js/chat-client.js
- Imports and initializes the util on DOMContentLoaded.
- Enable overlay via query parameter: `?debug=viewport`.
## How It Works
- CSS reads `--vvh` to size the vertical layout. This follows the visual viewport, so when the URL bar or keyboard affects the viewport, the chat layout adapts without extra page scroll.
- The `keyboard-open` body class allows us to slightly increase bottom padding for the input to avoid edge cases where the input is near the keyboard.
- On mobile, `.app-container` spans the full visual viewport height, and `.content-area` flexes to take remaining space under the mobile header without relying on height calculations.
## Testing Checklist
1. Open the chat on real devices (iOS Safari, Android Chrome).
2. Verify there are no white margins around the app; the overall page should not scroll.
3. Focus the message input:
- Keyboard appears; the message list should stay usable and the input should remain visible.
- The page should not jump in height unexpectedly.
4. Rotate device (portrait/landscape) and ensure the layout adapts.
5. Try with/without the browser UI (scroll a bit to collapse the address bar on Android Chrome).
6. Optional: enable overlay with `?debug=viewport` and verify reported values look sane.
## Tuning and Fallbacks
- If you need to support extremely old iOS versions that lack VisualViewport, we still fall back to `1vh` for `--vvh`. You can increase robustness by setting `--vvh` via a simple `window.innerHeight` measurement on load.
- Keyboard detection is heuristic-based (drop > 120px). Adjust the threshold in `viewport.js` if needed for specific device groups.
- Avoid adding fixed `height: 100vh` or `calc(100vh - X)` to new containers; prefer flex layouts with `min-height: 0` for scrollable children.
## Known Limitations
- On some embedded browsers (in-app webviews), viewport behavior can deviate; test with your target apps.
- The safe-area env variables require modern browsers; most iOS/Android are fine, but always test.
## Enabling/Disabling Debug Overlay
- Append `?debug=viewport` to the URL to enable.
- The overlay displays: `innerHeight`, `clientHeight`, `visualViewport.height`, `visualViewport.pageTop`, header height, current `--vvh`, and keyboard state.
## Next Steps (Optional Enhancements)
- PWA manifest with `display: standalone` to reduce address bar effects.
- Add smooth scroll-into-view on input focus for edge cases on particular webviews.
- Add E2E tests using BrowserStack for a small device matrix.
## Focus Zoom Prevention (2025-09-24)
Some mobile browsers (notably iOS Safari) auto-zoom when focusing inputs with small font sizes. To prevent this:
- We enforce font-size: 16px for inputs, selects, textareas, and buttons on mobile in chat-components.css.
- We stabilize text scaling via `-webkit-text-size-adjust: 100%`.
- We extended the meta viewport with `viewport-fit=cover`.
- The debug overlay now also shows `visualViewport.scale` to confirm no zoom is happening (expect 1.00 during focus).
Files:
- eveai_chat_client/static/assets/css/chat-components.css (mobile form control font-size)
- eveai_chat_client/static/assets/css/chat.css (text-size-adjust)
- eveai_chat_client/templates/base.html (viewport meta)
- frontend_src/js/utils/viewport.js (overlay shows vv.scale)
Validation checklist:
- Focus the chat input and language select on iOS Safari and Android Chrome; the UI should not scale up.
- Overlay shows `vv.scale: 1.00` with `?debug=viewport`.
## Mobile Header Overflow Fix (2025-09-24)
We observed that the mobile header could push the layout wider than the viewport on small devices, causing horizontal page scroll. We addressed this by:
- Allowing the header row to wrap: `.mobile-header { flex-wrap: wrap; }`.
- Ensuring children can shrink within the container: `min-width: 0` on `.mobile-logo` and `.mobile-language-selector`.
- Constraining controls: `:deep(.language-select)` uses `max-width: 100%` and on very small screens `max-width: 60vw`.
- Preventing accidental bleed: `.mobile-header { max-width: 100%; overflow: hidden; }`.
- Added `html { overflow-x: hidden; }` as a light global failsafe.
Files:
- eveai_chat_client/static/assets/vue-components/MobileHeader.vue (scoped styles updated)
- eveai_chat_client/static/assets/css/chat.css (added html { overflow-x: hidden; })
Validation checklist:
- On 320px width devices, header may wrap to 2 lines without horizontal scroll.
- Long tenant names and multiple languages do not cause page-wide overflow; content remains within viewport width.

View File

@@ -13,7 +13,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
<!-- Gebundelde CSS (bevat nu al je CSS) --> <!-- Gebundelde CSS (bevat nu al je CSS) -->
<link href="{{url_for('static', filename='dist/main.css')}}" rel="stylesheet" /> <link href="{{ asset_url('dist/main.css') }}" rel="stylesheet" />
<base href="/admin/"> <base href="/admin/">
</head> </head>

View File

@@ -1,5 +1,5 @@
{#dist/main.js contains all used javascript libraries#} {#dist/main.js contains all used javascript libraries#}
<script src="{{url_for('static', filename='dist/main.js')}}"></script> <script src="{{ asset_url('dist/main.js') }}"></script>
{% include 'eveai_json_editor.html' %} {% include 'eveai_json_editor.html' %}
{% include 'eveai_ordered_list_editor.html' %} {% include 'eveai_ordered_list_editor.html' %}

View File

@@ -79,6 +79,16 @@
.chat-input-area { .chat-input-area {
max-width: 100%; /* Op mobiel volledige breedte gebruiken */ max-width: 100%; /* Op mobiel volledige breedte gebruiken */
/* Respect safe-area at bottom */
padding-bottom: env(safe-area-inset-bottom, 0);
}
/* Prevent iOS focus zoom by ensuring form controls are >= 16px */
input, textarea, select, button,
.chat-input input, .chat-input textarea, .chat-input select, .chat-input button,
.mobile-language-selector :is(select, input, button) {
font-size: 16px;
line-height: 1.4;
} }
.chat-input { .chat-input {
@@ -97,6 +107,11 @@
} }
} }
/* When keyboard is open, allow a bit more space below */
body.keyboard-open .chat-input-area {
padding-bottom: calc(env(safe-area-inset-bottom, 0) + 8px);
}
/* Extra small screens */ /* Extra small screens */
@media (max-width: 480px) { @media (max-width: 480px) {
.chat-app-container { .chat-app-container {
@@ -251,15 +266,6 @@
margin: 20px 0; margin: 20px 0;
} }
.form-message .message-content {
max-width: 90%;
background: white;
border: 1px solid #e9ecef;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
/* System messages */ /* System messages */
.system-message { .system-message {
text-align: center; text-align: center;
@@ -437,24 +443,6 @@
opacity: 0.8; opacity: 0.8;
} }
/* Mobile responsiveness */
@media (max-width: 768px) {
.message {
padding: 0 15px;
}
/* moved to ChatMessage.vue scoped styles */
.message.user .message-content {
margin-left: 40px;
}
.message.ai .message-content,
.message.bot .message-content {
margin-right: 40px;
}
}
@media (max-width: 480px) { @media (max-width: 480px) {
.message { .message {
padding: 0 10px; padding: 0 10px;

View File

@@ -63,13 +63,6 @@
margin-top: 10px; margin-top: 10px;
} }
/* Dynamic form container transitions */
.dynamic-form-container {
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
transform: translateY(0);
opacity: 1;
}
/* Chat input transitions */ /* Chat input transitions */
.chat-input { .chat-input {
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;

View File

@@ -146,11 +146,6 @@
font-size: 14px; font-size: 14px;
} }
/* Ensure forms in messages use full available width */
.message .dynamic-form-container {
width: 100%;
max-width: none;
}
.message .dynamic-form { .message .dynamic-form {
width: 100%; width: 100%;

View File

@@ -14,8 +14,9 @@
/* App container layout */ /* App container layout */
.app-container { .app-container {
display: flex; display: flex;
height: 100vh; /* fallback */ /* Use visual viewport variable when available */
height: 100dvh; /* prefer dynamic viewport unit */ min-height: 0;
height: calc(var(--vvh, 1vh) * 100);
width: 100%; width: 100%;
} }
@@ -85,8 +86,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
height: 100vh; /* fallback for desktop */ height: auto; /* prefer dynamic viewport on desktop */
height: 100dvh; /* prefer dynamic viewport on desktop */
} }
.chat-container { .chat-container {
@@ -97,16 +97,19 @@
} }
html, body { html, body {
height: 100%; height: calc(var(--vvh, 1vh) * 100); min-height: 0;
} }
/* Base reset & overflow control */
* { box-sizing: border-box; }
html { overflow-x: hidden; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body { body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 1.6; line-height: 1.6;
color: var(--text-color); color: var(--text-color);
background-color: var(--background-color); background-color: var(--background-color);
height: 100vh; overflow: hidden; /* prevent double scroll; inner containers handle scrolling */
overflow: hidden;
} }
.container { .container {
@@ -281,14 +284,6 @@ body {
} }
:root { --mobile-header-height: 60px; } /* default/minimum */ :root { --mobile-header-height: 60px; } /* default/minimum */
/* Content area takes remaining space */
.content-area {
flex: 1;
height: calc(100vh - var(--mobile-header-height)); /* fallback */
height: calc(100dvh - var(--mobile-header-height)); /* prefer dynamic viewport */
min-height: 0;
}
} }
/* Responsieve design regels worden nu gedefinieerd in chat-components.css */ /* Responsieve design regels worden nu gedefinieerd in chat-components.css */

View File

@@ -1,9 +1,5 @@
/* Dynamisch formulier stijlen */ /* Dynamisch formulier stijlen */
.dynamic-form {
padding: 15px;
}
.form-header { .form-header {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -434,13 +434,13 @@ export default {
.chat-input-container { .chat-input-container {
width: 100%; width: 100%;
max-width: 1000px; max-width: 1000px;
padding: 20px; padding: 10px;
box-sizing: border-box; box-sizing: border-box;
background-color: var(--active-background-color); background-color: var(--active-background-color);
color: var(--human-message-text-color); color: var(--human-message-text-color);
border-top: 1px solid #e0e0e0; border-top: 1px solid #e0e0e0;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
font-size: 14px; font-size: 16px;
transition: opacity 0.2s ease-in-out; transition: opacity 0.2s ease-in-out;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@@ -470,7 +470,7 @@ export default {
outline: none; outline: none;
transition: border-color 0.2s; transition: border-color 0.2s;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
font-size: 14px; font-size: 16px;
/* Transparante achtergrond in plaats van wit */ /* Transparante achtergrond in plaats van wit */
background-color: var(--human-message-background); background-color: var(--human-message-background);
color: var(--human-message-text-color); color: var(--human-message-text-color);
@@ -534,14 +534,14 @@ export default {
/* Active AI Message Area - positioned at top of ChatInput */ /* Active AI Message Area - positioned at top of ChatInput */
.active-ai-message-area { .active-ai-message-area {
margin-bottom: 15px; margin-bottom: 10px;
padding: 12px;
background-color: var(--ai-message-background); background-color: var(--ai-message-background);
color: var(--ai-message-text-color); color: var(--ai-message-text-color);
border-radius: 8px; border-radius: 8px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
font-size: 14px; font-size: 16px;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
padding: 5px;
} }
/* Ensure the active AI message integrates well with ChatInput styling */ /* Ensure the active AI message integrates well with ChatInput styling */

View File

@@ -432,7 +432,7 @@ export default {
.message-content { .message-content {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
font-size: 12px; font-size: 12px;
max-width: 90%; max-width: 100%;
padding: 8px; padding: 8px;
border-radius: 10px; border-radius: 10px;
word-wrap: break-word; word-wrap: break-word;
@@ -663,8 +663,7 @@ export default {
.message.bot .message-content { .message.bot .message-content {
background: var(--ai-message-background); background: var(--ai-message-background);
color: var(--ai-message-text-color); color: var(--ai-message-text-color);
border-bottom-left-radius: 4px; margin-right: 20px;
margin-right: 60px;
} }
/* Hover effects for message bubbles */ /* Hover effects for message bubbles */
@@ -692,27 +691,18 @@ export default {
} }
.message.ai .message-content, .message.ai .message-content,
.message.bot .message-content { .message.bot .message-content {
margin-right: 40px; margin-right: 5px;
} }
/* Mobile: place logo inside bubble and prevent overlap with text */ /* Mobile: place logo inside bubble and prevent overlap with text */
.message.ai .ai-message-logo { .message.ai .ai-message-logo {
top: -12px; top: -12px;
left: 8px;
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
.message.ai .message-content { .message.ai .message-content {
/* Reserve space for the in-bubble logo */ /* Reserve space for the in-bubble logo */
padding-top: 1px; /* 24px logo + margins */ padding-top: 1px; /* 24px logo + margins */
padding-left: 1px; padding-left: 10px;
}
}
@media (max-width: 480px) {
.message-content {
max-width: 90%;
margin-left: 20px !important;
margin-right: 20px !important;
} }
} }

View File

@@ -538,14 +538,20 @@ export default {
<style scoped> <style scoped>
/* Dynamisch formulier stijlen */ /* Dynamisch formulier stijlen */
.dynamic-form-container { .dynamic-form-container {
margin-bottom: 15px;
overflow: hidden; overflow: hidden;
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
transform: translateY(0);
opacity: 1;
}
.message .dynamic-form-container {
width: 100%;
max-width: none;
} }
.dynamic-form { .dynamic-form {
background: var(--human-message-background); background: var(--human-message-background);
border-radius: 8px; border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 15px rgba(0,0,0,0.1); box-shadow: 0 2px 15px rgba(0,0,0,0.1);
box-sizing: border-box; box-sizing: border-box;
max-width: 100%; max-width: 100%;

View File

@@ -294,7 +294,7 @@ export default {
border-bottom: 2px solid #000; border-bottom: 2px solid #000;
} }
} }
/* migrated from global css: message-content within form-message */
.form-message .message-content { .form-message .message-content {
max-width: 90%; max-width: 90%;
background: white; background: white;

View File

@@ -80,21 +80,24 @@ const handleLanguageChange = (newLanguage) => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-wrap: wrap; /* allow wrapping to next line on narrow screens */
padding: 10px 15px; padding: 10px 15px;
background: var(--sidebar-background); background: var(--sidebar-background);
color: var(--sidebar-color); color: var(--sidebar-color);
border-bottom: 1px solid rgba(0,0,0,0.1); border-bottom: 1px solid rgba(0,0,0,0.1);
min-height: 60px; min-height: 60px;
max-width: 100%; /* never exceed viewport width */
overflow: hidden; /* clip any accidental overflow */
} }
/* Mobile logo container - meer specifieke styling */ /* Mobile logo container - meer specifieke styling */
.mobile-logo { .mobile-logo {
flex-shrink: 0; flex-shrink: 1; /* allow logo area to shrink */
min-width: 0; /* allow shrinking below intrinsic width */
display: flex !important; display: flex !important;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
height: 50px; /* Vaste hoogte voor consistentie */ height: 50px; /* Vaste hoogte voor consistentie */
min-width: 120px; /* Minimale breedte */
} }
/* Diepere styling voor het logo component */ /* Diepere styling voor het logo component */
@@ -105,6 +108,7 @@ const handleLanguageChange = (newLanguage) => {
align-items: center !important; align-items: center !important;
justify-content: flex-start !important; justify-content: flex-start !important;
height: 100% !important; height: 100% !important;
min-width: 0 !important; /* allow inner content to shrink */
} }
.mobile-logo :deep(.logo-image) { .mobile-logo :deep(.logo-image) {
@@ -128,7 +132,7 @@ const handleLanguageChange = (newLanguage) => {
/* Mobile language selector styling */ /* Mobile language selector styling */
.mobile-language-selector { .mobile-language-selector {
flex-shrink: 1; flex-shrink: 1;
min-width: 140px; min-width: 0; /* allow selector area to shrink */
} }
.mobile-language-selector :deep(.language-selector) { .mobile-language-selector :deep(.language-selector) {
@@ -142,10 +146,18 @@ const handleLanguageChange = (newLanguage) => {
.mobile-language-selector :deep(.language-select) { .mobile-language-selector :deep(.language-select) {
padding: 6px 10px; padding: 6px 10px;
font-size: 0.85rem; font-size: 0.85rem;
min-width: 120px; min-width: 0; /* allow the select to shrink */
max-width: 100%; /* never exceed container width */
margin: 0; margin: 0;
} }
/* Extra constraints on ultra-small screens */
@media (max-width: 360px) {
.mobile-language-selector :deep(.language-select) {
max-width: 60vw; /* avoid pushing beyond viewport */
}
}
/* Media queries voor responsiviteit */ /* Media queries voor responsiviteit */
@media (max-width: 768px) { @media (max-width: 768px) {
.mobile-header { .mobile-header {

View File

@@ -2,14 +2,17 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>{% block title %}EveAI Chat{% endblock %}</title> <title>{% block title %}EveAI Chat{% endblock %}</title>
<link href="{{url_for('static', filename='dist/chat-client.css')}}" rel="stylesheet" /> <link href="{{ asset_url('dist/chat-client.css') }}" rel="stylesheet" />
<!-- Custom theme colors from tenant settings --> <!-- Custom theme colors from tenant settings -->
<style> <style>
:root { :root {
/* Visual viewport vh unit fallback (1vh if JS not yet set) */
--vvh: 1vh;
/* Legacy support - keeping for backward compatibility only */ /* Legacy support - keeping for backward compatibility only */
/* These variables are deprecated and should not be used in new code */ /* These variables are deprecated and should not be used in new code */
--primary-color: {{ customisation.active_background_color|default('#ffffff') }}; --primary-color: {{ customisation.active_background_color|default('#ffffff') }};

View File

@@ -26,7 +26,7 @@
</script> </script>
<!-- Chat client JS - bundled met alle componenten en ES modules --> <!-- Chat client JS - bundled met alle componenten en ES modules -->
<script src="{{url_for('static', filename='dist/chat-client.js')}}"></script> <script src="{{ asset_url('dist/chat-client.js') }}"></script>
<script> <script>
// Voeg taalinstellingen toe aan chatConfig indien deze nog niet bestaan // Voeg taalinstellingen toe aan chatConfig indien deze nog niet bestaan
if (window.chatConfig) { if (window.chatConfig) {

View File

@@ -1,97 +0,0 @@
// Chat Client JavaScript modules
// Modern gebundelde versie met ES modules
// CSS imports - zorg dat deze bestanden bestaan en correct worden gebundeld door Parcel
import '../css/chat-client.css';
// CSS imports uit eveai_chat_client
import '../../../eveai_chat_client/static/assets/css/chat.css';
import '../../../eveai_chat_client/static/assets/css/chat-components.css';
import '../../../eveai_chat_client/static/assets/css/chat-input.css';
import '../../../eveai_chat_client/static/assets/css/chat-message.css';
import '../../../eveai_chat_client/static/assets/css/form.css';
import '../../../eveai_chat_client/static/assets/css/form-message.css';
import '../../../eveai_chat_client/static/assets/css/language-selector.css';
// Dependencies
import { createApp } from 'vue';
import { marked } from 'marked';
// Maak fundamentele libraries globaal beschikbaar
window.Vue = { createApp };
window.marked = marked;
// Gebruik barrel export voor componenten
import * as Components from '../../../eveai_chat_client/static/assets/vue-components/index.js';
// Maak Components globaal beschikbaar voor debugging
window.Components = Components;
console.log('Components loaded:', Object.keys(Components));
// Main chat application - moet als laatste worden geladen
import ChatApp from '../../../eveai_chat_client/static/assets/vue-components/ChatApp.vue';
// Wacht tot het document volledig is geladen voordat we Vue initialiseren
document.addEventListener('DOMContentLoaded', () => {
console.log('Initializing Chat Application');
try {
// Mount SideBar component to sidebar-container
const sidebarContainer = document.getElementById('sidebar-container');
if (sidebarContainer && Components.SideBar) {
const sidebarApp = createApp(Components.SideBar, {
tenantMake: window.chatConfig?.tenantMake || { name: 'EveAI', logo_url: '' },
explanationText: window.chatConfig?.explanationText || '',
initialLanguage: window.chatConfig?.language || 'en',
supportedLanguageDetails: window.chatConfig?.supportedLanguageDetails || {},
allowedLanguages: window.chatConfig?.allowedLanguages || ['nl', 'en', 'fr', 'de'],
apiPrefix: window.chatConfig?.apiPrefix || ''
});
// Registreer componenten voor sidebar app
for (const [name, component] of Object.entries(Components)) {
sidebarApp.component(name, component);
}
sidebarApp.mount('#sidebar-container');
console.log('SideBar mounted successfully');
}
// Mount MobileHeader component to mobile-header-container
const mobileHeaderContainer = document.getElementById('mobile-header-container');
if (mobileHeaderContainer && Components.MobileHeader) {
const mobileHeaderApp = createApp(Components.MobileHeader, {
tenantMake: window.chatConfig?.tenantMake || { name: 'EveAI', logo_url: '' },
initialLanguage: window.chatConfig?.language || 'en',
supportedLanguageDetails: window.chatConfig?.supportedLanguageDetails || {},
allowedLanguages: window.chatConfig?.allowedLanguages || ['nl', 'en', 'fr', 'de'],
apiPrefix: window.chatConfig?.apiPrefix || ''
});
// Registreer componenten voor mobile header app
for (const [name, component] of Object.entries(Components)) {
mobileHeaderApp.component(name, component);
}
mobileHeaderApp.mount('#mobile-header-container');
console.log('MobileHeader mounted successfully');
}
// Mount ChatApp to the chat container
const chatContainer = document.querySelector('.chat-container');
if (chatContainer) {
const chatApp = createApp(ChatApp);
// Registreer alle componenten globaal voor chat app
for (const [name, component] of Object.entries(Components)) {
chatApp.component(name, component);
}
chatApp.mount('.chat-container');
console.log('ChatApp mounted successfully');
}
console.log('All Vue apps mounted successfully');
} catch (error) {
console.error('Error initializing Vue applications:', error);
}
});

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chat Client Entry</title>
</head>
<body>
<!-- Minimal HTML entry used solely for Parcel to produce hashed JS/CSS bundles. -->
<script type="module" src="../js/chat-client.js"></script>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Main Entry</title>
</head>
<body>
<!-- Minimal HTML entry used solely for Parcel to produce hashed JS/CSS bundles. -->
<script type="module" src="../js/main.js"></script>
</body>
</html>

View File

@@ -62,6 +62,12 @@ http {
alias /etc/nginx/static/; alias /etc/nginx/static/;
} }
# Long-term caching for hashed assets built by Parcel
location ^~ /static/dist/ {
alias /etc/nginx/static/dist/;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
#error_page 404 /404.html; #error_page 404 /404.html;
# redirect server error pages to the static page /50x.html # redirect server error pages to the static page /50x.html

1357
nginx/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -38,14 +38,25 @@
"scripts": { "scripts": {
"prebuild": "mkdir -p static/dist && npm run sync-assets", "prebuild": "mkdir -p static/dist && npm run sync-assets",
"sync-assets": "rsync -av ../eveai_app/static/assets/ static/assets/ && rsync -av ../eveai_chat_client/static/assets/ static/assets/", "sync-assets": "rsync -av ../eveai_app/static/assets/ static/assets/ && rsync -av ../eveai_chat_client/static/assets/ static/assets/",
"build": "npm run prebuild && npm run build:main && npm run build:chat", "build": "npm run prebuild && npm run build:main && npm run build:chat && npm run postbuild",
"build:main": "parcel build frontend_src/js/main.js --dist-dir static/dist --public-url /static/dist/ --no-source-maps", "build:main": "NODE_ENV=production parcel build frontend_src/entries/main.html",
"build:chat": "parcel build frontend_src/js/chat-client.js --dist-dir static/dist --public-url /static/dist/ --no-source-maps", "build:chat": "NODE_ENV=production parcel build frontend_src/entries/chat-client.html",
"predev": "mkdir -p static/dist && npm run sync-assets", "predev": "mkdir -p static/dist && npm run sync-assets",
"dev": "npm run predev && parcel frontend_src/js/main.js --dist-dir static/dist --public-url /static/dist/ & parcel frontend_src/js/chat-client.js --dist-dir static/dist --public-url /static/dist/", "dev": "npm run predev && parcel frontend_src/js/main.js --dist-dir static/dist --public-url /static/dist/ & parcel frontend_src/js/chat-client.js --dist-dir static/dist --public-url /static/dist/",
"prewatch": "mkdir -p static/dist && npm run sync-assets", "prewatch": "mkdir -p static/dist && npm run sync-assets",
"watch": "npm run prewatch && parcel watch frontend_src/js/main.js --dist-dir static/dist --public-url /static/dist/ & parcel watch frontend_src/js/chat-client.js --dist-dir static/dist --public-url /static/dist/", "watch": "npm run prewatch && parcel watch frontend_src/js/main.js --dist-dir static/dist --public-url /static/dist/ & parcel watch frontend_src/js/chat-client.js --dist-dir static/dist --public-url /static/dist/",
"clean": "rm -rf static/dist/* static/assets .parcel-cache" "clean": "rm -rf static/dist/* static/assets .parcel-cache",
"postbuild": "node scripts/generate-manifest.mjs"
},
"targets": {
"default": {
"context": "browser",
"distDir": "static/dist",
"publicUrl": "/static/dist/",
"outputFormat": "esmodule",
"isLibrary": false,
"optimize": true,
"sourceMap": false
}
} }
} }

View File

@@ -0,0 +1,55 @@
import { promises as fs } from 'node:fs';
import path from 'node:path';
const DIST_DIR = path.resolve(process.cwd(), 'static', 'dist');
const OUT_FILE = path.join(DIST_DIR, 'manifest.json');
const LOGICAL_ENTRIES = [
{ key: 'dist/chat-client.js', base: 'chat-client', exts: ['.js'] },
{ key: 'dist/chat-client.css', base: 'chat-client', exts: ['.css'] },
// Optionally include main entries if referenced via templates in the future
{ key: 'dist/main.js', base: 'main', exts: ['.js'] },
{ key: 'dist/main.css', base: 'main', exts: ['.css'] },
];
function isHashedFile(file, base, ext) {
return file.startsWith(base + '.') && file.endsWith(ext) && file !== base + ext;
}
async function generate() {
const files = await fs.readdir(DIST_DIR);
const manifest = {};
for (const entry of LOGICAL_ENTRIES) {
const matches = [];
for (const ext of entry.exts) {
for (const f of files) {
if (isHashedFile(f, entry.base, ext)) {
matches.push(`dist/${f}`);
}
}
}
if (matches.length === 1) {
manifest[entry.key] = matches[0];
} else if (matches.length > 1) {
const stats = await Promise.all(
matches.map(async m => ({
file: m,
mtime: (await fs.stat(path.join(DIST_DIR, path.basename(m)))).mtimeMs,
}))
);
stats.sort((a, b) => b.mtime - a.mtime);
manifest[entry.key] = stats[0].file;
} else {
manifest[entry.key] = entry.key; // fallback zodat pagina niet breekt
}
}
await fs.writeFile(OUT_FILE, JSON.stringify(manifest, null, 2), 'utf8');
console.log(`[manifest] written: ${OUT_FILE}`);
}
generate().catch(err => {
console.error('[manifest] generation failed:', err);
process.exit(1);
});

View File

@@ -34,6 +34,9 @@ spec:
# Alle keys uit eveai-tem secret # Alle keys uit eveai-tem secret
- extract: - extract:
key: name:eveai-tem key: name:eveai-tem
# alle secrets uit eveai-pushgateway secret
- extract:
key: name:eveai-pushgateway
data: data:
# Certificaat als aparte data entry # Certificaat als aparte data entry
- secretKey: REDIS_CERT - secretKey: REDIS_CERT