9 Commits

Author SHA1 Message Date
Josako
67078ce925 - Add view to show release notes
- Update release notes for 2.3.1-alfa
2025-06-01 22:03:32 +02:00
Josako
ebdb836448 - Add view to show release notes
- Update release notes for 2.3.1-alfa
2025-06-01 22:03:15 +02:00
Josako
81e754317a - smaller changes to eveai.css to ensure background of selected buttons do not get all white and to ensure that the background of fiels in editable cells do not become white in a tabulator.
- The Role Definition Specialist now creates a new selection specialist upon completion
2025-06-01 10:09:34 +02:00
Josako
578981c745 Present the Specialist Editor in 1 screen 2025-05-30 12:51:34 +02:00
Josako
8fb2ad43c5 Moved styling elements in eveai_ordered_list_editor.html to eveai.css for consistency 2025-05-30 10:04:39 +02:00
Josako
49f9077a7b Improvement of the color scheme of the table editor. 2025-05-30 09:48:42 +02:00
Josako
d290b46a0c Improvement of the color scheme of the table editor. 2025-05-30 05:20:25 +02:00
Josako
73647e4795 We have a reasonable layout for the table-editor in the specialist. To be further refined. 2025-05-30 05:05:13 +02:00
Josako
25e169dbea - Replace old implementation of PROCESSOR_TYPES and CATALOG_TYPES with the new cached approach
- Add an ordered_list dynamic field type (to be refined)
- Add tabulator javascript library to project
2025-05-29 16:00:25 +02:00
34 changed files with 2118 additions and 254 deletions

View File

@@ -1,7 +1,15 @@
import uuid import uuid
from typing import Dict, Any, Tuple from datetime import datetime as dt, timezone as tz
from typing import Dict, Any, Tuple, Optional
from flask import current_app
from sqlalchemy.exc import SQLAlchemyError
from common.extensions import db, cache_manager
from common.models.interaction import (
Specialist, EveAIAgent, EveAITask, EveAITool
)
from common.utils.celery_utils import current_celery from common.utils.celery_utils import current_celery
from common.utils.model_logging_utils import set_logging_information, update_logging_information
class SpecialistServices: class SpecialistServices:
@@ -27,4 +35,188 @@ class SpecialistServices:
'status': 'queued', 'status': 'queued',
} }
@staticmethod
def initialize_specialist(specialist_id: int, specialist_type: str, specialist_version: str):
"""
Initialize an agentic specialist by creating all its components based on configuration.
Args:
specialist_id: ID of the specialist to initialize
specialist_type: Type of the specialist
specialist_version: Version of the specialist type to use
Raises:
ValueError: If specialist not found or invalid configuration
SQLAlchemyError: If database operations fail
"""
config = cache_manager.specialists_config_cache.get_config(specialist_type, specialist_version)
if not config:
raise ValueError(f"No configuration found for {specialist_type} version {specialist_version}")
if config['framework'] == 'langchain':
pass # Langchain does not require additional items to be initialized. All configuration is in the specialist.
specialist = Specialist.query.get(specialist_id)
if not specialist:
raise ValueError(f"Specialist with ID {specialist_id} not found")
if config['framework'] == 'crewai':
SpecialistServices.initialize_crewai_specialist(specialist, config)
@staticmethod
def initialize_crewai_specialist(specialist: Specialist, config: Dict[str, Any]):
timestamp = dt.now(tz=tz.utc)
try:
# Initialize agents
if 'agents' in config:
for agent_config in config['agents']:
SpecialistServices._create_agent(
specialist_id=specialist.id,
agent_type=agent_config['type'],
agent_version=agent_config['version'],
name=agent_config.get('name'),
description=agent_config.get('description'),
timestamp=timestamp
)
# Initialize tasks
if 'tasks' in config:
for task_config in config['tasks']:
SpecialistServices._create_task(
specialist_id=specialist.id,
task_type=task_config['type'],
task_version=task_config['version'],
name=task_config.get('name'),
description=task_config.get('description'),
timestamp=timestamp
)
# Initialize tools
if 'tools' in config:
for tool_config in config['tools']:
SpecialistServices._create_tool(
specialist_id=specialist.id,
tool_type=tool_config['type'],
tool_version=tool_config['version'],
name=tool_config.get('name'),
description=tool_config.get('description'),
timestamp=timestamp
)
db.session.commit()
current_app.logger.info(f"Successfully initialized crewai specialist {specialist.id}")
except SQLAlchemyError as e:
db.session.rollback()
current_app.logger.error(f"Database error initializing crewai specialist {specialist.id}: {str(e)}")
raise
except Exception as e:
db.session.rollback()
current_app.logger.error(f"Error initializing crewai specialist {specialist.id}: {str(e)}")
raise
@staticmethod
def _create_agent(
specialist_id: int,
agent_type: str,
agent_version: str,
name: Optional[str] = None,
description: Optional[str] = None,
timestamp: Optional[dt] = None
) -> EveAIAgent:
"""Create an agent with the given configuration."""
if timestamp is None:
timestamp = dt.now(tz=tz.utc)
# Get agent configuration from cache
agent_config = cache_manager.agents_config_cache.get_config(agent_type, agent_version)
agent = EveAIAgent(
specialist_id=specialist_id,
name=name or agent_config.get('name', agent_type),
description=description or agent_config.get('metadata').get('description', ''),
type=agent_type,
type_version=agent_version,
role=None,
goal=None,
backstory=None,
tuning=False,
configuration=None,
arguments=None
)
set_logging_information(agent, timestamp)
db.session.add(agent)
current_app.logger.info(f"Created agent {agent.id} of type {agent_type}")
return agent
@staticmethod
def _create_task(
specialist_id: int,
task_type: str,
task_version: str,
name: Optional[str] = None,
description: Optional[str] = None,
timestamp: Optional[dt] = None
) -> EveAITask:
"""Create a task with the given configuration."""
if timestamp is None:
timestamp = dt.now(tz=tz.utc)
# Get task configuration from cache
task_config = cache_manager.tasks_config_cache.get_config(task_type, task_version)
task = EveAITask(
specialist_id=specialist_id,
name=name or task_config.get('name', task_type),
description=description or task_config.get('metadata').get('description', ''),
type=task_type,
type_version=task_version,
task_description=None,
expected_output=None,
tuning=False,
configuration=None,
arguments=None,
context=None,
asynchronous=False,
)
set_logging_information(task, timestamp)
db.session.add(task)
current_app.logger.info(f"Created task {task.id} of type {task_type}")
return task
@staticmethod
def _create_tool(
specialist_id: int,
tool_type: str,
tool_version: str,
name: Optional[str] = None,
description: Optional[str] = None,
timestamp: Optional[dt] = None
) -> EveAITool:
"""Create a tool with the given configuration."""
if timestamp is None:
timestamp = dt.now(tz=tz.utc)
# Get tool configuration from cache
tool_config = cache_manager.tools_config_cache.get_config(tool_type, tool_version)
tool = EveAITool(
specialist_id=specialist_id,
name=name or tool_config.get('name', tool_type),
description=description or tool_config.get('metadata').get('description', ''),
type=tool_type,
type_version=tool_version,
tuning=False,
configuration=None,
arguments=None,
)
set_logging_information(tool, timestamp)
db.session.add(tool)
current_app.logger.info(f"Created tool {tool.id} of type {tool_type}")
return tool

View File

