- verbeteringen client

- removal of eveai_chat
This commit is contained in:
Josako
2025-07-20 21:19:22 +02:00
parent 854d889413
commit abc0a50dcc
17 changed files with 74 additions and 1665 deletions

View File

@@ -1,6 +1,18 @@
version: "1.0.0"
name: "Chat Client Customisation"
configuration:
"sidebar_markdown":
name: "Sidebar Markdown"
description: "Sidebar Markdown-formatted Text"
type: "text"
required: false
"reasoning_visibility":
name: "Reasoning Visibility"
description: "Level of Reasoning showing in Chat Client"
type: "enum"
allowed_values: ["No Visibility", "Detailed Visibility"]
default: "Detailed Visibility"
required: false
"primary_color":
name: "Primary Color"
description: "Primary Color"
@@ -51,11 +63,6 @@ configuration:
description: "End Color for the gradient in the Chat Area"
type: "color"
required: false
"sidebar_markdown":
name: "Sidebar Markdown"
description: "Sidebar Markdown-formatted Text"
type: "text"
required: false
metadata:
author: "Josako"
date_added: "2024-06-06"

View File

@@ -144,40 +144,6 @@ services:
networks:
- eveai-network
# eveai_chat:
# image: josakola/eveai_chat:latest
# build:
# context: ..
# dockerfile: ./docker/eveai_chat/Dockerfile
# platforms:
# - linux/amd64
# - linux/arm64
# ports:
# - 5002:5002
# environment:
# <<: *common-variables
# COMPONENT_NAME: eveai_chat
# volumes:
# - ../eveai_chat:/app/eveai_chat
# - ../common:/app/common
# - ../config:/app/config
# - ../scripts:/app/scripts
# - ../patched_packages:/app/patched_packages
# - ./eveai_logs:/app/logs
# depends_on:
# db:
# condition: service_healthy
# redis:
# condition: service_healthy
# healthcheck:
# test: [ "CMD", "curl", "-f", "http://localhost:5002/healthz/ready" ] # Adjust based on your health endpoint
# interval: 30s
# timeout: 1s
# retries: 3
# start_period: 30s
# networks:
# - eveai-network
eveai_chat_client:
image: josakola/eveai_chat_client:latest
build:

View File

@@ -1,70 +0,0 @@
ARG PYTHON_VERSION=3.12.7
FROM python:${PYTHON_VERSION}-slim as base
# Prevents Python from writing pyc files.
ENV PYTHONDONTWRITEBYTECODE=1
# Keeps Python from buffering stdout and stderr to avoid situations where
# the application crashes without emitting any logs due to buffering.
ENV PYTHONUNBUFFERED=1
# Create directory for patched packages and set permissions
RUN mkdir -p /app/patched_packages && \
chmod 777 /app/patched_packages
# Ensure patches are applied to the application.
ENV PYTHONPATH=/app/patched_packages:$PYTHONPATH
WORKDIR /app
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/bin/bash" \
--no-create-home \
--uid "${UID}" \
appuser
# Install necessary packages and build tools
RUN apt-get update && apt-get install -y \
build-essential \
gcc \
postgresql-client \
curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Create logs directory and set permissions
RUN mkdir -p /app/logs && chown -R appuser:appuser /app/logs
# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
# Leverage a bind mount to requirements.txt to avoid having to copy them into
# into this layer.
COPY requirements.txt /app/
RUN python -m pip install -r requirements.txt
# Copy the source code into the container.
COPY eveai_chat /app/eveai_chat
COPY common /app/common
COPY config /app/config
COPY scripts /app/scripts
COPY patched_packages /app/patched_packages
# Set permissions for entrypoint script
RUN chmod 777 /app/scripts/entrypoint.sh
# Set ownership of the application directory to the non-privileged user
RUN chown -R appuser:appuser /app
# Expose the port that the application listens on.
EXPOSE 5002
# Set entrypoint and command
ENTRYPOINT ["/app/scripts/entrypoint.sh"]
CMD ["/app/scripts/start_eveai_chat.sh"]

View File

