From 0d05499d2ba480f96b39d1635f834372ff1ac5a0 Mon Sep 17 00:00:00 2001 From: Josako Date: Wed, 4 Jun 2025 11:53:35 +0200 Subject: [PATCH] - Add Specialist Magic Links - correction of some bugs: - dynamic fields for adding documents / urls to dossier catalog - tabs in latest bootstrap version no longer functional - partner association of license tier not working when no partner selected - data-type dynamic field needs conversion to isoformat - Add public tables to env.py of tenant schema --- common/models/interaction.py | 21 ++ common/models/user.py | 10 + .../entitlements/license_tier_services.py | 4 +- common/services/user/tenant_services.py | 2 +- {eveai_app => common/utils}/errors.py | 44 ++- .../TRAICIE_SELECTION_SPECIALIST/1.0.0.yaml | 24 +- ...eai Chat Client Developer Documentation.md | 302 +++++++++++++++--- eveai_app/__init__.py | 2 +- .../templates/entitlements/edit_license.html | 6 +- eveai_app/templates/entitlements/license.html | 6 +- .../entitlements/license_periods.html | 6 +- .../templates/entitlements/license_tier.html | 9 +- .../edit_specialist_magic_link.html | 33 ++ .../interaction/specialist_magic_link.html | 23 ++ .../interaction/specialist_magic_links.html | 26 ++ .../templates/interaction/specialists.html | 2 +- eveai_app/templates/macros.html | 13 +- eveai_app/templates/navbar.html | 1 + .../partner/edit_partner_service.html | 12 +- eveai_app/templates/user/edit_tenant.html | 34 ++ eveai_app/templates/user/tenant.html | 27 +- eveai_app/templates/user/tenant_overview.html | 2 +- eveai_app/views/document_views.py | 17 +- eveai_app/views/dynamic_form_base.py | 10 + eveai_app/views/interaction_forms.py | 45 ++- eveai_app/views/interaction_views.py | 123 ++++++- eveai_app/views/partner_views.py | 22 +- eveai_app/views/user_forms.py | 2 +- eveai_client/platform/templates/macros.html | 6 +- ...30e_add_specialistmagiclinktenant_model.py | 35 ++ migrations/tenant/env.py | 4 +- ...520ec540d_add_specialistmagiclink_model.py | 47 +++ nginx/static/assets/css/eveai.css | 22 ++ scripts/initialize_data.py | 1 - 34 files changed, 822 insertions(+), 121 deletions(-) rename {eveai_app => common/utils}/errors.py (73%) create mode 100644 eveai_app/templates/interaction/edit_specialist_magic_link.html create mode 100644 eveai_app/templates/interaction/specialist_magic_link.html create mode 100644 eveai_app/templates/interaction/specialist_magic_links.html create mode 100644 migrations/public/versions/2b4cb553530e_add_specialistmagiclinktenant_model.py create mode 100644 migrations/tenant/versions/d69520ec540d_add_specialistmagiclink_model.py diff --git a/common/models/interaction.py b/common/models/interaction.py index 24f9ce9..9b32495 100644 --- a/common/models/interaction.py +++ b/common/models/interaction.py @@ -215,3 +215,24 @@ class SpecialistDispatcher(db.Model): dispatcher_id = db.Column(db.Integer, db.ForeignKey(Dispatcher.id, ondelete='CASCADE'), primary_key=True) dispatcher = db.relationship("Dispatcher", backref="specialist_dispatchers") + + +class SpecialistMagicLink(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), nullable=False) + description = db.Column(db.Text, nullable=True) + specialist_id = db.Column(db.Integer, db.ForeignKey(Specialist.id, ondelete='CASCADE'), nullable=False) + magic_link_code = db.Column(db.String(55), nullable=False, unique=True) + + valid_from = db.Column(db.DateTime, nullable=True) + valid_to = db.Column(db.DateTime, nullable=True) + + specialist_args = db.Column(JSONB, nullable=True) + + created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now()) + created_by = db.Column(db.Integer, db.ForeignKey(User.id), nullable=True) + updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now()) + updated_by = db.Column(db.Integer, db.ForeignKey(User.id)) + + def __repr__(self): + return f"" diff --git a/common/models/user.py b/common/models/user.py index 682341b..bedc4e4 100644 --- a/common/models/user.py +++ b/common/models/user.py @@ -271,3 +271,13 @@ class PartnerTenant(db.Model): created_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True) updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now()) updated_by = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True) + + +class SpecialistMagicLinkTenant(db.Model): + __bind_key__ = 'public' + __table_args__ = {'schema': 'public'} + + magic_link_code = db.Column(db.String(55), primary_key=True) + tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False) + + diff --git a/common/services/entitlements/license_tier_services.py b/common/services/entitlements/license_tier_services.py index d83ca3f..cff3bfe 100644 --- a/common/services/entitlements/license_tier_services.py +++ b/common/services/entitlements/license_tier_services.py @@ -6,7 +6,7 @@ from sqlalchemy.exc import SQLAlchemyError from common.extensions import db from common.models.entitlements import PartnerServiceLicenseTier from common.models.user import Partner -from common.utils.eveai_exceptions import EveAINoManagementPartnerService +from common.utils.eveai_exceptions import EveAINoManagementPartnerService, EveAINoSessionPartner from common.utils.model_logging_utils import set_logging_information @@ -19,7 +19,7 @@ class LicenseTierServices: # Get partner service (MANAGEMENT_SERVICE type) partner = Partner.query.get(partner_id) if not partner: - return + raise EveAINoSessionPartner() # Find a management service for this partner management_service = next((service for service in session['partner']['services'] diff --git a/common/services/user/tenant_services.py b/common/services/user/tenant_services.py index da5a3d7..f11e0dc 100644 --- a/common/services/user/tenant_services.py +++ b/common/services/user/tenant_services.py @@ -28,7 +28,7 @@ class TenantServices: if service.get('type') == 'MANAGEMENT_SERVICE'), None) if not management_service: - current_app.logger.error(f"No Management Service defined for partner {partner_id}" + current_app.logger.error(f"No Management Service defined for partner {partner_id} " f"while associating tenant {tenant_id} with partner.") raise EveAINoManagementPartnerService() diff --git a/eveai_app/errors.py b/common/utils/errors.py similarity index 73% rename from eveai_app/errors.py rename to common/utils/errors.py index aba1260..8578236 100644 --- a/eveai_app/errors.py +++ b/common/utils/errors.py @@ -1,3 +1,4 @@ + import traceback import jinja2 @@ -12,6 +13,7 @@ def not_found_error(error): if not current_user.is_authenticated: return redirect(prefixed_url_for('security.login')) current_app.logger.error(f"Not Found Error: {error}") + current_app.logger.error(traceback.format_exc()) return render_template('error/404.html'), 404 @@ -19,6 +21,7 @@ def internal_server_error(error): if not current_user.is_authenticated: return redirect(prefixed_url_for('security.login')) current_app.logger.error(f"Internal Server Error: {error}") + current_app.logger.error(traceback.format_exc()) return render_template('error/500.html'), 500 @@ -26,6 +29,7 @@ def not_authorised_error(error): if not current_user.is_authenticated: return redirect(prefixed_url_for('security.login')) current_app.logger.error(f"Not Authorised Error: {error}") + current_app.logger.error(traceback.format_exc()) return render_template('error/401.html') @@ -33,6 +37,7 @@ def access_forbidden(error): if not current_user.is_authenticated: return redirect(prefixed_url_for('security.login')) current_app.logger.error(f"Access Forbidden: {error}") + current_app.logger.error(traceback.format_exc()) return render_template('error/403.html') @@ -42,6 +47,7 @@ def key_error_handler(error): return redirect(prefixed_url_for('security.login')) # For other KeyErrors, you might want to log the error and return a generic error page current_app.logger.error(f"Key Error: {error}") + current_app.logger.error(traceback.format_exc()) return render_template('error/generic.html', error_message="An unexpected error occurred"), 500 @@ -76,6 +82,7 @@ def no_tenant_selected_error(error): a long period of inactivity. The user will be redirected to the login page. """ current_app.logger.error(f"No Session Tenant Error: {error}") + current_app.logger.error(traceback.format_exc()) flash('Your session expired. You will have to re-enter your credentials', 'warning') # Perform logout if user is authenticated @@ -95,6 +102,26 @@ def general_exception(e): error_details=str(e)), 500 +def template_not_found_error(error): + """Handle Jinja2 TemplateNotFound exceptions.""" + current_app.logger.error(f'Template not found: {error.name}') + current_app.logger.error(f'Search Paths: {current_app.jinja_loader.list_templates()}') + current_app.logger.error(traceback.format_exc()) + return render_template('error/500.html', + error_type="Template Not Found", + error_details=f"Template '{error.name}' could not be found."), 404 + + +def template_syntax_error(error): + """Handle Jinja2 TemplateSyntaxError exceptions.""" + current_app.logger.error(f'Template syntax error: {error.message}') + current_app.logger.error(f'In template {error.filename}, line {error.lineno}') + current_app.logger.error(traceback.format_exc()) + return render_template('error/500.html', + error_type="Template Syntax Error", + error_details=f"Error in template '{error.filename}' at line {error.lineno}: {error.message}"), 500 + + def register_error_handlers(app): app.register_error_handler(404, not_found_error) app.register_error_handler(500, internal_server_error) @@ -103,17 +130,6 @@ def register_error_handlers(app): app.register_error_handler(EveAINoSessionTenant, no_tenant_selected_error) app.register_error_handler(KeyError, key_error_handler) app.register_error_handler(AttributeError, attribute_error_handler) - app.register_error_handler(Exception, general_exception) - - @app.errorhandler(jinja2.TemplateNotFound) - def template_not_found(error): - app.logger.error(f'Template not found: {error.name}') - app.logger.error(f'Search Paths: {app.jinja_loader.list_templates()}') - return f'Template not found: {error.name}. Check logs for details.', 404 - - @app.errorhandler(jinja2.TemplateSyntaxError) - def template_syntax_error(error): - app.logger.error(f'Template syntax error: {error.message}') - app.logger.error(f'In template {error.filename}, line {error.lineno}') - return f'Template syntax error: {error.message}', 500 - + app.register_error_handler(jinja2.TemplateNotFound, template_not_found_error) + app.register_error_handler(jinja2.TemplateSyntaxError, template_syntax_error) + app.register_error_handler(Exception, general_exception) \ No newline at end of file diff --git a/config/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1.0.0.yaml b/config/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1.0.0.yaml index 1a14925..75fecf8 100644 --- a/config/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1.0.0.yaml +++ b/config/specialists/traicie/TRAICIE_SELECTION_SPECIALIST/1.0.0.yaml @@ -68,11 +68,27 @@ competency_details: required: true default: true arguments: - vacancy_text: - name: "vacancy_text" - type: "text" - description: "The Vacancy Text" + region: + name: "Region" + type: "str" + description: "The region of the specific vacancy" + required: false + working_schedule: + name: "Work Schedule" + type: "str" + description: "The work schedule or employment type of the specific vacancy" + required: false + start_date: + name: "Start Date" + type: "date" + description: "The start date of the specific vacancy" + required: false + language: + name: "Language" + type: "str" + description: "The language (2-letter code) used to start the conversation" required: true + results: competencies: name: "competencies" diff --git a/documentation/Eveai Chat Client Developer Documentation.md b/documentation/Eveai Chat Client Developer Documentation.md index 8a2c63e..01f7930 100644 --- a/documentation/Eveai Chat Client Developer Documentation.md +++ b/documentation/Eveai Chat Client Developer Documentation.md @@ -2,11 +2,12 @@ ## Overview -The Evie Chat Client is a modern, customizable chat interface for interacting with eveai specialists. It supports both anonymous and authenticated modes, with initial focus on anonymous mode. The client provides real-time interaction with AI specialists, customizable tenant branding, and European-compliant analytics tracking. +The Evie Chat Client is a modern, customizable chat interface for interacting with eveai specialists. It supports both anonymous and authenticated modes, with initial focus on anonymous mode. The client provides real-time interaction with AI specialists, customizable tenant branding, European-compliant analytics tracking, and secure QR code access. ## Key Features - **Anonymous Mode**: Public access with tenant UUID and API key authentication +- **QR Code Access**: Secure pre-authenticated landing pages for QR code integration - **Real-time Communication**: Server-Sent Events (SSE) for live updates and intermediate states - **Tenant Customization**: Simple CSS variable-based theming with visual editor - **Multiple Choice Options**: Dynamic button/dropdown responses from specialists @@ -16,38 +17,97 @@ The Evie Chat Client is a modern, customizable chat interface for interacting wi ## Architecture -### Component Structure +### Project Structure ``` -eveai_chat_client/ -├── app.py # Flask app entry point -├── routes/ -│ ├── __init__.py -│ ├── chat_routes.py # Main chat interface routes -│ └── api_routes.py # SSE/API endpoints -├── services/ -│ ├── chat_service.py # Chat session management -│ ├── specialist_service.py # Specialist interaction wrapper -│ └── tenant_service.py # Tenant config & theming -├── templates/ -│ ├── base.html # Base template -│ ├── chat.html # Main chat interface -│ └── components/ -│ ├── message.html # Individual message component -│ ├── options.html # Multiple choice options -│ └── thinking.html # Intermediate states display -└── utils/ - ├── auth.py # API key validation - └── tracking.py # Umami analytics integration +evie-project/ +├── common/ # Shared code across components +│ ├── services/ # Reusable business logic +│ │ ├── chat_service.py # Chat session management +│ │ ├── specialist_service.py # Specialist interaction wrapper +│ │ ├── tenant_service.py # Tenant config & theming +│ │ └── qr_service.py # QR code session management +│ └── utils/ # Utility functions +│ ├── auth.py # API key validation +│ ├── tracking.py # Umami analytics integration +│ └── qr_utils.py # QR code generation utilities +├── eveai_chat_client/ # Chat client component +│ ├── app.py # Flask app entry point +│ ├── routes/ +│ │ ├── __init__.py +│ │ ├── chat_routes.py # Main chat interface routes +│ │ ├── api_routes.py # SSE/API endpoints +│ │ └── qr_routes.py # QR code landing pages +│ └── templates/ +│ ├── base.html # Base template +│ ├── chat.html # Main chat interface +│ ├── qr_expired.html # QR code error page +│ └── components/ +│ ├── message.html # Individual message component +│ ├── options.html # Multiple choice options +│ └── thinking.html # Intermediate states display +└── eveai_app/ # Admin interface (existing) + └── qr_management/ # QR code creation interface + ├── create_qr.py + └── qr_templates.html ``` ### Integration Approach -- **Services Layer**: Direct integration with existing eveai services (not API) for better performance +- **Services Layer**: Direct integration with common/services for better performance - **Database**: Utilizes existing ChatSession and Interaction models - **Caching**: Leverages existing Redis setup - **Static Files**: Uses existing nginx/static structure +## QR Code Access Flow + +### QR Code System Architecture + +```mermaid +sequenceDiagram + participant Admin as Admin (eveai_app) + participant QRService as QR Service (common) + participant PublicDB as Public Schema + participant TenantDB as Tenant Schema + participant User as End User + participant ChatClient as Chat Client + participant ChatSession as Chat Session + + %% QR Code Creation Flow + Admin->>QRService: Create QR code with specialist config + QRService->>PublicDB: Store qr_lookup (qr_id → tenant_code) + QRService->>TenantDB: Store qr_sessions (full config + args) + QRService->>Admin: Return QR code image with /qr/{qr_id} + + %% QR Code Usage Flow + User->>ChatClient: Scan QR → GET /qr/{qr_id} + ChatClient->>PublicDB: Lookup tenant_code by qr_id + ChatClient->>TenantDB: Get full QR session data + ChatClient->>ChatSession: Create ChatSession with pre-filled args + ChatClient->>User: Set temp auth + redirect to chat interface + User->>ChatClient: Access chat with pre-authenticated session +``` + +### QR Code Data Flow + +```mermaid +flowchart TD + A[Admin Creates QR Code] --> B[Generate UUID for QR Session] + B --> C[Store Lookup in Public Schema] + C --> D[Store Full Data in Tenant Schema] + D --> E[Generate QR Code Image] + + F[User Scans QR Code] --> G[Extract QR Session ID from URL] + G --> H[Lookup Tenant Code in Public Schema] + H --> I[Retrieve Full QR Data from Tenant Schema] + I --> J{QR Valid & Not Expired?} + J -->|No| K[Show Error Page] + J -->|Yes| L[Create ChatSession with Pre-filled Args] + L --> M[Set Temporary Browser Authentication] + M --> N[Redirect to Chat Interface] + N --> O[Start Chat with Specialist] +``` + ## URL Structure & Parameters ### Main Chat Interface @@ -56,35 +116,141 @@ GET /chat/{tenant_code}/{specialist_id} ``` **Query Parameters:** -- `api_key` (required): Tenant API key for authentication +- `api_key` (required for direct access): Tenant API key for authentication +- `session` (optional): Existing chat session ID - `utm_source`, `utm_campaign`, `utm_medium` (optional): Analytics tracking -- Other tracking parameters as needed -**Example:** +**Examples:** ``` +# Direct access /chat/550e8400-e29b-41d4-a716-446655440000/document-analyzer?api_key=xxx&utm_source=email + +# QR code access (after redirect) +/chat/550e8400-e29b-41d4-a716-446655440000/document-analyzer?session=abc123-def456 +``` + +### QR Code Landing Pages +``` +GET /qr/{qr_session_id} # QR code entry point (redirects, no HTML page) ``` ### API Endpoints ``` -POST /api/chat/{tenant_code}/interact # Send message to specialist +POST /api/chat/{tenant_code}/interact # Send message to specialist GET /api/chat/{tenant_code}/status/{session_id} # SSE endpoint for updates -GET /api/tenant/{tenant_code}/theme.css # Dynamic tenant CSS (if needed) ``` ## Authentication & Security -### Anonymous Mode -- **Tenant Identification**: UUID-based tenant codes (not sequential IDs) -- **API Key Validation**: Required for all anonymous interactions -- **Rate Limiting**: Implement per-tenant/IP rate limiting -- **Input Validation**: Sanitize all user inputs and parameters +### Anonymous Mode Access Methods + +1. **Direct Access**: URL with API key parameter +2. **QR Code Access**: Pre-authenticated via secure landing page + +### QR Code Security Model +- **QR Code Contains**: Only a UUID session identifier +- **Sensitive Data**: Stored securely in tenant database schema +- **Usage Control**: Configurable expiration and usage limits +- **Audit Trail**: Track QR code creation and usage ### Security Considerations - Use tenant UUIDs to prevent enumeration attacks - Validate API keys against tenant database - Implement CORS policies for cross-origin requests - Sanitize all user messages and file uploads +- QR sessions have configurable expiration and usage limits + +## QR Code Management + +### Database Schema + +#### Public Schema (Routing Only) +```sql +CREATE TABLE qr_lookup ( + qr_session_id UUID PRIMARY KEY, + tenant_code UUID NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + INDEX idx_tenant_code (tenant_code) +); +``` + +#### Tenant Schema (Full QR Data) +```sql +CREATE TABLE qr_sessions ( + id UUID PRIMARY KEY, + specialist_id UUID NOT NULL, + api_key VARCHAR(255) NOT NULL, + specialist_args JSONB, + metadata JSONB, + created_at TIMESTAMP DEFAULT NOW(), + expires_at TIMESTAMP, + usage_count INTEGER DEFAULT 0, + usage_limit INTEGER, + created_by_user_id UUID +); +``` + +### QR Code Creation (eveai_app) +```python +# In eveai_app admin interface +from common.services.qr_service import QRService + +def create_specialist_qr_code(): + qr_data = { + 'tenant_code': current_tenant.code, + 'specialist_id': selected_specialist.id, + 'api_key': current_tenant.api_key, + 'specialist_args': { + 'department': 'sales', + 'language': 'en', + 'context': 'product_inquiry' + }, + 'metadata': { + 'name': 'Sales Support QR - Product Brochure', + 'usage_limit': 500, + 'expires_days': 90 + } + } + + qr_service = QRService() + qr_session_id, qr_image = qr_service.create_qr_session(qr_data) + return qr_image +``` + +### QR Code Processing (eveai_chat_client) +```python +# In eveai_chat_client routes +from common.services.qr_service import QRService +from common.services.chat_service import ChatService + +@app.route('/qr/') +def handle_qr_code(qr_session_id): + qr_service = QRService() + qr_data = qr_service.get_and_validate_qr_session(qr_session_id) + + if not qr_data: + return render_template('qr_expired.html'), 410 + + # Create ChatSession with pre-filled arguments + chat_service = ChatService() + chat_session = chat_service.create_session( + tenant_code=qr_data['tenant_code'], + specialist_id=qr_data['specialist_id'], + initial_args=qr_data['specialist_args'], + source='qr_code' + ) + + # Set temporary authentication + flask_session['qr_auth'] = { + 'tenant_code': qr_data['tenant_code'], + 'api_key': qr_data['api_key'], + 'chat_session_id': chat_session.id, + 'expires_at': datetime.utcnow() + timedelta(hours=24) + } + + # Redirect to chat interface + return redirect(f"/chat/{qr_data['tenant_code']}/{qr_data['specialist_id']}?session={chat_session.id}") +``` ## Real-time Communication @@ -185,11 +351,12 @@ def render_options(options_list): - **European Hosting**: Self-hosted Umami instance - **Privacy Compliant**: No cookies, GDPR compliant by design - **Tracking Events**: - - Chat session start + - Chat session start (including QR code source) - Message sent - Option selected - Session duration - Specialist interaction completion + - QR code usage ### Tracking Implementation ```javascript @@ -199,6 +366,14 @@ function trackEvent(eventName, eventData) { umami.track(eventName, eventData); } } + +// Track QR code usage +function trackQRUsage(qrSessionId, tenantCode) { + trackEvent('qr_code_used', { + qr_session_id: qrSessionId, + tenant_code: tenantCode + }); +} ``` ## File Upload Support (Future) @@ -219,20 +394,40 @@ function trackEvent(eventName, eventData) { ## Development Guidelines ### Code Organization -- Follow existing eveai project structure and conventions -- Use existing common/services and common/utils where applicable -- Maintain multi-tenant data isolation -- Implement proper error handling and logging +- **Services**: Place reusable business logic in `common/services/` +- **Utils**: Place utility functions in `common/utils/` +- **Multi-tenant**: Maintain data isolation using existing patterns +- **Error Handling**: Implement proper error handling and logging + +### Service Layer Examples +```python +# common/services/qr_service.py +class QRService: + def create_qr_session(self, qr_data): + # Create QR session with hybrid storage approach + pass + + def get_and_validate_qr_session(self, qr_session_id): + # Validate and retrieve QR session data + pass + +# common/services/chat_service.py +class ChatService: + def create_session(self, tenant_code, specialist_id, initial_args=None, source='direct'): + # Create chat session with optional pre-filled arguments + pass +``` ### Testing Strategy -- Unit tests for services and utilities -- Integration tests for chat flow +- Unit tests for services and utilities in `common/` +- Integration tests for chat flow including QR code access - UI tests for theme customization - Load testing for SSE connections - Cross-browser compatibility testing ### Performance Considerations - Cache tenant configurations in Redis +- Cache QR session lookups in Redis - Optimize SSE connection management - Implement connection pooling for database - Use CDN for static assets @@ -251,6 +446,7 @@ function trackEvent(eventName, eventData) { - Celery integration (existing) - PostgreSQL and Redis (existing) - Umami analytics client library +- QR code generation library (qrcode) ## Future Enhancements @@ -274,6 +470,8 @@ function trackEvent(eventName, eventData) { CHAT_CLIENT_PORT=5000 TENANT_API_VALIDATION_CACHE_TTL=3600 SSE_CONNECTION_TIMEOUT=300 +QR_SESSION_DEFAULT_EXPIRY_DAYS=30 +QR_SESSION_MAX_USAGE_LIMIT=1000 UMAMI_WEBSITE_ID=your-website-id UMAMI_SCRIPT_URL=https://your-umami.domain/script.js ``` @@ -293,4 +491,26 @@ UMAMI_SCRIPT_URL=https://your-umami.domain/script.js } ``` -This documentation provides a comprehensive foundation for developing the Evie Chat Client while maintaining consistency with the existing eveai architecture and meeting the specific requirements for anonymous mode interactions with customizable tenant branding. \ No newline at end of file +### Sample QR Session Data +```json +{ + "id": "550e8400-e29b-41d4-a716-446655440000", + "tenant_code": "123e4567-e89b-12d3-a456-426614174000", + "specialist_id": "789e0123-e45f-67g8-h901-234567890123", + "api_key": "tenant_api_key_here", + "specialist_args": { + "department": "technical_support", + "product_category": "software", + "priority": "high", + "language": "en" + }, + "metadata": { + "name": "Technical Support QR - Software Issues", + "created_by": "admin_user_id", + "usage_limit": 100, + "expires_at": "2025-09-01T00:00:00Z" + } +} +``` + +This documentation provides a comprehensive foundation for developing the Evie Chat Client with secure QR code integration while maintaining consistency with the existing eveai multi-tenant architecture. \ No newline at end of file diff --git a/eveai_app/__init__.py b/eveai_app/__init__.py index aee03c0..4d081c0 100644 --- a/eveai_app/__init__.py +++ b/eveai_app/__init__.py @@ -15,7 +15,7 @@ import common.models.document from common.utils.startup_eveai import perform_startup_actions from config.logging_config import LOGGING from common.utils.security import set_tenant_session_data -from .errors import register_error_handlers +from common.utils.errors import register_error_handlers from common.utils.celery_utils import make_celery, init_celery from common.utils.template_filters import register_filters from config.config import get_config diff --git a/eveai_app/templates/entitlements/edit_license.html b/eveai_app/templates/entitlements/edit_license.html index 6502984..0e56dd2 100644 --- a/eveai_app/templates/entitlements/edit_license.html +++ b/eveai_app/templates/entitlements/edit_license.html @@ -19,17 +19,17 @@