@@ -7,7 +7,7 @@ from flask import current_app
from common.utils.cache.base import CacheHandler, CacheKey from common.utils.cache.base import CacheHandler, CacheKey
from config.type_defs import agent_types, task_types, tool_types, specialist_types, retriever_types, prompt_types, \ from config.type_defs import agent_types, task_types, tool_types, specialist_types, retriever_types, prompt_types, \
catalog_types, partner_service_types catalog_types, partner_service_types, processor_types
def is_major_minor(version: str) -> bool: def is_major_minor(version: str) -> bool:
@@ -59,7 +59,7 @@ class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
"""Set the version tree cache dependency.""" """Set the version tree cache dependency."""
self.version_tree_cache = cache self.version_tree_cache = cache
def _load_specific_config(self, type_name: str, version_str: str) -> Dict[str, Any]: def _load_specific_config(self, type_name: str, version_str: str = 'latest') -> Dict[str, Any]:
""" """
Load a specific configuration version Load a specific configuration version
Automatically handles global vs partner-specific configs Automatically handles global vs partner-specific configs
@@ -456,6 +456,13 @@ CatalogConfigCacheHandler, CatalogConfigVersionTreeCacheHandler, CatalogConfigTy
types_module=catalog_types.CATALOG_TYPES types_module=catalog_types.CATALOG_TYPES
)) ))
ProcessorConfigCacheHandler, ProcessorConfigVersionTreeCacheHandler, ProcessorConfigTypesCacheHandler = (
create_config_cache_handlers(
config_type='processors',
config_dir='config/processors',
types_module=processor_types.PROCESSOR_TYPES
))
# Add to common/utils/cache/config_cache.py # Add to common/utils/cache/config_cache.py
PartnerServiceConfigCacheHandler, PartnerServiceConfigVersionTreeCacheHandler, PartnerServiceConfigTypesCacheHandler = ( PartnerServiceConfigCacheHandler, PartnerServiceConfigVersionTreeCacheHandler, PartnerServiceConfigTypesCacheHandler = (
create_config_cache_handlers( create_config_cache_handlers(
@@ -487,6 +494,9 @@ def register_config_cache_handlers(cache_manager) -> None:
cache_manager.register_handler(CatalogConfigCacheHandler, 'eveai_config') cache_manager.register_handler(CatalogConfigCacheHandler, 'eveai_config')
cache_manager.register_handler(CatalogConfigTypesCacheHandler, 'eveai_config') cache_manager.register_handler(CatalogConfigTypesCacheHandler, 'eveai_config')
cache_manager.register_handler(CatalogConfigVersionTreeCacheHandler, 'eveai_config') cache_manager.register_handler(CatalogConfigVersionTreeCacheHandler, 'eveai_config')
cache_manager.register_handler(ProcessorConfigCacheHandler, 'eveai_config')
cache_manager.register_handler(ProcessorConfigTypesCacheHandler, 'eveai_config')
cache_manager.register_handler(ProcessorConfigVersionTreeCacheHandler, 'eveai_config')
cache_manager.register_handler(AgentConfigCacheHandler, 'eveai_config') cache_manager.register_handler(AgentConfigCacheHandler, 'eveai_config')
cache_manager.register_handler(AgentConfigTypesCacheHandler, 'eveai_config') cache_manager.register_handler(AgentConfigTypesCacheHandler, 'eveai_config')
cache_manager.register_handler(AgentConfigVersionTreeCacheHandler, 'eveai_config') cache_manager.register_handler(AgentConfigVersionTreeCacheHandler, 'eveai_config')
@@ -500,4 +510,6 @@ def register_config_cache_handlers(cache_manager) -> None:
cache_manager.specialists_config_cache.set_version_tree_cache(cache_manager.specialists_version_tree_cache) cache_manager.specialists_config_cache.set_version_tree_cache(cache_manager.specialists_version_tree_cache)
cache_manager.retrievers_config_cache.set_version_tree_cache(cache_manager.retrievers_version_tree_cache) cache_manager.retrievers_config_cache.set_version_tree_cache(cache_manager.retrievers_version_tree_cache)
cache_manager.prompts_config_cache.set_version_tree_cache(cache_manager.prompts_version_tree_cache) cache_manager.prompts_config_cache.set_version_tree_cache(cache_manager.prompts_version_tree_cache)
cache_manager.catalogs_config_cache.set_version_tree_cache(cache_manager.catalogs_version_tree_cache)
cache_manager.processors_config_cache.set_version_tree_cache(cache_manager.processors_version_tree_cache)
cache_manager.partner_services_config_cache.set_version_tree_cache(cache_manager.partner_services_version_tree_cache) cache_manager.partner_services_config_cache.set_version_tree_cache(cache_manager.partner_services_version_tree_cache)

View File

@@ -0,0 +1,21 @@
version: "1.0.0"
name: "Dossier Catalog"
description: "A Catalog with information in Evie's Library in which several Dossiers can be stored"
configuration:
tagging_fields:
name: "Tagging Fields"
type: "tagging_fields"
description: "Define the metadata fields that will be used for tagging documents.
Each field must have:
- type: one of 'string', 'integer', 'float', 'date', 'enum'
- required: boolean indicating if the field is mandatory
- description: field description
- allowed_values: list of values (for enum type only)
- min_value/max_value: range limits (for numeric types only)"
required: true
default: {}
document_version_configurations: ["tagging_fields"]
metadata:
author: "System"
date_added: "2023-01-01"
description: "A Catalog with information in Evie's Library in which several Dossiers can be stored"

View File

@@ -0,0 +1,9 @@
version: "1.0.0"
name: "Standard Catalog"
description: "A Catalog with information in Evie's Library, to be considered as a whole"
configuration: {}
document_version_configurations: []
metadata:
author: "System"
date_added: "2023-01-01"
description: "A Catalog with information in Evie's Library, to be considered as a whole"

View File

@@ -0,0 +1,9 @@
version: "1.0.0"
name: "AUDIO Processor"
file_types: "mp3, mp4, ogg"
description: "A Processor for audio files"
configuration: {}
metadata:
author: "System"
date_added: "2023-01-01"
description: "A Processor for audio files"

View File

@@ -0,0 +1,59 @@
version: "1.0.0"
name: "DOCX Processor"
file_types: "docx"
description: "A processor for DOCX files"
configuration:
chunking_patterns:
name: "Chunking Patterns"
description: "A list of Patterns used to chunk files into logical pieces"
type: "chunking_patterns"
required: false
chunking_heading_level:
name: "Chunking Heading Level"
type: "integer"
description: "Maximum heading level to consider for chunking (1-6)"
required: false
default: 2
extract_comments:
name: "Extract Comments"
type: "boolean"
description: "Whether to include document comments in the markdown"
required: false
default: false
extract_headers_footers:
name: "Extract Headers/Footers"
type: "boolean"
description: "Whether to include headers and footers in the markdown"
required: false
default: false
preserve_formatting:
name: "Preserve Formatting"
type: "boolean"
description: "Whether to preserve bold, italic, and other text formatting"
required: false
default: true
list_style:
name: "List Style"
type: "enum"
description: "How to format lists in markdown"
required: false
default: "dash"
allowed_values: ["dash", "asterisk", "plus"]
image_handling:
name: "Image Handling"
type: "enum"
description: "How to handle embedded images"
required: false
default: "skip"
allowed_values: ["skip", "extract", "placeholder"]
table_alignment:
name: "Table Alignment"
type: "enum"
description: "How to align table contents"
required: false
default: "left"
allowed_values: ["left", "center", "preserve"]
metadata:
author: "System"
date_added: "2023-01-01"
description: "A processor for DOCX files"

View File

@@ -0,0 +1,49 @@
version: "1.0.0"
name: "HTML Processor"
file_types: "html"
description: "A processor for HTML files"
configuration:
chunking_patterns:
name: "Chunking Patterns"
description: "A list of Patterns used to chunk files into logical pieces"
type: "chunking_patterns"
required: false
chunking_heading_level:
name: "Chunking Heading Level"
type: "integer"
description: "Maximum heading level to consider for chunking (1-6)"
required: false
default: 2
html_tags:
name: "HTML Tags"
type: "string"
description: "A comma-separated list of HTML tags"
required: true
default: "p, h1, h2, h3, h4, h5, h6, li, table, thead, tbody, tr, td"
html_end_tags:
name: "HTML End Tags"
type: "string"
description: "A comma-separated list of HTML end tags (where can the chunk end)"
required: true
default: "p, li, table"
html_included_elements:
name: "HTML Included Elements"
type: "string"
description: "A comma-separated list of elements to be included"
required: true
default: "article, main"
html_excluded_elements:
name: "HTML Excluded Elements"
type: "string"
description: "A comma-separated list of elements to be excluded"
required: false
default: "header, footer, nav, script"
html_excluded_classes:
name: "HTML Excluded Classes"
type: "string"
description: "A comma-separated list of classes to be excluded"
required: false
metadata:
author: "System"
date_added: "2023-01-01"
description: "A processor for HTML files"

View File

@@ -0,0 +1,20 @@
version: "1.0.0"
name: "Markdown Processor"
file_types: "md"
description: "A Processor for markdown files"
configuration:
chunking_patterns:
name: "Chunking Patterns"
description: "A list of Patterns used to chunk files into logical pieces"
type: "chunking_patterns"
required: false
chunking_heading_level:
name: "Chunking Heading Level"
type: "integer"
description: "Maximum heading level to consider for chunking (1-6)"
required: false
default: 2
metadata:
author: "System"
date_added: "2023-01-01"
description: "A Processor for markdown files"

View File

@@ -0,0 +1,20 @@
version: "1.0.0"
name: "PDF Processor"
file_types: "pdf"
description: "A Processor for PDF files"
configuration:
chunking_patterns:
name: "Chunking Patterns"
description: "A list of Patterns used to chunk files into logical pieces"
type: "chunking_patterns"
required: false
chunking_heading_level:
name: "Chunking Heading Level"
type: "integer"
description: "Maximum heading level to consider for chunking (1-6)"
required: false
default: 2
metadata:
author: "System"
date_added: "2023-01-01"
description: "A Processor for PDF files"

View File

@@ -0,0 +1,44 @@
version: "1.1.0"
name: "Traicie Role Definition Specialist"
framework: "crewai"
partner: "traicie"
chat: false
configuration: {}
arguments:
role_name:
name: "Role Name"
description: "The name of the role that is being processed. Will be used to create a selection specialist"
type: "str"
required: true
specialist_name:
name: "Specialist Name"
description: "The name the specialist will be called upon"
type: str
required: true
role_reference:
name: "Role Reference"
description: "A customer reference to the role"
type: "str"
required: false
vacancy_text:
name: "vacancy_text"
type: "text"
description: "The Vacancy Text"
required: true
results:
competencies:
name: "competencies"
type: "List[str, str]"
description: "List of vacancy competencies and their descriptions"
required: false
agents:
- type: "TRAICIE_HR_BP_AGENT"
version: "1.0"
tasks:
- type: "TRAICIE_GET_COMPETENCIES_TASK"
version: "1.1"
metadata:
author: "Josako"
date_added: "2025-05-27"
changes: "Updated for unified competencies and ko criteria"
description: "Assistant to create a new Vacancy based on Vacancy Text"

View File

@@ -5,12 +5,17 @@ partner: "traicie"
chat: false chat: false
configuration: configuration:
name: name:
name: "name" name: "Name"
description: "The name the specialist is called upon." description: "The name the specialist is called upon."
type: "str" type: "str"
required: true required: true
role_reference:
name: "Role Reference"
description: "A customer reference to the role"
type: "str"
required: false
competencies: competencies:
name: "competencies" name: "Competencies"
description: "An ordered list of competencies." description: "An ordered list of competencies."
type: "ordered_list" type: "ordered_list"
list_type: "competency_details" list_type: "competency_details"
@@ -41,17 +46,17 @@ configuration:
required: false required: false
competency_details: competency_details:
title: title:
name: "title" name: "Title"
description: "Competency Title" description: "Competency Title"
type: "str" type: "str"
required: true required: true
description: description:
name: "description" name: "Description"
description: "Description (in context of the role) of the competency" description: "Description (in context of the role) of the competency"
type: "text" type: "text"
required: true required: true
is_knockout: is_knockout:
name: "Is Knockout" name: "KO"
description: "Defines if the competency is a knock-out criterium" description: "Defines if the competency is a knock-out criterium"
type: "boolean" type: "boolean"
required: true required: true

View File

@@ -2,28 +2,10 @@
CATALOG_TYPES = { CATALOG_TYPES = {
"STANDARD_CATALOG": { "STANDARD_CATALOG": {
"name": "Standard Catalog", "name": "Standard Catalog",
"Description": "A Catalog with information in Evie's Library, to be considered as a whole", "description": "A Catalog with information in Evie's Library, to be considered as a whole",
"configuration": {},
"document_version_configurations": []
}, },
"DOSSIER_CATALOG": { "DOSSIER_CATALOG": {
"name": "Dossier Catalog", "name": "Dossier Catalog",
"Description": "A Catalog with information in Evie's Library in which several Dossiers can be stored", "description": "A Catalog with information in Evie's Library in which several Dossiers can be stored",
"configuration": {
"tagging_fields": {
"name": "Tagging Fields",
"type": "tagging_fields",
"description": """Define the metadata fields that will be used for tagging documents.
Each field must have:
- type: one of 'string', 'integer', 'float', 'date', 'enum'
- required: boolean indicating if the field is mandatory
- description: field description
- allowed_values: list of values (for enum type only)
- min_value/max_value: range limits (for numeric types only)""",
"required": True,
"default": {},
}
},
"document_version_configurations": ["tagging_fields"]
}, },
} }

View File

@@ -1,168 +1,28 @@
# Catalog Types # Processor Types
PROCESSOR_TYPES = { PROCESSOR_TYPES = {
"HTML_PROCESSOR": { "HTML_PROCESSOR": {
"name": "HTML Processor", "name": "HTML Processor",
"description": "A processor for HTML files",
"file_types": "html", "file_types": "html",
"Description": "A processor for HTML files",
"configuration": {
"chunking_patterns": {
"name": "Chunking Patterns",
"description": "A list of Patterns used to chunk files into logical pieces",
"type": "chunking_patterns",
"required": False
},
"chunking_heading_level": {
"name": "Chunking Heading Level",
"type": "integer",
"description": "Maximum heading level to consider for chunking (1-6)",
"required": False,
"default": 2
},
"html_tags": {
"name": "HTML Tags",
"type": "string",
"description": "A comma-separated list of HTML tags",
"required": True,
"default": "p, h1, h2, h3, h4, h5, h6, li, table, thead, tbody, tr, td"
},
"html_end_tags": {
"name": "HTML End Tags",
"type": "string",
"description": "A comma-separated list of HTML end tags (where can the chunk end)",
"required": True,
"default": "p, li, table"
},
"html_included_elements": {
"name": "HTML Included Elements",
"type": "string",
"description": "A comma-separated list of elements to be included",
"required": True,
"default": "article, main"
},
"html_excluded_elements": {
"name": "HTML Excluded Elements",
"type": "string",
"description": "A comma-separated list of elements to be excluded",
"required": False,
"default": "header, footer, nav, script"
},
"html_excluded_classes": {
"name": "HTML Excluded Classes",
"type": "string",
"description": "A comma-separated list of classes to be excluded",
"required": False,
},
},
}, },
"PDF_PROCESSOR": { "PDF_PROCESSOR": {
"name": "PDF Processor", "name": "PDF Processor",
"description": "A Processor for PDF files",
"file_types": "pdf", "file_types": "pdf",
"Description": "A Processor for PDF files",
"configuration": {
"chunking_patterns": {
"name": "Chunking Patterns",
"description": "A list of Patterns used to chunk files into logical pieces",
"type": "chunking_patterns",
"required": False
},
"chunking_heading_level": {
"name": "Chunking Heading Level",
"type": "integer",
"description": "Maximum heading level to consider for chunking (1-6)",
"required": False,
"default": 2
},
},
}, },
"AUDIO_PROCESSOR": { "AUDIO_PROCESSOR": {
"name": "AUDIO Processor", "name": "AUDIO Processor",
"description": "A Processor for audio files",
"file_types": "mp3, mp4, ogg", "file_types": "mp3, mp4, ogg",
"Description": "A Processor for audio files",
"configuration": {}
}, },
"MARKDOWN_PROCESSOR": { "MARKDOWN_PROCESSOR": {
"name": "Markdown Processor", "name": "Markdown Processor",
"description": "A Processor for markdown files",
"file_types": "md", "file_types": "md",
"Description": "A Processor for markdown files",
"configuration": {
"chunking_patterns": {
"name": "Chunking Patterns",
"description": "A list of Patterns used to chunk files into logical pieces",
"type": "chunking_patterns",
"required": False
},
"chunking_heading_level": {
"name": "Chunking Heading Level",
"type": "integer",
"description": "Maximum heading level to consider for chunking (1-6)",
"required": False,
"default": 2
},
}
}, },
"DOCX_PROCESSOR": { "DOCX_PROCESSOR": {
"name": "DOCX Processor", "name": "DOCX Processor",
"description": "A processor for DOCX files",
"file_types": "docx", "file_types": "docx",
"Description": "A processor for DOCX files",
"configuration": {
"chunking_patterns": {
"name": "Chunking Patterns",
"description": "A list of Patterns used to chunk files into logical pieces",
"type": "chunking_patterns",
"required": False
},
"chunking_heading_level": {
"name": "Chunking Heading Level",
"type": "integer",
"description": "Maximum heading level to consider for chunking (1-6)",
"required": False,
"default": 2
},
"extract_comments": {
"name": "Extract Comments",
"type": "boolean",
"description": "Whether to include document comments in the markdown",
"required": False,
"default": False
},
"extract_headers_footers": {
"name": "Extract Headers/Footers",
"type": "boolean",
"description": "Whether to include headers and footers in the markdown",
"required": False,
"default": False
},
"preserve_formatting": {
"name": "Preserve Formatting",
"type": "boolean",
"description": "Whether to preserve bold, italic, and other text formatting",
"required": False,
"default": True
},
"list_style": {
"name": "List Style",
"type": "enum",
"description": "How to format lists in markdown",
"required": False,
"default": "dash",
"allowed_values": ["dash", "asterisk", "plus"]
},
"image_handling": {
"name": "Image Handling",
"type": "enum",
"description": "How to handle embedded images",
"required": False,
"default": "skip",
"allowed_values": ["skip", "extract", "placeholder"]
},
"table_alignment": {
"name": "Table Alignment",
"type": "enum",
"description": "How to align table contents",
"required": False,
"default": "left",
"allowed_values": ["left", "center", "preserve"]
}
}
} }
} }

View File

@@ -16,5 +16,9 @@ SPECIALIST_TYPES = {
"name": "Traicie Role Definition Specialist", "name": "Traicie Role Definition Specialist",
"description": "Assistant Defining Competencies and KO Criteria", "description": "Assistant Defining Competencies and KO Criteria",
"partner": "traicie" "partner": "traicie"
},
"TRAICIE_SELECTION_SPECIALIST": {
"name": "Traicie Selection Specialist",
"description": "Recruitment Selection Assistant",
} }
} }

View File

@@ -54,6 +54,7 @@
<hr> <hr>
{% include 'footer.html' %} {% include 'footer.html' %}
{% include 'scripts.html' %} {% include 'scripts.html' %}
{% include 'ordered_list_configs.html' %}
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
</body> </body>
</html> </html>

View File

@@ -0,0 +1,364 @@
<script type="module">
window.EveAI = window.EveAI || {};
window.EveAI.OrderedListEditors = {
instances: {},
initialize: function(containerId, data, listType, options = {}) {
console.log('Initializing OrderedListEditor for', containerId, 'with data', data, 'and listType', listType);
const container = document.getElementById(containerId);
if (!container || typeof container !== 'object' || !('classList' in container)) {
console.error(`Container with ID ${containerId} not found or not a valid element:`, container);
return null;
}
if (this.instances[containerId]) return this.instances[containerId];
if (typeof window.Tabulator !== 'function') {
console.error('Tabulator not loaded (window.Tabulator missing).');
container.innerHTML = `<div class="alert alert-danger p-3">
<strong>Error:</strong> Tabulator not loaded
</div>`;
return null;
}
// Get the list type configuration
const listTypeConfig = this._getListTypeConfig(listType);
if (!listTypeConfig) {
console.error(`List type configuration for ${listType} not found.`);
container.innerHTML = `<div class="alert alert-danger p-3">
<strong>Error:</strong> List type configuration for ${listType} not found
</div>`;
return null;
}
// Create column definitions from list type
const columns = this._createColumnsFromListType(listTypeConfig);
// Debug log for data and columns
console.log('Data for Tabulator:', data);
console.log('Columns for Tabulator:', columns);
// Debug log for column titles
console.log('Column titles:', columns.map(col => col.title || ''));
// Initialize Tabulator
try {
console.log('Creating Tabulator for', containerId);
const table = new Tabulator(container, {
data: data || [],
columns: columns,
layout: "fitColumns", // Changed to fitColumns to ensure columns display horizontally
movableRows: true,
movableRowsPlaceholder: false, // Don't use placeholder, show actual row content
movableRowsSender: "table", // Keep a copy of the row in the table while dragging
rowHeader: {headerSort:false, resizable: false, minWidth:30, width:30, rowHandle:true, formatter:"handle"},
maxHeight: "50%", // Auto height to display all rows
placeholder: "No Data Available",
autoResize: false,
resizableColumnFit: true,
responsiveLayout: false,
tooltips: true, // Enable tooltips
tooltipsHeader: true,
selectable: false, // Disable row selection to prevent jumping
selectableRangeMode: "click", // Only select on click, not on drag
selectableRollingSelection: false, // Disable rolling selection
scrollToRowIfVisible: false, // Don't scroll to row even if it's already visible
scrollToRowPosition: "nearest",
...options
});
console.log('Tabulator created for', containerId);
container.classList.add('tabulator-initialized');
// Debug: Log table structure
console.log('Table structure:', {
tableElement: container,
tableData: table.getData(),
tableColumns: table.getColumnDefinitions()
});
// Add row button
const addRowBtn = document.createElement('button');
addRowBtn.className = 'btn btn-sm btn-primary mt-2';
addRowBtn.innerHTML = 'Add Row';
addRowBtn.addEventListener('click', () => {
const newRow = {};
// Create empty row with default values
Object.entries(listTypeConfig).forEach(([key, field]) => {
if (field.type === 'boolean') {
newRow[key] = field.default === true;
} else {
newRow[key] = field.default !== undefined ? field.default : '';
}
});
table.addRow(newRow);
this._updateTextarea(containerId, table);
});
container.parentNode.insertBefore(addRowBtn, container.nextSibling);
// Add explode button for fullscreen mode
const explodeBtn = document.createElement('button');
explodeBtn.className = 'btn btn-sm btn-secondary mt-2 ms-2';
explodeBtn.innerHTML = '<i class="material-icons">fullscreen</i> Expand';
explodeBtn.addEventListener('click', () => {
container.classList.toggle('fullscreen-mode');
// Update button text based on current state
if (container.classList.contains('fullscreen-mode')) {
explodeBtn.innerHTML = '<i class="material-icons">fullscreen_exit</i> Collapse';
} else {
explodeBtn.innerHTML = '<i class="material-icons">fullscreen</i> Expand';
}
// Redraw table to adjust to new size
table.redraw(true);
});
container.parentNode.insertBefore(explodeBtn, addRowBtn.nextSibling);
// Store instance
this.instances[containerId] = {
table: table,
textarea: document.getElementById(containerId.replace('-editor', ''))
};
// Prevent scrolling when clicking on cells
container.addEventListener('click', function(e) {
// Prevent the default behavior which might cause scrolling
if (e.target.closest('.tabulator-cell')) {
e.preventDefault();
}
}, { passive: false });
// Update textarea on various events that change data
table.on("dataChanged", () => {
console.log("dataChanged event triggered");
this._updateTextarea(containerId, table);
});
// Listen for row movement
table.on("rowMoved", () => {
console.log("rowMoved event triggered");
this._updateTextarea(containerId, table);
});
// Listen for cell edits
table.on("cellEdited", () => {
console.log("cellEdited event triggered");
this._updateTextarea(containerId, table);
});
return table;
} catch (e) {
console.error(`Error initializing Tabulator for ${containerId}:`, e);
container.innerHTML = `<div class="alert alert-danger p-3">
<strong>Error initializing Tabulator:</strong><br>${e.message}
</div>`;
return null;
}
},
_updateTextarea: function(containerId, table) {
const instance = this.instances[containerId];
if (instance && instance.textarea) {
const data = table.getData();
console.log('Updating textarea with data:', data);
instance.textarea.value = JSON.stringify(data);
console.log('Textarea value updated:', instance.textarea.value);
// Trigger change event on textarea to ensure form validation recognizes the change
const event = new Event('change', { bubbles: true });
instance.textarea.dispatchEvent(event);
// Also trigger input event for any listeners that might be using that
const inputEvent = new Event('input', { bubbles: true });
instance.textarea.dispatchEvent(inputEvent);
} else {
console.error('Cannot update textarea: instance or textarea not found for', containerId);
}
},
_getListTypeConfig: function(listType) {
// Try to get the list type configuration from window.listTypeConfigs
if (window.listTypeConfigs && window.listTypeConfigs[listType]) {
return window.listTypeConfigs[listType];
}
// If not found, log a warning and return a default configuration
console.warn(`List type configuration for ${listType} not found in window.listTypeConfigs. Using a default configuration.`);
return {
title: {
name: "Title",
description: "Title",
type: "str",
required: true
},
description: {
name: "Description",
description: "Description",
type: "text",
required: true
}
};
},
// Custom formatter for text columns to truncate text in normal mode
_truncateFormatter: function(cell, formatterParams, onRendered) {
const value = cell.getValue();
const maxLength = formatterParams.maxLength || 100;
if (value && value.length > maxLength) {
// Create a truncated version with "..." and show more indicator
const truncated = value.substring(0, maxLength) + "...";
// Return HTML with truncated text and a "show more" button
return `<div class="truncated-cell">
<div class="truncated-content">${truncated}</div>
<div class="show-more" title="Click to edit and see full text">
<i class="material-icons">more_horiz</i>
</div>
</div>`;
}
return value;
},
_createColumnsFromListType: function(listTypeConfig) {
const columns = [];
// Add columns for each field in the list type
Object.entries(listTypeConfig).forEach(([key, field]) => {
const column = {
title: field.name || key,
field: key,
headerTooltip: field.description,
headerSort: false,
visible: true,
resizable: "header",
};
console.log("Column ", field.name, " type: ", field.type)
// Set width based on field type
if (field.type === 'boolean') {
column.minWidth = 50;
column.maxWidth = 80; // Limit maximum width
column.widthGrow = 0; // Don't allow boolean columns to grow
} else if (field.type === 'text') {
column.width = 400; // Much larger width for text columns (especially description)
column.minWidth = 300; // Ensure text columns have adequate minimum width
column.widthGrow = 2; // Allow text columns to grow significantly more
} else {
column.width = 150; // Default width for other columns
column.minWidth = 100;
column.widthGrow = 1; // Allow some growth
}
// Ensure consistent width calculation
column.widthShrink = 0; // Don't allow shrinking below minWidth
// Set editor based on field type
if (field.type === 'boolean') {
column.formatter = 'tickCross';
column.editor = 'tickCross';
column.hozAlign = 'center';
column.headerHozAlign = 'center';
column.formatterParams = {
allowEmpty: true,
allowTruthy: true,
tickElement: "<i class='material-icons'>check_circle</i>",
crossElement: "<i class='material-icons'>cancel</i>"
};
} else if (field.type === 'enum' && field.allowed_values) {
column.editor = 'select';
column.editorParams = {
values: field.allowed_values
};
column.hozAlign = 'left';
column.headerHozAlign = 'left';
} else if (field.type === 'text') {
column.editor = 'textarea';
column.formatter = this._truncateFormatter; // Use custom formatter to truncate text
column.variableHeight = true;
// Configure formatter parameters
column.formatterParams = {
maxLength: 50,
autoResize: true
};
// Prevent scrolling when editing text cells
column.editorParams = {
elementAttributes: {
preventScroll: true
}
};
column.hozAlign = 'left';
column.headerHozAlign = 'left';
} else {
column.editor = 'input';
column.hozAlign = 'left';
column.headerHozAlign = 'left';
}
columns.push(column);
});
// We don't add a delete button column as per requirements
// to prevent users from deleting rows
return columns;
},
get: function(containerId) {
return this.instances[containerId] || null;
},
destroy: function(containerId) {
if (this.instances[containerId]) {
if (this.instances[containerId].table && typeof this.instances[containerId].table.destroy === 'function') {
this.instances[containerId].table.destroy();
}
delete this.instances[containerId];
}
const container = document.getElementById(containerId);
if (container) {
container.classList.remove('tabulator-initialized');
container.innerHTML = '';
}
}
};
document.addEventListener('DOMContentLoaded', function() {
// Initialize list type configurations
window.listTypeConfigs = window.listTypeConfigs || {};
// Initialize ordered list editors
document.querySelectorAll('.ordered-list-field').forEach(function(textarea) {
const containerId = textarea.id + '-editor';
console.log('Initializing ordered list editor for', containerId);
// Create container if it doesn't exist
let container = document.getElementById(containerId);
if (!container) {
container = document.createElement('div');
container.id = containerId;
container.className = 'ordered-list-editor';
textarea.parentNode.insertBefore(container, textarea.nextSibling);
textarea.classList.add('d-none'); // Hide the textarea
}
try {
const data = textarea.value ? JSON.parse(textarea.value) : [];
const listType = textarea.getAttribute('data-list-type');
// Check if we have the list type configuration
if (listType && !window.listTypeConfigs[listType]) {
console.warn(`List type configuration for ${listType} not found. Using default configuration.`);
}
window.EveAI.OrderedListEditors.initialize(containerId, data, listType);
} catch (e) {
console.error('Error initializing ordered list editor:', e);
container.innerHTML = `<div class="alert alert-danger p-3">
<strong>Error initializing ordered list editor:</strong><br>${e.message}
</div>`;
}
});
});
</script>

View File

@@ -15,29 +15,17 @@
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
{% set disabled_fields = ['type', 'type_version'] %} {% set disabled_fields = ['type', 'type_version'] %}
{% set exclude_fields = [] %} {% set exclude_fields = [] %}
<!-- Render Static Fields -->
{% for field in form.get_static_fields() %}
{{ render_field(field, disabled_fields, exclude_fields) }}
{% endfor %}
<!-- Overview Section -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="specialist-overview" id="specialist-svg">
<img src="{{ svg_path }}" alt="Specialist Overview" class="w-100">
</div>
</div>
</div>
</div>
</div>
<!-- Nav Tabs --> <!-- Nav Tabs -->
<div class="row mt-5"> <div class="row mt-5">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="nav-wrapper position-relative end-0"> <div class="nav-wrapper position-relative end-0">
<ul class="nav nav-pills nav-fill p-1" role="tablist"> <ul class="nav nav-pills nav-fill p-1" role="tablist">
<li class="nav-item">
<a class="nav-link mb-0 px-0 py-1" data-bs-toggle="tab" href="#general-tab" role="tab">
General
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link mb-0 px-0 py-1 active" data-bs-toggle="tab" href="#configuration-tab" role="tab"> <a class="nav-link mb-0 px-0 py-1 active" data-bs-toggle="tab" href="#configuration-tab" role="tab">
Configuration Configuration
@@ -67,6 +55,27 @@
</div> </div>
<div class="tab-content tab-space"> <div class="tab-content tab-space">
<!-- General Tab -->
<div class="tab-pane fade" id="general-tab" role="tabpanel">
<!-- Render Static Fields -->
{% for field in form.get_static_fields() %}
{{ render_field(field, disabled_fields, exclude_fields) }}
{% endfor %}
<!-- Overview Section -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<div class="specialist-overview" id="specialist-svg">
<img src="{{ svg_path }}" alt="Specialist Overview" class="w-100">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Configuration Tab --> <!-- Configuration Tab -->
<div class="tab-pane fade show active" id="configuration-tab" role="tabpanel"> <div class="tab-pane fade show active" id="configuration-tab" role="tabpanel">
{% for collection_name, fields in form.get_dynamic_fields().items() %} {% for collection_name, fields in form.get_dynamic_fields().items() %}
@@ -420,6 +429,14 @@ document.addEventListener('DOMContentLoaded', function() {
color: #344767 !important; /* Default dark color */ color: #344767 !important; /* Default dark color */
} }
/* Style for active tabs */
.nav-link.active {
background-color: #5e72e4 !important; /* Primary blue color */
color: white !important;
font-weight: 600;
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
}
/* Style for disabled tabs */ /* Style for disabled tabs */
.nav-link.disabled { .nav-link.disabled {
opacity: 0.5; opacity: 0.5;
@@ -476,4 +493,3 @@ document.addEventListener('DOMContentLoaded', function() {
} }
</style> </style>
{% endblock %} {% endblock %}

View File

@@ -1,3 +1,12 @@
{# Helper functie om veilig de class van een veld te krijgen #}
{% macro get_field_class(field, default='') %}
{% if field.render_kw is not none and field.render_kw.get('class') is not none %}
{{ field.render_kw.get('class') }}
{% else %}
{{ default }}
{% endif %}
{% endmacro %}
{% macro render_field_content(field, disabled=False, readonly=False, class='') %} {% macro render_field_content(field, disabled=False, readonly=False, class='') %}
{% if field.type == 'BooleanField' %} {% if field.type == 'BooleanField' %}
<div class="form-group"> <div class="form-group">
@@ -55,9 +64,14 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% set field_class = get_field_class(field) %}
{% if field.type == 'TextAreaField' and 'json-editor' in class %} {% if field.type == 'TextAreaField' and 'json-editor' in class %}
<div id="{{ field.id }}-editor" class="json-editor-container"></div> <div id="{{ field.id }}-editor" class="json-editor-container"></div>
{{ field(class="form-control d-none " + class, disabled=disabled, readonly=readonly) }} {{ field(class="form-control d-none " + class, disabled=disabled, readonly=readonly) }}
{% elif field.type == 'OrderedListField' or 'ordered-list-field' in field_class %}
{# Create container for ordered list editor and hide the textarea #}
<div id="{{ field.id }}-editor" class="ordered-list-editor"></div>
{{ field(class="form-control d-none " + field_class|trim, disabled=disabled, readonly=readonly) }}
{% elif field.type == 'SelectField' %} {% elif field.type == 'SelectField' %}
{{ field(class="form-control form-select " + class, disabled=disabled, readonly=readonly) }} {{ field(class="form-control form-select " + class, disabled=disabled, readonly=readonly) }}
{% else %} {% else %}
@@ -76,6 +90,7 @@
{% endmacro %} {% endmacro %}
{% macro render_field(field, disabled_fields=[], readonly_fields=[], exclude_fields=[], class='') %} {% macro render_field(field, disabled_fields=[], readonly_fields=[], exclude_fields=[], class='') %}
<!-- Debug info --> <!-- Debug info -->
<!-- Field name: {{ field.name }}, Field type: {{ field.__class__.__name__ }} --> <!-- Field name: {{ field.name }}, Field type: {{ field.__class__.__name__ }} -->
@@ -435,4 +450,3 @@
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}

View File

@@ -120,6 +120,7 @@
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
{{ dropdown(current_user.user_name, 'person', [ {{ dropdown(current_user.user_name, 'person', [
{'name': 'Session Defaults', 'url': '/session_defaults', 'roles': ['Super User', 'Tenant Admin']}, {'name': 'Session Defaults', 'url': '/session_defaults', 'roles': ['Super User', 'Tenant Admin']},
{'name': 'Release Notes', 'url': '/release_notes', 'roles': ['Super User', 'Partner Admin', 'Tenant Admin']},
{'name': 'Logout', 'url': '/logout'} {'name': 'Logout', 'url': '/logout'}
]) }} ]) }}
{% else %} {% else %}

View File

@@ -0,0 +1,7 @@
{# Include this template in any page that uses ordered_list fields #}
{# Usage: {% include 'ordered_list_configs.html' %} #}
{# The form must be available in the template context as 'form' #}
{% if form and form.get_list_type_configs_js %}
{{ form.get_list_type_configs_js()|safe }}
{% endif %}

View File

@@ -9,14 +9,19 @@
<script src="{{url_for('static', filename='assets/js/material-kit-pro.min.js')}}"></script> <script src="{{url_for('static', filename='assets/js/material-kit-pro.min.js')}}"></script>
{% include 'eveai_json_editor.html' %} {% include 'eveai_json_editor.html' %}
{% include 'eveai_ordered_list_editor.html' %}
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize tooltips // Initialize tooltips if bootstrap is available
if (typeof bootstrap !== 'undefined') {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')) var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl) return new bootstrap.Tooltip(tooltipTriggerEl)
}); });
} else {
console.warn('Bootstrap is not defined. Tooltips will not be initialized.');
}
// De JSON editor initialisatie is hierboven al samengevoegd. // De JSON editor initialisatie is hierboven al samengevoegd.
// Deze dubbele DOMContentLoaded listener en .json-editor initialisatie kan verwijderd worden. // Deze dubbele DOMContentLoaded listener en .json-editor initialisatie kan verwijderd worden.
@@ -46,9 +51,12 @@ document.addEventListener('DOMContentLoaded', function() {
// Find and click the corresponding tab button // Find and click the corresponding tab button
const tabButton = document.querySelector(`[data-bs-toggle="tab"][data-bs-target="#${tabId}"]`); const tabButton = document.querySelector(`[data-bs-toggle="tab"][data-bs-target="#${tabId}"]`);
if (tabButton) { if (tabButton && typeof bootstrap !== 'undefined') {
const tab = new bootstrap.Tab(tabButton); const tab = new bootstrap.Tab(tabButton);
tab.show(); tab.show();
} else if (tabButton) {
// Fallback if bootstrap is not available
tabButton.click();
} }
// Scroll the invalid field into view and focus it // Scroll the invalid field into view and focus it

View File

@@ -1,6 +1,8 @@
from flask import request, render_template, Blueprint, session, current_app, jsonify, flash, redirect from flask import request, render_template, Blueprint, session, current_app, jsonify, flash, redirect, url_for
from flask_security import roles_required, roles_accepted from flask_security import roles_required, roles_accepted
from flask_wtf.csrf import generate_csrf from flask_wtf.csrf import generate_csrf
import os
import requests
from common.models.document import Catalog from common.models.document import Catalog
from common.models.user import Tenant from common.models.user import Tenant
@@ -101,3 +103,29 @@ def check_csrf():
'session_data': dict(session) 'session_data': dict(session)
}) })
@basic_bp.route('/release_notes', methods=['GET'])
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
def release_notes():
"""Display the CHANGELOG.md file."""
try:
# Construct the URL to the CHANGELOG.md file in the static directory
static_url = url_for('static', filename='docs/CHANGELOG.md', _external=True)
# Make a request to get the content of the CHANGELOG.md file
response = requests.get(static_url)
response.raise_for_status() # Raise an exception for HTTP errors
# Get the content of the response
markdown_content = response.text
return render_template(
'basic/view_markdown.html',
title='Release Notes',
description='EveAI Release Notes and Change History',
markdown_content=markdown_content
)
except Exception as e:
current_app.logger.error(f"Error displaying release notes: {str(e)}")
flash(f'Error displaying release notes: {str(e)}', 'danger')
return redirect(prefixed_url_for('basic_bp.index'))