@@ -1,101 +0,0 @@
import logging
import logging.config
from flask import Flask, jsonify, request
import os
from flask_jwt_extended import verify_jwt_in_request, get_jwt_identity
from common.extensions import db, socketio, jwt, cors, session, simple_encryption, metrics
from config.logging_config import LOGGING
from eveai_chat.socket_handlers import chat_handler
from common.utils.cors_utils import create_cors_after_request, get_allowed_origins
from common.utils.celery_utils import make_celery, init_celery
from config.config import get_config
def create_app(config_file=None):
app = Flask(__name__)
environment = os.getenv('FLASK_ENV', 'development')
match environment:
case 'development':
app.config.from_object(get_config('dev'))
case 'production':
app.config.from_object(get_config('prod'))
case _:
app.config.from_object(get_config('dev'))
app.config['SESSION_KEY_PREFIX'] = 'eveai_chat_'
logging.config.dictConfig(LOGGING)
register_extensions(app)
app.celery = make_celery(app.name, app.config)
init_celery(app.celery, app)
@app.before_request
def check_cors():
if request.method == 'OPTIONS':
app.logger.debug("Handling OPTIONS request")
return '', 200 # Allow OPTIONS to pass through
origin = request.headers.get('Origin')
if not origin:
return # Not a CORS request
# Get tenant ID from request
if verify_jwt_in_request():
tenant_id = get_jwt_identity()
if not tenant_id:
return
else:
return
# Check if origin is allowed for this tenant
allowed_origins = get_allowed_origins(tenant_id)
if origin not in allowed_origins:
app.logger.warning(f'Origin {origin} not allowed for tenant {tenant_id}')
return {'error': 'Origin not allowed'}, 403
app.logger.info("EveAI Chat Server Started Successfully")
app.logger.info("-------------------------------------------------------------------------------------------------")
return app
def register_extensions(app):
db.init_app(app)
socketio.init_app(app,
message_queue=app.config.get('SOCKETIO_MESSAGE_QUEUE'),
cors_allowed_origins=app.config.get('SOCKETIO_CORS_ALLOWED_ORIGINS'),
async_mode=app.config.get('SOCKETIO_ASYNC_MODE'),
logger=app.config.get('SOCKETIO_LOGGER'),
engineio_logger=app.config.get('SOCKETIO_ENGINEIO_LOGGER'),
path='/socket.io/',
ping_timeout=app.config.get('SOCKETIO_PING_TIMEOUT'),
ping_interval=app.config.get('SOCKETIO_PING_INTERVAL'),
)
jwt.init_app(app)
simple_encryption.init_app(app)
metrics.init_app(app)
# Cors setup
cors.init_app(app, resources={
r"/*": { # Make sure this matches your setup
"origins": "*",
"methods": ["GET", "POST", "PUT", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization", "X-Requested-With"],
"expose_headers": ["Content-Length", "Content-Range"],
"supports_credentials": True,
"max_age": 1728000,
"allow_credentials": True
}
})
session.init_app(app)
def register_blueprints(app):
from views.healthz_views import healthz_bp
app.register_blueprint(healthz_bp)

View File

@@ -1,358 +0,0 @@
import uuid
from functools import wraps
from flask_jwt_extended import create_access_token, get_jwt_identity, verify_jwt_in_request, decode_token
from flask_socketio import emit, disconnect, join_room, leave_room
from flask import current_app, request, session
from sqlalchemy.exc import SQLAlchemyError
from datetime import datetime, timedelta
from prometheus_client import Counter, Histogram
from time import time
import re
from common.extensions import socketio, db, simple_encryption
from common.models.user import Tenant
from common.models.interaction import Interaction
from common.utils.celery_utils import current_celery
from common.utils.database import Database
from common.utils.token_validation import TokenValidator
from common.utils.eveai_exceptions import EveAISocketInputException
# Define custom metrics
socketio_message_counter = Counter('socketio_message_count', 'Count of SocketIO messages', ['event_type'])
socketio_message_latency = Histogram('socketio_message_latency_seconds', 'Latency of SocketIO message processing', ['event_type'])
class RoomManager:
def __init__(self):
self.active_rooms = {} # Store active room metadata
def validate_room_format(self, room_id: str) -> bool:
"""Validate room ID format: tenant_id_sessionid_timestamp"""
pattern = r'^\d+_[a-zA-Z0-9]+_\d+$'
return bool(re.match(pattern, room_id))
def is_room_active(self, room_id: str) -> bool:
return room_id in self.active_rooms
def validate_room_ownership(self, room_id: str, tenant_id: int, token: str) -> bool:
if not self.is_room_active(room_id):
return False
room_data = self.active_rooms[room_id]
return (room_data['tenant_id'] == tenant_id and
room_data['token'] == token)
def create_room(self, tenant_id: int, token: str) -> str:
"""Create new room with metadata"""
timestamp = int(datetime.now().timestamp())
room_id = f"{tenant_id}_{request.sid}_{timestamp}"
self.active_rooms[room_id] = {
'tenant_id': tenant_id,
'token': token,
'created_at': datetime.now(),
'last_activity': datetime.now()
}
return room_id
def update_room_activity(self, room_id: str):
"""Update room's last activity timestamp"""
if room_id in self.active_rooms:
self.active_rooms[room_id]['last_activity'] = datetime.now()
def cleanup_inactive_rooms(self, max_age_hours: int = 1):
"""Remove inactive rooms"""
now = datetime.now()
cutoff = now - timedelta(hours=max_age_hours)
inactive_rooms = [
room_id for room_id, data in self.active_rooms.items()
if data['last_activity'] < cutoff
]
for room_id in inactive_rooms:
del self.active_rooms[room_id]
room_manager = RoomManager()
# Decorator to measure SocketIO events
def track_socketio_event(func):
@wraps(func)
def wrapper(*args, **kwargs):
event_type = func.__name__
socketio_message_counter.labels(event_type=event_type).inc()
start_time = time()
result = func(*args, **kwargs)
latency = time() - start_time
socketio_message_latency.labels(event_type=event_type).observe(latency)
return result
return wrapper
@socketio.on('connect')
@track_socketio_event
def handle_connect():
"""Handle incoming socket connections with enhanced security"""
try:
current_app.logger.debug('Handle Connection')
token = request.args.get('token')
if not token:
raise ValueError("Missing token")
current_app.logger.debug(f"Token received: {token}")
if not token:
raise ValueError("Missing token")
current_app.logger.info(f"Trying to connect with: {token}")
validator = TokenValidator()
validation_result = validator.validate_token(token)
if not validation_result.is_valid:
current_app.logger.error(f"Socket connection failed: {validation_result.error_message}")
emit('connect_error', {'error': validation_result.error_message})
disconnect()
return
# Create room and setup session
room = room_manager.create_room(validation_result.tenant_id, token)
join_room(room)
session['session_id'] = str(uuid.uuid4())
session['last_activity'] = datetime.now()
session['room'] = room
# Emit success events
emit('connect', {
'status': 'Connected',
'tenant_id': validation_result.tenant_id,
'room': room
})
emit('authenticated', {'token': token, 'room': room})
current_app.logger.info(f"Socket connection succeeded: {token} / {room}")
except Exception as e:
current_app.logger.error(f"Socket connection failed: {str(e)}")
emit('connect_error', {'status': 'Connection Failed'})
disconnect()
@socketio.on('rejoin_room')
def handle_room_rejoin(data):
try:
token = data.get('token')
tenant_id = data.get('tenant_id')
previous_room = data.get('previousRoom')
validator = TokenValidator()
validation_result = validator.validate_token(token, require_session=True)
if not validation_result.is_valid:
emit('room_rejoin_result', {'success': False, 'error': validation_result.error_message})
return
if not all([token, tenant_id, previous_room]):
raise ValueError("Missing required rejoin data")
# Validate room ownership
if not room_manager.validate_room_ownership(previous_room, tenant_id, token):
raise ValueError("Invalid room ownership")
# Rejoin room
join_room(previous_room)
session['room'] = previous_room
room_manager.update_room_activity(previous_room)
emit('room_rejoin_result', {
'success': True,
'room': previous_room
})
except Exception as e:
current_app.logger.error(f'Room rejoin failed: {e}')
emit('room_rejoin_result', {
'success': False,
'error': str(e)
})
@socketio.on('disconnect')
@track_socketio_event
def handle_disconnect():
room = session.get('room')
if room:
leave_room(room)
@socketio.on('heartbeat')
def handle_heartbeat():
last_activity = session.get('last_activity')
if datetime.now() - last_activity > current_app.config.get('SOCKETIO_MAX_IDLE_TIME'):
disconnect()
@socketio.on('user_message')
def handle_message(data):
current_app.logger.debug(f"SocketIO: Received message: {data}")
try:
validator = TokenValidator()
validation_result = validator.validate_token(data.get('token'))
if not validation_result.is_valid:
emit('error', {'message': validation_result.error_message})
return
current_app.logger.debug(f"SocketIO: token validated: {validation_result}")
room = session.get('room')
current_app.logger.debug(f"SocketIO: Room in session: {room}, Room in arguments: {data.get('room')}")
current_app.logger.debug(f"SocketIO: Room: {room}")
if not room or not room_manager.is_room_active(room):
raise Exception("Invalid or inactive room")
current_app.logger.debug(f"SocketIO: Room active: {room}")
if not room_manager.validate_room_ownership(room, data['tenant_id'], data['token']):
raise Exception("Room ownership validation failed")
current_app.logger.debug(f"SocketIO: Room ownership validated: {room}")
room_manager.update_room_activity(room)
current_app.logger.debug(f"SocketIO: Room activity updated: {room}")
session['last_activity'] = datetime.now()
current_tenant_id = validate_incoming_data(data)
current_app.logger.debug(f"SocketIO: Incoming data validated: {current_tenant_id}")
# Offload actual processing of question
task = current_celery.send_task('execute_specialist',
queue='llm_interactions',
args=[
current_tenant_id,
data['specialist_id'],
data['arguments'],
session['session_id'],
data['timezone'],
room
])
response = {
'tenantId': current_tenant_id,
'message': f'Processing question ... Session ID = {session["session_id"]}',
'taskId': task.id,
'room': room,
}
current_app.logger.debug(f"SocketIO: Sent response {response}")
emit('bot_response', response, room=room)
except Exception as e:
current_app.logger.error(f'SocketIO: Message handling failed: {str(e)}')
emit('error', {'message': 'Failed to process message'}, room=room)
@socketio.on('check_task_status')
def check_task_status(data):
current_app.logger.debug(f'SocketIO: Checking Task Status ... {data}')
validator = TokenValidator()
validation_result = validator.validate_token(data.get('token'))
if not validation_result.is_valid:
emit('feedback_received', {'status': 'error', 'error': validation_result.error_message})
return
task_id = data.get('task_id')
room = session.get('room')
if not task_id:
emit('task_status', {'status': 'error', 'message': 'Missing task ID'}, room=room)
return
task_result = current_celery.AsyncResult(task_id)
if task_result.state == 'PENDING':
emit('task_status', {'status': 'pending', 'taskId': task_id}, room=room)
elif task_result.state == 'SUCCESS':
result = task_result.result
current_app.logger.debug(f'SocketIO: Task {task_id} returned: {result}')
# Access the result structure correctly
specialist_result = result['result'] # This contains the SpecialistResult model_dump
response = {
'status': 'success',
'taskId': task_id,
'results': {
'answer': specialist_result.get('answer'),
'citations': specialist_result.get('citations', []),
'insufficient_info': specialist_result.get('insufficient_info', False)
},
'interaction_id': result['interaction_id'],
'room': room
}
emit('task_status', response, room=room)
else:
current_app.logger.error(f'SocketIO: Task {task_id} has failed. Error: {task_result.info}')
emit('task_status', {'status': task_result.state, 'message': str(task_result.info)}, room=room)
@socketio.on('feedback')
def handle_feedback(data):
current_app.logger.debug(f'SocketIO: Received feedback: {data}')
try:
validator = TokenValidator()
validation_result = validator.validate_token(data.get('token'))
if not validation_result.is_valid:
emit('feedback_received', {'status': 'error', 'error': validation_result.error_message})
return
current_tenant_id = validate_incoming_data(data)
interaction_id = data.get('interactionId')
feedback = data.get('feedback') # 'up' or 'down'
Database(current_tenant_id).switch_schema()
interaction = Interaction.query.get_or_404(interaction_id)
interaction.appreciation = 0 if feedback == 'down' else 100
room = session.get('room')
if not room:
emit('feedback_received', {'status': 'error', 'message': 'No active room'})
return
try:
db.session.commit()
emit('feedback_received', {'status': 'success', 'interaction_id': interaction_id, 'room': room}, room=room)
except SQLAlchemyError as e:
current_app.logger.error(f'SocketIO: Feedback handling failed: {e}')
db.session.rollback()
emit('feedback_received', {'status': 'Could not register feedback', 'interaction_id': interaction_id})
raise e
except Exception as e:
current_app.logger.error(f'SocketIO: Feedback handling failed: {e}')
disconnect()
def validate_api_key(tenant_id, api_key):
tenant = Tenant.query.get_or_404(tenant_id)
decrypted_api_key = simple_encryption.decrypt_api_key(tenant.encrypted_chat_api_key)
return decrypted_api_key == api_key
def validate_incoming_data(data):
current_app.logger.debug(f'SocketIO: Validating incoming data: {data}')
token = data.get('token')
if not token:
raise EveAISocketInputException("SocketIO: Missing token in input")
decoded_token = decode_token(token)
if not decoded_token:
raise EveAISocketInputException("SocketIO: Invalid token in input")
current_app.logger.debug(f'SocketIO: Decoded token: {decoded_token}')
current_tenant_id = decoded_token.get('sub')
if not current_tenant_id:
raise EveAISocketInputException("SocketIO: Missing tenant_id (sub) in input")
return current_tenant_id

View File

@@ -1,66 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Chat Client</title>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
</head>
<body>
<h1>Chat Client</h1>
<button onclick="registerClient()">Register Client</button>
<div>
<input type="text" id="message" placeholder="Enter your message">
<button onclick="sendMessage()">Send Message</button>
</div>
<div id="messages"></div>
<script>
let socket;
async function registerClient() {
const tenantId = '1';
const apiKey = 'EveAI-CHAT-8553-7987-2800-9115-6454';
const response = await fetch('http://127.0.0.1:5001/chat/register_client', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ tenant_id: tenantId, api_key: apiKey })
});
if (response.ok) {
const data = await response.json();
const token = data.token;
socket = io('http://127.0.0.1:5001/chat', {
auth: {
token: `Bearer ${token}`
}
});
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('response', (msg) => {
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('div');
messageElement.innerText = `Response: ${msg.data}`;
messagesDiv.appendChild(messageElement);
});
} else {
console.error('Registration failed');
}
}
function sendMessage() {
const message = document.getElementById('message').value;
if (socket) {
socket.emit('message', { content: message });
} else {
console.error('Socket not connected');
}
}
</script>
</body>
</html>

