diff --git a/common/models/interaction.py b/common/models/interaction.py index d429ce9..8a9ef6b 100644 --- a/common/models/interaction.py +++ b/common/models/interaction.py @@ -122,6 +122,8 @@ class EveAIAgent(db.Model): role = db.Column(db.Text, nullable=True) goal = db.Column(db.Text, nullable=True) backstory = db.Column(db.Text, nullable=True) + temperature = db.Column(db.Float, nullable=True) + llm_model = db.Column(db.String(50), nullable=True) tuning = db.Column(db.Boolean, nullable=True, default=False) configuration = db.Column(JSONB, nullable=True) arguments = db.Column(JSONB, nullable=True) diff --git a/common/models/user.py b/common/models/user.py index 4fff20e..91b7770 100644 --- a/common/models/user.py +++ b/common/models/user.py @@ -386,14 +386,14 @@ class TranslationCache(db.Model): last_used_at = db.Column(db.DateTime, nullable=True) -class PartnerRAGRetriever(db.Model): - __bind_key__ = 'public' - __table_args__ = ( - db.PrimaryKeyConstraint('tenant_id', 'retriever_id'), - db.UniqueConstraint('partner_id', 'tenant_id', 'retriever_id'), - {'schema': 'public'}, - ) - - partner_id = db.Column(db.Integer, db.ForeignKey('public.partner.id'), nullable=False) - tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False) - retriever_id = db.Column(db.Integer, nullable=False) +# class PartnerRAGRetriever(db.Model): +# __bind_key__ = 'public' +# __table_args__ = ( +# db.PrimaryKeyConstraint('tenant_id', 'retriever_id'), +# db.UniqueConstraint('partner_id', 'tenant_id', 'retriever_id'), +# {'schema': 'public'}, +# ) +# +# partner_id = db.Column(db.Integer, db.ForeignKey('public.partner.id'), nullable=False) +# tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False) +# retriever_id = db.Column(db.Integer, nullable=False) diff --git a/common/utils/cache/base.py b/common/utils/cache/base.py index 4e7cd01..46de589 100644 --- a/common/utils/cache/base.py +++ b/common/utils/cache/base.py @@ -121,7 +121,7 @@ class CacheHandler(Generic[T]): region_name = getattr(self.region, 'name', 'default_region') key = CacheKey({k: identifiers[k] for k in self._key_components}) - return f"{region_name}_{self.prefix}:{str(key)}" + return f"{region_name}:{self.prefix}:{str(key)}" def get(self, creator_func, **identifiers) -> T: """ @@ -179,7 +179,7 @@ class CacheHandler(Generic[T]): Deletes all keys that start with the region prefix. """ # Construct the pattern for all keys in this region - pattern = f"{self.region}_{self.prefix}:*" + pattern = f"{self.region}:{self.prefix}:*" # Assuming Redis backend with dogpile, use `delete_multi` or direct Redis access if hasattr(self.region.backend, 'client'): diff --git a/common/utils/database.py b/common/utils/database.py index 1f14869..bb05d0f 100644 --- a/common/utils/database.py +++ b/common/utils/database.py @@ -1,9 +1,9 @@ """Database related functions""" from os import popen -from sqlalchemy import text +from sqlalchemy import text, event from sqlalchemy.schema import CreateSchema from sqlalchemy.exc import InternalError -from sqlalchemy.orm import sessionmaker, scoped_session +from sqlalchemy.orm import sessionmaker, scoped_session, Session as SASession from sqlalchemy.exc import SQLAlchemyError from flask import current_app @@ -16,6 +16,66 @@ class Database: def __init__(self, tenant: str) -> None: self.schema = str(tenant) + # --- Session / Transaction events to ensure correct search_path per transaction --- + @event.listens_for(SASession, "after_begin") + def _set_search_path_per_tx(session, transaction, connection): + """Ensure each transaction sees the right tenant schema, regardless of + which pooled connection is used. Uses SET LOCAL so it is scoped to the tx. + """ + schema = session.info.get("tenant_schema") + if schema: + try: + connection.exec_driver_sql(f'SET LOCAL search_path TO "{schema}", public') + # Optional visibility/logging for debugging + sp = connection.exec_driver_sql("SHOW search_path").scalar() + try: + current_app.logger.info(f"DBCTX tx_begin conn_id={id(connection.connection)} search_path={sp}") + except Exception: + pass + except Exception as e: + try: + current_app.logger.error(f"Failed to SET LOCAL search_path for schema {schema}: {e!r}") + except Exception: + pass + + def _log_db_context(self, origin: str = "") -> None: + """Log key DB context info to diagnose schema/search_path issues. + + Collects and logs in a single structured line: + - current_database() + - inet_server_addr(), inet_server_port() + - SHOW search_path + - current_schema() + - to_regclass('interaction') + - to_regclass('.interaction') + """ + try: + db_name = db.session.execute(text("SELECT current_database()"))\ + .scalar() + host = db.session.execute(text("SELECT inet_server_addr()"))\ + .scalar() + port = db.session.execute(text("SELECT inet_server_port()"))\ + .scalar() + search_path = db.session.execute(text("SHOW search_path"))\ + .scalar() + current_schema = db.session.execute(text("SELECT current_schema()"))\ + .scalar() + reg_unqualified = db.session.execute(text("SELECT to_regclass('interaction')"))\ + .scalar() + qualified = f"{self.schema}.interaction" + reg_qualified = db.session.execute( + text("SELECT to_regclass(:qn)"), + {"qn": qualified} + ).scalar() + current_app.logger.info( + "DBCTX origin=%s db=%s host=%s port=%s search_path=%s current_schema=%s to_regclass(interaction)=%s to_regclass(%s)=%s", + origin, db_name, host, port, search_path, current_schema, reg_unqualified, qualified, reg_qualified + ) + except SQLAlchemyError as e: + current_app.logger.error( + f"DBCTX logging failed at {origin} for schema {self.schema}: {e!r}" + ) + def get_engine(self): """create new schema engine""" return db.engine.execution_options( @@ -52,9 +112,32 @@ class Database: current_app.logger.error(f"šŸ’” Error creating tables for schema {self.schema}: {e.args}") def switch_schema(self): - """switch between tenant/public database schema""" - db.session.execute(text(f'set search_path to "{self.schema}", public')) - db.session.commit() + """switch between tenant/public database schema with diagnostics logging""" + # Record the desired tenant schema on the active Session so events can use it + try: + db.session.info["tenant_schema"] = self.schema + except Exception: + pass + # Log the context before switching + self._log_db_context("before_switch") + try: + db.session.execute(text(f'set search_path to "{self.schema}", public')) + db.session.commit() + except SQLAlchemyError as e: + # Rollback on error to avoid InFailedSqlTransaction and log details + try: + db.session.rollback() + except Exception: + pass + current_app.logger.error( + f"Error switching search_path to {self.schema}: {e!r}" + ) + # Also log context after failure + self._log_db_context("after_switch_failed") + # Re-raise to let caller decide handling if needed + raise + # Log the context after successful switch + self._log_db_context("after_switch") def migrate_tenant_schema(self): """migrate tenant database schema for new tenant""" diff --git a/common/utils/execution_progress.py b/common/utils/execution_progress.py index fdd1616..92eda38 100644 --- a/common/utils/execution_progress.py +++ b/common/utils/execution_progress.py @@ -10,6 +10,13 @@ import time class ExecutionProgressTracker: """Tracks progress of specialist executions using Redis""" + # Normalized processing types and aliases + PT_COMPLETE = 'EVEAI_COMPLETE' + PT_ERROR = 'EVEAI_ERROR' + + _COMPLETE_ALIASES = {'EveAI Specialist Complete', 'Task Complete', 'task complete'} + _ERROR_ALIASES = {'EveAI Specialist Error', 'Task Error', 'task error'} + def __init__(self): try: # Use shared pubsub pool (lazy connect; no eager ping) @@ -40,6 +47,16 @@ class ExecutionProgressTracker: # Exhausted retries raise last_exc + def _normalize_processing_type(self, processing_type: str) -> str: + if not processing_type: + return processing_type + p = str(processing_type).strip() + if p in self._COMPLETE_ALIASES: + return self.PT_COMPLETE + if p in self._ERROR_ALIASES: + return self.PT_ERROR + return p + def send_update(self, ctask_id: str, processing_type: str, data: dict): """Send an update about execution progress""" try: @@ -47,7 +64,7 @@ class ExecutionProgressTracker: f"{data}") key = self._get_key(ctask_id) - + processing_type = self._normalize_processing_type(processing_type) update = { 'processing_type': processing_type, 'data': data, @@ -96,14 +113,16 @@ class ExecutionProgressTracker: self._retry(lambda: pubsub.subscribe(key)) try: + # Hint client reconnect interval (optional but helpful) + yield "retry: 3000\n\n" + # First yield any existing updates length = self._retry(lambda: self.redis.llen(key)) if length > 0: updates = self._retry(lambda: self.redis.lrange(key, 0, -1)) for update in updates: update_data = json.loads(update.decode('utf-8')) - # Use processing_type for the event - yield f"event: {update_data['processing_type']}\n" + update_data['processing_type'] = self._normalize_processing_type(update_data.get('processing_type')) yield f"data: {json.dumps(update_data)}\n\n" # Then listen for new updates @@ -121,13 +140,20 @@ class ExecutionProgressTracker: if message['type'] == 'message': # This is Redis pub/sub type update_data = json.loads(message['data'].decode('utf-8')) - yield f"data: {message['data'].decode('utf-8')}\n\n" + update_data['processing_type'] = self._normalize_processing_type(update_data.get('processing_type')) + yield f"data: {json.dumps(update_data)}\n\n" - # Check processing_type for completion - if update_data['processing_type'] in ['Task Complete', 'Task Error', 'EveAI Specialist Complete']: + # Unified completion check + if update_data['processing_type'] in [self.PT_COMPLETE, self.PT_ERROR]: + # Give proxies/clients a chance to flush + yield ": closing\n\n" break finally: try: pubsub.unsubscribe() except Exception: pass + try: + pubsub.close() + except Exception: + pass diff --git a/common/utils/security_utils.py b/common/utils/security_utils.py index 68c1918..567112d 100644 --- a/common/utils/security_utils.py +++ b/common/utils/security_utils.py @@ -140,21 +140,17 @@ def enforce_tenant_consent_ui(): """Check if the user has consented to the terms of service""" path = getattr(request, 'path', '') or '' if path.startswith('/healthz') or path.startswith('/_healthz'): - current_app.logger.debug(f'Health check request, bypassing consent guard: {path}') return None if not current_user.is_authenticated: - current_app.logger.debug('Not authenticated, bypassing consent guard') return None endpoint = request.endpoint or '' if is_exempt_endpoint(endpoint) or request.method == 'OPTIONS': - current_app.logger.debug(f'Endpoint exempt from consent guard: {endpoint}') return None # Global bypass: Super User and Partner Admin always allowed if current_user.has_roles('Super User') or current_user.has_roles('Partner Admin'): - current_app.logger.debug('Global bypass: Super User or Partner Admin') return None tenant_id = getattr(current_user, 'tenant_id', None) @@ -176,16 +172,13 @@ def enforce_tenant_consent_ui(): status = ConsentStatus.NOT_CONSENTED if status == ConsentStatus.CONSENTED: - current_app.logger.debug('User has consented') return None if status == ConsentStatus.NOT_CONSENTED: - current_app.logger.debug('User has not consented') if current_user.has_roles('Tenant Admin'): return redirect(prefixed_url_for('user_bp.tenant_consent', for_redirect=True)) return redirect(prefixed_url_for('user_bp.no_consent', for_redirect=True)) if status == ConsentStatus.RENEWAL_REQUIRED: - current_app.logger.debug('Consent renewal required') if current_user.has_roles('Tenant Admin'): flash( "You need to renew your consent to our DPA or T&Cs. Failing to do so in time will stop you from accessing our services.", diff --git a/config/agents/globals/EMAIL_CONTENT_AGENT/1.0.0.yaml b/config/agents/globals/EMAIL_CONTENT_AGENT/1.0.0.yaml deleted file mode 100644 index 981a88a..0000000 --- a/config/agents/globals/EMAIL_CONTENT_AGENT/1.0.0.yaml +++ /dev/null @@ -1,17 +0,0 @@ -version: "1.0.0" -name: "Email Content Agent" -role: > - Email Content Writer -goal: > - Craft a highly personalized email that resonates with the {end_user_role}'s context and identification (personal and - company if available). - {custom_goal} -backstory: > - You are an expert in writing compelling, personalized emails that capture the {end_user_role}'s attention and drive - engagement. You are perfectly multilingual, and can write the mail in the native language of the {end_user_role}. - {custom_backstory} -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "An Agent that writes engaging emails." - changes: "Initial version" diff --git a/config/agents/globals/EMAIL_ENGAGEMENT_AGENT/1.0.0.yaml b/config/agents/globals/EMAIL_ENGAGEMENT_AGENT/1.0.0.yaml deleted file mode 100644 index 6581c92..0000000 --- a/config/agents/globals/EMAIL_ENGAGEMENT_AGENT/1.0.0.yaml +++ /dev/null @@ -1,16 +0,0 @@ -version: "1.0.0" -name: "Email Engagement Agent" -role: > - Engagement Optimization Specialist {custom_role} -goal: > - You ensure that the email includes strong CTAs and strategically placed engagement hooks that encourage the - {end_user_role} to take immediate action. {custom_goal} -backstory: > - You specialize in optimizing content to ensure that it not only resonates with the recipient but also encourages them - to take the desired action. - {custom_backstory} -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "An Agent that ensures the email is engaging and lead to maximal desired action" - changes: "Initial version" diff --git a/config/agents/globals/IDENTIFICATION_AGENT/1.0.0.yaml b/config/agents/globals/IDENTIFICATION_AGENT/1.0.0.yaml deleted file mode 100644 index 09e08e5..0000000 --- a/config/agents/globals/IDENTIFICATION_AGENT/1.0.0.yaml +++ /dev/null @@ -1,20 +0,0 @@ -version: "1.0.0" -name: "Identification Agent" -role: > - Identification Administrative force. {custom_role} -goal: > - You are an administrative force that tries to gather identification information to complete the administration of an - end-user, the company he or she works for, through monitoring conversations and advising on questions to help you do - your job. You are responsible for completing the company's backend systems (like CRM, ERP, ...) with inputs from the - end user in the conversation. - {custom_goal} -backstory: > - You are and administrative force for {company}, and very proficient in gathering information for the company's backend - systems. You do so by monitoring conversations between one of your colleagues (e.g. sales, finance, support, ...) and - an end user. You ask your colleagues to request additional information to complete your task. - {custom_backstory} -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "An Agent that gathers administrative information" - changes: "Initial version" diff --git a/config/agents/globals/RAG_AGENT/1.1.0.yaml b/config/agents/globals/RAG_AGENT/1.1.0.yaml index d6cb7c1..8719d96 100644 --- a/config/agents/globals/RAG_AGENT/1.1.0.yaml +++ b/config/agents/globals/RAG_AGENT/1.1.0.yaml @@ -1,4 +1,4 @@ -version: "1.0.0" +version: "1.1.0" name: "Rag Agent" role: > {tenant_name} Spokesperson. {custom_role} @@ -7,7 +7,7 @@ goal: > of the current conversation. {custom_goal} backstory: > - You are the primary contact for {tenant_name}. You are known by {name}, and can be addressed by this name, or you. You are + You are the primary contact for {tenant_name}. You are known by {name}, and can be addressed by this name, or 'you'. You are a very good communicator, and adapt to the style used by the human asking for information (e.g. formal or informal). You always stay correct and polite, whatever happens. And you ensure no discriminating language is used. You are perfectly multilingual in all known languages, and do your best to answer questions in {language}, whatever diff --git a/config/agents/globals/RAG_AGENT/1.2.0.yaml b/config/agents/globals/RAG_AGENT/1.2.0.yaml new file mode 100644 index 0000000..bb64f2d --- /dev/null +++ b/config/agents/globals/RAG_AGENT/1.2.0.yaml @@ -0,0 +1,29 @@ +version: "1.2.0" +name: "Rag Agent" +role: > + {tenant_name}'s Spokesperson. {custom_role} +goal: > + You get questions by a human correspondent, and give answers based on a given context, taking into account the history + of the current conversation. + {custom_goal} +backstory: > + You are the primary contact for {tenant_name}, and have been it's spokesperson for a very long time. You are used to + addressing customers, prospects, press, ... + You are known by {name}, and can be addressed by this name, or 'you'. + You are a very good communicator, that knows how to adapt his style to the public your interacting with. + You always stay correct and polite, whatever happens. And you ensure no discriminating language is used. + You are perfectly multilingual in all known languages, and do your best to answer questions in {language}, whatever + language the context provided to you is in. You are participating in a conversation, not writing e.g. an email or + essay. Do not include a salutation or closing greeting in your answer. + {custom_backstory} +full_model_name: "mistral.mistral-medium-latest" +allowed_models: + - "mistral.mistral-small-latest" + - "mistral.mistral-medium-latest" + - "mistral.magistral-medium-latest" +temperature: 0.3 +metadata: + author: "Josako" + date_added: "2025-01-08" + description: "An Agent that does RAG based on a user's question, RAG content & history" + changes: "Initial version" diff --git a/config/agents/globals/RAG_COMMUNICATION_AGENT/1.0.0.yaml b/config/agents/globals/RAG_COMMUNICATION_AGENT/1.0.0.yaml deleted file mode 100644 index a0c8c82..0000000 --- a/config/agents/globals/RAG_COMMUNICATION_AGENT/1.0.0.yaml +++ /dev/null @@ -1,26 +0,0 @@ -version: "1.0.0" -name: "Rag Communication Agent" -role: > - {company} Interaction Responsible. {custom_role} -goal: > - Your team has collected answers to a question asked. But it also created some additional questions to be asked. You - ensure the necessary answers are returned, and make an informed selection of the additional questions that can be - asked (combining them when appropriate), ensuring the human you're communicating to does not get overwhelmed. - {custom_goal} -backstory: > - You are the online communication expert for {company}. You handled a lot of online communications with both customers - and internal employees. You are a master in redacting one coherent reply in a conversation that includes all the - answers, and a selection of additional questions to be asked in a conversation. Although your backoffice team might - want to ask a myriad of questions, you understand that doesn't fit with the way humans communicate. You know how to - combine multiple related questions, and understand how to interweave the questions in the answers when related. - You are perfectly multilingual in all known languages, and do your best to answer questions in {language}, whatever - language the context provided to you is in. Also, ensure that questions asked do not contradict with the answers - given, or aren't obsolete given the answer provided. - You are participating in a conversation, not writing e.g. an email. Do not include a salutation or closing greeting - in your answer. - {custom_backstory} -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "An Agent that consolidates both answers and questions in a consistent reply" - changes: "Initial version" diff --git a/config/agents/globals/RAG_PROOFREADER_AGENT/1.0.0.yaml b/config/agents/globals/RAG_PROOFREADER_AGENT/1.0.0.yaml new file mode 100644 index 0000000..fb95a44 --- /dev/null +++ b/config/agents/globals/RAG_PROOFREADER_AGENT/1.0.0.yaml @@ -0,0 +1,24 @@ +version: "1.0.0" +name: "Rag Proofreader Agent" +role: > + Proofreader for {tenant_name}. {custom_role} +goal: > + You get a prepared answer to be send out, and adapt it to comply to best practices. + {custom_goal} +backstory: > + You are the primary contact for {tenant_name}, and have been it's spokesperson for a very long time. You are used to + addressing customers, prospects, press, ... + You are known by {name}, and can be addressed by this name, or 'you'. + You review communications and ensure they are clear and follow best practices. + {custom_backstory} +full_model_name: "mistral.mistral-medium-latest" +allowed_models: + - "mistral.mistral-small-latest" + - "mistral.mistral-medium-latest" + - "mistral.magistral-medium-latest" +temperature: 0.4 +metadata: + author: "Josako" + date_added: "2025-10-22" + description: "An Agent that does QA Activities on provided answers" + changes: "Initial version" diff --git a/config/agents/globals/SPIN_DETECTION_AGENT/1.0.0.yaml b/config/agents/globals/SPIN_DETECTION_AGENT/1.0.0.yaml deleted file mode 100644 index 2348272..0000000 --- a/config/agents/globals/SPIN_DETECTION_AGENT/1.0.0.yaml +++ /dev/null @@ -1,22 +0,0 @@ -version: "1.0.0" -name: "SPIN Sales Assistant" -role: > - Sales Assistant for {company} on {products}. {custom_role} -goal: > - Your main job is to help your sales specialist to analyze an ongoing conversation with a customer, and detect - SPIN-related information. {custom_goal} -backstory: > - You are a sales assistant for {company} on {products}. You are known by {name}, and can be addressed by this name, or you. You are - trained to understand an analyse ongoing conversations. Your are proficient in detecting SPIN-related information in a - conversation. - SPIN stands for: - - Situation information - Understanding the customer's current context - - Problem information - Uncovering challenges and pain points - - Implication information - Exploring consequences of those problems - - Need-payoff information - Helping customers realize value of solutions - {custom_backstory} -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "An Agent that detects SPIN information in an ongoing conversation" - changes: "Initial version" diff --git a/config/agents/globals/SPIN_SALES_SPECIALIST_AGENT/1.0.0.yaml b/config/agents/globals/SPIN_SALES_SPECIALIST_AGENT/1.0.0.yaml deleted file mode 100644 index 9fb68c3..0000000 --- a/config/agents/globals/SPIN_SALES_SPECIALIST_AGENT/1.0.0.yaml +++ /dev/null @@ -1,25 +0,0 @@ -version: "1.0.0" -name: "SPIN Sales Specialist" -role: > - Sales Specialist for {company} on {products}. {custom_role} -goal: > - Your main job is to do sales using the SPIN selling methodology in a first conversation with a potential customer. - {custom_goal} -backstory: > - You are a sales specialist for {company} on {products}. You are known by {name}, and can be addressed by this name, - or you. You have an assistant that provides you with already detected SPIN-information in an ongoing conversation. You - decide on follow-up questions for more in-depth information to ensure we get the required information that may lead to - selling {products}. - SPIN stands for: - - Situation information - Understanding the customer's current context - - Problem information - Uncovering challenges and pain points - - Implication information - Exploring consequences of those problems - - Need-payoff information - Helping customers realize value of solutions - {custom_backstory} - You are acquainted with the following product information: - {product_information} -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "An Agent that asks for Follow-up questions for SPIN-process" - changes: "Initial version" diff --git a/config/specialist_forms/globals/PROFESSIONAL_CONTACT_FORM/1.0.0.yaml b/config/specialist_forms/globals/PROFESSIONAL_CONTACT_FORM/1.0.0.yaml index b894cb3..68cd20b 100644 --- a/config/specialist_forms/globals/PROFESSIONAL_CONTACT_FORM/1.0.0.yaml +++ b/config/specialist_forms/globals/PROFESSIONAL_CONTACT_FORM/1.0.0.yaml @@ -11,7 +11,7 @@ fields: email: name: "Email" type: "str" - description: "Your Name" + description: "Your Email" required: true phone: name: "Phone Number" @@ -28,16 +28,6 @@ fields: type: "str" description: "Job Title" required: false - address: - name: "Address" - type: "str" - description: "Your Address" - required: false - zip: - name: "Postal Code" - type: "str" - description: "Postal Code" - required: false city: name: "City" type: "str" diff --git a/config/specialists/globals/RAG_SPECIALIST/1.2.0.yaml b/config/specialists/globals/RAG_SPECIALIST/1.2.0.yaml new file mode 100644 index 0000000..4e94b9d --- /dev/null +++ b/config/specialists/globals/RAG_SPECIALIST/1.2.0.yaml @@ -0,0 +1,81 @@ +version: "1.2.0" +name: "RAG Specialist" +framework: "crewai" +chat: true +configuration: + name: + name: "name" + type: "str" + description: "The name the specialist is called upon." + required: true + tone_of_voice: + name: "Tone of Voice" + description: "The tone of voice the specialist uses to communicate" + type: "enum" + allowed_values: [ "Professional & Neutral", "Warm & Empathetic", "Energetic & Enthusiastic", "Accessible & Informal", "Expert & Trustworthy", "No-nonsense & Goal-driven" ] + default: "Professional & Neutral" + required: true + language_level: + name: "Language Level" + description: "Language level to be used when communicating, relating to CEFR levels" + type: "enum" + allowed_values: [ "Basic", "Standard", "Professional" ] + default: "Standard" + required: true + response_depth: + name: "Response Depth" + description: "Response depth to be used when communicating" + type: "enum" + allowed_values: [ "Concise", "Balanced", "Detailed",] + default: "Balanced" + required: true + conversation_purpose: + name: "Conversation Purpose" + description: "Purpose of the conversation, resulting in communication style" + type: "enum" + allowed_values: [ "Informative", "Persuasive", "Supportive", "Collaborative" ] + default: "Informative" + required: true + welcome_message: + name: "Welcome Message" + type: "string" + description: "Welcome Message to be given to the end user" + required: false +arguments: + language: + name: "Language" + type: "str" + description: "Language code to be used for receiving questions and giving answers" + required: true +results: + rag_output: + answer: + name: "answer" + type: "str" + description: "Answer to the query" + required: true + citations: + name: "citations" + type: "List[str]" + description: "List of citations" + required: false + insufficient_info: + name: "insufficient_info" + type: "bool" + description: "Whether or not the query is insufficient info" + required: true +agents: + - type: "RAG_AGENT" + version: "1.2" + - type: "RAG_PROOFREADER_AGENT" + version: "1.0" +tasks: + - type: "RAG_TASK" + version: "1.1" + - type: "RAG_PROOFREADING_TASK" + version: "1.0" +metadata: + author: "Josako" + date_added: "2025-01-08" + changes: "Initial version" + description: "A Specialist that performs Q&A activities" \ No newline at end of file diff --git a/config/specialists/globals/SPIN_SPECIALIST/1.0.0.yaml b/config/specialists/globals/SPIN_SPECIALIST/1.0.0.yaml deleted file mode 100644 index 8fcc113..0000000 --- a/config/specialists/globals/SPIN_SPECIALIST/1.0.0.yaml +++ /dev/null @@ -1,183 +0,0 @@ -version: "1.0.0" -name: "Spin Sales Specialist" -framework: "crewai" -chat: true -configuration: - name: - name: "name" - type: "str" - description: "The name the specialist is called upon." - required: true - company: - name: "company" - type: "str" - description: "The name of your company. If not provided, your tenant's name will be used." - required: false - products: - name: "products" - type: "List[str]" - description: "The products or services you're providing" - required: false - product_information: - name: "product_information" - type: "text" - description: "Information on the products you are selling, such as ICP (Ideal Customer Profile), Pitch, ..." - required: false - engagement_options: - name: "engagement_options" - type: "text" - description: "Engagement options such as email, phone number, booking link, ..." - tenant_language: - name: "tenant_language" - type: "str" - description: "The language code used for internal information. If not provided, the tenant's default language will be used" - required: false - nr_of_questions: - name: "nr_of_questions" - type: "int" - description: "The maximum number of questions to formulate extra questions" - required: true - default: 3 -arguments: - language: - name: "Language" - type: "str" - description: "Language code to be used for receiving questions and giving answers" - required: true - query: - name: "query" - type: "str" - description: "Query or response to process" - required: true - identification: - name: "identification" - type: "text" - description: "Initial identification information when available" - required: false -results: - rag_output: - answer: - name: "answer" - type: "str" - description: "Answer to the query" - required: true - citations: - name: "citations" - type: "List[str]" - description: "List of citations" - required: false - insufficient_info: - name: "insufficient_info" - type: "bool" - description: "Whether or not the query is insufficient info" - required: true - spin: - situation: - name: "situation" - type: "str" - description: "A description of the customer's current situation / context" - required: false - problem: - name: "problem" - type: "str" - description: "The current problems the customer is facing, for which he/she seeks a solution" - required: false - implication: - name: "implication" - type: "str" - description: "A list of implications" - required: false - needs: - name: "needs" - type: "str" - description: "A list of needs" - required: false - additional_info: - name: "additional_info" - type: "str" - description: "Additional information that may be commercially interesting" - required: false - lead_info: - lead_personal_info: - name: - name: "name" - type: "str" - description: "name of the lead" - required: "true" - job_title: - name: "job_title" - type: "str" - description: "job title" - required: false - email: - name: "email" - type: "str" - description: "lead email" - required: "false" - phone: - name: "phone" - type: "str" - description: "lead phone" - required: false - additional_info: - name: "additional_info" - type: "str" - description: "additional info on the lead" - required: false - lead_company_info: - company_name: - name: "company_name" - type: "str" - description: "Name of the lead company" - required: false - industry: - name: "industry" - type: "str" - description: "The industry of the company" - required: false - company_size: - name: "company_size" - type: "int" - description: "The size of the company" - required: false - company_website: - name: "company_website" - type: "str" - description: "The main website for the company" - required: false - additional_info: - name: "additional_info" - type: "str" - description: "Additional information that may be commercially interesting" - required: false -agents: - - type: "RAG_AGENT" - version: "1.0" - - type: "RAG_COMMUNICATION_AGENT" - version: "1.0" - - type: "SPIN_DETECTION_AGENT" - version: "1.0" - - type: "SPIN_SALES_SPECIALIST_AGENT" - version: "1.0" - - type: "IDENTIFICATION_AGENT" - version: "1.0" - - type: "RAG_COMMUNICATION_AGENT" - version: "1.0" -tasks: - - type: "RAG_TASK" - version: "1.0" - - type: "SPIN_DETECT_TASK" - version: "1.0" - - type: "SPIN_QUESTIONS_TASK" - version: "1.0" - - type: "IDENTIFICATION_DETECTION_TASK" - version: "1.0" - - type: "IDENTIFICATION_QUESTIONS_TASK" - version: "1.0" - - type: "RAG_CONSOLIDATION_TASK" - version: "1.0" -metadata: - author: "Josako" - date_added: "2025-01-08" - changes: "Initial version" - description: "A Specialist that performs both Q&A as SPIN (Sales Process) activities" \ No newline at end of file diff --git a/config/specialists/globals/SPIN_SPECIALIST/1.0.0_overview.svg b/config/specialists/globals/SPIN_SPECIALIST/1.0.0_overview.svg deleted file mode 100644 index c374ee4..0000000 --- a/config/specialists/globals/SPIN_SPECIALIST/1.0.0_overview.svg +++ /dev/null @@ -1,299 +0,0 @@ - - - - - - - - - - - - - - - - EveAI Agent - - - - - - - - - - - - - - - - - - - - Identification Agent - - - - - - - - - - - - - - - - SPIN Sales Assistant - - - - - - - - - - - - - - - - SPIN Sales Specialist - - - - - - - - - - - - - - - - - - - - Identification Agent - - - - - - - - - - - - - - - - Consolidation Agent - - - - - - - - - - - - - - - - RAG Agent - - - - - - - - - - - - - - - - - EveAI Task - - - - - - - - - - - - - RAG Task - - - - - - - - - - - - - - - SPIN Detection - - - - - - - - - - - - - - - - - - - Identification Gathering - - - - - - - - - - - - - - - - - - - Identification Questions - - - - - - - - - - - - - - - Consolidate Q&A - - - - - - - - - - - - - - - SPIN Questions - - - - - - - - - - - - - EveAI Tool - - - - - - - - - - - - - - - - RAG Task - - - - - - - - - - - - - Retrieval - - - - - - - Q&A Processing - - - - - - - - - - - - - - - - - diff --git a/config/static-manifest/manifest.json b/config/static-manifest/manifest.json index f5e2a58..a6ff778 100644 --- a/config/static-manifest/manifest.json +++ b/config/static-manifest/manifest.json @@ -1,6 +1,6 @@ { - "dist/chat-client.js": "dist/chat-client.59b28883.js", - "dist/chat-client.css": "dist/chat-client.79757200.css", - "dist/main.js": "dist/main.c5b0c81d.js", + "dist/chat-client.js": "dist/chat-client.f7f06623.js", + "dist/chat-client.css": "dist/chat-client.cf7bc0ef.css", + "dist/main.js": "dist/main.6a617099.js", "dist/main.css": "dist/main.06893f70.css" } \ No newline at end of file diff --git a/config/tasks/globals/EMAIL_LEAD_DRAFTING_TASK/1.0.0.yaml b/config/tasks/globals/EMAIL_LEAD_DRAFTING_TASK/1.0.0.yaml deleted file mode 100644 index d958958..0000000 --- a/config/tasks/globals/EMAIL_LEAD_DRAFTING_TASK/1.0.0.yaml +++ /dev/null @@ -1,35 +0,0 @@ -version: "1.0.0" -name: "Email Lead Draft Creation" -task_description: > - Craft a highly personalized email using the lead's name, job title, company information, and any relevant personal or - company achievements when available. The email should speak directly to the lead's interests and the needs - of their company. - This mail is the consequence of a first conversation. You have information available from that conversation in the - - SPIN-context (in between triple %) - - personal and company information (in between triple $) - Information might be missing however, as it might not be gathered in that first conversation. - Don't use any salutations or closing remarks, nor too complex sentences. - - Our Company and Product: - - Company Name: {company} - - Products: {products} - - Product information: {product_information} - - {customer_role}'s Identification: - $$${Identification}$$$ - - SPIN context: - %%%{SPIN}%%% - - {custom_description} -expected_output: > - A personalized email draft that: - - Addresses the lead by name - - Acknowledges their role and company - - Highlights how {company} can meet their specific needs or interests - {customer_expected_output} -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "Email Drafting Task towards a Lead" - changes: "Initial version" diff --git a/config/tasks/globals/EMAIL_LEAD_ENGAGEMENT_TASK/1.0.0.yaml b/config/tasks/globals/EMAIL_LEAD_ENGAGEMENT_TASK/1.0.0.yaml deleted file mode 100644 index 1302c57..0000000 --- a/config/tasks/globals/EMAIL_LEAD_ENGAGEMENT_TASK/1.0.0.yaml +++ /dev/null @@ -1,28 +0,0 @@ -version: "1.0.0" -name: "Email Lead Engagement Creation" -task_description: > - Review a personalized email and optimize it with strong CTAs and engagement hooks. Keep in mind that this email is - the consequence of a first conversation. - Don't use any salutations or closing remarks, nor too complex sentences. Keep it short and to the point. - Don't use any salutations or closing remarks, nor too complex sentences. - Ensure the email encourages the lead to schedule a meeting or take - another desired action immediately. - - Our Company and Product: - - Company Name: {company} - - Products: {products} - - Product information: {product_information} - - Engagement options: - {engagement_options} - - {custom_description} -expected_output: > - An optimized email ready for sending, complete with: - - Strong CTAs - - Strategically placed engagement hooks that encourage immediate action -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "Make an Email draft more engaging" - changes: "Initial version" diff --git a/config/tasks/globals/IDENTIFICATION_DETECTION_TASK/1.0.0.yaml b/config/tasks/globals/IDENTIFICATION_DETECTION_TASK/1.0.0.yaml deleted file mode 100644 index d8ca17d..0000000 --- a/config/tasks/globals/IDENTIFICATION_DETECTION_TASK/1.0.0.yaml +++ /dev/null @@ -1,24 +0,0 @@ -version: "1.0.0" -name: "Identification Gathering" -task_description: > - You are asked to gather lead information in a conversation with a new prospect. This is information about the person - participating in the conversation, and information on the company he or she is working for. Try to be as precise as - possible. - Take into account information already gathered in the historic lead info (between triple backquotes) and add - information found in the latest reply. Also, some identification information may be given by the end user. - - historic lead info: - ```{historic_lead_info}``` - latest reply: - {query} - identification: - {identification} - - {custom_description} -expected_output: > - -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "A Task that gathers identification information from a conversation" - changes: "Initial version" diff --git a/config/tasks/globals/IDENTIFICATION_QUESTIONS_TASK/1.0.0.yaml b/config/tasks/globals/IDENTIFICATION_QUESTIONS_TASK/1.0.0.yaml deleted file mode 100644 index 624c033..0000000 --- a/config/tasks/globals/IDENTIFICATION_QUESTIONS_TASK/1.0.0.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: "1.0.0" -name: "Define Identification Questions" -task_description: > - Gather the identification information gathered by your team mates. Ensure no information in the historic lead - information (in between triple backquotes) and the latest reply of the user is lost. - Define questions to be asked to complete the personal and company information for the end user in the conversation. - historic lead info: - ```{historic_lead_info}``` - latest reply: - {query} - - {custom_description} -expected_output: > - -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "A Task to define identification (person & company) questions" - changes: "Initial version" diff --git a/config/tasks/globals/RAG_CONSOLIDATION_TASK/1.0.0.yaml b/config/tasks/globals/RAG_CONSOLIDATION_TASK/1.0.0.yaml deleted file mode 100644 index a70f1e1..0000000 --- a/config/tasks/globals/RAG_CONSOLIDATION_TASK/1.0.0.yaml +++ /dev/null @@ -1,27 +0,0 @@ -version: "1.0.0" -name: "Rag Consolidation" -task_description: > - Your teams have collected answers to a user's query (in between triple backquotes), and collected additional follow-up - questions (in between triple %) to reach their goals. Ensure the answers are provided, and select a maximum of - {nr_of_questions} out of the additional questions to be asked in order not to overwhelm the user. The questions are - in no specific order, so don't just pick the first ones. Make a good mixture of different types of questions, - different topics or subjects! - Questions are to be asked when your team proposes questions. You ensure both answers and additional questions are - bundled into 1 clear communication back to the user. Use {language} for your consolidated communication. - Be sure to format your answer in markdown when appropriate. Ensure enumerations or bulleted lists are formatted as - lists in markdown. - {custom_description} - - Anwers: - ```{prepared_answers}``` - - Additional Questions: - %%%{additional_questions}%%% - -expected_output: > - {custom_expected_output} -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "A Task to consolidate questions and answers" - changes: "Initial version" diff --git a/config/tasks/globals/RAG_PROOFREADING_TASK/1.0.0.yaml b/config/tasks/globals/RAG_PROOFREADING_TASK/1.0.0.yaml new file mode 100644 index 0000000..17322bc --- /dev/null +++ b/config/tasks/globals/RAG_PROOFREADING_TASK/1.0.0.yaml @@ -0,0 +1,26 @@ +version: "1.0.0" +name: "RAG QA Task" +task_description: > + You have to improve this first draft answering the following question: + + £££ + {question} + £££ + + We want you to pay extra attention and adapt to the following requirements: + + - The answer uses the following Tone of Voice: {tone_of_voice}, i.e. {tone_of_voice_context} + - The answer is adapted to the following Language Level: {language_level}, i.e. {language_level_context} + - The answer is suited to be {conversation_purpose}, i.e. {conversation_purpose_context} + - And we want the answer to have the following depth: {response_depth}, i.e. {response_depth_context} + + Ensure the following {language} is used. + If there was insufficient information to answer, answer "I have insufficient information to answer this + question." and give the appropriate indication. +expected_output: > + Your answer. +metadata: + author: "Josako" + date_added: "2025-01-08" + description: "A Task that gives RAG-based answers" + changes: "Initial version" diff --git a/config/tasks/globals/RAG_TASK/1.1.0.yaml b/config/tasks/globals/RAG_TASK/1.1.0.yaml index 457a51e..8887007 100644 --- a/config/tasks/globals/RAG_TASK/1.1.0.yaml +++ b/config/tasks/globals/RAG_TASK/1.1.0.yaml @@ -3,30 +3,43 @@ name: "RAG Task" task_description: > Answer the following question (in between triple Ā£): - £££{question}£££ + £££ + {question} + £££ - Base your answer on the following context (in between triple $): - - $$${context}$$$ - - Take into account the following history of the conversation (in between triple €): - - €€€{history}€€€ - - The HUMAN parts indicate the interactions by the end user, the AI parts are your interactions. + Base your answer on the context below, in between triple '$'. + Take into account the history of the conversion , in between triple '€'. The parts in the history preceded by 'HUMAN' + indicate the interactions by the end user, the parts preceded with 'AI' are your interactions. Best Practices are: - - Answer the provided question as precisely and directly as you can, combining elements of the provided context. - - Always focus your answer on the actual HUMAN question. - - Try not to repeat your answers (preceded by AI), unless absolutely necessary. - - Focus your answer on the question at hand. + - Answer the provided question, combining elements of the provided context. + - Always focus your answer on the actual question. + - Try not to repeat your historic answers, unless absolutely necessary. - Always be friendly and helpful for the end user. + + Tune your answer with the following: + + - You use the following Tone of Voice for your answer: {tone_of_voice}, i.e. {tone_of_voice_context} + - You use the following Language Level for your answer: {language_level}, i.e. {language_level_context} + - The purpose of the conversation is to be {conversation_purpose}, i.e. {conversation_purpose_context} + - We expect you to answer with the following depth: {response_depth}, i.e. {response_depth_context} {custom_description} + Use the following {language} in your communication. If the question cannot be answered using the given context, answer "I have insufficient information to answer this question." and give the appropriate indication. + + Context: + $$$ + {context} + $$$ + + History: + €€€ + {history} + €€€ expected_output: > Your answer. metadata: diff --git a/config/tasks/globals/SPIN_DETECT_TASK/1.0.0.yaml b/config/tasks/globals/SPIN_DETECT_TASK/1.0.0.yaml deleted file mode 100644 index f496764..0000000 --- a/config/tasks/globals/SPIN_DETECT_TASK/1.0.0.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "1.0.0" -name: "SPIN Information Detection" -task_description: > - Complement the historic SPIN context (in between triple backquotes) with information found in the latest reply of the - end user. - {custom_description} - Use the following {tenant_language} to define the SPIN-elements. - Historic SPIN: - ```{historic_spin}``` - Latest reply: - {query} -expected_output: > - -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "A Task that performs SPIN Information Detection" - changes: "Initial version" diff --git a/config/tasks/globals/SPIN_QUESTIONS_TASK/1.0.0.yaml b/config/tasks/globals/SPIN_QUESTIONS_TASK/1.0.0.yaml deleted file mode 100644 index 87f722f..0000000 --- a/config/tasks/globals/SPIN_QUESTIONS_TASK/1.0.0.yaml +++ /dev/null @@ -1,20 +0,0 @@ -version: "1.0.0" -name: "SPIN Question Identification" -task_description: > - Revise the final SPIN provided by your colleague, and ensure no information is lost from the histoic SPIN and the - latest reply from the user. Define the top questions that need to be asked to understand the full SPIN context - of the customer. If you think this user could be a potential customer, please indicate so. - {custom_description} - Use the following {tenant_language} to define the SPIN-elements. If you have a satisfying SPIN context, just skip and - don't ask for more information or confirmation. - Historic SPIN: - ```{historic_spin}``` - Latest reply: - {query} -expected_output: > - -metadata: - author: "Josako" - date_added: "2025-01-08" - description: "A Task that identifies questions to complete the SPIN context in a conversation" - changes: "Initial version" diff --git a/config/type_defs/agent_types.py b/config/type_defs/agent_types.py index a64b1c5..d30d67d 100644 --- a/config/type_defs/agent_types.py +++ b/config/type_defs/agent_types.py @@ -1,32 +1,12 @@ # Agent Types AGENT_TYPES = { - "EMAIL_CONTENT_AGENT": { - "name": "Email Content Agent", - "description": "An Agent that writes engaging emails.", - }, - "EMAIL_ENGAGEMENT_AGENT": { - "name": "Email Engagement Agent", - "description": "An Agent that ensures the email is engaging and lead to maximal desired action", - }, - "IDENTIFICATION_AGENT": { - "name": "Identification Agent", - "description": "An Agent that gathers identification information", - }, "RAG_AGENT": { "name": "Rag Agent", "description": "An Agent that does RAG based on a user's question, RAG content & history", }, - "RAG_COMMUNICATION_AGENT": { - "name": "Rag Communication Agent", - "description": "An Agent that consolidates both answers and questions in a consistent reply", - }, - "SPIN_DETECTION_AGENT": { - "name": "SPIN Sales Assistant", - "description": "An Agent that detects SPIN information in an ongoing conversation", - }, - "SPIN_SALES_SPECIALIST_AGENT": { - "name": "SPIN Sales Specialist", - "description": "An Agent that asks for Follow-up questions for SPIN-process", + "RAG_PROOFREADER_AGENT": { + "name": "Rag Proofreader Agent", + "description": "An Agent that checks the quality of RAG answers and adapts when required", }, "TRAICIE_HR_BP_AGENT": { "name": "Traicie HR BP Agent", diff --git a/config/type_defs/specialist_types.py b/config/type_defs/specialist_types.py index a7274f0..6d5f9be 100644 --- a/config/type_defs/specialist_types.py +++ b/config/type_defs/specialist_types.py @@ -9,10 +9,6 @@ SPECIALIST_TYPES = { "description": "Q&A through Partner RAG Specialist (for documentation purposes)", "partner": "evie_partner" }, - "SPIN_SPECIALIST": { - "name": "Spin Sales Specialist", - "description": "A specialist that allows to answer user queries, try to get SPIN-information and Identification", - }, "TRAICIE_ROLE_DEFINITION_SPECIALIST": { "name": "Traicie Role Definition Specialist", "description": "Assistant Defining Competencies and KO Criteria", diff --git a/config/type_defs/task_types.py b/config/type_defs/task_types.py index 3c7df5a..a602d5c 100644 --- a/config/type_defs/task_types.py +++ b/config/type_defs/task_types.py @@ -1,36 +1,16 @@ # Agent Types TASK_TYPES = { - "EMAIL_LEAD_DRAFTING_TASK": { - "name": "Email Lead Draft Creation", - "description": "Email Drafting Task towards a Lead", - }, - "EMAIL_LEAD_ENGAGEMENT_TASK": { - "name": "Email Lead Engagement Creation", - "description": "Make an Email draft more engaging", - }, - "IDENTIFICATION_DETECTION_TASK": { - "name": "Identification Gathering", - "description": "A Task that gathers identification information from a conversation", - }, - "IDENTIFICATION_QUESTIONS_TASK": { - "name": "Define Identification Questions", - "description": "A Task to define identification (person & company) questions", - }, "RAG_TASK": { "name": "RAG Task", "description": "A Task that gives RAG-based answers", }, - "SPIN_DETECT_TASK": { - "name": "SPIN Information Detection", - "description": "A Task that performs SPIN Information Detection", + "ADVANCED_RAG_TASK": { + "name": "Advanced RAG Task", + "description": "A Task that gives RAG-based answers taking into account previous questions, tone of voice and language level", }, - "SPIN_QUESTIONS_TASK": { - "name": "SPIN Question Identification", - "description": "A Task that identifies questions to complete the SPIN context in a conversation", - }, - "RAG_CONSOLIDATION_TASK": { - "name": "RAG Consolidation", - "description": "A Task to consolidate questions and answers", + "RAG_PROOFREADING_TASK": { + "name": "Rag Proofreading Task", + "description": "A Task that performs RAG Proofreading", }, "TRAICIE_GET_COMPETENCIES_TASK": { "name": "Traicie Get Competencies", diff --git a/config/user_actions/globals/SHARE_PROFESSIONAL_CONTACT_DATA/1.0.0.yaml b/config/user_actions/globals/SHARE_PROFESSIONAL_CONTACT_DATA/1.0.0.yaml new file mode 100644 index 0000000..f3049b3 --- /dev/null +++ b/config/user_actions/globals/SHARE_PROFESSIONAL_CONTACT_DATA/1.0.0.yaml @@ -0,0 +1,9 @@ +type: "SHARE_PROFESSIONAL_CONTACT_DATA" +version: "1.0.0" +name: "Share Professional Contact Data" +icon: "account_circle" +title: "Share Contact Data" +action_type: "specialist_form" +configuration: + specialist_form_name: "PROFESSIONAL_CONTACT_FORM" + specialist_form_version: "1.0.0" diff --git a/content/changelog/1.0/1.0.0.md b/content/changelog/1.0/1.0.0.md index 31256ad..ffb76f1 100644 --- a/content/changelog/1.0/1.0.0.md +++ b/content/changelog/1.0/1.0.0.md @@ -5,6 +5,31 @@ 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/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 3.1.15-beta + +Release date: 2025-10-29 + +### Fixed +- small bugfix where an old form being shown when no form was sent back. + +## 3.1.14-beta + +Release date: 2025-10-28 + +### Added +- Introduction of User Actions - TBC +- Additional configuration options for Agents: temperature and llm_model can now be configured (if allowed in Agent configuration) + +### Changed +- Improvement of RAG Specialist, including proofreading on generated output. +- Specialist Editor - separate modal editors for Agents, Tasks and Tools to allow for more complex configuration. + +### Removed +- PartnerRagRetriever model - not used. + +### Fixed +- Bug fix where client appears to return no result on an interaction, due to connections without correct search path (out of the connection pool) + ## 3.1.13-beta Release date: 2025-10-17 diff --git a/docker/eveai_app/Dockerfile b/docker/eveai_app/Dockerfile index 7b88912..d4193d8 100644 --- a/docker/eveai_app/Dockerfile +++ b/docker/eveai_app/Dockerfile @@ -2,3 +2,4 @@ FROM registry.ask-eve-ai-local.com/josakola/eveai-base:latest # Copy the source code into the container. COPY eveai_app /app/eveai_app COPY content /app/content +COPY migrations /app/migrations diff --git a/eveai_app/__init__.py b/eveai_app/__init__.py index 5bcacec..5f1bd73 100644 --- a/eveai_app/__init__.py +++ b/eveai_app/__init__.py @@ -113,7 +113,6 @@ def create_app(config_file=None): # Register global consent guard via extension @app.before_request def enforce_tenant_consent(): - app.logger.debug("Enforcing tenant consent") return enforce_tenant_consent_ui() # @app.before_request diff --git a/eveai_app/static/assets/js/eveai-list-view.js b/eveai_app/static/assets/js/eveai-list-view.js index 31cdc76..798a876 100644 --- a/eveai_app/static/assets/js/eveai-list-view.js +++ b/eveai_app/static/assets/js/eveai-list-view.js @@ -12,6 +12,21 @@ if (typeof window.EveAI === 'undefined') { window.EveAI.ListView = { // Opslag voor lijst-view instanties instances: {}, + // Registry voor custom formatters (kan uitgebreid worden door templates) + formatters: { + // typeBadge: toont een badge voor agent/task/tool (robuust met Bootstrap 5 classes) + typeBadge: function(cell) { + const raw = (cell.getValue() || '').toString(); + const val = raw.toLowerCase(); + const map = { + 'agent': { cls: 'badge text-bg-primary', label: 'Agent' }, + 'task': { cls: 'badge text-bg-warning', label: 'Task' }, + 'tool': { cls: 'badge text-bg-info', label: 'Tool' }, + }; + const conf = map[val] || { cls: 'badge text-bg-secondary', label: (raw ? raw : 'Item') }; + return `${conf.label}`; + } + }, /** * Initialiseer een Tabulator lijst-view @@ -24,19 +39,50 @@ window.EveAI.ListView = { const defaultConfig = { height: 600, layout: "fitColumns", - selectable: true, + selectable: 1, // single-row selection for consistent UX across Tabulator versions movableColumns: true, pagination: "local", paginationSize: 15, paginationSizeSelector: [10, 15, 20, 50, 100], }; + // Respecteer eventueel meegegeven tableHeight alias + if (config && typeof config.tableHeight !== 'undefined' && typeof config.height === 'undefined') { + config.height = config.tableHeight; + } + + // Los string-formatters op naar functies via registry + if (config && Array.isArray(config.columns)) { + config.columns = config.columns.map(col => { + const newCol = { ...col }; + if (typeof newCol.formatter === 'string' && window.EveAI && window.EveAI.ListView && window.EveAI.ListView.formatters) { + const key = newCol.formatter.trim(); + const fmt = window.EveAI.ListView.formatters[key]; + if (typeof fmt === 'function') { + newCol.formatter = fmt; + } + } + return newCol; + }); + } + const tableConfig = {...defaultConfig, ...config}; + // Enforce single-row selection across Tabulator versions + if (tableConfig.selectable === true) { + tableConfig.selectable = 1; + } + + // Respect and enforce unique row index across Tabulator versions + if (config && typeof config.index === 'string' && config.index) { + // Tabulator v4/v5 + tableConfig.index = config.index; + // Tabulator v6+ (alias) + tableConfig.indexField = config.index; + } + // Voeg rij selectie event toe tableConfig.rowSelectionChanged = (data, rows) => { - console.log("Rij selectie gewijzigd:", rows.length, "rijen geselecteerd"); - // Update de geselecteerde rij in onze instance if (this.instances[elementId]) { this.instances[elementId].selectedRow = rows.length > 0 ? rows[0].getData() : null; @@ -60,6 +106,26 @@ window.EveAI.ListView = { this.updateActionButtons(elementId); }, 0); + // Forceer enkelvoudige selectie op klik voor consistente UX + try { + table.on('rowClick', function(e, row) { + // voorkom multi-select: altijd eerst deselecteren + row.getTable().deselectRow(); + row.select(); + }); + table.on('cellClick', function(e, cell) { + const row = cell.getRow(); + row.getTable().deselectRow(); + row.select(); + }); + // Optioneel: cursor als pointer bij hover + table.on('rowFormatter', function(row) { + row.getElement().style.cursor = 'pointer'; + }); + } catch (e) { + console.warn('Kon click-selectie handlers niet registreren:', e); + } + return table; } catch (error) { console.error(`Fout bij het initialiseren van Tabulator voor ${elementId}:`, error); @@ -168,16 +234,94 @@ window.EveAI.ListView = { } }; -// Functie om beschikbaar te maken in templates -function handleListViewAction(action, requiresSelection) { - // Vind het tableId op basis van de button die is aangeklikt - const target = event?.target || event?.srcElement; +// Functie om beschikbaar te maken in templates (met guard en expliciete event-parameter) +if (typeof window.handleListViewAction !== 'function') { + window.handleListViewAction = function(action, requiresSelection, e) { + const evt = e || undefined; // geen gebruik van deprecated window.event + const target = evt && (evt.target || evt.srcElement); - // Vind het formulier en tableId op basis daarvan - const form = target ? target.closest('form') : null; - const tableId = form ? form.id.replace('-form', '') : 'unknown_table'; + // 1) Bepaal tableId zo robuust mogelijk + let tableId = null; + if (target) { + // Zoek het werkelijke trigger element (button/anchor) i.p.v. een child node + const trigger = (typeof target.closest === 'function') ? target.closest('button, a') : target; - return window.EveAI.ListView.handleAction(action, requiresSelection, tableId); + // a) Respecteer expliciete data-attribute op knop + tableId = trigger && trigger.getAttribute ? trigger.getAttribute('data-table-id') : null; + + if (!tableId) { + // b) Zoek dichtstbijzijnde container met een tabulator-list-view erin + const containerEl = trigger && typeof trigger.closest === 'function' ? trigger.closest('.container') : null; + const scopedTable = containerEl ? containerEl.querySelector('.tabulator-list-view') : null; + tableId = scopedTable ? scopedTable.id : null; + } + if (!tableId) { + // c) Val terug op dichtstbijzijnde form id-afleiding (enkel als het een -form suffix heeft) + const form = trigger && typeof trigger.closest === 'function' ? trigger.closest('form') : null; + if (form && typeof form.id === 'string' && form.id.endsWith('-form')) { + tableId = form.id.slice(0, -'-form'.length); + } + } + } + if (!tableId) { + // d) Laatste redmiddel: pak de eerste tabulator-list-view op de pagina + const anyTable = document.querySelector('.tabulator-list-view'); + tableId = anyTable ? anyTable.id : null; + } + if (!tableId) { + console.error('Kan tableId niet bepalen voor action:', action); + return false; + } + + const listView = window.EveAI && window.EveAI.ListView ? window.EveAI.ListView : null; + const instance = listView && listView.instances ? listView.instances[tableId] : null; + + // 2) Indien selectie vereist, enforce + if (requiresSelection === true) { + if (!instance || !instance.selectedRow) { + // Probeer nog de Tabulator API als instance ontbreekt + try { + const table = Tabulator.findTable(`#${tableId}`)[0]; + const rows = table ? table.getSelectedRows() : []; + if (!rows || rows.length === 0) { + alert('Selecteer eerst een item uit de lijst.'); + return false; + } + if (instance) instance.selectedRow = rows[0].getData(); + } catch (_) { + alert('Selecteer eerst een item uit de lijst.'); + return false; + } + } + } + + // 3) Embedded handler krijgt voorrang + const embeddedHandlers = listView && listView.embeddedHandlers ? listView.embeddedHandlers : null; + const embedded = embeddedHandlers && embeddedHandlers[tableId]; + if (typeof embedded === 'function') { + try { + embedded(action, instance ? instance.selectedRow : null, tableId); + return true; + } catch (err) { + console.error('Embedded handler error:', err); + return false; + } + } + + // 4) Vervallen naar legacy form submit/JS handler + if (listView && typeof listView.handleAction === 'function') { + return listView.handleAction(action, requiresSelection, tableId); + } + + // 5) Allerbeste laatste fallback – probeer form submit met hidden inputs + const actionInput = document.getElementById(`${tableId}-action`); + if (actionInput) actionInput.value = action; + const form = document.getElementById(`${tableId}-form`); + if (form) { form.submit(); return true; } + + console.error('Geen geldige handler gevonden voor action:', action); + return false; + } } console.log('EveAI List View component geladen'); diff --git a/eveai_app/templates/eveai_list_view.html b/eveai_app/templates/eveai_list_view.html index c9723fb..140b455 100644 --- a/eveai_app/templates/eveai_list_view.html +++ b/eveai_app/templates/eveai_list_view.html @@ -16,7 +16,7 @@
{% for action in actions if action.position != 'right' %} + {% endif %} + + +
+ + {% if enable_reset_defaults %} + {% endif %} -
- - -
{% endblock %} {% block content_footer %} diff --git a/eveai_app/templates/interaction/edit_specialist.html b/eveai_app/templates/interaction/edit_specialist.html index 3ea1708..4e23a57 100644 --- a/eveai_app/templates/interaction/edit_specialist.html +++ b/eveai_app/templates/interaction/edit_specialist.html @@ -32,23 +32,8 @@ - - - @@ -63,17 +48,17 @@ {% endfor %} -
-
-
-
-
- Specialist Overview -
-
-
-
-
+{#
#} +{#
#} +{#
#} +{#
#} +{#
#} +{# Specialist Overview#} +{#
#} +{#
#} +{#
#} +{#
#} +{#
#} @@ -88,79 +73,28 @@ {% endfor %} - -
+ +
- {{ render_selectable_table( - headers=["Agent ID", "Name", "Type", "Type Version"], - rows=agent_rows if agent_rows else [], - selectable=True, - id="agentsTable", - is_component_selector=True - ) }} -
- +
+ + +
+
+
+ +
+
- - -
-
-
- {{ render_selectable_table( - headers=["Task ID", "Name", "Type", "Type Version"], - rows=task_rows if task_rows else [], - selectable=True, - id="tasksTable", - is_component_selector=True - ) }} -
- -
-
-
-
- - -
-
-
- {{ render_selectable_table( - headers=["Tool ID", "Name", "Type", "Type Version"], - rows=tool_rows if tool_rows else [], - selectable=True, - id="toolsTable", - is_component_selector=True - ) }} -
- -
-
-
-
- -
-
-
-
-
-
- -
-
-
@@ -170,245 +104,219 @@ + + + {% endblock %} {% block scripts %} {{ super() }} + diff --git a/eveai_app/templates/scripts.html b/eveai_app/templates/scripts.html index 1397bd8..f298616 100644 --- a/eveai_app/templates/scripts.html +++ b/eveai_app/templates/scripts.html @@ -122,21 +122,41 @@ function validateTableSelection(formId) { }