View File

@@ -20,7 +20,6 @@ from common.utils.document_utils import create_document_stack, start_embedding_t
from common.utils.dynamic_field_utils import create_default_config_from_type_config from common.utils.dynamic_field_utils import create_default_config_from_type_config
from common.utils.eveai_exceptions import EveAIInvalidLanguageException, EveAIUnsupportedFileType, \ from common.utils.eveai_exceptions import EveAIInvalidLanguageException, EveAIUnsupportedFileType, \
EveAIDoubleURLException, EveAIException EveAIDoubleURLException, EveAIException
from config.type_defs.processor_types import PROCESSOR_TYPES
from .document_forms import AddDocumentForm, AddURLForm, EditDocumentForm, EditDocumentVersionForm, \ from .document_forms import AddDocumentForm, AddURLForm, EditDocumentForm, EditDocumentVersionForm, \
CatalogForm, EditCatalogForm, RetrieverForm, EditRetrieverForm, ProcessorForm, EditProcessorForm CatalogForm, EditCatalogForm, RetrieverForm, EditRetrieverForm, ProcessorForm, EditProcessorForm
from common.utils.middleware import mw_before_request from common.utils.middleware import mw_before_request
@@ -29,7 +28,6 @@ from common.utils.nginx_utils import prefixed_url_for
from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro
from .document_list_view import DocumentListView from .document_list_view import DocumentListView
from .document_version_list_view import DocumentVersionListView from .document_version_list_view import DocumentVersionListView
from config.type_defs.catalog_types import CATALOG_TYPES
document_bp = Blueprint('document_bp', __name__, url_prefix='/document') document_bp = Blueprint('document_bp', __name__, url_prefix='/document')
@@ -126,8 +124,8 @@ def edit_catalog(catalog_id):
tenant_id = session.get('tenant').get('id') tenant_id = session.get('tenant').get('id')
form = EditCatalogForm(request.form, obj=catalog) form = EditCatalogForm(request.form, obj=catalog)
configuration_config = CATALOG_TYPES[catalog.type]["configuration"] full_config = cache_manager.catalogs_config_cache.get_config(catalog.type)
form.add_dynamic_fields("configuration", configuration_config, catalog.configuration) form.add_dynamic_fields("configuration", full_config, catalog.configuration)
if request.method == 'POST' and form.validate_on_submit(): if request.method == 'POST' and form.validate_on_submit():
form.populate_obj(catalog) form.populate_obj(catalog)
@@ -160,8 +158,9 @@ def processor():
new_processor = Processor() new_processor = Processor()
form.populate_obj(new_processor) form.populate_obj(new_processor)
new_processor.catalog_id = form.catalog.data.id new_processor.catalog_id = form.catalog.data.id
processor_config = cache_manager.processors_config_cache.get_config(new_processor.type)
new_processor.configuration = create_default_config_from_type_config( new_processor.configuration = create_default_config_from_type_config(
PROCESSOR_TYPES[new_processor.type]["configuration"]) processor_config["configuration"])
set_logging_information(new_processor, dt.now(tz.utc)) set_logging_information(new_processor, dt.now(tz.utc))
@@ -197,8 +196,8 @@ def edit_processor(processor_id):
# Create form instance with the processor # Create form instance with the processor
form = EditProcessorForm(request.form, obj=processor) form = EditProcessorForm(request.form, obj=processor)
configuration_config = PROCESSOR_TYPES[processor.type]["configuration"] full_config = cache_manager.processors_config_cache.get_config(processor.type)
form.add_dynamic_fields("configuration", configuration_config, processor.configuration) form.add_dynamic_fields("configuration", full_config, processor.configuration)
if form.validate_on_submit(): if form.validate_on_submit():
# Update basic fields # Update basic fields
@@ -390,9 +389,10 @@ def add_document():
catalog = Catalog.query.get_or_404(catalog_id) catalog = Catalog.query.get_or_404(catalog_id)
if catalog.configuration and len(catalog.configuration) > 0: if catalog.configuration and len(catalog.configuration) > 0:
document_version_configurations = CATALOG_TYPES[catalog.type]['document_version_configurations'] full_config = cache_manager.catalogs_config_cache.get_config(catalog.type)
document_version_configurations = full_config['document_version_configurations']
for config in document_version_configurations: for config in document_version_configurations:
form.add_dynamic_fields(config, catalog.configuration[config]) form.add_dynamic_fields(config, full_config, catalog.configuration[config])
if form.validate_on_submit(): if form.validate_on_submit():
try: try:
@@ -403,7 +403,8 @@ def add_document():
filename = secure_filename(file.filename) filename = secure_filename(file.filename)
extension = filename.rsplit('.', 1)[1].lower() extension = filename.rsplit('.', 1)[1].lower()
catalog_properties = {} catalog_properties = {}
document_version_configurations = CATALOG_TYPES[catalog.type]['document_version_configurations'] full_config = cache_manager.catalogs_config_cache.get_config(catalog.type)
document_version_configurations = full_config['document_version_configurations']
for config in document_version_configurations: for config in document_version_configurations:
catalog_properties[config] = form.get_dynamic_data(config) catalog_properties[config] = form.get_dynamic_data(config)
@@ -445,9 +446,10 @@ def add_url():
catalog = Catalog.query.get_or_404(catalog_id) catalog = Catalog.query.get_or_404(catalog_id)
if catalog.configuration and len(catalog.configuration) > 0: if catalog.configuration and len(catalog.configuration) > 0:
document_version_configurations = CATALOG_TYPES[catalog.type]['document_version_configurations'] full_config = cache_manager.catalogs_config_cache.get_config(catalog.type)
document_version_configurations = full_config['document_version_configurations']
for config in document_version_configurations: for config in document_version_configurations:
form.add_dynamic_fields(config, catalog.configuration[config]) form.add_dynamic_fields(config, full_config, catalog.configuration[config])
if form.validate_on_submit(): if form.validate_on_submit():
try: try:
@@ -459,7 +461,8 @@ def add_url():
file_content, filename, extension = process_url(url, tenant_id) file_content, filename, extension = process_url(url, tenant_id)
catalog_properties = {} catalog_properties = {}
document_version_configurations = CATALOG_TYPES[catalog.type]['document_version_configurations'] full_config = cache_manager.catalogs_config_cache.get_config(catalog.type)
document_version_configurations = full_config['document_version_configurations']
for config in document_version_configurations: for config in document_version_configurations:
catalog_properties[config] = form.get_dynamic_data(config) catalog_properties[config] = form.get_dynamic_data(config)
@@ -582,13 +585,14 @@ def edit_document_version_view(document_version_id):
catalog = Catalog.query.get_or_404(catalog_id) catalog = Catalog.query.get_or_404(catalog_id)
if catalog.configuration and len(catalog.configuration) > 0: if catalog.configuration and len(catalog.configuration) > 0:
document_version_configurations = CATALOG_TYPES[catalog.type]['document_version_configurations'] full_config = cache_manager.catalogs_config_cache.get_config(catalog.type)
document_version_configurations = full_config['document_version_configurations']
for config in document_version_configurations: for config in document_version_configurations:
form.add_dynamic_fields(config, catalog.configuration[config], doc_vers.catalog_properties[config]) form.add_dynamic_fields(config, full_config, doc_vers.catalog_properties[config])
if form.validate_on_submit(): if form.validate_on_submit():
catalog_properties = {} catalog_properties = {}
document_version_configurations = CATALOG_TYPES[catalog.type]['document_version_configurations'] # Use the full_config variable we already defined
for config in document_version_configurations: for config in document_version_configurations:
catalog_properties[config] = form.get_dynamic_data(config) catalog_properties[config] = form.get_dynamic_data(config)
@@ -897,4 +901,3 @@ def clean_markdown(markdown):
if markdown.endswith("```"): if markdown.endswith("```"):
markdown = markdown[:-3].strip() markdown = markdown[:-3].strip()
return markdown return markdown