View File

@@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat Client</title>
<link rel="stylesheet" href="css/eveai-chat-style.css">
<script src="js/eveai-sdk.js" defer></script>
<script src="js/eveai-chat-widget.js" defer></script>
</head>
<body>
<div id="chat-container"></div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const eveAI = new EveAI('tenant-id', 'EVEAI-CHAT-xxxx-xxxx-xxxx-xxxx-xxxx');
eveAI.initializeChat('chat-container');
});
</script>
</body>
</html>

View File

@@ -1,70 +0,0 @@
from flask import Blueprint, current_app, request
from flask_healthz import HealthError
from sqlalchemy.exc import SQLAlchemyError
from celery.exceptions import TimeoutError as CeleryTimeoutError
from common.extensions import db, metrics, minio_client
from common.utils.celery_utils import current_celery
from eveai_chat.socket_handlers.chat_handler import socketio_message_counter, socketio_message_latency
healthz_bp = Blueprint('healthz', __name__, url_prefix='/_healthz')
def liveness():
try:
# Basic check to see if the app is running
return True
except Exception:
raise HealthError("Liveness check failed")
def readiness():
checks = {
"database": check_database(),
"celery": check_celery(),
# Add more checks as needed
}
if not all(checks.values()):
raise HealthError("Readiness check failed")
def check_database():
try:
# Perform a simple database query
db.session.execute("SELECT 1")
return True
except SQLAlchemyError:
current_app.logger.error("Database check failed", exc_info=True)
return False
def check_celery():
try:
# Send a simple task to Celery
result = current_celery.send_task('ping', queue='llm_interactions')
response = result.get(timeout=10) # Wait for up to 10 seconds for a response
return response == 'pong'
except CeleryTimeoutError:
current_app.logger.error("Celery check timed out", exc_info=True)
return False
except Exception as e:
current_app.logger.error(f"Celery check failed: {str(e)}", exc_info=True)
return False
@healthz_bp.route('/metrics')
@metrics.do_not_track()
def prometheus_metrics():
return metrics.generate_latest()
def init_healtz(app):
app.config.update(
HEALTHZ={
"live": "healthz_views.liveness",
"ready": "healthz_views.readiness",
}
)
# Register SocketIO metrics with Prometheus
metrics.register(socketio_message_counter)
metrics.register(socketio_message_latency)

