Compare commits
9 Commits
v2.3.0-alf
...
v2.3.1-alf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67078ce925 | ||
|
|
ebdb836448 | ||
|
|
81e754317a | ||
|
|
578981c745 | ||
|
|
8fb2ad43c5 | ||
|
|
49f9077a7b | ||
|
|
d290b46a0c | ||
|
|
73647e4795 | ||
|
|
25e169dbea |
@@ -1,7 +1,15 @@
|
||||
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.model_logging_utils import set_logging_information, update_logging_information
|
||||
|
||||
|
||||
class SpecialistServices:
|
||||
@@ -27,4 +35,188 @@ class SpecialistServices:
|
||||
'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
|
||||
|
||||
16
common/utils/cache/config_cache.py
vendored
16
common/utils/cache/config_cache.py
vendored
@@ -7,7 +7,7 @@ from flask import current_app
|
||||
|
||||
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, \
|
||||
catalog_types, partner_service_types
|
||||
catalog_types, partner_service_types, processor_types
|
||||
|
||||
|
||||
def is_major_minor(version: str) -> bool:
|
||||
@@ -59,7 +59,7 @@ class BaseConfigCacheHandler(CacheHandler[Dict[str, Any]]):
|
||||
"""Set the version tree cache dependency."""
|
||||
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
|
||||
Automatically handles global vs partner-specific configs
|
||||
@@ -456,6 +456,13 @@ CatalogConfigCacheHandler, CatalogConfigVersionTreeCacheHandler, CatalogConfigTy
|
||||
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
|
||||
PartnerServiceConfigCacheHandler, PartnerServiceConfigVersionTreeCacheHandler, PartnerServiceConfigTypesCacheHandler = (
|
||||
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(CatalogConfigTypesCacheHandler, '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(AgentConfigTypesCacheHandler, '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.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.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)
|
||||
|
||||
21
config/catalogs/globals/DOSSIER_CATALOG/1.0.0.yaml
Normal file
21
config/catalogs/globals/DOSSIER_CATALOG/1.0.0.yaml
Normal 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"
|
||||
9
config/catalogs/globals/STANDARD_CATALOG/1.0.0.yaml
Normal file
9
config/catalogs/globals/STANDARD_CATALOG/1.0.0.yaml
Normal 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"
|
||||
9
config/processors/globals/AUDIO_PROCESSOR/1.0.0.yaml
Normal file
9
config/processors/globals/AUDIO_PROCESSOR/1.0.0.yaml
Normal 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"
|
||||
59
config/processors/globals/DOCX_PROCESSOR/1.0.0.yaml
Normal file
59
config/processors/globals/DOCX_PROCESSOR/1.0.0.yaml
Normal 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"
|
||||
49
config/processors/globals/HTML_PROCESSOR/1.0.0.yaml
Normal file
49
config/processors/globals/HTML_PROCESSOR/1.0.0.yaml
Normal 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"
|
||||
20
config/processors/globals/MARKDOWN_PROCESSOR/1.0.0.yaml
Normal file
20
config/processors/globals/MARKDOWN_PROCESSOR/1.0.0.yaml
Normal 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"
|
||||
20
config/processors/globals/PDF_PROCESSOR/1.0.0.yaml
Normal file
20
config/processors/globals/PDF_PROCESSOR/1.0.0.yaml
Normal 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"
|
||||
@@ -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"
|
||||
@@ -5,12 +5,17 @@ partner: "traicie"
|
||||
chat: false
|
||||
configuration:
|
||||
name:
|
||||
name: "name"
|
||||
name: "Name"
|
||||
description: "The name the specialist is called upon."
|
||||
type: "str"
|
||||
required: true
|
||||
role_reference:
|
||||
name: "Role Reference"
|
||||
description: "A customer reference to the role"
|
||||
type: "str"
|
||||
required: false
|
||||
competencies:
|
||||
name: "competencies"
|
||||
name: "Competencies"
|
||||
description: "An ordered list of competencies."
|
||||
type: "ordered_list"
|
||||
list_type: "competency_details"
|
||||
@@ -41,17 +46,17 @@ configuration:
|
||||
required: false
|
||||
competency_details:
|
||||
title:
|
||||
name: "title"
|
||||
name: "Title"
|
||||
description: "Competency Title"
|
||||
type: "str"
|
||||
required: true
|
||||
description:
|
||||
name: "description"
|
||||
name: "Description"
|
||||
description: "Description (in context of the role) of the competency"
|
||||
type: "text"
|
||||
required: true
|
||||
is_knockout:
|
||||
name: "Is Knockout"
|
||||
name: "KO"
|
||||
description: "Defines if the competency is a knock-out criterium"
|
||||
type: "boolean"
|
||||
required: true
|
||||
|
||||
@@ -2,28 +2,10 @@
|
||||
CATALOG_TYPES = {
|
||||
"STANDARD_CATALOG": {
|
||||
"name": "Standard Catalog",
|
||||
"Description": "A Catalog with information in Evie's Library, to be considered as a whole",
|
||||
"configuration": {},
|
||||
"document_version_configurations": []
|
||||
"description": "A Catalog with information in Evie's Library, to be considered as a whole",
|
||||
},
|
||||
"DOSSIER_CATALOG": {
|
||||
"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"]
|
||||
"description": "A Catalog with information in Evie's Library in which several Dossiers can be stored",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,168 +1,28 @@
|
||||
# Catalog Types
|
||||
# Processor Types
|
||||
PROCESSOR_TYPES = {
|
||||
"HTML_PROCESSOR": {
|
||||
"name": "HTML Processor",
|
||||
"description": "A processor for HTML files",
|
||||
"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": {
|
||||
"name": "PDF Processor",
|
||||
"description": "A Processor for PDF files",
|
||||
"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": {
|
||||
"name": "AUDIO Processor",
|
||||
"description": "A Processor for audio files",
|
||||
"file_types": "mp3, mp4, ogg",
|
||||
"Description": "A Processor for audio files",
|
||||
"configuration": {}
|
||||
},
|
||||
"MARKDOWN_PROCESSOR": {
|
||||
"name": "Markdown Processor",
|
||||
"description": "A Processor for markdown files",
|
||||
"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": {
|
||||
"name": "DOCX Processor",
|
||||
"description": "A processor for DOCX files",
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,5 +16,9 @@ SPECIALIST_TYPES = {
|
||||
"name": "Traicie Role Definition Specialist",
|
||||
"description": "Assistant Defining Competencies and KO Criteria",
|
||||
"partner": "traicie"
|
||||
},
|
||||
"TRAICIE_SELECTION_SPECIALIST": {
|
||||
"name": "Traicie Selection Specialist",
|
||||
"description": "Recruitment Selection Assistant",
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@
|
||||
<hr>
|
||||
{% include 'footer.html' %}
|
||||
{% include 'scripts.html' %}
|
||||
{% include 'ordered_list_configs.html' %}
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
364
eveai_app/templates/eveai_ordered_list_editor.html
Normal file
364
eveai_app/templates/eveai_ordered_list_editor.html
Normal 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>
|
||||
|
||||
@@ -15,29 +15,17 @@
|
||||
{{ form.hidden_tag() }}
|
||||
{% set disabled_fields = ['type', 'type_version'] %}
|
||||
{% 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 -->
|
||||
<div class="row mt-5">
|
||||
<div class="col-lg-12">
|
||||
<div class="nav-wrapper position-relative end-0">
|
||||
<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">
|
||||
<a class="nav-link mb-0 px-0 py-1 active" data-bs-toggle="tab" href="#configuration-tab" role="tab">
|
||||
Configuration
|
||||
@@ -67,6 +55,27 @@
|
||||
</div>
|
||||
|
||||
<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 -->
|
||||
<div class="tab-pane fade show active" id="configuration-tab" role="tabpanel">
|
||||
{% for collection_name, fields in form.get_dynamic_fields().items() %}
|
||||
@@ -420,6 +429,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
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 */
|
||||
.nav-link.disabled {
|
||||
opacity: 0.5;
|
||||
@@ -476,4 +493,3 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -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='') %}
|
||||
{% if field.type == 'BooleanField' %}
|
||||
<div class="form-group">
|
||||
@@ -55,9 +64,14 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% set field_class = get_field_class(field) %}
|
||||
{% if field.type == 'TextAreaField' and 'json-editor' in class %}
|
||||
<div id="{{ field.id }}-editor" class="json-editor-container"></div>
|
||||
{{ 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' %}
|
||||
{{ field(class="form-control form-select " + class, disabled=disabled, readonly=readonly) }}
|
||||
{% else %}
|
||||
@@ -76,6 +90,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
||||
{% macro render_field(field, disabled_fields=[], readonly_fields=[], exclude_fields=[], class='') %}
|
||||
<!-- Debug info -->
|
||||
<!-- Field name: {{ field.name }}, Field type: {{ field.__class__.__name__ }} -->
|
||||
@@ -435,4 +450,3 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
{% if current_user.is_authenticated %}
|
||||
{{ dropdown(current_user.user_name, 'person', [
|
||||
{'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'}
|
||||
]) }}
|
||||
{% else %}
|
||||
@@ -133,4 +134,4 @@
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
7
eveai_app/templates/ordered_list_configs.html
Normal file
7
eveai_app/templates/ordered_list_configs.html
Normal 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 %}
|
||||
@@ -9,14 +9,19 @@
|
||||
<script src="{{url_for('static', filename='assets/js/material-kit-pro.min.js')}}"></script>
|
||||
|
||||
{% include 'eveai_json_editor.html' %}
|
||||
{% include 'eveai_ordered_list_editor.html' %}
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize tooltips
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
});
|
||||
// Initialize tooltips if bootstrap is available
|
||||
if (typeof bootstrap !== 'undefined') {
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (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.
|
||||
// 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
|
||||
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);
|
||||
tab.show();
|
||||
} else if (tabButton) {
|
||||
// Fallback if bootstrap is not available
|
||||
tabButton.click();
|
||||
}
|
||||
|
||||
// Scroll the invalid field into view and focus it
|
||||
|
||||
@@ -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_wtf.csrf import generate_csrf
|
||||
import os
|
||||
import requests
|
||||
|
||||
from common.models.document import Catalog
|
||||
from common.models.user import Tenant
|
||||
@@ -101,3 +103,29 @@ def check_csrf():
|
||||
'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'))
|
||||
|
||||
@@ -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.eveai_exceptions import EveAIInvalidLanguageException, EveAIUnsupportedFileType, \
|
||||
EveAIDoubleURLException, EveAIException
|
||||
from config.type_defs.processor_types import PROCESSOR_TYPES
|
||||
from .document_forms import AddDocumentForm, AddURLForm, EditDocumentForm, EditDocumentVersionForm, \
|
||||
CatalogForm, EditCatalogForm, RetrieverForm, EditRetrieverForm, ProcessorForm, EditProcessorForm
|
||||
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 .document_list_view import DocumentListView
|
||||
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')
|
||||
|
||||
@@ -126,8 +124,8 @@ def edit_catalog(catalog_id):
|
||||
tenant_id = session.get('tenant').get('id')
|
||||
|
||||
form = EditCatalogForm(request.form, obj=catalog)
|
||||
configuration_config = CATALOG_TYPES[catalog.type]["configuration"]
|
||||
form.add_dynamic_fields("configuration", configuration_config, catalog.configuration)
|
||||
full_config = cache_manager.catalogs_config_cache.get_config(catalog.type)
|
||||
form.add_dynamic_fields("configuration", full_config, catalog.configuration)
|
||||
|
||||
if request.method == 'POST' and form.validate_on_submit():
|
||||
form.populate_obj(catalog)
|
||||
@@ -160,8 +158,9 @@ def processor():
|
||||
new_processor = Processor()
|
||||
form.populate_obj(new_processor)
|
||||
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(
|
||||
PROCESSOR_TYPES[new_processor.type]["configuration"])
|
||||
processor_config["configuration"])
|
||||
|
||||
set_logging_information(new_processor, dt.now(tz.utc))
|
||||
|
||||
@@ -197,8 +196,8 @@ def edit_processor(processor_id):
|
||||
# Create form instance with the processor
|
||||
form = EditProcessorForm(request.form, obj=processor)
|
||||
|
||||
configuration_config = PROCESSOR_TYPES[processor.type]["configuration"]
|
||||
form.add_dynamic_fields("configuration", configuration_config, processor.configuration)
|
||||
full_config = cache_manager.processors_config_cache.get_config(processor.type)
|
||||
form.add_dynamic_fields("configuration", full_config, processor.configuration)
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Update basic fields
|
||||
@@ -390,9 +389,10 @@ def add_document():
|
||||
|
||||
catalog = Catalog.query.get_or_404(catalog_id)
|
||||
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:
|
||||
form.add_dynamic_fields(config, catalog.configuration[config])
|
||||
form.add_dynamic_fields(config, full_config, catalog.configuration[config])
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
@@ -403,7 +403,8 @@ def add_document():
|
||||
filename = secure_filename(file.filename)
|
||||
extension = filename.rsplit('.', 1)[1].lower()
|
||||
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:
|
||||
catalog_properties[config] = form.get_dynamic_data(config)
|
||||
|
||||
@@ -445,9 +446,10 @@ def add_url():
|
||||
|
||||
catalog = Catalog.query.get_or_404(catalog_id)
|
||||
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:
|
||||
form.add_dynamic_fields(config, catalog.configuration[config])
|
||||
form.add_dynamic_fields(config, full_config, catalog.configuration[config])
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
@@ -459,7 +461,8 @@ def add_url():
|
||||
file_content, filename, extension = process_url(url, tenant_id)
|
||||
|
||||
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:
|
||||
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)
|
||||
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:
|
||||
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():
|
||||
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:
|
||||
catalog_properties[config] = form.get_dynamic_data(config)
|
||||
|
||||
@@ -897,4 +901,3 @@ def clean_markdown(markdown):
|
||||
if markdown.endswith("```"):
|
||||
markdown = markdown[:-3].strip()
|
||||
return markdown
|
||||
|
||||
|
||||
@@ -49,6 +49,51 @@ class ChunkingPatternsField(TextAreaField):
|
||||
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):
|
||||
def __init__(self, formdata=None, *args, **kwargs):
|
||||
super(DynamicFormBase, self).__init__(*args, **kwargs)
|
||||
@@ -89,6 +134,8 @@ class DynamicFormBase(FlaskForm):
|
||||
validators_list.append(self._validate_tagging_fields_filter)
|
||||
elif field_type == 'dynamic_arguments':
|
||||
validators_list.append(self._validate_dynamic_arguments)
|
||||
elif field_type == 'ordered_list':
|
||||
validators_list.append(self._validate_ordered_list)
|
||||
|
||||
return validators_list
|
||||
|
||||
@@ -227,10 +274,52 @@ class DynamicFormBase(FlaskForm):
|
||||
except Exception as 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):
|
||||
"""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] = []
|
||||
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
|
||||
full_field_name = f"{collection_name}_{field_name}"
|
||||
label = field_def.get('name', field_name)
|
||||
@@ -264,6 +353,12 @@ class DynamicFormBase(FlaskForm):
|
||||
field_class = ChunkingPatternsField
|
||||
extra_classes = ['monospace-text', 'pattern-input']
|
||||
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:
|
||||
extra_classes = ''
|
||||
field_class = {
|
||||
@@ -289,6 +384,12 @@ class DynamicFormBase(FlaskForm):
|
||||
except (TypeError, ValueError) as e:
|
||||
current_app.logger.error(f"Error converting initial data to JSON: {e}")
|
||||
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':
|
||||
try:
|
||||
field_data = json_to_patterns(field_data)
|
||||
@@ -305,6 +406,9 @@ class DynamicFormBase(FlaskForm):
|
||||
render_kw['data-bs-toggle'] = 'tooltip'
|
||||
render_kw['data-bs-placement'] = 'right'
|
||||
|
||||
|
||||
current_app.logger.debug(f"render_kw for {full_field_name}: {render_kw}")
|
||||
|
||||
# Create the field
|
||||
field_kwargs.update({
|
||||
'label': label,
|
||||
@@ -340,6 +444,73 @@ class DynamicFormBase(FlaskForm):
|
||||
# Return all fields that are not dynamic
|
||||
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):
|
||||
"""Return a dictionary of dynamic fields per collection."""
|
||||
result = {}
|
||||
@@ -361,7 +532,7 @@ class DynamicFormBase(FlaskForm):
|
||||
if field.type == 'BooleanField':
|
||||
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]}")
|
||||
elif isinstance(field, (TaggingFieldsField, TaggingFieldsFilterField, DynamicArgumentsField)) and field.data:
|
||||
elif isinstance(field, (TaggingFieldsField, TaggingFieldsFilterField, DynamicArgumentsField, OrderedListField)) and field.data:
|
||||
try:
|
||||
data[original_field_name] = json.loads(field.data)
|
||||
except json.JSONDecodeError:
|
||||
@@ -443,4 +614,4 @@ def validate_tagging_fields(form, field):
|
||||
except json.JSONDecodeError:
|
||||
raise ValidationError("Invalid JSON format")
|
||||
except (TypeError, ValueError) as e:
|
||||
raise ValidationError(f"Invalid field definition: {str(e)}")
|
||||
raise ValidationError(f"Invalid field definition: {str(e)}")
|
||||
|
||||
@@ -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.nginx_utils import prefixed_url_for
|
||||
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,
|
||||
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}!')
|
||||
|
||||
# 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))
|
||||
|
||||
@@ -204,8 +201,7 @@ def edit_specialist(specialist_id):
|
||||
form = EditSpecialistForm(request.form, obj=specialist)
|
||||
|
||||
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", configuration_config, specialist.configuration)
|
||||
form.add_dynamic_fields("configuration", specialist_config, specialist.configuration)
|
||||
|
||||
agent_rows = prepare_table_for_macro(specialist.agents,
|
||||
[('id', ''), ('name', ''), ('type', ''), ('type_version', '')])
|
||||
@@ -521,8 +517,7 @@ def edit_asset_version(asset_version_id):
|
||||
form = EditEveAIAssetVersionForm(asset_version)
|
||||
asset_config = cache_manager.assets_config_cache.get_config(asset_version.asset.type,
|
||||
asset_version.asset.type_version)
|
||||
configuration_config = asset_config.get('configuration')
|
||||
form.add_dynamic_fields("configuration", configuration_config, asset_version.configuration)
|
||||
form.add_dynamic_fields("configuration", asset_config, asset_version.configuration)
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Update the configuration dynamic fields
|
||||
@@ -582,9 +577,8 @@ def execute_specialist(specialist_id):
|
||||
return redirect(prefixed_url_for('interaction_bp.specialists'))
|
||||
|
||||
form = ExecuteSpecialistForm(request.form, obj=specialist)
|
||||
arguments_config = specialist_config.get('arguments', None)
|
||||
if arguments_config:
|
||||
form.add_dynamic_fields('arguments', arguments_config)
|
||||
if 'arguments' in specialist_config:
|
||||
form.add_dynamic_fields('arguments', specialist_config)
|
||||
|
||||
if form.validate_on_submit():
|
||||
# We're only interested in gathering the dynamic arguments
|
||||
@@ -674,4 +668,4 @@ def session_interactions(chat_session_id):
|
||||
This route shows all interactions for a given chat_session_id (int).
|
||||
"""
|
||||
chat_session = ChatSession.query.get_or_404(chat_session_id)
|
||||
return session_interactions_by_session_id(chat_session.session_id)
|
||||
return session_interactions_by_session_id(chat_session.session_id)
|
||||
|
||||
@@ -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
|
||||
@@ -16,7 +16,9 @@ import * as Popper from '@popperjs/core';
|
||||
window.Popper = Popper; // Maak het globaal beschikbaar als Bootstrap het extern verwacht.
|
||||
|
||||
// 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.
|
||||
// Als je 'bootstrap' als object nodig hebt (bijv. voor new bootstrap.Modal()), importeer het dan als:
|
||||
// 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.
|
||||
window.createJSONEditor = createJSONEditor;
|
||||
|
||||
import './tabulator-setup.js';
|
||||
|
||||
// Eventueel een log om te bevestigen dat de bundel is geladen
|
||||
console.log('JavaScript bibliotheken gebundeld en geladen via main.js.');
|
||||
|
||||
8
nginx/frontend_src/js/tabulator-setup.js
Normal file
8
nginx/frontend_src/js/tabulator-setup.js
Normal 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;
|
||||
7
nginx/package-lock.json
generated
7
nginx/package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"datatables.net": "^2.3.1",
|
||||
"jquery": "^3.7.1",
|
||||
"select2": "^4.1.0-rc.0",
|
||||
"tabulator-tables": "^6.3.1",
|
||||
"vanilla-jsoneditor": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -3648,6 +3649,12 @@
|
||||
"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": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"datatables.net": "^2.3.1",
|
||||
"jquery": "^3.7.1",
|
||||
"select2": "^4.1.0-rc.0",
|
||||
"tabulator-tables": "^6.3.1",
|
||||
"vanilla-jsoneditor": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
--bs-danger: #9c2d66;
|
||||
}
|
||||
|
||||
/* Overriding the background gradient and text colors */
|
||||
/* Overriding the background gradient and text colors ------------------------------------------ */
|
||||
.bg-gradient-success {
|
||||
background: linear-gradient(90deg, var(--bs-primary) 0%, var(--bs-secondary) 100%);
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
font-weight: 700; /* Retain bold text */
|
||||
}
|
||||
|
||||
/* Navbar customization */
|
||||
/* Navbar customization ------------------------------------------------------------------------ */
|
||||
.navbar-light .navbar-brand {
|
||||
color: var(--bs-primary) !important; /* Primary color for the brand text */
|
||||
}
|
||||
@@ -114,7 +114,7 @@
|
||||
color: var(--bs-white) !important;
|
||||
}
|
||||
|
||||
/* Page header customization */
|
||||
/* Page header customization ------------------------------------------------------------------- */
|
||||
.page-header {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
@@ -148,7 +148,7 @@
|
||||
margin-top: -5rem; /* Adjust margin to improve vertical alignment */
|
||||
}
|
||||
|
||||
/* Card and table customization */
|
||||
/* Card and table customization ---------------------------------------------------------------- */
|
||||
.card {
|
||||
border: 1px solid var(--bs-secondary) !important; /* Secondary color for the card border */
|
||||
border-radius: 0.5rem; /* Keeps the border-radius consistent */
|
||||
@@ -258,7 +258,6 @@ input[type="radio"] {
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
border-radius: 0.375rem;
|
||||
margin-left: 0 !important;
|
||||
@@ -314,7 +313,7 @@ input[type="radio"] {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
/* Form and Input Fields */
|
||||
/* Form and Input Fields ----------------------------------------------------------------------- */
|
||||
.form-group label.form-label {
|
||||
color: var(--bs-secondary) !important; /* Secondary color for 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 */
|
||||
}
|
||||
|
||||
/* Tabs Navigation */
|
||||
/* Tabs Navigation ----------------------------------------------------------------------------- */
|
||||
.nav-pills .nav-link {
|
||||
color: var(--bs-primary) !important; /* Primary color for inactive tab text */
|
||||
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 */
|
||||
}
|
||||
|
||||
/* Tabs Content */
|
||||
/* Tabs Content -------------------------------------------------------------------------------- */
|
||||
.tab-pane {
|
||||
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 */
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
/* Buttons ------------------------------------------------------------------------------------- */
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--bs-secondary) !important;
|
||||
@@ -399,8 +398,8 @@ input[type="radio"] {
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: darken(var(--bs-danger), 10%) !important; /* Darken the background on hover */
|
||||
border-color: darken(var(--bs-danger), 10%) !important; /* Darken the border on hover */
|
||||
background-color: var(--bs-secondary) !important;
|
||||
border-color: var(--bs-secondary) !important;
|
||||
color: var(--bs-white) !important; /* Ensure the text remains white and readable */
|
||||
}
|
||||
|
||||
@@ -436,7 +435,7 @@ input[type="radio"] {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Custom styles for chat session view */
|
||||
/* Custom styles for chat session view --------------------------------------------------------- */
|
||||
.accordion-button:not(.collapsed) {
|
||||
background-color: var(--bs-primary);
|
||||
color: var(--bs-white);
|
||||
@@ -489,7 +488,7 @@ input[type="radio"] {
|
||||
background-color: var(--bs-light);
|
||||
}
|
||||
|
||||
/* Markdown content styles */
|
||||
/* Markdown content styles --------------------------------------------------------------------- */
|
||||
.markdown-content {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
@@ -533,7 +532,7 @@ input[type="radio"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Ensure the original select is visible and styled */
|
||||
/* Ensure the original select is visible and styled -------------------------------------------- */
|
||||
select.select2 {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
@@ -554,7 +553,7 @@ select.select2[multiple] {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
/* REQUIRED FIELD SETTINGS ---------------------------------------------------- */
|
||||
/* REQUIRED FIELD SETTINGS --------------------------------------------------------------------- */
|
||||
/* Required field indicator styling */
|
||||
.field-label-wrapper {
|
||||
display: flex;
|
||||
@@ -614,7 +613,7 @@ select.select2[multiple] {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* TAB ERROR STYLES ----------------------------------------------------------- */
|
||||
/* TAB ERROR STYLES ---------------------------------------------------------------------------- */
|
||||
/* Style for tabs with errors */
|
||||
.nav-link.has-error {
|
||||
position: relative;
|
||||
@@ -704,7 +703,7 @@ select.select2[multiple] {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* JSON Editor Styling - EveAI Aanpassingen */
|
||||
/* JSON Editor Styling - EveAI Aanpassingen ---------------------------------------------------- */
|
||||
:root {
|
||||
/* Hoofdkleuren gebaseerd op EveAI kleurenschema */
|
||||
--jse-theme-color: var(--bs-primary); /* Paars als hoofdkleur */
|
||||
@@ -813,4 +812,385 @@ select.select2[multiple] {
|
||||
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
2
nginx/static/dist/main.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
nginx/static/dist/main.js
vendored
9
nginx/static/dist/main.js
vendored
File diff suppressed because one or more lines are too long
365
nginx/static/docs/CHANGELOG.md
Normal file
365
nginx/static/docs/CHANGELOG.md
Normal 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
|
||||
Reference in New Issue
Block a user