iatoolkit 1.4.2__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iatoolkit/__init__.py +1 -1
- iatoolkit/common/interfaces/database_provider.py +13 -8
- iatoolkit/common/routes.py +8 -3
- iatoolkit/common/util.py +21 -1
- iatoolkit/infra/connectors/file_connector_factory.py +1 -0
- iatoolkit/infra/connectors/s3_connector.py +4 -2
- iatoolkit/locales/en.yaml +37 -3
- iatoolkit/locales/es.yaml +38 -2
- iatoolkit/repositories/database_manager.py +27 -47
- iatoolkit/services/company_context_service.py +44 -20
- iatoolkit/services/configuration_service.py +143 -6
- iatoolkit/services/knowledge_base_service.py +14 -1
- iatoolkit/services/load_documents_service.py +10 -3
- iatoolkit/services/sql_service.py +17 -0
- iatoolkit/templates/chat.html +2 -1
- iatoolkit/views/configuration_api_view.py +163 -0
- {iatoolkit-1.4.2.dist-info → iatoolkit-1.7.0.dist-info}/METADATA +1 -1
- {iatoolkit-1.4.2.dist-info → iatoolkit-1.7.0.dist-info}/RECORD +22 -22
- iatoolkit/views/load_company_configuration_api_view.py +0 -49
- {iatoolkit-1.4.2.dist-info → iatoolkit-1.7.0.dist-info}/WHEEL +0 -0
- {iatoolkit-1.4.2.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-1.4.2.dist-info → iatoolkit-1.7.0.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-1.4.2.dist-info → iatoolkit-1.7.0.dist-info}/top_level.txt +0 -0
iatoolkit/__init__.py
CHANGED
|
@@ -9,14 +9,19 @@ class DatabaseProvider(abc.ABC):
|
|
|
9
9
|
|
|
10
10
|
# --- Schema Methods ---
|
|
11
11
|
@abc.abstractmethod
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
def get_database_structure(self) -> dict:
|
|
13
|
+
"""
|
|
14
|
+
Returns the structure of the database (tables, columns, types)
|
|
15
|
+
Format:
|
|
16
|
+
{
|
|
17
|
+
"table_name": {
|
|
18
|
+
"columns": [
|
|
19
|
+
{"name": "col1", "type": "VARCHAR", "nullable": True, "pk": True},
|
|
20
|
+
...
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
"""
|
|
20
25
|
pass
|
|
21
26
|
|
|
22
27
|
# --- Execution Methods ---
|
iatoolkit/common/routes.py
CHANGED
|
@@ -24,7 +24,7 @@ def register_views(app):
|
|
|
24
24
|
from iatoolkit.views.profile_api_view import UserLanguageApiView
|
|
25
25
|
from iatoolkit.views.embedding_api_view import EmbeddingApiView
|
|
26
26
|
from iatoolkit.views.login_view import LoginView, FinalizeContextView
|
|
27
|
-
from iatoolkit.views.
|
|
27
|
+
from iatoolkit.views.configuration_api_view import ConfigurationApiView, ValidateConfigurationApiView
|
|
28
28
|
from iatoolkit.views.logout_api_view import LogoutApiView
|
|
29
29
|
from iatoolkit.views.home_view import HomeView
|
|
30
30
|
from iatoolkit.views.chat_view import ChatView
|
|
@@ -128,8 +128,13 @@ def register_views(app):
|
|
|
128
128
|
view_func=EmbeddingApiView.as_view('embedding_api'))
|
|
129
129
|
|
|
130
130
|
# company configuration
|
|
131
|
-
app.add_url_rule('/<company_short_name>/api/
|
|
132
|
-
view_func=
|
|
131
|
+
app.add_url_rule('/<company_short_name>/api/configuration',
|
|
132
|
+
view_func=ConfigurationApiView.as_view('configuration'),
|
|
133
|
+
methods=['GET', 'POST', 'PATCH'],)
|
|
134
|
+
|
|
135
|
+
app.add_url_rule('/<company_short_name>/api/configuration/validate',
|
|
136
|
+
view_func=ValidateConfigurationApiView.as_view('configuration-validate'),
|
|
137
|
+
methods=['GET'])
|
|
133
138
|
|
|
134
139
|
# static pages
|
|
135
140
|
# url: /pages/foundation o /pages/implementation_plan
|
iatoolkit/common/util.py
CHANGED
|
@@ -168,6 +168,20 @@ class Utility:
|
|
|
168
168
|
logging.error(f"Error parsing YAML string: {e}")
|
|
169
169
|
return {}
|
|
170
170
|
|
|
171
|
+
def dump_yaml_to_string(self, config: dict) -> str:
|
|
172
|
+
"""
|
|
173
|
+
Dumps a dictionary into a YAML formatted string.
|
|
174
|
+
It uses default flow style False to ensure block format (readable YAML).
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
# default_flow_style=False ensures lists and dicts are expanded (not inline like JSON)
|
|
178
|
+
# allow_unicode=True ensures characters like accents are preserved
|
|
179
|
+
return yaml.safe_dump(config, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
180
|
+
except yaml.YAMLError as e:
|
|
181
|
+
logging.error(f"Error dumping YAML to string: {e}")
|
|
182
|
+
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
183
|
+
f"Failed to generate YAML: {e}")
|
|
184
|
+
|
|
171
185
|
def generate_context_for_schema(self, entity_name: str, schema_file: str = None, schema: dict = {}) -> str:
|
|
172
186
|
if not schema_file and not schema:
|
|
173
187
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
@@ -197,9 +211,13 @@ class Utility:
|
|
|
197
211
|
root_name = list(schema.keys())[0]
|
|
198
212
|
root_details = schema[root_name]
|
|
199
213
|
|
|
214
|
+
# support this format
|
|
215
|
+
if root_details.get('columns'):
|
|
216
|
+
root_details = root_details['columns']
|
|
217
|
+
|
|
200
218
|
if isinstance(root_details, dict):
|
|
201
219
|
# Las claves de metadatos describen el objeto en sí, no sus propiedades hijas.
|
|
202
|
-
METADATA_KEYS = ['description', 'type', 'format', 'items', 'properties']
|
|
220
|
+
METADATA_KEYS = ['description', 'type', 'format', 'items', 'properties', 'pk']
|
|
203
221
|
|
|
204
222
|
# Las propiedades son las claves restantes en el diccionario.
|
|
205
223
|
properties = {
|
|
@@ -242,6 +260,8 @@ class Utility:
|
|
|
242
260
|
description = details.get('description', '')
|
|
243
261
|
data_type = details.get('type', 'any')
|
|
244
262
|
output.append(f"{indent_str}- **`{name.lower()}`** ({data_type}): {description}")
|
|
263
|
+
# if 'pk' in details and details['pk']:
|
|
264
|
+
# output.append(f"{indent_str}- **Primary Key**: {details['pk']}")
|
|
245
265
|
|
|
246
266
|
child_indent_str = ' ' * (indent_level + 1)
|
|
247
267
|
|
|
@@ -9,14 +9,16 @@ from typing import List
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class S3Connector(FileConnector):
|
|
12
|
-
def __init__(self, bucket: str, prefix: str, auth: dict):
|
|
12
|
+
def __init__(self, bucket: str, prefix: str, folder: str, auth: dict):
|
|
13
13
|
self.bucket = bucket
|
|
14
14
|
self.prefix = prefix
|
|
15
|
+
self.folder = folder
|
|
15
16
|
self.s3 = boto3.client('s3', **auth)
|
|
16
17
|
|
|
17
18
|
def list_files(self) -> List[dict]:
|
|
18
19
|
# list all the files as dictionaries, with keys: 'path', 'name' y 'metadata'.
|
|
19
|
-
|
|
20
|
+
prefix = f'{self.prefix}/{self.folder}/'
|
|
21
|
+
response = self.s3.list_objects_v2(Bucket=self.bucket, Prefix=prefix)
|
|
20
22
|
files = response.get('Contents', [])
|
|
21
23
|
|
|
22
24
|
return [
|
iatoolkit/locales/en.yaml
CHANGED
|
@@ -78,7 +78,6 @@ ui:
|
|
|
78
78
|
saved_ok: "File saved successfully"
|
|
79
79
|
save_file: "Save"
|
|
80
80
|
credentials: "Email and password required for login."
|
|
81
|
-
saved_ok: "Saved successfully"
|
|
82
81
|
deleted_ok: "Deleted successfully"
|
|
83
82
|
user_manager: "User management"
|
|
84
83
|
admin_page_title: "Admin access"
|
|
@@ -88,6 +87,38 @@ ui:
|
|
|
88
87
|
error_loading: "Error loading file content"
|
|
89
88
|
loading: "Loading..."
|
|
90
89
|
|
|
90
|
+
db_explorer:
|
|
91
|
+
data_explorer: "Data Explorer"
|
|
92
|
+
database_connection: "Database Connection"
|
|
93
|
+
tables: "Tables"
|
|
94
|
+
data_explorer_description: "Explore your database tables ."
|
|
95
|
+
select_database: "Please select a database above to view its tables."
|
|
96
|
+
not_table_selected: "No table selected"
|
|
97
|
+
view_yaml: "View YAML"
|
|
98
|
+
save_schema: "Save schema"
|
|
99
|
+
select_table: "Select a table from the left panel"
|
|
100
|
+
table_semantics: "Table Semantics"
|
|
101
|
+
table_description: "Description (AI Context)"
|
|
102
|
+
table_placeholder: "Describe what data this table contains (e.g., 'Active and inactive customer records including billing addresses')..."
|
|
103
|
+
column_metadata: "Column Metadata"
|
|
104
|
+
auto_detect: "Auto-detected from DB"
|
|
105
|
+
meta_column: "Column"
|
|
106
|
+
meta_type: "Type"
|
|
107
|
+
meta_description: "Description"
|
|
108
|
+
meta_synonyms: "Synonyms"
|
|
109
|
+
pii_sesitive: "PII Sensitive"
|
|
110
|
+
|
|
111
|
+
config:
|
|
112
|
+
editor_description: "IAToolkit configuration file"
|
|
113
|
+
title: "Configuration Editor"
|
|
114
|
+
sections: "Sections"
|
|
115
|
+
refresh: "Refresh"
|
|
116
|
+
validate: "Validate"
|
|
117
|
+
select_section: "Select a section from the left panel"
|
|
118
|
+
no_section_selected: "No section selected"
|
|
119
|
+
view_yaml: "View YAML"
|
|
120
|
+
|
|
121
|
+
|
|
91
122
|
rag:
|
|
92
123
|
ingestion: "Ingestion"
|
|
93
124
|
ingestion_description: "Ingest documents into the knowledge base."
|
|
@@ -115,8 +146,11 @@ ui:
|
|
|
115
146
|
delete_message: "This action cannot be undone. The file will be permanently removed."
|
|
116
147
|
delete_button: "Delete"
|
|
117
148
|
delete_cancel: "Cancel"
|
|
118
|
-
|
|
119
|
-
|
|
149
|
+
target_collection: "Target collection"
|
|
150
|
+
select_collection_placeholder: "Select a collection"
|
|
151
|
+
collection_required: "Collection is required"
|
|
152
|
+
collection: "Collection"
|
|
153
|
+
all_collections: "All collections"
|
|
120
154
|
|
|
121
155
|
tooltips:
|
|
122
156
|
history: "History of my queries"
|
iatoolkit/locales/es.yaml
CHANGED
|
@@ -62,7 +62,6 @@ ui:
|
|
|
62
62
|
monitoring: "Monitoreo"
|
|
63
63
|
teams: "Usuarios"
|
|
64
64
|
billing: "Facturación"
|
|
65
|
-
company: "Empresa"
|
|
66
65
|
files: "Archivos"
|
|
67
66
|
select_file: "Seleccione un archivo a editar"
|
|
68
67
|
select_category: "Seleccione una categoría"
|
|
@@ -85,6 +84,38 @@ ui:
|
|
|
85
84
|
goto_chat: "Ir al chat"
|
|
86
85
|
logout: "Cerrar sesión"
|
|
87
86
|
|
|
87
|
+
db_explorer:
|
|
88
|
+
data_explorer: "Explorador de datos"
|
|
89
|
+
database_connection: "Bases de datos"
|
|
90
|
+
tables: "Tablas"
|
|
91
|
+
data_explorer_description: "Explora tus tablas de base de datos."
|
|
92
|
+
select_database: "Seleccione una base de datos para ver sus tablas."
|
|
93
|
+
not_table_selected: "No hay tabla seleccionada."
|
|
94
|
+
view_yaml: "Ver YAML"
|
|
95
|
+
save_schema: "Guardar esquema"
|
|
96
|
+
select_table: "Seleccionar una tabla del panel izquierdo"
|
|
97
|
+
table_semantics: "Significado de la tabla"
|
|
98
|
+
table_description: "Descripción de la tabla (Contexto IA)"
|
|
99
|
+
table_placeholder: "Describe el significado de la tabla, ejemplo: tabla de usuarios."
|
|
100
|
+
column_metadata: "Metadatos de columna"
|
|
101
|
+
auto_detect: "Auto-detectar desde la BD"
|
|
102
|
+
meta_column: "Columna"
|
|
103
|
+
meta_type: "Tipo"
|
|
104
|
+
meta_description: "Descripción"
|
|
105
|
+
meta_synonyms: "Sinonimos"
|
|
106
|
+
pii_sesitive: "IP Sensible"
|
|
107
|
+
|
|
108
|
+
config:
|
|
109
|
+
editor_description: "Editor de configuración"
|
|
110
|
+
title: "Editor de configuraciones"
|
|
111
|
+
sections: "Secciones"
|
|
112
|
+
refresh: "Refrescar"
|
|
113
|
+
validate: "Validar"
|
|
114
|
+
select_section: "Seleccione una sección del panel izquierdo"
|
|
115
|
+
no_section_selected: "No hay sección seleccionada."
|
|
116
|
+
view_yaml: "Ver YAML"
|
|
117
|
+
|
|
118
|
+
|
|
88
119
|
rag:
|
|
89
120
|
ingestion: "Ingesta"
|
|
90
121
|
ingestion_description: "Ingestar documentos en la base de conocimiento."
|
|
@@ -101,7 +132,7 @@ ui:
|
|
|
101
132
|
filename_placeholder: "Contiene..."
|
|
102
133
|
user: "Usuario"
|
|
103
134
|
status: "Estado"
|
|
104
|
-
all_status: "
|
|
135
|
+
all_status: "Estados"
|
|
105
136
|
status_active: "Activo"
|
|
106
137
|
status_pending: "Pendiente"
|
|
107
138
|
status_processing: "Procesando"
|
|
@@ -112,6 +143,11 @@ ui:
|
|
|
112
143
|
delete_message: "Esta acción no se puede deshacer. El archivo se eliminará permanentemente."
|
|
113
144
|
delete_button: "Eliminar"
|
|
114
145
|
delete_cancel: "Cancelar"
|
|
146
|
+
target_collection: "Categoría seleccionada"
|
|
147
|
+
select_collection_placeholder: "Selecciona una categoría"
|
|
148
|
+
collection_required: "Debe seleccionar una categoría"
|
|
149
|
+
all_collections: "Todas las categorías"
|
|
150
|
+
collection: "Categoría"
|
|
115
151
|
|
|
116
152
|
|
|
117
153
|
tooltips:
|
|
@@ -11,6 +11,7 @@ from iatoolkit.repositories.models import Base
|
|
|
11
11
|
from injector import inject
|
|
12
12
|
from pgvector.psycopg2 import register_vector
|
|
13
13
|
from iatoolkit.common.interfaces.database_provider import DatabaseProvider
|
|
14
|
+
import logging
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class DatabaseManager(DatabaseProvider):
|
|
@@ -135,51 +136,30 @@ class DatabaseManager(DatabaseProvider):
|
|
|
135
136
|
self.get_session().rollback()
|
|
136
137
|
|
|
137
138
|
# -- schema methods ----
|
|
138
|
-
def
|
|
139
|
-
# Returns a list of all table names in the database
|
|
139
|
+
def get_database_structure(self) -> dict:
|
|
140
140
|
inspector = inspect(self._engine)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if schema_object_name:
|
|
168
|
-
json_dict["description"] += (
|
|
169
|
-
f"The meaning of each field in this table is detailed in the **`{schema_object_name}`** object."
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
# now add every column to the json dictionary
|
|
173
|
-
for col in columns:
|
|
174
|
-
name = col["name"]
|
|
175
|
-
|
|
176
|
-
# omit the excluded columns.
|
|
177
|
-
if name in exclude_columns:
|
|
178
|
-
continue
|
|
179
|
-
|
|
180
|
-
json_dict["fields"].append({
|
|
181
|
-
"name": name,
|
|
182
|
-
"type": str(col["type"]),
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
return "\n\n" + str(json_dict)
|
|
141
|
+
structure = {}
|
|
142
|
+
for table in inspector.get_table_names(schema=self.schema):
|
|
143
|
+
columns_data = []
|
|
144
|
+
|
|
145
|
+
# get columns
|
|
146
|
+
try:
|
|
147
|
+
columns = inspector.get_columns(table, schema=self.schema)
|
|
148
|
+
# Obtener PKs para marcarlas
|
|
149
|
+
pks = inspector.get_pk_constraint(table, schema=self.schema).get('constrained_columns', [])
|
|
150
|
+
|
|
151
|
+
for col in columns:
|
|
152
|
+
columns_data.append({
|
|
153
|
+
"name": col['name'],
|
|
154
|
+
"type": str(col['type']),
|
|
155
|
+
"nullable": col.get('nullable', True),
|
|
156
|
+
"pk": col['name'] in pks
|
|
157
|
+
})
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logging.warning(f"Could not inspect columns for table {table}: {e}")
|
|
160
|
+
|
|
161
|
+
structure[table] = {
|
|
162
|
+
"columns": columns_data
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return structure
|
|
@@ -104,16 +104,17 @@ class CompanyContextService:
|
|
|
104
104
|
continue
|
|
105
105
|
|
|
106
106
|
# get database schema definition, for this source.
|
|
107
|
-
database_schema_name = source.get('schema')
|
|
107
|
+
database_schema_name = source.get('schema', 'public')
|
|
108
108
|
|
|
109
109
|
try:
|
|
110
|
-
|
|
110
|
+
# 1. Get the full database structure at once using the SQL service
|
|
111
|
+
db_structure = self.sql_service.get_database_structure(company_short_name, db_name)
|
|
111
112
|
except IAToolkitException as e:
|
|
112
|
-
logging.warning(f"Could not get DB
|
|
113
|
+
logging.warning(f"Could not get DB structure for '{db_name}': {e}")
|
|
113
114
|
continue
|
|
114
115
|
|
|
115
116
|
db_description = source.get('description', '')
|
|
116
|
-
sql_context
|
|
117
|
+
sql_context += f"***Database (`database_key`)***: {db_name}\n"
|
|
117
118
|
|
|
118
119
|
if db_description:
|
|
119
120
|
sql_context += (
|
|
@@ -131,28 +132,32 @@ class CompanyContextService:
|
|
|
131
132
|
f"Use exactly: `database_key='{db_name}'`.\n"
|
|
132
133
|
)
|
|
133
134
|
|
|
134
|
-
#
|
|
135
|
+
# 2. get the list of tables to process based on structure and config
|
|
135
136
|
tables_to_process = []
|
|
136
137
|
if source.get('include_all_tables', False):
|
|
137
|
-
|
|
138
|
+
# Use keys from the fetched structure
|
|
139
|
+
all_tables = list(db_structure.keys())
|
|
138
140
|
tables_to_exclude = set(source.get('exclude_tables', []))
|
|
139
141
|
tables_to_process = [t for t in all_tables if t not in tables_to_exclude]
|
|
140
142
|
elif 'tables' in source:
|
|
141
|
-
#
|
|
142
|
-
|
|
143
|
+
# Use keys from the config map, but check if they exist in DB structure
|
|
144
|
+
config_tables = list(source['tables'].keys())
|
|
145
|
+
tables_to_process = [t for t in config_tables if t in db_structure]
|
|
143
146
|
|
|
144
|
-
#
|
|
147
|
+
# 3. get the global settings and overrides.
|
|
145
148
|
global_exclude_columns = source.get('exclude_columns', [])
|
|
146
149
|
table_prefix = source.get('table_prefix')
|
|
147
150
|
table_overrides = source.get('tables', {})
|
|
148
151
|
|
|
149
|
-
#
|
|
152
|
+
# 4. iterate over the tables.
|
|
150
153
|
for table_name in tables_to_process:
|
|
151
154
|
try:
|
|
152
|
-
|
|
155
|
+
table_data = db_structure[table_name]
|
|
156
|
+
|
|
157
|
+
# 5. get the table specific configuration.
|
|
153
158
|
table_config = table_overrides.get(table_name, {})
|
|
154
159
|
|
|
155
|
-
#
|
|
160
|
+
# 6. define the schema object name, using the override if it exists.
|
|
156
161
|
# Priority 1: Explicit override from the 'tables' map.
|
|
157
162
|
schema_object_name = table_config.get('schema_name')
|
|
158
163
|
|
|
@@ -164,17 +169,36 @@ class CompanyContextService:
|
|
|
164
169
|
# Priority 4: Default to the table name itself.
|
|
165
170
|
schema_object_name = table_name
|
|
166
171
|
|
|
167
|
-
#
|
|
172
|
+
# 7. define the list of columns to exclude, (local vs. global).
|
|
168
173
|
local_exclude_columns = table_config.get('exclude_columns')
|
|
169
174
|
final_exclude_columns = local_exclude_columns if local_exclude_columns is not None else global_exclude_columns
|
|
170
175
|
|
|
171
|
-
#
|
|
172
|
-
|
|
173
|
-
table_name
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
# 8. Build the table definition dictionary manually using the structure data
|
|
177
|
+
json_dict = {
|
|
178
|
+
"table": table_name,
|
|
179
|
+
"schema": database_schema_name,
|
|
180
|
+
"description": f"The table belongs to the **`{database_schema_name}`** schema.",
|
|
181
|
+
"fields": []
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if schema_object_name:
|
|
185
|
+
json_dict["description"] += (
|
|
186
|
+
f"The meaning of each field in this table is detailed in the **`{schema_object_name}`** object."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
for col in table_data.get('columns', []):
|
|
190
|
+
name = col["name"]
|
|
191
|
+
if name in final_exclude_columns:
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
json_dict["fields"].append({
|
|
195
|
+
"name": name,
|
|
196
|
+
"type": col["type"]
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
# Append as string representation of dict (consistent with previous behavior)
|
|
200
|
+
sql_context += "\n\n" + str(json_dict)
|
|
201
|
+
|
|
178
202
|
except (KeyError, RuntimeError) as e:
|
|
179
203
|
logging.warning(f"Could not generate schema for table '{table_name}': {e}")
|
|
180
204
|
|
|
@@ -97,6 +97,141 @@ class ConfigurationService:
|
|
|
97
97
|
logging.info(f"✅ Company '{company_short_name}' configured successfully.")
|
|
98
98
|
return config, errors
|
|
99
99
|
|
|
100
|
+
def update_configuration_key(self, company_short_name: str, key: str, value) -> tuple[dict, list[str]]:
|
|
101
|
+
"""
|
|
102
|
+
Updates a specific key in the company's configuration file, validates the result,
|
|
103
|
+
and saves it to the asset repository if valid.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
company_short_name: The company identifier.
|
|
107
|
+
key: The configuration key to update (supports dot notation, e.g., 'llm.model').
|
|
108
|
+
value: The new value for the key.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
A tuple containing the updated configuration dict and a list of error strings (if any).
|
|
112
|
+
"""
|
|
113
|
+
# 1. Load raw config from file (to avoid working with merged supplementary files if possible,
|
|
114
|
+
# but for simplicity we load the main yaml structure)
|
|
115
|
+
main_config_filename = "company.yaml"
|
|
116
|
+
|
|
117
|
+
if not self.asset_repo.exists(company_short_name, AssetType.CONFIG, main_config_filename):
|
|
118
|
+
raise FileNotFoundError(f"Configuration file not found for {company_short_name}")
|
|
119
|
+
|
|
120
|
+
yaml_content = self.asset_repo.read_text(company_short_name, AssetType.CONFIG, main_config_filename)
|
|
121
|
+
config = self.utility.load_yaml_from_string(yaml_content) or {}
|
|
122
|
+
|
|
123
|
+
# 2. Update the key in the dictionary
|
|
124
|
+
self._set_nested_value(config, key, value)
|
|
125
|
+
|
|
126
|
+
# 3. Validate the new configuration structure
|
|
127
|
+
errors = self._validate_configuration(company_short_name, config)
|
|
128
|
+
|
|
129
|
+
if errors:
|
|
130
|
+
logging.warning(f"Configuration update failed validation: {errors}")
|
|
131
|
+
return config, errors
|
|
132
|
+
|
|
133
|
+
# 4. Save back to repository
|
|
134
|
+
# Assuming Utility has a method to dump YAML. If not, standard yaml library would be needed.
|
|
135
|
+
# For this example, we assume self.utility.dump_yaml_to_string exists.
|
|
136
|
+
new_yaml_content = self.utility.dump_yaml_to_string(config)
|
|
137
|
+
self.asset_repo.write_text(company_short_name, AssetType.CONFIG, main_config_filename, new_yaml_content)
|
|
138
|
+
|
|
139
|
+
# 5. Invalidate cache so next reads get the new version
|
|
140
|
+
if company_short_name in self._loaded_configs:
|
|
141
|
+
del self._loaded_configs[company_short_name]
|
|
142
|
+
|
|
143
|
+
return config, []
|
|
144
|
+
|
|
145
|
+
def add_configuration_key(self, company_short_name: str, parent_key: str, key: str, value) -> tuple[dict, list[str]]:
|
|
146
|
+
"""
|
|
147
|
+
Adds a new key-value pair under a specific parent key in the configuration.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
company_short_name: The company identifier.
|
|
151
|
+
parent_key: The parent configuration key under which to add the new key (e.g., 'llm').
|
|
152
|
+
key: The new key name to add.
|
|
153
|
+
value: The value for the new key.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A tuple containing the updated configuration dict and a list of error strings (if any).
|
|
157
|
+
"""
|
|
158
|
+
# 1. Load raw config from file
|
|
159
|
+
main_config_filename = "company.yaml"
|
|
160
|
+
|
|
161
|
+
if not self.asset_repo.exists(company_short_name, AssetType.CONFIG, main_config_filename):
|
|
162
|
+
raise FileNotFoundError(f"Configuration file not found for {company_short_name}")
|
|
163
|
+
|
|
164
|
+
yaml_content = self.asset_repo.read_text(company_short_name, AssetType.CONFIG, main_config_filename)
|
|
165
|
+
config = self.utility.load_yaml_from_string(yaml_content) or {}
|
|
166
|
+
|
|
167
|
+
# 2. Construct full path and set the value
|
|
168
|
+
# If parent_key is provided, we append the new key to it (e.g., 'llm.new_setting')
|
|
169
|
+
full_path = f"{parent_key}.{key}" if parent_key else key
|
|
170
|
+
self._set_nested_value(config, full_path, value)
|
|
171
|
+
|
|
172
|
+
# 3. Validate the new configuration structure
|
|
173
|
+
errors = self._validate_configuration(company_short_name, config)
|
|
174
|
+
|
|
175
|
+
if errors:
|
|
176
|
+
logging.warning(f"Configuration add failed validation: {errors}")
|
|
177
|
+
return config, errors
|
|
178
|
+
|
|
179
|
+
# 4. Save back to repository
|
|
180
|
+
new_yaml_content = self.utility.dump_yaml_to_string(config)
|
|
181
|
+
self.asset_repo.write_text(company_short_name, AssetType.CONFIG, main_config_filename, new_yaml_content)
|
|
182
|
+
|
|
183
|
+
# 5. Invalidate cache
|
|
184
|
+
if company_short_name in self._loaded_configs:
|
|
185
|
+
del self._loaded_configs[company_short_name]
|
|
186
|
+
|
|
187
|
+
return config, []
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def validate_configuration(self, company_short_name: str) -> list[str]:
|
|
191
|
+
"""
|
|
192
|
+
Public method to trigger validation of the current configuration.
|
|
193
|
+
"""
|
|
194
|
+
config = self._load_and_merge_configs(company_short_name)
|
|
195
|
+
return self._validate_configuration(company_short_name, config)
|
|
196
|
+
|
|
197
|
+
def _set_nested_value(self, data: dict, key: str, value):
|
|
198
|
+
"""
|
|
199
|
+
Helper to set a value in a nested dictionary or list using dot notation (e.g. 'llm.model', 'tools.0.name').
|
|
200
|
+
Handles traversal through both dictionaries and lists.
|
|
201
|
+
"""
|
|
202
|
+
keys = key.split('.')
|
|
203
|
+
current = data
|
|
204
|
+
|
|
205
|
+
# Traverse up to the parent of the target key
|
|
206
|
+
for i, k in enumerate(keys[:-1]):
|
|
207
|
+
if isinstance(current, dict):
|
|
208
|
+
# If it's a dict, we can traverse or create the path
|
|
209
|
+
current = current.setdefault(k, {})
|
|
210
|
+
elif isinstance(current, list):
|
|
211
|
+
# If it's a list, we MUST use an integer index
|
|
212
|
+
try:
|
|
213
|
+
idx = int(k)
|
|
214
|
+
current = current[idx]
|
|
215
|
+
except (ValueError, IndexError) as e:
|
|
216
|
+
raise ValueError(
|
|
217
|
+
f"Invalid path: cannot access index '{k}' in list at '{'.'.join(keys[:i + 1])}'") from e
|
|
218
|
+
else:
|
|
219
|
+
raise ValueError(
|
|
220
|
+
f"Invalid path: '{k}' is not a container (got {type(current)}) at '{'.'.join(keys[:i + 1])}'")
|
|
221
|
+
|
|
222
|
+
# Set the final value
|
|
223
|
+
last_key = keys[-1]
|
|
224
|
+
if isinstance(current, dict):
|
|
225
|
+
current[last_key] = value
|
|
226
|
+
elif isinstance(current, list):
|
|
227
|
+
try:
|
|
228
|
+
idx = int(last_key)
|
|
229
|
+
current[idx] = value
|
|
230
|
+
except (ValueError, IndexError) as e:
|
|
231
|
+
raise ValueError(f"Invalid path: cannot assign to index '{last_key}' in list") from e
|
|
232
|
+
else:
|
|
233
|
+
raise ValueError(f"Cannot assign value to non-container type {type(current)} at '{key}'")
|
|
234
|
+
|
|
100
235
|
def _load_and_merge_configs(self, company_short_name: str) -> dict:
|
|
101
236
|
"""
|
|
102
237
|
Loads the main company.yaml and merges data from supplementary files
|
|
@@ -239,13 +374,12 @@ class ConfigurationService:
|
|
|
239
374
|
from iatoolkit.services.prompt_service import PromptService
|
|
240
375
|
prompt_service = current_iatoolkit().get_injector().get(PromptService)
|
|
241
376
|
|
|
242
|
-
prompts_config = config.get('prompts',
|
|
243
|
-
categories_config = config.get('prompt_categories', [])
|
|
377
|
+
prompts_config = config.get('prompts', {})
|
|
244
378
|
|
|
245
379
|
prompt_service.sync_company_prompts(
|
|
246
380
|
company_short_name=company_short_name,
|
|
247
|
-
prompts_config=prompts_config,
|
|
248
|
-
categories_config=
|
|
381
|
+
prompts_config=prompts_config.get('prompt_list', []),
|
|
382
|
+
categories_config=prompts_config.get('prompt_categories', []),
|
|
249
383
|
)
|
|
250
384
|
|
|
251
385
|
def _register_knowledge_base(self, company_short_name: str, config: dict):
|
|
@@ -328,8 +462,11 @@ class ConfigurationService:
|
|
|
328
462
|
add_error(f"tools[{i}]", "'params' key must be a dictionary.")
|
|
329
463
|
|
|
330
464
|
# 6. Prompts
|
|
331
|
-
|
|
332
|
-
|
|
465
|
+
prompt_list = config.get("prompts", {}).get("prompt_list", [])
|
|
466
|
+
prompt_categories = config.get("prompts", {}).get("prompt_categories", [])
|
|
467
|
+
|
|
468
|
+
category_set = set(prompt_categories)
|
|
469
|
+
for i, prompt in enumerate(prompt_list):
|
|
333
470
|
prompt_name = prompt.get("name")
|
|
334
471
|
if not prompt_name:
|
|
335
472
|
add_error(f"prompts[{i}]", "Missing required key: 'name'")
|
|
@@ -308,7 +308,7 @@ class KnowledgeBaseService:
|
|
|
308
308
|
|
|
309
309
|
# filter by collection
|
|
310
310
|
if collection:
|
|
311
|
-
query = query.join(
|
|
311
|
+
query = query.join(Document.collection_type).filter(CollectionType.name == collection)
|
|
312
312
|
|
|
313
313
|
# Filter by user identifier
|
|
314
314
|
if user_identifier:
|
|
@@ -403,6 +403,19 @@ class KnowledgeBaseService:
|
|
|
403
403
|
|
|
404
404
|
session.commit()
|
|
405
405
|
|
|
406
|
+
def get_collection_names(self, company_short_name: str) -> List[str]:
|
|
407
|
+
"""
|
|
408
|
+
Retrieves the names of all collections defined for a specific company.
|
|
409
|
+
"""
|
|
410
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
411
|
+
if not company:
|
|
412
|
+
logging.warning(f"Company {company_short_name} not found when listing collections.")
|
|
413
|
+
return []
|
|
414
|
+
|
|
415
|
+
session = self.document_repo.session
|
|
416
|
+
collections = session.query(CollectionType).filter_by(company_id=company.id).all()
|
|
417
|
+
return [c.name for c in collections]
|
|
418
|
+
|
|
406
419
|
def _get_collection_type_id(self, company_id: int, collection_name: str) -> Optional[int]:
|
|
407
420
|
"""Helper to get ID by name"""
|
|
408
421
|
if not collection_name:
|
|
@@ -65,17 +65,24 @@ class LoadDocumentsService:
|
|
|
65
65
|
logging.warning(f"Source '{source_name}' not found in configuration for company '{company.short_name}'. Skipping.")
|
|
66
66
|
continue
|
|
67
67
|
|
|
68
|
+
collection = source_config.get('collection')
|
|
69
|
+
if not collection:
|
|
70
|
+
logging.warning(
|
|
71
|
+
f"Document Source '{source_name}' missing collection definition en company.yaml, Skipping.")
|
|
72
|
+
continue
|
|
73
|
+
|
|
68
74
|
try:
|
|
69
|
-
logging.info(f"
|
|
75
|
+
logging.info(f"company {company.short_name}: loading source '{source_name}' into collection '{collection}'...")
|
|
70
76
|
|
|
71
77
|
# Combine the base connector configuration with the specific path from the source.
|
|
72
78
|
full_connector_config = base_connector_config.copy()
|
|
73
79
|
full_connector_config['path'] = source_config.get('path')
|
|
80
|
+
full_connector_config['folder'] = source_config.get('folder')
|
|
74
81
|
|
|
75
82
|
# Prepare the context for the callback function.
|
|
76
83
|
context = {
|
|
77
84
|
'company': company,
|
|
78
|
-
'collection':
|
|
85
|
+
'collection': collection,
|
|
79
86
|
'metadata': source_config.get('metadata', {})
|
|
80
87
|
}
|
|
81
88
|
|
|
@@ -132,7 +139,7 @@ class LoadDocumentsService:
|
|
|
132
139
|
company=company,
|
|
133
140
|
filename=filename,
|
|
134
141
|
content=content,
|
|
135
|
-
collection=
|
|
142
|
+
collection=context.get('collection'),
|
|
136
143
|
metadata=predefined_metadata
|
|
137
144
|
)
|
|
138
145
|
|
|
@@ -171,4 +171,21 @@ class SqlService:
|
|
|
171
171
|
logging.error(f"Error while committing sql: '{str(e)}'")
|
|
172
172
|
raise IAToolkitException(
|
|
173
173
|
IAToolkitException.ErrorType.DATABASE_ERROR, str(e)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def get_database_structure(self, company_short_name: str, db_name: str) -> dict:
|
|
177
|
+
"""
|
|
178
|
+
Introspects the specified database and returns its structure (Tables & Columns).
|
|
179
|
+
Used for the Schema Editor 2.0
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
provider = self.get_database_provider(company_short_name, db_name)
|
|
183
|
+
return provider.get_database_structure()
|
|
184
|
+
except IAToolkitException:
|
|
185
|
+
raise
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logging.error(f"Error introspecting database '{db_name}': {e}")
|
|
188
|
+
raise IAToolkitException(
|
|
189
|
+
IAToolkitException.ErrorType.DATABASE_ERROR,
|
|
190
|
+
f"Failed to introspect database: {str(e)}"
|
|
174
191
|
)
|
iatoolkit/templates/chat.html
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
{# Movemos los estilos y los links aquí para que se rendericen en el <head> #}
|
|
9
9
|
<style>
|
|
10
10
|
{{ branding.css_variables | safe }}
|
|
11
|
+
{{ branding.css_variables | safe }}
|
|
11
12
|
</style>
|
|
12
13
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
|
13
14
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
@@ -104,7 +105,7 @@
|
|
|
104
105
|
style="color: {{ branding.header_text_color }};">
|
|
105
106
|
<i class="bi bi-question-circle-fill"></i>
|
|
106
107
|
</a>
|
|
107
|
-
{% if user_role
|
|
108
|
+
{% if user_role != "user" and license == 'enterprise' %}
|
|
108
109
|
<a href="/{{ company_short_name }}/admin/dashboard"
|
|
109
110
|
target="_blank"
|
|
110
111
|
id="preferences-button"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask import jsonify, request
|
|
7
|
+
from flask.views import MethodView
|
|
8
|
+
from injector import inject
|
|
9
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
10
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
11
|
+
from iatoolkit.services.auth_service import AuthService
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConfigurationApiView(MethodView):
|
|
16
|
+
"""
|
|
17
|
+
API View to manage company configuration.
|
|
18
|
+
Supports loading, updating specific keys, and validating the configuration.
|
|
19
|
+
"""
|
|
20
|
+
@inject
|
|
21
|
+
def __init__(self,
|
|
22
|
+
configuration_service: ConfigurationService,
|
|
23
|
+
profile_service: ProfileService,
|
|
24
|
+
auth_service: AuthService):
|
|
25
|
+
self.configuration_service = configuration_service
|
|
26
|
+
self.profile_service = profile_service
|
|
27
|
+
self.auth_service = auth_service
|
|
28
|
+
|
|
29
|
+
def get(self, company_short_name: str = None):
|
|
30
|
+
"""
|
|
31
|
+
Loads the current configuration for the company.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
# 1. Verify authentication
|
|
35
|
+
auth_result = self.auth_service.verify(anonymous=True)
|
|
36
|
+
if not auth_result.get("success"):
|
|
37
|
+
return jsonify(auth_result), auth_result.get("status_code", 401)
|
|
38
|
+
|
|
39
|
+
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
40
|
+
if not company:
|
|
41
|
+
return jsonify({"error": "company not found."}), 404
|
|
42
|
+
|
|
43
|
+
config, errors = self.configuration_service.load_configuration(company_short_name)
|
|
44
|
+
|
|
45
|
+
# Register data sources to ensure services are up to date with loaded config
|
|
46
|
+
if config:
|
|
47
|
+
self.configuration_service.register_data_sources(company_short_name)
|
|
48
|
+
|
|
49
|
+
# Remove non-serializable objects
|
|
50
|
+
if 'company' in config:
|
|
51
|
+
config.pop('company')
|
|
52
|
+
|
|
53
|
+
status_code = 200 if not errors else 400
|
|
54
|
+
return jsonify({'config': config, 'errors': errors}), status_code
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logging.exception(f"Unexpected error loading config: {e}")
|
|
57
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
58
|
+
|
|
59
|
+
def patch(self, company_short_name: str):
|
|
60
|
+
"""
|
|
61
|
+
Updates a specific configuration key.
|
|
62
|
+
Body: { "key": "llm.model", "value": "gpt-4" }
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
auth_result = self.auth_service.verify(anonymous=False) # Require valid user for updates
|
|
66
|
+
if not auth_result.get("success"):
|
|
67
|
+
return jsonify(auth_result), 401
|
|
68
|
+
|
|
69
|
+
payload = request.get_json()
|
|
70
|
+
key = payload.get('key')
|
|
71
|
+
value = payload.get('value')
|
|
72
|
+
|
|
73
|
+
if not key:
|
|
74
|
+
return jsonify({'error': 'Missing "key" in payload'}), 400
|
|
75
|
+
|
|
76
|
+
logging.info(f"Updating config key '{key}' for company '{company_short_name}'")
|
|
77
|
+
|
|
78
|
+
updated_config, errors = self.configuration_service.update_configuration_key(
|
|
79
|
+
company_short_name, key, value
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Remove non-serializable objects
|
|
83
|
+
if 'company' in updated_config:
|
|
84
|
+
updated_config.pop('company')
|
|
85
|
+
|
|
86
|
+
if errors:
|
|
87
|
+
return jsonify({'status': 'invalid', 'errors': errors, 'config': updated_config}), 400
|
|
88
|
+
|
|
89
|
+
return jsonify({'status': 'success', 'config': updated_config}), 200
|
|
90
|
+
|
|
91
|
+
except FileNotFoundError:
|
|
92
|
+
return jsonify({'error': 'Configuration file not found'}), 404
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logging.exception(f"Error updating config: {e}")
|
|
95
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
96
|
+
|
|
97
|
+
def post(self, company_short_name: str):
|
|
98
|
+
"""
|
|
99
|
+
Adds a new configuration key.
|
|
100
|
+
Body: { "parent_key": "llm", "key": "max_tokens", "value": 2048 }
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
auth_result = self.auth_service.verify(anonymous=False)
|
|
104
|
+
if not auth_result.get("success"):
|
|
105
|
+
return jsonify(auth_result), 401
|
|
106
|
+
|
|
107
|
+
payload = request.get_json()
|
|
108
|
+
parent_key = payload.get('parent_key', '') # Optional, defaults to root
|
|
109
|
+
key = payload.get('key')
|
|
110
|
+
value = payload.get('value')
|
|
111
|
+
|
|
112
|
+
if not key:
|
|
113
|
+
return jsonify({'error': 'Missing "key" in payload'}), 400
|
|
114
|
+
|
|
115
|
+
logging.info(f"Adding config key '{key}' under '{parent_key}' for company '{company_short_name}'")
|
|
116
|
+
|
|
117
|
+
updated_config, errors = self.configuration_service.add_configuration_key(
|
|
118
|
+
company_short_name, parent_key, key, value
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Remove non-serializable objects
|
|
122
|
+
if 'company' in updated_config:
|
|
123
|
+
updated_config.pop('company')
|
|
124
|
+
|
|
125
|
+
if errors:
|
|
126
|
+
return jsonify({'status': 'invalid', 'errors': errors, 'config': updated_config}), 400
|
|
127
|
+
|
|
128
|
+
return jsonify({'status': 'success', 'config': updated_config}), 200
|
|
129
|
+
|
|
130
|
+
except FileNotFoundError:
|
|
131
|
+
return jsonify({'error': 'Configuration file not found'}), 404
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logging.exception(f"Error adding config key: {e}")
|
|
134
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
135
|
+
|
|
136
|
+
class ValidateConfigurationApiView(MethodView):
|
|
137
|
+
"""
|
|
138
|
+
API View to trigger an explicit validation of the current configuration.
|
|
139
|
+
Useful for UI to check status without modifying data.
|
|
140
|
+
"""
|
|
141
|
+
@inject
|
|
142
|
+
def __init__(self,
|
|
143
|
+
configuration_service: ConfigurationService,
|
|
144
|
+
auth_service: AuthService):
|
|
145
|
+
self.configuration_service = configuration_service
|
|
146
|
+
self.auth_service = auth_service
|
|
147
|
+
|
|
148
|
+
def get(self, company_short_name: str):
|
|
149
|
+
try:
|
|
150
|
+
auth_result = self.auth_service.verify(anonymous=False)
|
|
151
|
+
if not auth_result.get("success"):
|
|
152
|
+
return jsonify(auth_result), 401
|
|
153
|
+
|
|
154
|
+
errors = self.configuration_service.validate_configuration(company_short_name)
|
|
155
|
+
|
|
156
|
+
if errors:
|
|
157
|
+
return jsonify({'status': 'invalid', 'errors': errors}), 200 # 200 OK because check succeeded
|
|
158
|
+
|
|
159
|
+
return jsonify({'status': 'valid', 'errors': []}), 200
|
|
160
|
+
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logging.exception(f"Error validating config: {e}")
|
|
163
|
+
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
iatoolkit/__init__.py,sha256=
|
|
1
|
+
iatoolkit/__init__.py,sha256=lBC4IhNqHSyQzU5dQEHh49nkOgc97JfQq2E27-VVPa0,1425
|
|
2
2
|
iatoolkit/base_company.py,sha256=TzGvdseDpJMi9fl5nbR2pMARgU6pN6ISatFoMJKtNxw,648
|
|
3
3
|
iatoolkit/cli_commands.py,sha256=EDky1QpvHnatzXsd1evYW0OQt-k3JIbPGMc13vgDvy4,1814
|
|
4
4
|
iatoolkit/company_registry.py,sha256=XopFzqIpUxBtQJzdZwu2-c0v98MLT7SP_xLPxn_iENM,4176
|
|
@@ -6,12 +6,12 @@ iatoolkit/core.py,sha256=QKqu-QDrNWunDU9uX96a86kFNv3a_aAxonkpxJptzoE,20885
|
|
|
6
6
|
iatoolkit/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
iatoolkit/common/exceptions.py,sha256=xmNi5Et13pCvCM1so79z36I-7Kdie8nVUAqW9acGs9k,1202
|
|
8
8
|
iatoolkit/common/model_registry.py,sha256=HiFr1FtRKGjpp3YurkiF4l6sGuirigDVL-b_3_R-vlM,5561
|
|
9
|
-
iatoolkit/common/routes.py,sha256=
|
|
9
|
+
iatoolkit/common/routes.py,sha256=iVZawjKfGt6iK9tZ8t3y6dofP4KJpIQex48OVVvlRYo,7540
|
|
10
10
|
iatoolkit/common/session_manager.py,sha256=OUYMzT8hN1U-NCUidR5tUAXB1drd8nYTOpo60rUNYeY,532
|
|
11
|
-
iatoolkit/common/util.py,sha256=
|
|
11
|
+
iatoolkit/common/util.py,sha256=NfSuDbzrGVS59fIgqJETYUlD5otMLwAjyp7OVhaMoo8,16310
|
|
12
12
|
iatoolkit/common/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
iatoolkit/common/interfaces/asset_storage.py,sha256=IYXyc73gmwFT7T8RQhC4MymQDW0vGDSEzuKk43w7Ct4,989
|
|
14
|
-
iatoolkit/common/interfaces/database_provider.py,sha256=
|
|
14
|
+
iatoolkit/common/interfaces/database_provider.py,sha256=7ayYuEYRunkm4udoOGumoX9PfdgohBcrj0JDPfyfM00,1156
|
|
15
15
|
iatoolkit/infra/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
16
16
|
iatoolkit/infra/brevo_mail_app.py,sha256=xvy3KxEEgjFpHlIAogP6SU5KXmg3w7lC16nnNmOYU8Y,5323
|
|
17
17
|
iatoolkit/infra/call_service.py,sha256=iRk9VxbXaAwlLIl8fUzGDWIAdzwfsbs1MtP84YeENxg,4929
|
|
@@ -21,19 +21,19 @@ iatoolkit/infra/llm_response.py,sha256=6hSmI909m0Pdgs7UnJHrv_nokbCZLzHpdwBUED6VN
|
|
|
21
21
|
iatoolkit/infra/redis_session_manager.py,sha256=EPr3E_g7LHxn6U4SV5lT_L8WQsAwg8VzA_WIEZ3TwOw,3667
|
|
22
22
|
iatoolkit/infra/connectors/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
23
23
|
iatoolkit/infra/connectors/file_connector.py,sha256=HOjRTFd-WfDOcFyvHncAhnGNZuFgChIwC-P6osPo9ZM,352
|
|
24
|
-
iatoolkit/infra/connectors/file_connector_factory.py,sha256=
|
|
24
|
+
iatoolkit/infra/connectors/file_connector_factory.py,sha256=iCOhklUaYG3YxtJGBYOrLN8spF5mjKu6vyA6nj71FHI,2167
|
|
25
25
|
iatoolkit/infra/connectors/google_cloud_storage_connector.py,sha256=IXpL3HTo7Ft4EQsYiQq5wXRRQK854jzOEB7ZdWjLa4U,2050
|
|
26
26
|
iatoolkit/infra/connectors/google_drive_connector.py,sha256=WR1AlO5-Bl3W89opdja0kKgHTJzVOjTsy3H4SlIvwVg,2537
|
|
27
27
|
iatoolkit/infra/connectors/local_file_connector.py,sha256=hrzIgpMJOTuwTqzlQeTIU_50ZbZ6yl8lcWPv6hMnoqI,1739
|
|
28
|
-
iatoolkit/infra/connectors/s3_connector.py,sha256=
|
|
28
|
+
iatoolkit/infra/connectors/s3_connector.py,sha256=r8bgye8ydMzayAAg5RS22oakpKd-vNw-cSrOCvlwG9g,1209
|
|
29
29
|
iatoolkit/infra/llm_providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
30
|
iatoolkit/infra/llm_providers/deepseek_adapter.py,sha256=ui4V05xplUvLWHu7y_dUMKpP4Af-BkZi5tvdgZY16dU,10719
|
|
31
31
|
iatoolkit/infra/llm_providers/gemini_adapter.py,sha256=xjYAMgS4bh7qfpCEv6n92tzpKdjnNBVFfr8GkSrcatM,14555
|
|
32
32
|
iatoolkit/infra/llm_providers/openai_adapter.py,sha256=9SuHYtPJcadWSV_GHfx8DDj9BqkUS3RvLSyDd_ZVB2M,4848
|
|
33
|
-
iatoolkit/locales/en.yaml,sha256=
|
|
34
|
-
iatoolkit/locales/es.yaml,sha256=
|
|
33
|
+
iatoolkit/locales/en.yaml,sha256=NX1LMzpBNtgRl0q5RqD9wwTbF9IKzdVheTOaGzI1P7E,13571
|
|
34
|
+
iatoolkit/locales/es.yaml,sha256=ote9JSzWq8lGwbfX7wKKIp7ccdZ76-sbXIDnTp_szQQ,14443
|
|
35
35
|
iatoolkit/repositories/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
|
|
36
|
-
iatoolkit/repositories/database_manager.py,sha256=
|
|
36
|
+
iatoolkit/repositories/database_manager.py,sha256=F6oYrmNp29SrYHJeoe7UIzQuXdZ6fVyRMgdWhGo6oCs,5905
|
|
37
37
|
iatoolkit/repositories/document_repo.py,sha256=5oCrq8pjcyOCBrOc4XYeUvza9nUd0VJ4eNL0g1MQb68,1441
|
|
38
38
|
iatoolkit/repositories/filesystem_asset_repository.py,sha256=ZFr_FpHQD5nszZnJSki6nc-W3PKvmhTxKmVZMd2DUbM,1825
|
|
39
39
|
iatoolkit/repositories/llm_query_repo.py,sha256=8rGSVx7WZlGbUfeVw_uGygTUuJXRK5vGfFHj1znc1ss,3981
|
|
@@ -44,8 +44,8 @@ iatoolkit/services/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X
|
|
|
44
44
|
iatoolkit/services/auth_service.py,sha256=B0gTUHDf56WiTnA5viBEvbmf2A6y_6bkAoFlMvu0IWY,7756
|
|
45
45
|
iatoolkit/services/benchmark_service.py,sha256=CdbFYyS3FHFhNzWQEa9ZNjUlmON10DT1nKNbZQ1EUi8,5880
|
|
46
46
|
iatoolkit/services/branding_service.py,sha256=Fsjcos7hGLx2qhoZ9hTaFCgb7x5eYHMDGvXg4mXx8D0,8120
|
|
47
|
-
iatoolkit/services/company_context_service.py,sha256=
|
|
48
|
-
iatoolkit/services/configuration_service.py,sha256=
|
|
47
|
+
iatoolkit/services/company_context_service.py,sha256=Oqldfdo8MSGv_KB8-4EztUVcqycoabrVLSbfky2n5s4,10435
|
|
48
|
+
iatoolkit/services/configuration_service.py,sha256=TsrTzou2Z8rC50favIlPgAloHBcy2p1jlZjRNtg3cq8,24184
|
|
49
49
|
iatoolkit/services/dispatcher_service.py,sha256=f3aPbKU9UkNS61dNVZjT-J9hg3x-H2Nb_n-cZRnZ9aI,5247
|
|
50
50
|
iatoolkit/services/document_service.py,sha256=XrAwbNPyhmP94bGdDwHy76Mg_PCg6O2QyZwii1T6kZ8,6469
|
|
51
51
|
iatoolkit/services/embedding_service.py,sha256=ngRnFXEpko-rqpicqbJbVdr91IjSe1jXSYVWprOVlE0,6296
|
|
@@ -54,16 +54,16 @@ iatoolkit/services/file_processor_service.py,sha256=5xS7zrdL_kpCFXQ5seqNl9tiseI7
|
|
|
54
54
|
iatoolkit/services/history_manager_service.py,sha256=nq1hk39WDbyHG6s8irpIdCS98EBDagF0djH6JHq9WuY,8734
|
|
55
55
|
iatoolkit/services/i18n_service.py,sha256=mR4pS0z56NgZLeSnEvDXiMvVBeOCl5CkUWdYBTVEhyM,3941
|
|
56
56
|
iatoolkit/services/jwt_service.py,sha256=pVoH1rzRwWixpvT3AhFdiE1BDmdbo4KQMyOF0P7tz-Y,2939
|
|
57
|
-
iatoolkit/services/knowledge_base_service.py,sha256=
|
|
57
|
+
iatoolkit/services/knowledge_base_service.py,sha256=UuFWdjobFqwsBuLwAqi4FRNaVuYabSIjSj4vPKxZzLk,17098
|
|
58
58
|
iatoolkit/services/language_service.py,sha256=ktNzj0hrCPm_VMIpWTxFEpEHKQ1lOHOfuhzCbZud2Dc,3428
|
|
59
59
|
iatoolkit/services/license_service.py,sha256=pdl9szSEPbIu2ISl6g-WIR1p4HQu2gSGdAjViKxY6Hg,2751
|
|
60
60
|
iatoolkit/services/llm_client_service.py,sha256=YSvoNKeBQRGL3znMDIyLH2PGZt4ImmYKD_aBlBeS91k,19024
|
|
61
|
-
iatoolkit/services/load_documents_service.py,sha256=
|
|
61
|
+
iatoolkit/services/load_documents_service.py,sha256=WEaPMRxginbsxOXopofPj6FeH-iIrSTlipFFRV20t4I,7069
|
|
62
62
|
iatoolkit/services/mail_service.py,sha256=6Kx1CIbXzAr_ucoqwqTlhhwE6y2Jw64eNDjXysaZP-c,8297
|
|
63
63
|
iatoolkit/services/profile_service.py,sha256=mUXW5dwFF4SBH04adQuoSj-E0FmuLDt_VcPo9oxdr1M,23680
|
|
64
64
|
iatoolkit/services/prompt_service.py,sha256=mr3szmOThNi6MtqCbh4rwJJOlk-Be35bkIJXqh7_hvM,12898
|
|
65
65
|
iatoolkit/services/query_service.py,sha256=aS2-NtEMdu-QViw58d-g9iZI1j3KZIJb0xrN8N_c__U,20406
|
|
66
|
-
iatoolkit/services/sql_service.py,sha256=
|
|
66
|
+
iatoolkit/services/sql_service.py,sha256=nafxpDdv1AbOYMeHzoiD6G8V64TPMniUlLhrJ0J_KOU,7802
|
|
67
67
|
iatoolkit/services/tool_service.py,sha256=FQuplPsgs1AdyOk2pxUBkPIvAvUqJU1ElOpOWz5wkr8,10002
|
|
68
68
|
iatoolkit/services/user_feedback_service.py,sha256=KbjMjl0CDF7WpPHioJ3sqQVwocjfq_cisrpeqYUqtas,5361
|
|
69
69
|
iatoolkit/services/user_session_context_service.py,sha256=RSv_NxOSTVPA6nFLiZKSIuUWa59cmZWBwiN_TWSljBo,9074
|
|
@@ -95,7 +95,7 @@ iatoolkit/templates/_company_header.html,sha256=L1QtrMpqYXALqtinCa3WhG4CrgHKLObl
|
|
|
95
95
|
iatoolkit/templates/_login_widget.html,sha256=qziV70-n7SwC11rL4cU1LenJM83jYvuyw2xSz6OORJA,2063
|
|
96
96
|
iatoolkit/templates/base.html,sha256=sRHUnHaKIycLRKZ_PQOTb88kSBC6kigzxlm60vpXj1I,3434
|
|
97
97
|
iatoolkit/templates/change_password.html,sha256=p0GWZ9gldluYQM8OPSz2Q4CYhU8UJmb-72iz_Sl_6Ho,3485
|
|
98
|
-
iatoolkit/templates/chat.html,sha256=
|
|
98
|
+
iatoolkit/templates/chat.html,sha256=8tLQlpCS7Nd1-07kefIZzv4S5Hcd1UIhQmC5P_Z760s,15685
|
|
99
99
|
iatoolkit/templates/chat_modals.html,sha256=x0Do7I7X9cfgZFHSAwWreIN55nYPCpZK7PTBxGXjrYY,8501
|
|
100
100
|
iatoolkit/templates/error.html,sha256=4aT6cIYQUILciekAceHEiUuZxcAqkmNPUMnB9CD4BNA,1778
|
|
101
101
|
iatoolkit/templates/forgot_password.html,sha256=XLVMmLJK5SAXmPqIH_OyTlSbu5QTAdekO6ggzfcNup0,2376
|
|
@@ -105,6 +105,7 @@ iatoolkit/views/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,1
|
|
|
105
105
|
iatoolkit/views/base_login_view.py,sha256=l45msQUrLGxxxZrSb5l7tnTLLQyIoPTu4TgbkHBvVlc,3981
|
|
106
106
|
iatoolkit/views/change_password_view.py,sha256=TXFvvtNrR37yrNAHYzcMSSIkV4-MIzmIpwbPffaJJyE,4957
|
|
107
107
|
iatoolkit/views/chat_view.py,sha256=QNlC0n1hxjWbI3wyLhV6D-U2kJZkIqClNg94iQP4PrA,3534
|
|
108
|
+
iatoolkit/views/configuration_api_view.py,sha256=K8aclK6oKkoTZoYwck-mozLchEOFCkrXFtraHh7tI00,6370
|
|
108
109
|
iatoolkit/views/embedding_api_view.py,sha256=9n5utihq8zHiG0V2sVCOYQ72wOKa4denqE951gqaJWo,2371
|
|
109
110
|
iatoolkit/views/forgot_password_view.py,sha256=C4eJe9GmGrl6M3bTt_anwuPQib9ouXZtKYp6kB2xBeU,3380
|
|
110
111
|
iatoolkit/views/help_content_api_view.py,sha256=8EUw2iSE6X-Lm2dGlYZxSp-29HzaAGwD3CpL4Otqp5k,2011
|
|
@@ -112,7 +113,6 @@ iatoolkit/views/history_api_view.py,sha256=oNYfo-fZbz4sY6VpE_goaCFQyH2k9IX85VhPo
|
|
|
112
113
|
iatoolkit/views/home_view.py,sha256=GaU2-qyTnE8jE1ejdNjHW3azo72X_Awhp1y1Zx9BXRg,2662
|
|
113
114
|
iatoolkit/views/init_context_api_view.py,sha256=axfNno8E1v4dCNuXNkdV1XpDZtypICj48hW3cRyl_eA,2961
|
|
114
115
|
iatoolkit/views/llmquery_api_view.py,sha256=ohlYmFcvhvZL2BTMeCzd7ZPeTBzIPcIZbC9CRWqQM70,2290
|
|
115
|
-
iatoolkit/views/load_company_configuration_api_view.py,sha256=ufX_jrSO7NTUsgNJNrAXxFKmyp2JyNYoplMUm4biULs,1902
|
|
116
116
|
iatoolkit/views/load_document_api_view.py,sha256=RaO4KRz19uSOv3LL7Fn8FJSLzHtRkPAIYRPU-yT1nxQ,2485
|
|
117
117
|
iatoolkit/views/login_view.py,sha256=NKPjR9cCOsw7FhOfXCM32m6veKxRdLhsc0H7DLc5_ug,7517
|
|
118
118
|
iatoolkit/views/logout_api_view.py,sha256=7c0rL5sLTuoRPqQs73wlH_7eb3--S6GLcr7Yu4hNOYU,2035
|
|
@@ -125,9 +125,9 @@ iatoolkit/views/static_page_view.py,sha256=V0oTybQ5lFo3hp5xOKau-yMO8vK8NCM6zWiwV
|
|
|
125
125
|
iatoolkit/views/user_feedback_api_view.py,sha256=QOTBtpNqYAmewXDgVAoaIprnVjvf1xM0ExWxSFp3ICI,2098
|
|
126
126
|
iatoolkit/views/users_api_view.py,sha256=hVR6rxLNyhGiXhCIHvttUeSinH071Pyk-2_Fcm-v2TM,1139
|
|
127
127
|
iatoolkit/views/verify_user_view.py,sha256=Uc9P2wiRjZ7pMRTjWUn8Y-p0DFqw_AwNYQsdbAwhbg8,2659
|
|
128
|
-
iatoolkit-1.
|
|
129
|
-
iatoolkit-1.
|
|
130
|
-
iatoolkit-1.
|
|
131
|
-
iatoolkit-1.
|
|
132
|
-
iatoolkit-1.
|
|
133
|
-
iatoolkit-1.
|
|
128
|
+
iatoolkit-1.7.0.dist-info/licenses/LICENSE,sha256=5tOLQdjoCvSXEx_7Lr4bSab3ha4NSwzamvua0fwCXi8,1075
|
|
129
|
+
iatoolkit-1.7.0.dist-info/licenses/LICENSE_COMMUNITY.md,sha256=9lVNcggPNUnleMF3_h3zu9PbNTeaRqB1tHAbiBLQJZU,566
|
|
130
|
+
iatoolkit-1.7.0.dist-info/METADATA,sha256=V0ZDSh08B6jbggfwSY10wfIt7oDiFVWHuPTg-Dbkg30,8395
|
|
131
|
+
iatoolkit-1.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
132
|
+
iatoolkit-1.7.0.dist-info/top_level.txt,sha256=V_w4QvDx0b1RXiy8zTCrD1Bp7AZkFe3_O0-9fMiwogg,10
|
|
133
|
+
iatoolkit-1.7.0.dist-info/RECORD,,
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
-
# Product: IAToolkit
|
|
3
|
-
#
|
|
4
|
-
# IAToolkit is open source software.
|
|
5
|
-
|
|
6
|
-
from flask.views import MethodView
|
|
7
|
-
from flask import redirect, url_for, jsonify, request, g
|
|
8
|
-
from injector import inject
|
|
9
|
-
from iatoolkit.services.auth_service import AuthService
|
|
10
|
-
from iatoolkit.services.profile_service import ProfileService
|
|
11
|
-
from iatoolkit.services.configuration_service import ConfigurationService
|
|
12
|
-
import logging
|
|
13
|
-
|
|
14
|
-
class LoadCompanyConfigurationApiView(MethodView):
|
|
15
|
-
@inject
|
|
16
|
-
def __init__(self,
|
|
17
|
-
configuration_service: ConfigurationService,
|
|
18
|
-
profile_service: ProfileService,
|
|
19
|
-
auth_service: AuthService):
|
|
20
|
-
self.configuration_service = configuration_service
|
|
21
|
-
self.profile_service = profile_service
|
|
22
|
-
self.auth_service = auth_service
|
|
23
|
-
|
|
24
|
-
def get(self, company_short_name: str = None):
|
|
25
|
-
try:
|
|
26
|
-
# 1. Get the authenticated user's
|
|
27
|
-
auth_result = self.auth_service.verify(anonymous=True)
|
|
28
|
-
if not auth_result.get("success"):
|
|
29
|
-
return jsonify(auth_result), auth_result.get("status_code", 401)
|
|
30
|
-
|
|
31
|
-
company = self.profile_service.get_company_by_short_name(company_short_name)
|
|
32
|
-
if not company:
|
|
33
|
-
return jsonify({"error": "company not found."}), 404
|
|
34
|
-
|
|
35
|
-
config, errors = self.configuration_service.load_configuration(company_short_name)
|
|
36
|
-
if config:
|
|
37
|
-
self.configuration_service.register_data_sources(company_short_name)
|
|
38
|
-
|
|
39
|
-
# this is fo avoid serialization issues
|
|
40
|
-
if 'company' in config:
|
|
41
|
-
config.pop('company')
|
|
42
|
-
|
|
43
|
-
status_code = 200 if not errors else 400
|
|
44
|
-
return {'config': config, 'errors': [errors]}, status_code
|
|
45
|
-
except Exception as e:
|
|
46
|
-
logging.exception(f"Unexpected error: {e}")
|
|
47
|
-
return {'status': 'error'}, 500
|
|
48
|
-
|
|
49
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|