View File

@@ -14,7 +14,7 @@
></progress-tracker>
<!-- Form data display if available (alleen in user messages) -->
<div v-if="message.formValues && message.sender === 'user'" class="form-display user-form-values">
<div v-if="hasMeaningfulFormValues(message)" class="form-display user-form-values">
<dynamic-form
:form-data="message.formData"
:form-values="message.formValues"
@@ -181,6 +181,39 @@ export default {
}
},
methods: {
hasMeaningfulFormValues(message) {
// Check if message is user message and has formValues
if (!message || message.sender !== 'user' || !message.formValues) {
return false;
}
// Check if formData exists and has fields
if (!message.formData || !message.formData.fields || Object.keys(message.formData.fields).length === 0) {
return false;
}
// Check if formValues object exists and isn't empty
if (!message.formValues || Object.keys(message.formValues).length === 0) {
return false;
}
// Check if any formValues have actual meaningful content
const hasActualValues = Object.entries(message.formValues).some(([key, value]) => {
// Skip if the field doesn't exist in formData
if (!message.formData.fields[key]) return false;
// Check for meaningful values
if (value === null || value === undefined) return false;
if (typeof value === 'string' && value.trim() === '') return false;
if (typeof value === 'boolean') return true; // Boolean values are always meaningful
if (Array.isArray(value) && value.length === 0) return false;
return true;
});
return hasActualValues;
},
async handleLanguageChange(event) {
// Controleer of dit het eerste bericht is in een gesprek met maar één bericht
// Dit wordt al afgehandeld door MessageHistory component, dus we hoeven hier niets te doen

View File

@@ -9,8 +9,8 @@
<div class="form-title">{{ formData.title || formData.name }}</div>
</div>
<!-- Form fields -->
<div class="form-fields">
<!-- Form fields (alleen tonen als NIET read-only) -->
<div v-if="!readOnly" class="form-fields">
<template v-if="Array.isArray(formData.fields)">
<form-field
v-for="field in formData.fields"

View File

@@ -29,7 +29,31 @@ export default {
},
computed: {
hasFormData() {
return this.formData && this.formData.fields && Object.keys(this.formData.fields).length > 0;
// Check if formData and fields exist
if (!this.formData || !this.formData.fields || Object.keys(this.formData.fields).length === 0) {
return false;
}
// Check if formValues exist and contain meaningful data
if (!this.formValues || Object.keys(this.formValues).length === 0) {
return false;
}
// Check if any formValues have actual content (not empty, null, or undefined)
const hasActualValues = Object.entries(this.formValues).some(([key, value]) => {
// Skip if the field doesn't exist in formData
if (!this.formData.fields[key]) return false;
// Check for meaningful values
if (value === null || value === undefined) return false;
if (typeof value === 'string' && value.trim() === '') return false;
if (typeof value === 'boolean') return true; // Boolean values are always meaningful
if (Array.isArray(value) && value.length === 0) return false;
return true;
});
return hasActualValues;
},
formattedFields() {
if (!this.hasFormData) return [];
@@ -267,7 +291,7 @@ export default {
}
/* High contrast mode */
@media (prefers-contrast: high) {
@media (prefers-contrast: more) {
.form-message {
border: 2px solid #000;
background-color: #fff;

View File

@@ -1,141 +0,0 @@
#!/usr/bin/env node
// Test script to verify LanguageSelector component functionality
// This script tests if the component can be imported and has the correct structure
const fs = require('fs');
const path = require('path');
console.log('🔍 Testing Vue component refactoring...');
// Read the LanguageSelector component file
const languageSelectorPath = path.join(__dirname, 'eveai_chat_client/static/assets/js/components/LanguageSelector.js');
if (!fs.existsSync(languageSelectorPath)) {
console.error('❌ LanguageSelector.js file not found!');
process.exit(1);
}
const componentContent = fs.readFileSync(languageSelectorPath, 'utf8');
// Test 1: Check that renderComponent method is removed
console.log('🔍 Test 1: Checking renderComponent method is removed...');
if (componentContent.includes('renderComponent()')) {
console.error('❌ FAIL: renderComponent method still exists!');
process.exit(1);
} else {
console.log('✅ PASS: renderComponent method successfully removed');
}
// Test 2: Check that render() fallback method is removed
console.log('🔍 Test 2: Checking render() fallback method is removed...');
if (componentContent.includes('render()')) {
console.error('❌ FAIL: render() fallback method still exists!');
process.exit(1);
} else {
console.log('✅ PASS: render() fallback method successfully removed');
}
// Test 3: Check that Vue template exists
console.log('🔍 Test 3: Checking Vue template exists...');
if (componentContent.includes('template:') && componentContent.includes('v-model="selectedLanguage"')) {
console.log('✅ PASS: Vue template with v-model exists');
} else {
console.error('❌ FAIL: Vue template missing or incorrect!');
process.exit(1);
}
// Test 4: Check that component has proper Vue structure
console.log('🔍 Test 4: Checking Vue component structure...');
const hasName = componentContent.includes("name: 'LanguageSelector'");
const hasProps = componentContent.includes('props:');
const hasData = componentContent.includes('data()');
const hasMethods = componentContent.includes('methods:');
const hasMounted = componentContent.includes('mounted()');
if (hasName && hasProps && hasData && hasMethods && hasMounted) {
console.log('✅ PASS: Component has proper Vue structure');
} else {
console.error('❌ FAIL: Component missing Vue structure elements');
console.error(` - name: ${hasName}`);
console.error(` - props: ${hasProps}`);
console.error(` - data: ${hasData}`);
console.error(` - methods: ${hasMethods}`);
console.error(` - mounted: ${hasMounted}`);
process.exit(1);
}
// Test 5: Check that chat-client.js fallback logic is removed
console.log('🔍 Test 5: Checking chat-client.js fallback logic is removed...');
const chatClientPath = path.join(__dirname, 'nginx/frontend_src/js/chat-client.js');
if (!fs.existsSync(chatClientPath)) {
console.error('❌ chat-client.js file not found!');
process.exit(1);
}
const chatClientContent = fs.readFileSync(chatClientPath, 'utf8');
if (chatClientContent.includes('LanguageSelector.renderComponent') ||
chatClientContent.includes('Noodoplossing: Handmatige DOM manipulatie')) {
console.error('❌ FAIL: Fallback logic still exists in chat-client.js!');
process.exit(1);
} else {
console.log('✅ PASS: Fallback logic successfully removed from chat-client.js');
}
// Test 6: Check build output exists
console.log('🔍 Test 6: Checking build output exists...');
const chatClientBundlePath = path.join(__dirname, 'nginx/static/dist/chat-client.js');
if (!fs.existsSync(chatClientBundlePath)) {
console.error('❌ FAIL: chat-client.js bundle not found!');
process.exit(1);
} else {
const bundleStats = fs.statSync(chatClientBundlePath);
console.log(`✅ PASS: chat-client.js bundle exists (${Math.round(bundleStats.size / 1024)} KB)`);
}
// Test 7: Check ChatInput component
console.log('🔍 Test 7: Checking ChatInput component refactoring...');
const chatInputPath = path.join(__dirname, 'eveai_chat_client/static/assets/js/components/ChatInput.js');
if (!fs.existsSync(chatInputPath)) {
console.error('❌ ChatInput.js file not found!');
process.exit(1);
}
const chatInputContent = fs.readFileSync(chatInputPath, 'utf8');
if (chatInputContent.includes('renderComponent(container, props, app)')) {
console.error('❌ FAIL: ChatInput renderComponent method still exists!');
process.exit(1);
} else {
console.log('✅ PASS: ChatInput renderComponent method successfully removed');
}
// Check ChatInput has proper Vue template
if (chatInputContent.includes('template:') && chatInputContent.includes('v-model="localMessage"')) {
console.log('✅ PASS: ChatInput Vue template with v-model exists');
} else {
console.error('❌ FAIL: ChatInput Vue template missing or incorrect!');
process.exit(1);
}
console.log('\n🎉 All tests passed! LanguageSelector and ChatInput components successfully refactored to use pure Vue templates.');
console.log('\n📋 Summary of changes:');
console.log(' ✅ LanguageSelector: Removed renderComponent() method');
console.log(' ✅ LanguageSelector: Removed render() fallback method');
console.log(' ✅ LanguageSelector: Removed renderComponent() call from mounted()');
console.log(' ✅ ChatInput: Removed renderComponent() method');
console.log(' ✅ Removed fallback logic from chat-client.js');
console.log(' ✅ Kept clean Vue templates with proper reactivity');
console.log(' ✅ Build completed successfully');
console.log('\n🔧 Next components to refactor:');
console.log(' - MessageHistory.js');
console.log(' - ChatMessage.js');
console.log(' - TypingIndicator.js');
console.log(' - ProgressTracker.js');
console.log(' - DynamicForm.js');
console.log(' - FormField.js');
console.log(' - FormMessage.js');

View File

@@ -1,168 +0,0 @@
#!/usr/bin/env python3
"""
Test script to verify the new list view implementation works correctly.
This script tests the specialists view to ensure it returns the expected data format.
"""
import sys
import os
import json
# Add the project root to the Python path
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
def test_specialists_view_data_format():
"""Test that the specialists view returns data in the expected format for Tabulator."""
try:
# Import the Flask app and required modules
from eveai_app import create_app
from common.models.interaction import Specialist
from flask import url_for
# Create the Flask app
app = create_app()
with app.app_context():
# Test the data format that would be returned by the specialists view
specialists_query = Specialist.query.order_by(Specialist.id)
all_specialists = specialists_query.all()
# Prepare data for Tabulator (same logic as in the view)
specialists_data = []
for specialist in all_specialists:
specialists_data.append({
'id': specialist.id,
'name': specialist.name,
'type': specialist.type,
'type_version': specialist.type_version,
'active': specialist.active
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'type': 'number', 'width': 80},
{'title': 'Name', 'field': 'name', 'type': 'text', 'dynamicValues': True},
{'title': 'Type', 'field': 'type', 'type': 'text', 'dynamicValues': True},
{'title': 'Type Version', 'field': 'type_version', 'type': 'text'},
{'title': 'Active', 'field': 'active', 'type': 'boolean', 'formatter': 'tickCross'}
]
# Action definitions
actions = [
{'value': 'edit_specialist', 'text': 'Edit Specialist', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'execute_specialist', 'text': 'Execute Specialist', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_specialist', 'text': 'Register Specialist', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False}
]
# Initial sort configuration
initial_sort = [{'column': 'id', 'dir': 'asc'}]
# Test that data can be serialized to JSON (required for template)
try:
json.dumps(specialists_data)
json.dumps(columns)
json.dumps(actions)
json.dumps(initial_sort)
print("✓ All data structures can be serialized to JSON")
except Exception as e:
print(f"✗ JSON serialization failed: {e}")
return False
# Test data structure validation
if not isinstance(specialists_data, list):
print("✗ specialists_data should be a list")
return False
if specialists_data and not isinstance(specialists_data[0], dict):
print("✗ specialists_data items should be dictionaries")
return False
if not isinstance(columns, list):
print("✗ columns should be a list")
return False
if not isinstance(actions, list):
print("✗ actions should be a list")
return False
print(f"✓ Found {len(specialists_data)} specialists")
print(f"✓ Configured {len(columns)} columns")
print(f"✓ Configured {len(actions)} actions")
print("✓ Data structure validation passed")
# Test that required fields are present in data
if specialists_data:
required_fields = ['id', 'name', 'type', 'type_version', 'active']
sample_data = specialists_data[0]
for field in required_fields:
if field not in sample_data:
print(f"✗ Required field '{field}' missing from data")
return False
print("✓ All required fields present in data")
# Test column configuration
for column in columns:
if 'title' not in column or 'field' not in column:
print(f"✗ Column missing required 'title' or 'field': {column}")
return False
print("✓ Column configuration validation passed")
# Test action configuration
for action in actions:
required_action_fields = ['value', 'text']
for field in required_action_fields:
if field not in action:
print(f"✗ Action missing required field '{field}': {action}")
return False
print("✓ Action configuration validation passed")
return True
except Exception as e:
print(f"✗ Test failed with exception: {e}")
import traceback
traceback.print_exc()
return False
def test_template_files_exist():
"""Test that the required template files exist."""
template_files = [
'eveai_app/templates/eveai_list_view.html',
'eveai_app/templates/interaction/specialists.html'
]
for template_file in template_files:
full_path = os.path.join(project_root, template_file)
if not os.path.exists(full_path):
print(f"✗ Template file missing: {template_file}")
return False
else:
print(f"✓ Template file exists: {template_file}")
return True
def main():
"""Run all tests."""
print("Testing new list view implementation...")
print("=" * 50)
# Test template files
print("\n1. Testing template files...")
template_test_passed = test_template_files_exist()
# Test data format
print("\n2. Testing data format...")
data_test_passed = test_specialists_view_data_format()
# Summary
print("\n" + "=" * 50)
if template_test_passed and data_test_passed:
print("✓ All tests passed! The list view implementation appears to be working correctly.")
return 0
else:
print("✗ Some tests failed. Please check the implementation.")
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,185 +0,0 @@
// Test script for SideBar Vue components
// This can be run in the browser console when the chat client is loaded
console.log('Testing SideBar Vue components...');
// Test if all components are available
function testComponentAvailability() {
console.log('🧪 Testing component availability...');
// Check if Vue is available
if (!window.Vue || !window.Vue.createApp) {
console.error('❌ Vue is not available');
return false;
}
console.log('✅ Vue is available');
// Check if chatConfig is available
if (!window.chatConfig) {
console.error('❌ window.chatConfig is not available');
return false;
}
console.log('✅ window.chatConfig is available');
// Check if sidebar container exists
const container = document.getElementById('sidebar-container');
if (!container) {
console.error('❌ #sidebar-container not found in DOM');
return false;
}
console.log('✅ #sidebar-container found in DOM');
return true;
}
// Test sidebar component props
function testSidebarProps() {
console.log('🧪 Testing sidebar props...');
const expectedProps = {
tenantMake: window.chatConfig.tenantMake || {},
explanationText: window.chatConfig.explanation || '',
initialLanguage: window.chatConfig.language || 'nl',
supportedLanguageDetails: window.chatConfig.supportedLanguageDetails || {},
allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de'],
apiPrefix: window.chatConfig.apiPrefix || ''
};
console.log('📋 Expected props:', expectedProps);
// Validate props
if (!expectedProps.tenantMake.name) {
console.warn('⚠️ tenantMake.name is empty');
}
if (!expectedProps.explanationText) {
console.warn('⚠️ explanationText is empty');
}
if (!expectedProps.supportedLanguageDetails || Object.keys(expectedProps.supportedLanguageDetails).length === 0) {
console.warn('⚠️ supportedLanguageDetails is empty, using defaults');
}
console.log('✅ Props validation completed');
return expectedProps;
}
// Test if old sidebar elements are removed
function testOldSidebarCleanup() {
console.log('🧪 Testing old sidebar cleanup...');
const oldElements = [
'sidebar-explanation',
'language-selector-container'
];
let foundOldElements = false;
oldElements.forEach(id => {
const element = document.getElementById(id);
if (element) {
console.warn(`⚠️ Old element #${id} still exists in DOM`);
foundOldElements = true;
}
});
if (!foundOldElements) {
console.log('✅ No old sidebar elements found - cleanup successful');
}
return !foundOldElements;
}
// Test translation functionality
async function testTranslationIntegration() {
console.log('🧪 Testing translation integration...');
try {
// Test if translation API is accessible
const response = await fetch('/api/translate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'same-origin',
body: JSON.stringify({
text: 'Test translation',
target_lang: 'nl',
context: 'sidebar_test'
})
});
if (response.ok) {
const result = await response.json();
console.log('✅ Translation API is accessible:', result.success ? 'Success' : 'Failed');
return result.success;
} else {
console.warn('⚠️ Translation API returned error:', response.status);
return false;
}
} catch (error) {
console.warn('⚠️ Translation API test failed:', error.message);
return false;
}
}
// Run all tests
async function runAllSidebarTests() {
console.log('🚀 Starting SideBar component tests...');
console.log('\n1. Testing component availability...');
const availabilityTest = testComponentAvailability();
console.log('\n2. Testing sidebar props...');
const props = testSidebarProps();
console.log('\n3. Testing old sidebar cleanup...');
const cleanupTest = testOldSidebarCleanup();
console.log('\n4. Testing translation integration...');
const translationTest = await testTranslationIntegration();
console.log('\n📊 Test Results Summary:');
console.log(`Component Availability: ${availabilityTest ? '✅' : '❌'}`);
console.log(`Props Validation: ✅`);
console.log(`Old Sidebar Cleanup: ${cleanupTest ? '✅' : '⚠️'}`);
console.log(`Translation Integration: ${translationTest ? '✅' : '⚠️'}`);
const allPassed = availabilityTest && cleanupTest && translationTest;
console.log(`\n🎯 Overall Status: ${allPassed ? '✅ All tests passed!' : '⚠️ Some tests need attention'}`);
return {
availability: availabilityTest,
cleanup: cleanupTest,
translation: translationTest,
props: props,
overall: allPassed
};
}
// Export for manual testing
if (typeof window !== 'undefined') {
window.testComponentAvailability = testComponentAvailability;
window.testSidebarProps = testSidebarProps;
window.testOldSidebarCleanup = testOldSidebarCleanup;
window.testTranslationIntegration = testTranslationIntegration;
window.runAllSidebarTests = runAllSidebarTests;
console.log('SideBar test functions available:');
console.log('- testComponentAvailability()');
console.log('- testSidebarProps()');
console.log('- testOldSidebarCleanup()');
console.log('- testTranslationIntegration()');
console.log('- runAllSidebarTests()');
}
// Auto-run if in Node.js environment (for CI/CD)
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
testComponentAvailability,
testSidebarProps,
testOldSidebarCleanup,
testTranslationIntegration,
runAllSidebarTests
};
}

View File

@@ -1,160 +0,0 @@
#!/usr/bin/env python3
"""
Test script to verify that the Tabulator library is properly loaded and the table displays correctly.
This script will test the specialists view to ensure the table formatting is fixed.
"""
import sys
import os
import json
# Add the project root to the Python path
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
def test_tabulator_library_inclusion():
"""Test that Tabulator library is included in the templates."""
# Check head.html for Tabulator CSS
head_file = os.path.join(project_root, 'eveai_app/templates/head.html')
with open(head_file, 'r') as f:
head_content = f.read()
if 'tabulator' in head_content.lower():
print("✓ Tabulator CSS found in head.html")
else:
print("✗ Tabulator CSS not found in head.html")
return False
# Check scripts.html for Tabulator JavaScript
scripts_file = os.path.join(project_root, 'eveai_app/templates/scripts.html')
with open(scripts_file, 'r') as f:
scripts_content = f.read()
if 'tabulator' in scripts_content.lower():
print("✓ Tabulator JavaScript found in scripts.html")
else:
print("✗ Tabulator JavaScript not found in scripts.html")
return False
return True
def test_list_view_template():
"""Test that the list view template has proper column configuration."""
list_view_file = os.path.join(project_root, 'eveai_app/templates/eveai_list_view.html')
with open(list_view_file, 'r') as f:
template_content = f.read()
# Check for key Tabulator configuration elements
required_elements = [
'_buildColumns',
'new Tabulator',
'layout: "fitColumns"',
'columns:',
'data:'
]
for element in required_elements:
if element in template_content:
print(f"✓ Found required element: {element}")
else:
print(f"✗ Missing required element: {element}")
return False
return True
def test_specialists_template():
"""Test that the specialists template properly initializes the list view."""
specialists_file = os.path.join(project_root, 'eveai_app/templates/interaction/specialists.html')
with open(specialists_file, 'r') as f:
template_content = f.read()
# Check for proper initialization
required_elements = [
'EveAI.ListView.initialize',
'specialists-list-view',
'data: {{ specialists_data | tojson }}',
'columns: {{ columns | tojson }}',
'selectable: true'
]
for element in required_elements:
if element in template_content:
print(f"✓ Found required element: {element}")
else:
print(f"✗ Missing required element: {element}")
return False
return True
def test_column_configuration():
"""Test that the column configuration in the view is correct."""
try:
# Import the view function
from eveai_app.views.interaction_views import specialists
# Test the column configuration structure
sample_columns = [
{'title': 'ID', 'field': 'id', 'type': 'number', 'width': 80},
{'title': 'Name', 'field': 'name', 'type': 'text', 'dynamicValues': True},
{'title': 'Type', 'field': 'type', 'type': 'text', 'dynamicValues': True},
{'title': 'Type Version', 'field': 'type_version', 'type': 'text'},
{'title': 'Active', 'field': 'active', 'type': 'boolean', 'formatter': 'tickCross'}
]
# Validate column structure
for i, column in enumerate(sample_columns):
if 'title' in column and 'field' in column:
print(f"✓ Column {i+1} ({column['title']}) has required title and field")
else:
print(f"✗ Column {i+1} missing title or field")
return False
print("✓ Column configuration structure is correct")
return True
except Exception as e:
print(f"✗ Error testing column configuration: {e}")
return False
def main():
"""Run all tests."""
print("Testing Tabulator table formatting fix...")
print("=" * 50)
# Test Tabulator library inclusion
print("\n1. Testing Tabulator library inclusion...")
tabulator_test_passed = test_tabulator_library_inclusion()
# Test list view template
print("\n2. Testing list view template...")
list_view_test_passed = test_list_view_template()
# Test specialists template
print("\n3. Testing specialists template...")
specialists_test_passed = test_specialists_template()
# Test column configuration
print("\n4. Testing column configuration...")
column_test_passed = test_column_configuration()
# Summary
print("\n" + "=" * 50)
if all([tabulator_test_passed, list_view_test_passed, specialists_test_passed, column_test_passed]):
print("✓ All tests passed! The table formatting should now work correctly.")
print("\nThe table should now display with proper columns:")
print("- ID column (80px width)")
print("- Name column (with dynamic filtering)")
print("- Type column (with dynamic filtering)")
print("- Type Version column")
print("- Active column (with tick/cross formatter)")
return 0
else:
print("✗ Some tests failed. Please check the implementation.")
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,143 +0,0 @@
// Simple test script for the updated useTranslation composable
// This can be run in the browser console when the chat client is loaded
console.log('Testing updated useTranslation composable...');
// Test basic translation functionality
async function testTranslation() {
try {
// Test direct API call to /api/translate
const response = await fetch('/api/translate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'same-origin',
body: JSON.stringify({
text: 'Hello world',
target_lang: 'nl',
source_lang: 'en',
context: 'test'
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('✅ Direct API test successful:', result);
if (result.success) {
console.log('✅ Translation successful:', result.translated_text);
} else {
console.log('❌ Translation failed:', result.error);
}
return result;
} catch (error) {
console.error('❌ Direct API test failed:', error);
return null;
}
}
// Test empty text handling
async function testEmptyText() {
try {
const response = await fetch('/api/translate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'same-origin',
body: JSON.stringify({
text: '',
target_lang: 'nl'
})
});
const result = await response.json();
console.log('✅ Empty text test result:', result);
if (!result.success && result.error.includes('empty')) {
console.log('✅ Empty text validation working correctly');
}
return result;
} catch (error) {
console.error('❌ Empty text test failed:', error);
return null;
}
}
// Test missing parameters
async function testMissingParams() {
try {
const response = await fetch('/api/translate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'same-origin',
body: JSON.stringify({
text: 'Hello world'
// Missing target_lang
})
});
const result = await response.json();
console.log('✅ Missing params test result:', result);
if (!result.success && result.error.includes('missing')) {
console.log('✅ Parameter validation working correctly');
}
return result;
} catch (error) {
console.error('❌ Missing params test failed:', error);
return null;
}
}
// Run all tests
async function runAllTests() {
console.log('🚀 Starting translation API tests...');
console.log('\n1. Testing basic translation...');
await testTranslation();
console.log('\n2. Testing empty text validation...');
await testEmptyText();
console.log('\n3. Testing missing parameters validation...');
await testMissingParams();
console.log('\n✅ All tests completed!');
}
// Export for manual testing
if (typeof window !== 'undefined') {
window.testTranslation = testTranslation;
window.testEmptyText = testEmptyText;
window.testMissingParams = testMissingParams;
window.runAllTests = runAllTests;
console.log('Test functions available:');
console.log('- testTranslation()');
console.log('- testEmptyText()');
console.log('- testMissingParams()');
console.log('- runAllTests()');
}
// Auto-run if in Node.js environment (for CI/CD)
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
testTranslation,
testEmptyText,
testMissingParams,
runAllTests
};
}

View File

@@ -1,139 +0,0 @@
// Test script for UI fixes: SideBarExplanation height and DynamicForm single column
// This can be run in the browser console when the chat client is loaded
console.log('Testing UI fixes...');
// Test SideBarExplanation height fix
function testSideBarExplanationHeight() {
console.log('🧪 Testing SideBarExplanation height fix...');
const sidebarExplanation = document.querySelector('.sidebar-explanation');
if (!sidebarExplanation) {
console.warn('⚠️ SideBarExplanation element not found');
return false;
}
const computedStyle = window.getComputedStyle(sidebarExplanation);
const flexValue = computedStyle.getPropertyValue('flex');
console.log('📏 SideBarExplanation flex value:', flexValue);
if (flexValue === '1 1 0%' || flexValue === '1') {
console.error('❌ SideBarExplanation still has flex: 1 - height fix failed');
return false;
} else {
console.log('✅ SideBarExplanation height fix successful - no flex: 1 found');
return true;
}
}
// Test DynamicForm single column layout
function testDynamicFormSingleColumn() {
console.log('🧪 Testing DynamicForm single column layout...');
const formFields = document.querySelector('.form-fields');
if (!formFields) {
console.warn('⚠️ DynamicForm .form-fields element not found');
return false;
}
const computedStyle = window.getComputedStyle(formFields);
const gridColumns = computedStyle.getPropertyValue('grid-template-columns');
console.log('📐 DynamicForm grid-template-columns:', gridColumns);
// Check if it's single column (should be "1fr" or similar)
if (gridColumns.includes('1fr 1fr') || gridColumns.includes('repeat(2, 1fr)')) {
console.error('❌ DynamicForm still has 2-column layout - single column fix failed');
return false;
} else if (gridColumns === '1fr' || gridColumns.includes('1fr') && !gridColumns.includes('1fr 1fr')) {
console.log('✅ DynamicForm single column fix successful');
return true;
} else {
console.warn('⚠️ DynamicForm has unexpected grid layout:', gridColumns);
return false;
}
}
// Test CSS media query removal
function testMediaQueryRemoval() {
console.log('🧪 Testing media query removal...');
// Create a test element to check if media queries still apply
const testDiv = document.createElement('div');
testDiv.className = 'form-fields';
testDiv.style.display = 'none';
document.body.appendChild(testDiv);
// Force a larger screen width for testing
const originalWidth = window.innerWidth;
// Check computed style at different screen sizes
const computedStyle = window.getComputedStyle(testDiv);
const gridColumns = computedStyle.getPropertyValue('grid-template-columns');
document.body.removeChild(testDiv);
console.log('📱 Media query test - grid columns:', gridColumns);
if (gridColumns === '1fr') {
console.log('✅ Media query removal successful - single column maintained');
return true;
} else {
console.warn('⚠️ Media query might still be active:', gridColumns);
return false;
}
}
// Run all tests
function runAllUITests() {
console.log('🚀 Starting UI fixes tests...');
console.log('\n1. Testing SideBarExplanation height fix...');
const heightTest = testSideBarExplanationHeight();
console.log('\n2. Testing DynamicForm single column layout...');
const columnTest = testDynamicFormSingleColumn();
console.log('\n3. Testing media query removal...');
const mediaTest = testMediaQueryRemoval();
console.log('\n📊 Test Results Summary:');
console.log(`SideBarExplanation Height Fix: ${heightTest ? '✅' : '❌'}`);
console.log(`DynamicForm Single Column: ${columnTest ? '✅' : '❌'}`);
console.log(`Media Query Removal: ${mediaTest ? '✅' : '⚠️'}`);
const allPassed = heightTest && columnTest;
console.log(`\n🎯 Overall Status: ${allPassed ? '✅ All critical tests passed!' : '⚠️ Some tests need attention'}`);
return {
heightFix: heightTest,
singleColumn: columnTest,
mediaQuery: mediaTest,
overall: allPassed
};
}
// Export for manual testing
if (typeof window !== 'undefined') {
window.testSideBarExplanationHeight = testSideBarExplanationHeight;
window.testDynamicFormSingleColumn = testDynamicFormSingleColumn;
window.testMediaQueryRemoval = testMediaQueryRemoval;
window.runAllUITests = runAllUITests;
console.log('UI test functions available:');
console.log('- testSideBarExplanationHeight()');
console.log('- testDynamicFormSingleColumn()');
console.log('- testMediaQueryRemoval()');
console.log('- runAllUITests()');
}
// Auto-run if in Node.js environment (for CI/CD)
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
testSideBarExplanationHeight,
testDynamicFormSingleColumn,
testMediaQueryRemoval,
runAllUITests
};
}