View File

@@ -49,6 +49,51 @@ class ChunkingPatternsField(TextAreaField):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class OrderedListField(TextAreaField):
"""Field for ordered list data that will be rendered as a Tabulator table"""
def __init__(self, *args, **kwargs):
list_type = kwargs.pop('list_type', '')
# Behoud bestaande render_kw attributen als die er zijn
if 'render_kw' in kwargs:
existing_render_kw = kwargs['render_kw']
else:
existing_render_kw = {}
current_app.logger.debug(f"incomming render_kw for ordered list field: {existing_render_kw}")
# Stel nieuwe render_kw samen
new_render_kw = {
'data-list-type': list_type,
'data-handle-enter': 'true'
}
# Voeg klasse toe en behoud bestaande klassen
if 'class' in existing_render_kw:
existing_classes = existing_render_kw['class']
if isinstance(existing_classes, list):
existing_classes += ' ordered-list-field'
new_render_kw['class'] = existing_classes
else:
# String classes samenvoegen
new_render_kw['class'] = f"{existing_classes} ordered-list-field"
else:
new_render_kw['class'] = 'ordered-list-field'
# Voeg alle bestaande attributen toe aan nieuwe render_kw
for key, value in existing_render_kw.items():
if key != 'class': # Klassen hebben we al verwerkt
new_render_kw[key] = value
current_app.logger.debug(f"final render_kw for ordered list field: {new_render_kw}")
# Update kwargs met de nieuwe gecombineerde render_kw
kwargs['render_kw'] = new_render_kw
super().__init__(*args, **kwargs)
class DynamicFormBase(FlaskForm): class DynamicFormBase(FlaskForm):
def __init__(self, formdata=None, *args, **kwargs): def __init__(self, formdata=None, *args, **kwargs):
super(DynamicFormBase, self).__init__(*args, **kwargs) super(DynamicFormBase, self).__init__(*args, **kwargs)
@@ -89,6 +134,8 @@ class DynamicFormBase(FlaskForm):
validators_list.append(self._validate_tagging_fields_filter) validators_list.append(self._validate_tagging_fields_filter)
elif field_type == 'dynamic_arguments': elif field_type == 'dynamic_arguments':
validators_list.append(self._validate_dynamic_arguments) validators_list.append(self._validate_dynamic_arguments)
elif field_type == 'ordered_list':
validators_list.append(self._validate_ordered_list)
return validators_list return validators_list
@@ -227,10 +274,52 @@ class DynamicFormBase(FlaskForm):
except Exception as e: except Exception as e:
raise ValidationError(f"Invalid argument definition: {str(e)}") raise ValidationError(f"Invalid argument definition: {str(e)}")
def _validate_ordered_list(self, form, field):
"""Validate the ordered list structure"""
if not field.data:
return
try:
# Parse JSON data
list_data = json.loads(field.data)
# Validate it's a list
if not isinstance(list_data, list):
raise ValidationError("Ordered list must be a list")
# Validate each item in the list is a dictionary
for i, item in enumerate(list_data):
if not isinstance(item, dict):
raise ValidationError(f"Item {i} in ordered list must be an object")
except json.JSONDecodeError:
raise ValidationError("Invalid JSON format")
except Exception as e:
raise ValidationError(f"Invalid ordered list: {str(e)}")
def add_dynamic_fields(self, collection_name, config, initial_data=None): def add_dynamic_fields(self, collection_name, config, initial_data=None):
"""Add dynamic fields to the form based on the configuration.""" """Add dynamic fields to the form based on the configuration.
Args:
collection_name: The name of the collection of fields to add
config: The full configuration object, which should contain the field definitions
for the collection_name and may also contain list_type definitions
initial_data: Optional initial data for the fields
"""
current_app.logger.debug(f"Adding dynamic fields for collection {collection_name} with config: {config}")
# Store the full configuration for later use in get_list_type_configs_js
if not hasattr(self, '_full_configs'):
self._full_configs = {}
self._full_configs[collection_name] = config
# Get the specific field configuration for this collection
field_config = config.get(collection_name, {})
if not field_config:
# Handle the case where config is already the specific field configuration
return
self.dynamic_fields[collection_name] = [] self.dynamic_fields[collection_name] = []
for field_name, field_def in config.items(): for field_name, field_def in field_config.items():
# Prefix the field name with the collection name # Prefix the field name with the collection name
full_field_name = f"{collection_name}_{field_name}" full_field_name = f"{collection_name}_{field_name}"
label = field_def.get('name', field_name) label = field_def.get('name', field_name)
@@ -264,6 +353,12 @@ class DynamicFormBase(FlaskForm):
field_class = ChunkingPatternsField field_class = ChunkingPatternsField
extra_classes = ['monospace-text', 'pattern-input'] extra_classes = ['monospace-text', 'pattern-input']
field_kwargs = {} field_kwargs = {}
elif field_type == 'ordered_list':
current_app.logger.debug(f"Adding ordered list field for {full_field_name}")
field_class = OrderedListField
extra_classes = ''
list_type = field_def.get('list_type', '')
field_kwargs = {'list_type': list_type}
else: else:
extra_classes = '' extra_classes = ''
field_class = { field_class = {
@@ -289,6 +384,12 @@ class DynamicFormBase(FlaskForm):
except (TypeError, ValueError) as e: except (TypeError, ValueError) as e:
current_app.logger.error(f"Error converting initial data to JSON: {e}") current_app.logger.error(f"Error converting initial data to JSON: {e}")
field_data = "{}" field_data = "{}"
elif field_type == 'ordered_list' and isinstance(field_data, list):
try:
field_data = json.dumps(field_data)
except (TypeError, ValueError) as e:
current_app.logger.error(f"Error converting ordered list data to JSON: {e}")
field_data = "[]"
elif field_type == 'chunking_patterns': elif field_type == 'chunking_patterns':
try: try:
field_data = json_to_patterns(field_data) field_data = json_to_patterns(field_data)
@@ -305,6 +406,9 @@ class DynamicFormBase(FlaskForm):
render_kw['data-bs-toggle'] = 'tooltip' render_kw['data-bs-toggle'] = 'tooltip'
render_kw['data-bs-placement'] = 'right' render_kw['data-bs-placement'] = 'right'
current_app.logger.debug(f"render_kw for {full_field_name}: {render_kw}")
# Create the field # Create the field
field_kwargs.update({ field_kwargs.update({
'label': label, 'label': label,
@@ -340,6 +444,73 @@ class DynamicFormBase(FlaskForm):
# Return all fields that are not dynamic # Return all fields that are not dynamic
return [field for name, field in self._fields.items() if name not in dynamic_field_names] return [field for name, field in self._fields.items() if name not in dynamic_field_names]
def get_list_type_configs_js(self):
"""Generate JavaScript code for list type configurations used by ordered_list fields."""
from common.extensions import cache_manager
list_types = {}
# First check if we have any full configurations stored
if hasattr(self, '_full_configs'):
# Look for list types in the stored full configurations
for config_name, config in self._full_configs.items():
for key, value in config.items():
# Check if this is a list type definition (not a field definition)
if isinstance(value, dict) and all(isinstance(v, dict) for v in value.values()):
# This looks like a list type definition
list_types[key] = value
# Collect all list types used in ordered_list fields
for collection_name, field_names in self.dynamic_fields.items():
for full_field_name in field_names:
field = getattr(self, full_field_name)
if isinstance(field, OrderedListField):
list_type = field.render_kw.get('data-list-type')
if list_type and list_type not in list_types:
# First try to get from current_app.config
list_type_config = current_app.config.get('LIST_TYPES', {}).get(list_type)
if list_type_config:
list_types[list_type] = list_type_config
else:
# Try to find the list type in specialist configurations using the cache
try:
# Get all specialist types
specialist_types = cache_manager.specialists_types_cache.get_types()
# For each specialist type, check if it has the list type we're looking for
for specialist_type in specialist_types:
try:
# Get the latest version for this specialist type
latest_version = cache_manager.specialists_version_tree_cache.get_latest_version(specialist_type)
# Get the configuration for this specialist type and version
specialist_config = cache_manager.specialists_config_cache.get_config(specialist_type, latest_version)
# Check if this specialist has the list type we're looking for
if list_type in specialist_config:
list_types[list_type] = specialist_config[list_type]
break
except Exception as e:
current_app.logger.debug(f"Error checking specialist {specialist_type}: {e}")
continue
except Exception as e:
current_app.logger.error(f"Error retrieving specialist configurations: {e}")
# If no list types found, return empty script
if not list_types:
return ""
# Generate JavaScript code
js_code = "<script>\n"
js_code += "window.listTypeConfigs = window.listTypeConfigs || {};\n"
for list_type, config in list_types.items():
js_code += f"window.listTypeConfigs['{list_type}'] = {json.dumps(config, indent=2)};\n"
js_code += "</script>\n"
return js_code
def get_dynamic_fields(self): def get_dynamic_fields(self):
"""Return a dictionary of dynamic fields per collection.""" """Return a dictionary of dynamic fields per collection."""
result = {} result = {}
@@ -361,7 +532,7 @@ class DynamicFormBase(FlaskForm):
if field.type == 'BooleanField': if field.type == 'BooleanField':
data[original_field_name] = full_field_name in self.raw_formdata data[original_field_name] = full_field_name in self.raw_formdata
current_app.logger.debug(f"Value for {original_field_name} is {data[original_field_name]}") current_app.logger.debug(f"Value for {original_field_name} is {data[original_field_name]}")
elif isinstance(field, (TaggingFieldsField, TaggingFieldsFilterField, DynamicArgumentsField)) and field.data: elif isinstance(field, (TaggingFieldsField, TaggingFieldsFilterField, DynamicArgumentsField, OrderedListField)) and field.data:
try: try:
data[original_field_name] = json.loads(field.data) data[original_field_name] = json.loads(field.data)
except json.JSONDecodeError: except json.JSONDecodeError:

View File

@@ -24,9 +24,6 @@ from common.utils.model_logging_utils import set_logging_information, update_log
from common.utils.middleware import mw_before_request from common.utils.middleware import mw_before_request
from common.utils.nginx_utils import prefixed_url_for from common.utils.nginx_utils import prefixed_url_for
from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro
from common.utils.specialist_utils import initialize_specialist
from config.type_defs.specialist_types import SPECIALIST_TYPES
from .interaction_forms import (SpecialistForm, EditSpecialistForm, EditEveAIAgentForm, EditEveAITaskForm, from .interaction_forms import (SpecialistForm, EditSpecialistForm, EditEveAIAgentForm, EditEveAITaskForm,
EditEveAIToolForm, AddEveAIAssetForm, EditEveAIAssetVersionForm, ExecuteSpecialistForm) EditEveAIToolForm, AddEveAIAssetForm, EditEveAIAssetVersionForm, ExecuteSpecialistForm)
@@ -184,7 +181,7 @@ def specialist():
current_app.logger.info(f'Specialist {new_specialist.name} successfully added for tenant {tenant_id}!') current_app.logger.info(f'Specialist {new_specialist.name} successfully added for tenant {tenant_id}!')
# Initialize the newly create specialist # Initialize the newly create specialist
initialize_specialist(new_specialist.id, new_specialist.type, new_specialist.type_version) SpecialistServices.initialize_specialist(new_specialist.id, new_specialist.type, new_specialist.type_version)
return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=new_specialist.id)) return redirect(prefixed_url_for('interaction_bp.edit_specialist', specialist_id=new_specialist.id))
@@ -204,8 +201,7 @@ def edit_specialist(specialist_id):
form = EditSpecialistForm(request.form, obj=specialist) form = EditSpecialistForm(request.form, obj=specialist)
specialist_config = cache_manager.specialists_config_cache.get_config(specialist.type, specialist.type_version) specialist_config = cache_manager.specialists_config_cache.get_config(specialist.type, specialist.type_version)
configuration_config = specialist_config.get('configuration') form.add_dynamic_fields("configuration", specialist_config, specialist.configuration)
form.add_dynamic_fields("configuration", configuration_config, specialist.configuration)
agent_rows = prepare_table_for_macro(specialist.agents, agent_rows = prepare_table_for_macro(specialist.agents,
[('id', ''), ('name', ''), ('type', ''), ('type_version', '')]) [('id', ''), ('name', ''), ('type', ''), ('type_version', '')])
@@ -521,8 +517,7 @@ def edit_asset_version(asset_version_id):
form = EditEveAIAssetVersionForm(asset_version) form = EditEveAIAssetVersionForm(asset_version)
asset_config = cache_manager.assets_config_cache.get_config(asset_version.asset.type, asset_config = cache_manager.assets_config_cache.get_config(asset_version.asset.type,
asset_version.asset.type_version) asset_version.asset.type_version)
configuration_config = asset_config.get('configuration') form.add_dynamic_fields("configuration", asset_config, asset_version.configuration)
form.add_dynamic_fields("configuration", configuration_config, asset_version.configuration)
if form.validate_on_submit(): if form.validate_on_submit():
# Update the configuration dynamic fields # Update the configuration dynamic fields
@@ -582,9 +577,8 @@ def execute_specialist(specialist_id):
return redirect(prefixed_url_for('interaction_bp.specialists')) return redirect(prefixed_url_for('interaction_bp.specialists'))
form = ExecuteSpecialistForm(request.form, obj=specialist) form = ExecuteSpecialistForm(request.form, obj=specialist)
arguments_config = specialist_config.get('arguments', None) if 'arguments' in specialist_config:
if arguments_config: form.add_dynamic_fields('arguments', specialist_config)
form.add_dynamic_fields('arguments', arguments_config)
if form.validate_on_submit(): if form.validate_on_submit():
# We're only interested in gathering the dynamic arguments # We're only interested in gathering the dynamic arguments

View File

@@ -0,0 +1,197 @@
import asyncio
import json
from os import wait
from typing import Optional, List
from crewai.flow.flow import start, listen, and_
from flask import current_app
from pydantic import BaseModel, Field
from sqlalchemy.exc import SQLAlchemyError
from common.extensions import db
from common.models.user import Tenant
from common.models.interaction import Specialist
from eveai_chat_workers.outputs.globals.basic_types.list_item import ListItem
from eveai_chat_workers.specialists.crewai_base_specialist import CrewAIBaseSpecialistExecutor
from eveai_chat_workers.specialists.specialist_typing import SpecialistResult, SpecialistArguments
from eveai_chat_workers.outputs.traicie.competencies.competencies_v1_1 import Competencies
from eveai_chat_workers.specialists.crewai_base_classes import EveAICrewAICrew, EveAICrewAIFlow, EveAIFlowState
from common.services.interaction.specialist_services import SpecialistServices
class SpecialistExecutor(CrewAIBaseSpecialistExecutor):
"""
type: TRAICIE_ROLE_DEFINITION_SPECIALIST
type_version: 1.0
Traicie Role Definition Specialist Executor class
"""
def __init__(self, tenant_id, specialist_id, session_id, task_id, **kwargs):
self.role_definition_crew = None
super().__init__(tenant_id, specialist_id, session_id, task_id)
# Load the Tenant & set language
self.tenant = Tenant.query.get_or_404(tenant_id)
@property
def type(self) -> str:
return "TRAICIE_ROLE_DEFINITION_SPECIALIST"
@property
def type_version(self) -> str:
return "1.1"
def _config_task_agents(self):
self._add_task_agent("traicie_get_competencies_task", "traicie_hr_bp_agent")
def _config_pydantic_outputs(self):
self._add_pydantic_output("traicie_get_competencies_task", Competencies, "competencies")
def _instantiate_specialist(self):
verbose = self.tuning
role_definition_agents = [self.traicie_hr_bp_agent]
role_definition_tasks = [self.traicie_get_competencies_task]
self.role_definition_crew = EveAICrewAICrew(
self,
"Role Definition Crew",
agents=role_definition_agents,
tasks=role_definition_tasks,
verbose=verbose,
)
self.flow = RoleDefinitionFlow(
self,
self.role_definition_crew
)
def execute(self, arguments: SpecialistArguments, formatted_context, citations) -> SpecialistResult:
self.log_tuning("Traicie Role Definition Specialist execution started", {})
flow_inputs = {
"vacancy_text": arguments.vacancy_text,
"role_name": arguments.role_name,
'role_reference': arguments.role_reference,
}
flow_results = self.flow.kickoff(inputs=flow_inputs)
flow_state = self.flow.state
results = RoleDefinitionSpecialistResult.create_for_type(self.type, self.type_version)
if flow_state.competencies:
results.competencies = flow_state.competencies
self.create_selection_specialist(arguments, flow_state.competencies)
self.log_tuning(f"Traicie Role Definition Specialist execution ended", {"Results": results.model_dump()})
return results
def create_selection_specialist(self, arguments: SpecialistArguments, competencies: List[ListItem]):
"""This method creates a new TRAICIE_SELECTION_SPECIALIST specialist with the given competencies."""
current_app.logger.info(f"Creating selection with arguments: {arguments.model_dump()}")
selection_comptencies = []
for competency in competencies:
selection_competency = {
"title": competency.title,
"description": competency.description,
"assess": True,
"is_knockout": False,
}
selection_comptencies.append(selection_competency)
selection_config = {
"name": arguments.specialist_name,
"competencies": selection_comptencies,
"tone_of_voice": "Professional & Neutral",
"language_level": "Standard",
"role_reference": arguments.role_reference,
}
name = arguments.role_name
if len(name) > 50:
name = name[:47] + "..."
new_specialist = Specialist(
name=name,
description=f"Specialist for {arguments.role_name} role",
type="TRAICIE_SELECTION_SPECIALIST",
type_version="1.0",
tuning=False,
configuration=selection_config,
)
try:
db.session.add(new_specialist)
db.session.commit()
except SQLAlchemyError as e:
db.session.rollback()
current_app.logger.error(f"Error creating selection specialist: {str(e)}")
raise e
SpecialistServices.initialize_specialist(new_specialist.id, "TRAICIE_SELECTION_SPECIALIST", "1.0")
class RoleDefinitionSpecialistInput(BaseModel):
role_name: str = Field(..., alias="role_name")
role_reference: Optional[str] = Field(..., alias="role_reference")
vacancy_text: Optional[str] = Field(None, alias="vacancy_text")
class RoleDefinitionSpecialistResult(SpecialistResult):
competencies: Optional[List[ListItem]] = None
class RoleDefFlowState(EveAIFlowState):
"""Flow state for Traicie Role Definition specialist that automatically updates from task outputs"""
input: Optional[RoleDefinitionSpecialistInput] = None
competencies: Optional[List[ListItem]] = None
class RoleDefinitionFlow(EveAICrewAIFlow[RoleDefFlowState]):
def __init__(self,
specialist_executor: CrewAIBaseSpecialistExecutor,
role_definitiion_crew: EveAICrewAICrew,
**kwargs):
super().__init__(specialist_executor, "Traicie Role Definition Specialist Flow", **kwargs)
self.specialist_executor = specialist_executor
self.role_definition_crew = role_definitiion_crew
self.exception_raised = False
@start()
def process_inputs(self):
return ""
@listen(process_inputs)
async def execute_role_definition (self):
inputs = self.state.input.model_dump()
try:
current_app.logger.debug("In execute_role_definition")
crew_output = await self.role_definition_crew.kickoff_async(inputs=inputs)
# Unfortunately, crew_output will only contain the output of the latest task.
# As we will only take into account the flow state, we need to ensure both competencies and criteria
# are copies to the flow state.
update = {}
for task in self.role_definition_crew.tasks:
current_app.logger.debug(f"Task {task.name} output:\n{task.output}")
if task.name == "traicie_get_competencies_task":
# update["competencies"] = task.output.pydantic.competencies
self.state.competencies = task.output.pydantic.competencies
# crew_output.pydantic = crew_output.pydantic.model_copy(update=update)
current_app.logger.debug(f"State after execute_role_definition: {self.state}")
current_app.logger.debug(f"State dump after execute_role_definition: {self.state.model_dump()}")
return crew_output
except Exception as e:
current_app.logger.error(f"CREW execute_role_definition Kickoff Error: {str(e)}")
self.exception_raised = True
raise e
async def kickoff_async(self, inputs=None):
current_app.logger.debug(f"Async kickoff {self.name}")
current_app.logger.debug(f"Inputs: {inputs}")
self.state.input = RoleDefinitionSpecialistInput.model_validate(inputs)
current_app.logger.debug(f"State: {self.state}")
result = await super().kickoff_async(inputs)
return self.state

View File

@@ -16,7 +16,9 @@ import * as Popper from '@popperjs/core';
window.Popper = Popper; // Maak het globaal beschikbaar als Bootstrap het extern verwacht. window.Popper = Popper; // Maak het globaal beschikbaar als Bootstrap het extern verwacht.
// Bootstrap JavaScript // Bootstrap JavaScript
import 'bootstrap'; // Importeert alle BS JS componenten. import * as bootstrap from 'bootstrap'; // Importeer Bootstrap als object
window.bootstrap = bootstrap; // Maak bootstrap globaal beschikbaar
// Bootstrap's JS koppelt zichzelf meestal aan jQuery en gebruikt Popper. // Bootstrap's JS koppelt zichzelf meestal aan jQuery en gebruikt Popper.
// Als je 'bootstrap' als object nodig hebt (bijv. voor new bootstrap.Modal()), importeer het dan als: // Als je 'bootstrap' als object nodig hebt (bijv. voor new bootstrap.Modal()), importeer het dan als:
// import * as bootstrap from 'bootstrap'; // import * as bootstrap from 'bootstrap';
@@ -42,5 +44,7 @@ import { createJSONEditor } from 'vanilla-jsoneditor';
// Maak de factory functie globaal beschikbaar als je dit elders in je code gebruikt. // Maak de factory functie globaal beschikbaar als je dit elders in je code gebruikt.
window.createJSONEditor = createJSONEditor; window.createJSONEditor = createJSONEditor;
import './tabulator-setup.js';
// Eventueel een log om te bevestigen dat de bundel is geladen // Eventueel een log om te bevestigen dat de bundel is geladen
console.log('JavaScript bibliotheken gebundeld en geladen via main.js.'); console.log('JavaScript bibliotheken gebundeld en geladen via main.js.');

View File

@@ -0,0 +1,8 @@
// CSS importeren
import 'tabulator-tables/dist/css/tabulator.min.css';
// JavaScript imports
import { TabulatorFull as Tabulator } from 'tabulator-tables';
// Maak Tabulator globaal beschikbaar
window.Tabulator = Tabulator;

View File

@@ -10,6 +10,7 @@
"datatables.net": "^2.3.1", "datatables.net": "^2.3.1",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"select2": "^4.1.0-rc.0", "select2": "^4.1.0-rc.0",
"tabulator-tables": "^6.3.1",
"vanilla-jsoneditor": "^3.5.0" "vanilla-jsoneditor": "^3.5.0"
}, },
"devDependencies": { "devDependencies": {
@@ -3648,6 +3649,12 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/tabulator-tables": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/tabulator-tables/-/tabulator-tables-6.3.1.tgz",
"integrity": "sha512-qFW7kfadtcaISQIibKAIy0f3eeIXUVi8242Vly1iJfMD79kfEGzfczNuPBN/80hDxHzQJXYbmJ8VipI40hQtfA==",
"license": "MIT"
},
"node_modules/term-size": { "node_modules/term-size": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",

View File

@@ -5,6 +5,7 @@
"datatables.net": "^2.3.1", "datatables.net": "^2.3.1",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"select2": "^4.1.0-rc.0", "select2": "^4.1.0-rc.0",
"tabulator-tables": "^6.3.1",
"vanilla-jsoneditor": "^3.5.0" "vanilla-jsoneditor": "^3.5.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -8,7 +8,7 @@
--bs-danger: #9c2d66; --bs-danger: #9c2d66;
} }
/* Overriding the background gradient and text colors */ /* Overriding the background gradient and text colors ------------------------------------------ */
.bg-gradient-success { .bg-gradient-success {
background: linear-gradient(90deg, var(--bs-primary) 0%, var(--bs-secondary) 100%); background: linear-gradient(90deg, var(--bs-primary) 0%, var(--bs-secondary) 100%);
} }
@@ -44,7 +44,7 @@
font-weight: 700; /* Retain bold text */ font-weight: 700; /* Retain bold text */
} }
/* Navbar customization */ /* Navbar customization ------------------------------------------------------------------------ */
.navbar-light .navbar-brand { .navbar-light .navbar-brand {
color: var(--bs-primary) !important; /* Primary color for the brand text */ color: var(--bs-primary) !important; /* Primary color for the brand text */
} }
@@ -114,7 +114,7 @@
color: var(--bs-white) !important; color: var(--bs-white) !important;
} }
/* Page header customization */ /* Page header customization ------------------------------------------------------------------- */
.page-header { .page-header {
background-size: cover; background-size: cover;
background-position: center; background-position: center;
@@ -148,7 +148,7 @@
margin-top: -5rem; /* Adjust margin to improve vertical alignment */ margin-top: -5rem; /* Adjust margin to improve vertical alignment */
} }
/* Card and table customization */ /* Card and table customization ---------------------------------------------------------------- */
.card { .card {
border: 1px solid var(--bs-secondary) !important; /* Secondary color for the card border */ border: 1px solid var(--bs-secondary) !important; /* Secondary color for the card border */
border-radius: 0.5rem; /* Keeps the border-radius consistent */ border-radius: 0.5rem; /* Keeps the border-radius consistent */
@@ -258,7 +258,6 @@ input[type="radio"] {
.pagination { .pagination {
display: flex; display: flex;
justify-content: center; justify-content: center;
padding-left: 0;
list-style: none; list-style: none;
border-radius: 0.375rem; border-radius: 0.375rem;
margin-left: 0 !important; margin-left: 0 !important;
@@ -314,7 +313,7 @@ input[type="radio"] {
text-align: center !important; text-align: center !important;
} }
/* Form and Input Fields */ /* Form and Input Fields ----------------------------------------------------------------------- */
.form-group label.form-label { .form-group label.form-label {
color: var(--bs-secondary) !important; /* Secondary color for labels */ color: var(--bs-secondary) !important; /* Secondary color for labels */
font-weight: 500; /* Slightly bolder labels */ font-weight: 500; /* Slightly bolder labels */
@@ -353,7 +352,7 @@ input[type="radio"] {
color: var(--bs-body-color) !important; /* Consistent text color for check labels */ color: var(--bs-body-color) !important; /* Consistent text color for check labels */
} }
/* Tabs Navigation */ /* Tabs Navigation ----------------------------------------------------------------------------- */
.nav-pills .nav-link { .nav-pills .nav-link {
color: var(--bs-primary) !important; /* Primary color for inactive tab text */ color: var(--bs-primary) !important; /* Primary color for inactive tab text */
border-radius: 0.375rem !important; /* Rounded corners for tabs */ border-radius: 0.375rem !important; /* Rounded corners for tabs */
@@ -370,7 +369,7 @@ input[type="radio"] {
color: var(--bs-white) !important; /* White text on hover */ color: var(--bs-white) !important; /* White text on hover */
} }
/* Tabs Content */ /* Tabs Content -------------------------------------------------------------------------------- */
.tab-pane { .tab-pane {
padding-top: 1rem; /* Consistent padding inside tabs */ padding-top: 1rem; /* Consistent padding inside tabs */
} }
@@ -379,7 +378,7 @@ input[type="radio"] {
background-color: var(--bs-primary) !important; /* Primary color for the moving tab indicator */ background-color: var(--bs-primary) !important; /* Primary color for the moving tab indicator */
} }
/* Buttons */ /* Buttons ------------------------------------------------------------------------------------- */
.btn-primary:hover { .btn-primary:hover {
background-color: var(--bs-secondary) !important; background-color: var(--bs-secondary) !important;
@@ -399,8 +398,8 @@ input[type="radio"] {
} }
.btn-danger:hover { .btn-danger:hover {
background-color: darken(var(--bs-danger), 10%) !important; /* Darken the background on hover */ background-color: var(--bs-secondary) !important;
border-color: darken(var(--bs-danger), 10%) !important; /* Darken the border on hover */ border-color: var(--bs-secondary) !important;
color: var(--bs-white) !important; /* Ensure the text remains white and readable */ color: var(--bs-white) !important; /* Ensure the text remains white and readable */
} }
@@ -436,7 +435,7 @@ input[type="radio"] {
box-shadow: none; box-shadow: none;
} }
/* Custom styles for chat session view */ /* Custom styles for chat session view --------------------------------------------------------- */
.accordion-button:not(.collapsed) { .accordion-button:not(.collapsed) {
background-color: var(--bs-primary); background-color: var(--bs-primary);
color: var(--bs-white); color: var(--bs-white);
@@ -489,7 +488,7 @@ input[type="radio"] {
background-color: var(--bs-light); background-color: var(--bs-light);
} }
/* Markdown content styles */ /* Markdown content styles --------------------------------------------------------------------- */
.markdown-content { .markdown-content {
font-size: 1rem; font-size: 1rem;
line-height: 1.5; line-height: 1.5;
@@ -533,7 +532,7 @@ input[type="radio"] {
display: none !important; display: none !important;
} }
/* Ensure the original select is visible and styled */ /* Ensure the original select is visible and styled -------------------------------------------- */
select.select2 { select.select2 {
display: block !important; display: block !important;
width: 100% !important; width: 100% !important;
@@ -554,7 +553,7 @@ select.select2[multiple] {
height: auto !important; height: auto !important;
} }
/* REQUIRED FIELD SETTINGS ---------------------------------------------------- */ /* REQUIRED FIELD SETTINGS --------------------------------------------------------------------- */
/* Required field indicator styling */ /* Required field indicator styling */
.field-label-wrapper { .field-label-wrapper {
display: flex; display: flex;
@@ -614,7 +613,7 @@ select.select2[multiple] {
border: 0; border: 0;
} }
/* TAB ERROR STYLES ----------------------------------------------------------- */ /* TAB ERROR STYLES ---------------------------------------------------------------------------- */
/* Style for tabs with errors */ /* Style for tabs with errors */
.nav-link.has-error { .nav-link.has-error {
position: relative; position: relative;
@@ -704,7 +703,7 @@ select.select2[multiple] {
font-size: 1.1rem; font-size: 1.1rem;
} }
/* JSON Editor Styling - EveAI Aanpassingen */ /* JSON Editor Styling - EveAI Aanpassingen ---------------------------------------------------- */
:root { :root {
/* Hoofdkleuren gebaseerd op EveAI kleurenschema */ /* Hoofdkleuren gebaseerd op EveAI kleurenschema */
--jse-theme-color: var(--bs-primary); /* Paars als hoofdkleur */ --jse-theme-color: var(--bs-primary); /* Paars als hoofdkleur */
@@ -813,4 +812,385 @@ select.select2[multiple] {
line-height: 1.5; line-height: 1.5;
} }
/* Tabulator styling / ordered_list ------------------------------------------------------------ */
.ordered-list-editor {
margin-bottom: 1rem;
min-height: 200px; /* Minimum height, will expand as needed */
}
/* Make sure the Tabulator container has a proper height */
.ordered-list-editor .tabulator {
height: auto; /* Auto height to display all rows */
min-height: 200px; /* Minimum height */
width: 100%;
border: 1px solid var(--bs-primary); /* Primary color for border */
border-radius: 0.375rem; /* Match application's border-radius */
margin-bottom: 0.5rem;
box-shadow: 0 4px 8px rgba(118, 89, 154, 0.2); /* Match application's shadow style */
}
/* Ensure the table holder has a scrollbar */
.ordered-list-editor .tabulator-tableholder {
/* overflow-y: auto !important; - Removed to allow Tabulator to handle overflow */
/* max-height: calc(100% - 42px) !important; - Removed to allow Tabulator to handle height */
/* Consider using non-!important values if specific scrolling behavior is needed */
overflow-y: auto;
max-height: calc(100% - 42px);
}
/* Style for the table element */
.ordered-list-editor .tabulator-table {
display: table !important; /* Force display as table */
width: 100% !important;
table-layout: fixed !important; /* Use fixed table layout for consistent column widths */
}
/* Style for the handle column */
.ordered-list-editor .tabulator-row-handle {
cursor: move;
background-color: var(--bs-light, #f8f9fa);
border-right: 1px solid var(--bs-gray-300, #dee2e6);
transition: background-color 0.3s ease; /* Smooth transition for hover effect */
}
/* Hover effect for handle column */
.ordered-list-editor .tabulator-row:hover .tabulator-row-handle {
background-color: var(--bs-secondary); /* Secondary color on hover */
}
/* Style for the handle bars to make them more visible */
.ordered-list-editor .tabulator-row-handle-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.ordered-list-editor .tabulator-row-handle-bar {
background: var(--bs-primary); /* Primary color for handle bars */
display: inline-block;
width: 10px;
height: 2px;
margin: 1px 0;
transition: background-color 0.3s ease; /* Smooth transition for hover effect */
}
/* Change handle bar color on hover */
.ordered-list-editor .tabulator-row:hover .tabulator-row-handle-bar {
background: #ffffff; /* White handle bars on hover */
}
/* Style for the delete button */
.ordered-list-editor .tabulator-cell button.btn-danger {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
/* Style for boolean columns */
.ordered-list-editor .tabulator-cell[data-type="boolean"] {
text-align: center;
}
/* Style for boolean cell icons */
.ordered-list-editor .tabulator-cell .material-icons {
font-size: 1.2rem;
vertical-align: middle;
}
/* Style for true/checked icons */
.ordered-list-editor .tabulator-cell[aria-checked="true"] .material-icons {
color: var(--bs-primary); /* Primary color for checked state */
}
/* Style for false/unchecked icons */
.ordered-list-editor .tabulator-cell[aria-checked="false"] .material-icons {
color: var(--bs-danger); /* Danger color for unchecked state */
}
/* Style for the table header */
.ordered-list-editor .tabulator-header {
background: linear-gradient(90deg, var(--bs-primary) 0%, var(--bs-secondary) 100%); /* Match JSE gradient */
border-bottom: 2px solid var(--bs-secondary); /* Secondary color for border */
color: #ffffff; /* White text for better contrast on gradient */
}
/* Style for the headers container */
.ordered-list-editor .tabulator-headers {
display: table-row !important; /* Force display as table row */
}
/* Style for the header cells */
.ordered-list-editor .tabulator-col {
background: transparent; /* Let the header gradient show through */
padding: 8px;
font-weight: bold;
text-align: center;
display: table-cell !important; /* Force display as table cell */
box-sizing: border-box !important; /* Include padding in width calculation */
position: relative !important; /* Ensure proper positioning */
color: #ffffff; /* White text for better contrast on gradient */
}
/* Override any inline styles that might hide column headers */
.ordered-list-editor .tabulator-col[style*="display: none"] {
display: table-cell !important; /* Force display as table cell */
}
/* Ensure header cells have the same width as their corresponding data cells */
.ordered-list-editor .tabulator-col,
.ordered-list-editor .tabulator-cell
{
}
/* Style for the header cell content */
.ordered-list-editor .tabulator-col-title {
white-space: normal; /* Allow header text to wrap */
word-break: break-word; /* Break words to prevent horizontal overflow */
font-weight: bold;
color: #ffffff; /* White text for better contrast on gradient */
}
/* Style for the table rows */
.ordered-list-editor .tabulator-row {
border-bottom: 1px solid var(--bs-gray-300, #dee2e6); /* Match application's row border color */
display: table-row !important; /* Force display as table row */
}
/* Hover effect for rows */
.ordered-list-editor .tabulator-row:hover {
background-color: var(--bs-secondary) !important; /* Secondary color on hover */
color: #ffffff !important; /* White text on hover */
}
/* Ensure all text in hovered rows changes to white */
.ordered-list-editor .tabulator-row:hover .tabulator-cell,
.ordered-list-editor .tabulator-row:hover .tabulator-cell * {
color: #ffffff !important; /* White text for all elements in hovered rows */
}
/* Style for even rows */
.ordered-list-editor .tabulator-row-even {
background-color: #f8f9fa; /* Light gray for even rows */
}
/* Style for odd rows */
.ordered-list-editor .tabulator-row-odd {
background-color: #ffffff; /* White for odd rows */
}
/* Style for selected rows */
.ordered-list-editor .tabulator-row.tabulator-selected {
background-color: var(--bs-primary) !important; /* Primary color for selected rows */
color: #ffffff !important; /* White text for contrast */
}
/* Style for row being moved */
.ordered-list-editor .tabulator-row.tabulator-moving {
background-color: var(--bs-primary) !important; /* Primary color for moving rows */
color: #ffffff !important; /* White text for contrast */
border: 2px dashed var(--bs-secondary) !important; /* Dashed border to indicate movement */
opacity: 0.9 !important; /* Slightly transparent to distinguish from other rows */
box-shadow: 0 0 10px rgba(118, 89, 154, 0.5) !important; /* Shadow for depth */
z-index: 100 !important; /* Ensure it appears above other rows */
pointer-events: none !important; /* Allow events to pass through to elements below */
transform: scale(1.02) !important; /* Slightly larger to stand out */
transition: transform 0.2s ease !important; /* Smooth transition */
}
/* Style for cells in the row being moved */
.ordered-list-editor .tabulator-row.tabulator-moving .tabulator-cell {
color: #ffffff !important; /* Ensure text is white for contrast */
background-color: transparent !important; /* Use the row's background color */
border-color: transparent !important; /* Hide cell borders */
display: table-cell !important; /* Ensure cells are visible */
overflow: visible !important; /* Show all content */
}
/* Style for the moving element (the ghost row that follows the cursor) */
.tabulator-moving-element {
background-color: var(--bs-primary) !important; /* Primary color for moving element */
color: #ffffff !important; /* White text for contrast */
border: 2px dashed var(--bs-secondary) !important; /* Dashed border to indicate movement */
opacity: 0.9 !important; /* Slightly transparent */
box-shadow: 0 0 15px rgba(118, 89, 154, 0.7) !important; /* Stronger shadow for better visibility */
border-radius: 0.375rem !important; /* Rounded corners */
overflow: visible !important; /* Show all content */
width: auto !important; /* Allow width to adjust to content */
max-width: none !important; /* Don't limit width */
pointer-events: none !important; /* Allow events to pass through */
display: table !important; /* Ensure it's displayed as a table */
table-layout: fixed !important; /* Fixed table layout for consistent cell widths */
}
/* Style for cells in the moving element */
.tabulator-moving-element .tabulator-cell,
.tabulator-moving-element .tabulator-row .tabulator-cell {
color: #ffffff !important; /* White text for contrast */
background-color: transparent !important; /* Use the row's background color */
border-color: transparent !important; /* Hide cell borders */
display: table-cell !important; /* Ensure cells are visible */
overflow: visible !important; /* Show all content */
padding: 8px !important; /* Consistent padding */
white-space: normal !important; /* Allow text to wrap */
word-break: break-word !important; /* Break words to prevent overflow */
font-size: 0.85rem !important; /* Consistent font size */
vertical-align: middle !important; /* Center content vertically */
}
/* Style for the active moving element */
.tabulator-moving-element-active {
opacity: 1 !important; /* Fully opaque when active */
transform: scale(1.05) !important; /* Slightly larger when active */
box-shadow: 0 0 20px rgba(118, 89, 154, 0.8) !important; /* Stronger shadow when active */
z-index: 1000 !important; /* Higher z-index to ensure it's on top */
}
/* Style for the table cells */
.ordered-list-editor .tabulator-cell {
padding: 8px;
white-space: normal; /* Allow text to wrap */
overflow: visible; /* Show overflowing content */
height: auto !important; /* Allow cell to grow as needed */
word-break: break-word; /* Break words to prevent horizontal overflow */
display: table-cell !important; /* Force display as table cell */
scroll-margin-top: 100px; /* Prevent unwanted scrolling when focusing */
scroll-behavior: auto; /* Disable smooth scrolling which might cause jumping */
font-size: 0.85rem; /* Smaller font size */
}
/* Style for truncated cells */
.ordered-list-editor .truncated-cell {
position: relative;
padding-right: 20px;
}
.ordered-list-editor .truncated-content {
white-space: normal;
word-break: break-word;
}
.ordered-list-editor .show-more {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
color: var(--bs-primary); /* Use primary color for the show more indicator */
cursor: pointer;
transition: color 0.3s ease; /* Smooth transition for hover effect */
}
.ordered-list-editor .show-more:hover {
color: var(--bs-secondary); /* Use secondary color on hover */
}
/* Style for the visible cells */
.ordered-list-editor .tabulator-cell-visible {
display: table-cell !important; /* Force display as table cell */
}
/* Override any inline styles that might hide cells */
.ordered-list-editor .tabulator-cell[style*="display: none"] {
display: table-cell !important; /* Force display as table cell */
}
/* Style for the textarea editor */
.ordered-list-editor .tabulator-cell textarea {
min-height: 60px;
resize: vertical;
width: 100%; /* Ensure textarea fills the cell */
border: 1px solid var(--bs-gray-300, #dee2e6); /* Match application's input border */
border-radius: 0.375rem; /* Match application's border-radius */
padding: 0.625rem 0.75rem; /* Match application's input padding */
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for focus effect */
}
/* Focus state for textarea */
.ordered-list-editor .tabulator-cell textarea:focus {
border-color: var(--bs-primary); /* Primary color for focus state */
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(118, 89, 154, 0.25); /* Subtle glow with primary color */
}
/* Style for the placeholder */
.ordered-list-editor .tabulator-placeholder {
padding: 20px;
text-align: center;
color: var(--bs-secondary); /* Secondary color for placeholder text */
font-style: italic;
background-color: var(--bs-light, #f8f9fa); /* Light background for placeholder */
border-radius: 0.375rem; /* Match application's border-radius */
margin: 10px;
border: 1px dashed var(--bs-gray-300, #dee2e6); /* Dashed border for empty state */
}
/* Style for the Add Row button */
.ordered-list-editor + .btn-primary {
margin-top: 0.5rem;
background-color: var(--bs-primary) !important;
border-color: var(--bs-primary) !important;
color: #ffffff !important;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
/* Hover effect for primary button */
.ordered-list-editor + .btn-primary:hover {
background-color: var(--bs-secondary) !important;
border-color: var(--bs-secondary) !important;
}
/* Style for the Expand button */
.ordered-list-editor + .btn-primary + .btn-secondary {
margin-top: 0.5rem;
background-color: var(--bs-secondary) !important;
border-color: var(--bs-secondary) !important;
color: #ffffff !important;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
/* Hover effect for secondary button */
.ordered-list-editor + .btn-primary + .btn-secondary:hover {
background-color: var(--bs-primary) !important;
border-color: var(--bs-primary) !important;
}
/* Fullscreen mode styles */
.ordered-list-editor.fullscreen-mode {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
background: var(--bs-light, #f8f9fa); /* Use light background color */
padding: 20px;
margin: 0;
overflow: auto;
box-sizing: border-box;
border: 2px solid var(--bs-primary); /* Primary color border */
box-shadow: 0 0 20px rgba(118, 89, 154, 0.3); /* Larger shadow for modal effect */
}
.ordered-list-editor.fullscreen-mode .tabulator {
height: calc(100vh - 100px) !important;
width: 100% !important;
border: 1px solid var(--bs-primary); /* Consistent border */
box-shadow: 0 4px 8px rgba(118, 89, 154, 0.2); /* Consistent shadow */
}
/* Tekst in invoervelden zwart maken voor betere leesbaarheid */
.ordered-list-editor .tabulator-row:hover .tabulator-cell input,
.ordered-list-editor .tabulator-row:hover .tabulator-cell select,
.ordered-list-editor .tabulator-row:hover .tabulator-cell textarea,
.ordered-list-editor .tabulator-row:hover .tabulator-cell .tabulator-editor,
.ordered-list-editor .tabulator-row.tabulator-selected .tabulator-cell input,
.ordered-list-editor .tabulator-row.tabulator-selected .tabulator-cell select,
.ordered-list-editor .tabulator-row.tabulator-selected .tabulator-cell textarea,
.ordered-list-editor .tabulator-row.tabulator-selected .tabulator-cell .tabulator-editor {
color: #000000 !important; /* Zwarte tekst op witte achtergrond */
background-color: #ffffff !important; /* Witte achtergrond verzekeren */
border: 1px solid var(--bs-primary) !important; /* Duidelijke rand toevoegen */
}

2
nginx/static/dist/main.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,365 @@
# Changelog
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).
## [2.3.1-alfa]
### Added
- Introduction of ordered_list dynamic field type (using tabulator)
### Changed
- Bring configuration of PROCESSOR_TYPES & CATALOG_TYPES to new config standard
- Specialist Editor: move general information in tab
- Role Definition Specialist creates Selection Specialist from generated competencies
- Improvements to Selection Specialist (Agent definition to be started)
### Deprecated
- For soon-to-be removed features.
### Removed
- For now removed features.
### Fixed
- For any bug fixes.
### Security
- In case of vulnerabilities.
## [2.3.0-alfa]
### Added
- Introduction of Push Gateway for Prometheus
- Introduction of Partner Models
- Introduction of Tenant and Partner codes for more security
- Introduction of 'Management Partner' type and additional 'Partner Admin'-role
- Introduction of a technical services layer
- Introduction of partner-specific configurations
- Introduction of additional test environment
- Introduction of strict no-overage usage
- Introduction of LicensePeriod, Payments & Invoices
- Introduction of Processed File Viewer
- Introduction of Traicie Role Definition Specialist
- Allow invocation of non-interactive specialists in administrative interface (eveai_app)
- Introduction of advanced JSON editor
- Introduction of ChatSession (Specialist Execution) follow-up in administrative interface
- Introduce npm for javascript libraries usage and optimisations
- Introduction of new top bar in administrative interface to show session defaults (removing old navbar buttons)
-
### Changed
- Add 'Register'-button to list views, replacing register menu-items
- Add additional environment capabilities in docker
- PDF Processor now uses Mistral OCR
- Allow additional chunking mechanisms for very long chunks (in case of very large documents)
- Allow for TrackedMistralAIEmbedding batching to allow for processing long documents
- RAG & SPIN Specialist improvements
- Move mail messaging from standard SMTP to Scaleway TEM mails
- Improve mail layouts
- Add functionality to add a default dictionary for dynamic forms
- AI model choices defined by Ask Eve AI iso Tenant (replaces ModelVariables completely)
- Improve HTML Processing
- Pagination improvements
- Update Material Kit Pro to latest version
### Removed
- Repopack implementation ==> Using PyCharm's new AI capabilities instead
### Fixed
- Synchronous vs Asynchronous behaviour in crewAI type specialists
- Nasty dynamic boolean fields bug corrected
- Several smaller bugfixes
- Tasks & Tools editors finished
### Security
- In case of vulnerabilities.
## [2.2.0-alfa]
### Added
- Mistral AI as main provider for embeddings, chains and specialists
- Usage measuring for specialists
- RAG from chain to specialist technology
- Dossier catalog management possibilities added to eveai_app
- Asset definition (Paused - other priorities)
- Prometheus and Grafana
- Add prometheus monitoring to business events
- Asynchronous execution of specialists
### Changed
- Moved choice for AI providers / models to specialists and prompts
- Improve RAG to not repeat historic answers
- Fixed embedding model, no more choices allowed
- clean url (of tracking parameters) before adding it to a catalog
### Deprecated
- For soon-to-be removed features.
### Removed
- Add Multiple URLs removed from menu
- Old Specialist items removed from interaction menu
-
### Fixed
- Set default language when registering Documents or URLs.
### Security
- In case of vulnerabilities.
## [2.1.0-alfa]
### Added
- Zapier Refresh Document
- SPIN Specialist definition - from start to finish
- Introduction of startup scripts in eveai_app
- Caching for all configurations added
- Caching for processed specialist configurations
- Caching for specialist history
- Augmented Specialist Editor, including Specialist graphic presentation
- Introduction of specialist_execution_api, introducting SSE
- Introduction of crewai framework for specialist implementation
- Test app for testing specialists - also serves as a sample client application for SSE
-
### Changed
- Improvement of startup of applications using gevent, and better handling and scaling of multiple connections
- STANDARD_RAG Specialist improvement
-
### Deprecated
- eveai_chat - using sockets - will be replaced with new specialist_execution_api and SSE
## [2.0.1-alfa]
### Added
- Zapîer Integration (partial - only adding files).
- Addition of general chunking parameters (chunking_heading_level and chunking_patterns)
- Addition of DocX and markdown Processor Types
### Changed
- For changes in existing functionality.
### Deprecated
- For soon-to-be removed features.
### Removed
- For now removed features.
### Fixed
- Ensure the RAG Specialist is using the detailed_question
- Wordpress Chat Plugin: languages dropdown filled again
- OpenAI update - proxies no longer supported
- Build & Release script for Wordpress Plugins (including end user download folder)
### Security
- In case of vulnerabilities.
## [2.0.0-alfa]
### Added
- Introduction of dynamic Retrievers & Specialists
- Introduction of dynamic Processors
- Introduction of caching system
- Introduction of a better template manager
- Modernisation of external API/Socket authentication using projects
- Creation of new eveai_chat WordPress plugin to support specialists
### Changed
- Update of eveai_sync WordPress plugin
### Fixed
- Set default language when registering Documents or URLs.
### Security
- Security improvements to Docker images
## [1.0.14-alfa]
### Added
- New release script added to tag images with release number
- Allow the addition of multiple types of Catalogs
- Generic functionality to enable dynamic fields
- Addition of Retrievers to allow for smart collection of information in Catalogs
- Add dynamic fields to Catalog / Retriever / DocumentVersion
### Changed
- Processing parameters defined at Catalog level iso Tenant level
- Reroute 'blank' paths to 'admin'
### Deprecated
- For soon-to-be removed features.
### Removed
- For now removed features.
### Fixed
- Set default language when registering Documents or URLs.
### Security
- In case of vulnerabilities.
## [1.0.13-alfa]
### Added
- Finished Catalog introduction
- Reinitialization of WordPress site for syncing
### Changed
- Modification of WordPress Sync Component
- Cleanup of attributes in Tenant
### Fixed
- Overall bugfixes as result from the Catalog introduction
## [1.0.12-alfa]
### Added
- Added Catalog functionality
### Changed
- For changes in existing functionality.
### Deprecated
- For soon-to-be removed features.
### Removed
- For now removed features.
### Fixed
- Set default language when registering Documents or URLs.
### Security
- In case of vulnerabilities.
## [1.0.11-alfa]
### Added
- License Usage Calculation realised
- View License Usages
- Celery Beat container added
- First schedule in Celery Beat for calculating usage (hourly)
### Changed
- repopack can now split for different components
### Fixed
- Various fixes as consequence of changing file_location / file_name ==> bucket_name / object_name
- Celery Routing / Queuing updated
## [1.0.10-alfa]
### Added
- BusinessEventLog monitoring using Langchain native code
### Changed
- Allow longer audio files (or video) to be uploaded and processed
- Storage and Embedding usage now expressed in MiB iso tokens (more logical)
- Views for License / LicenseTier
### Removed
- Portkey removed for monitoring usage
## [1.0.9-alfa] - 2024/10/01
### Added
- Business Event tracing (eveai_workers & eveai_chat_workers)
- Flower Container added for monitoring
### Changed
- Healthcheck improvements
- model_utils turned into a class with lazy loading
### Deprecated
- For soon-to-be removed features.
### Removed
- For now removed features.
### Fixed
- Set default language when registering Documents or URLs.
## [1.0.8-alfa] - 2024-09-12
### Added
- Tenant type defined to allow for active, inactive, demo ... tenants
- Search and filtering functionality on Tenants
- Implementation of health checks (1st version)
- Provision for Prometheus monitoring (no implementation yet)
- Refine audio_processor and srt_processor to reduce duplicate code and support larger files
- Introduction of repopack to reason in LLMs about the code
### Fixed
- Refine audio_processor and srt_processor to reduce duplicate code and support larger files
## [1.0.7-alfa] - 2024-09-12
### Added
- Full Document API allowing for creation, updating and invalidation of documents.
- Metadata fields (JSON) added to DocumentVersion, allowing end-users to add structured information
- Wordpress plugin eveai_sync to synchronize Wordpress content with EveAI
### Fixed
- Maximal deduplication of code between views and api in document_utils.py
## [1.0.6-alfa] - 2024-09-03
### Fixed
- Problems with tenant scheme migrations - may have to be revisited
- Correction of default language settings when uploading docs or URLs
- Addition of a CHANGELOG.md file
## [1.0.5-alfa] - 2024-09-02
### Added
- Allow chatwidget to connect to multiple servers (e.g. development and production)
- Start implementation of API
- Add API-key functionality to tenants
- Deduplication of API and Document view code
- Allow URL addition to accept all types of files, not just HTML
- Allow new file types upload: srt, mp3, ogg, mp4
- Improve processing of different file types using Processor classes
### Removed
- Removed direct upload of Youtube URLs, due to continuous changes in Youtube website
## [1.0.4-alfa] - 2024-08-27
Skipped
## [1.0.3-alfa] - 2024-08-27
### Added
- Refinement of HTML processing - allow for excluded classes and elements.
- Allow for multiple instances of Evie on 1 website (pure + Wordpress plugin)
### Changed
- PDF Processing extracted in new PDF Processor class.
- Allow for longer and more complex PDFs to be uploaded.
## [1.0.2-alfa] - 2024-08-22
### Fixed
- Bugfix for ResetPasswordForm in config.py
## [1.0.1-alfa] - 2024-08-21
### Added
- Full Document Version Overview
### Changed
- Improvements to user creation and registration, renewal of passwords, ...
## [1.0.0-alfa] - 2024-08-16
### Added
- Initial release of the project.
### Changed
- None
### Fixed
- None
[Unreleased]: https://github.com/username/repo/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/username/repo/releases/tag/v1.0.0