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 CHANGED
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- __version__ = "1.4.2"
6
+ __version__ = "1.7.0"
7
7
 
8
8
  # Expose main classes and functions at the top level of the package
9
9
 
@@ -9,14 +9,19 @@ class DatabaseProvider(abc.ABC):
9
9
 
10
10
  # --- Schema Methods ---
11
11
  @abc.abstractmethod
12
- def get_all_table_names(self) -> List[str]:
13
- pass
14
-
15
- @abc.abstractmethod
16
- def get_table_description(self,
17
- table_name: str,
18
- schema_object_name: str | None = None,
19
- exclude_columns: List[str] | None = None) -> str:
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 ---
@@ -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.load_company_configuration_api_view import LoadCompanyConfigurationApiView
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/load_configuration',
132
- view_func=LoadCompanyConfigurationApiView.as_view('load-configuration'))
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
 
@@ -40,6 +40,7 @@ class FileConnectorFactory:
40
40
  return S3Connector(
41
41
  bucket=config['bucket'],
42
42
  prefix=config.get('prefix', ''),
43
+ folder=config.get('folder', ''),
43
44
  auth=auth
44
45
  )
45
46
 
@@ -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
- response = self.s3.list_objects_v2(Bucket=self.bucket, Prefix=self.prefix)
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: "Todos los estados"
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 get_all_table_names(self) -> list[str]:
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
- return inspector.get_table_names(schema=self.schema)
142
-
143
- def get_table_description(self,
144
- table_name: str,
145
- schema_object_name: str | None = None,
146
- exclude_columns: list[str] | None = None) -> str:
147
- inspector = inspect(self._engine)
148
-
149
- # search the table in the specified schema
150
- if table_name not in inspector.get_table_names(schema=self.schema):
151
- raise RuntimeError(f"Table '{table_name}' does not exist in database schema '{self.schema}'.")
152
-
153
- if exclude_columns is None:
154
- exclude_columns = []
155
-
156
- # get all the table columns
157
- columns = inspector.get_columns(table_name, schema=self.schema)
158
-
159
- # construct a json dictionary with the table definition
160
- json_dict = {
161
- "table": table_name,
162
- "schema": self.schema,
163
- "description": f"The table belongs to the **`{self.schema}`** schema.",
164
- "fields": []
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
- db_provider = self.sql_service.get_database_provider(company_short_name, db_name)
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 provider for '{db_name}': {e}")
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 = f"***Database (`database_key`)***: {db_name}\n"
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
- # 1. get the list of tables to process.
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
- all_tables = db_provider.get_all_table_names()
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
- # if not include_all_tables, use the list of tables explicitly specified in the map.
142
- tables_to_process = list(source['tables'].keys())
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
- # 2. get the global settings and overrides.
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
- # 3. iterate over the tables.
152
+ # 4. iterate over the tables.
150
153
  for table_name in tables_to_process:
151
154
  try:
152
- # 4. get the table specific configuration.
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
- # 5. define the schema object name, using the override if it exists.
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
- # 6. define the list of columns to exclude, (local vs. global).
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
- # 7. get the table schema definition.
172
- table_definition = db_provider.get_table_description(
173
- table_name=table_name,
174
- schema_object_name=schema_object_name,
175
- exclude_columns=final_exclude_columns
176
- )
177
- sql_context += table_definition
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=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
- category_set = set(config.get("prompt_categories", []))
332
- for i, prompt in enumerate(config.get("prompts", [])):
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(CollectionType).filter(CollectionType.name == collection)
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"Processing source '{source_name}' for company '{company.short_name}'...")
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': source_config.get('metadata', {}).get('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=predefined_metadata.get('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
  )
@@ -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 == "admin" and license == 'enterprise' %}
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 1.4.2
3
+ Version: 1.7.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
- iatoolkit/__init__.py,sha256=4kPCwhdZff5rt-FhnHfafMCPvD5hDFGEdRwINPm8ATc,1425
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=cJCFLgSqrTXPOC9CraXLQtoOhVCphWXHyFadYiTXQGw,7292
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=e000sUjUXeY4BJrJ3HtnsVMMhn8guclRZkl0f2Dmfy0,15270
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=Cf-33SZ7HPxTv2b_ahew1TcqDmFH442OPwIv6YEiubk,1097
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=3qvyfH4ZHKuiMxJFkawOxhW2-TGKKtsBYHgoPpZMuKU,2118
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=Nj4_YaLobjfcnbZewJf21_K2EXohgcc3mJll1Pzn4zg,1123
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=cKPJQB2sV_AuFd80eOK4r-Y08cKlmG9eG7b34iuBqNo,12210
34
- iatoolkit/locales/es.yaml,sha256=1LT81MPQAW0mhHOVxWtgiOTxYs6B9Jyc6wgQGz04xGo,13027
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=JAi-A_siEZlyQeV7xk1NIYFZjfBCCo12RVSV_pLYYhI,6631
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=MV07jvef_9u_55n92yHCDwThBykn5rQT1tKg51Ebxoo,9339
48
- iatoolkit/services/configuration_service.py,sha256=bYjhR5cZcJunFr1OtX2ReOJaHbYrxBlxizEdoo52zaw,17933
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=kjVI04WjOf5q_fYEDNkg-lRN1hqbQrS0URzUGy3dkuo,16500
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=zK1mXsj9aLDDKoV9WKgNuFlg02ROnlf9umcTm_Y4OIs,6761
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=kgLEE7iiw9WjnyvOBfm4VJ60-F6JAg26cAKF6LlF-zU,7087
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=HtmTBbbBPvVHscEymSWqAFjCnxE4hlMckzRgdi-1MXs,15642
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.4.2.dist-info/licenses/LICENSE,sha256=5tOLQdjoCvSXEx_7Lr4bSab3ha4NSwzamvua0fwCXi8,1075
129
- iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md,sha256=9lVNcggPNUnleMF3_h3zu9PbNTeaRqB1tHAbiBLQJZU,566
130
- iatoolkit-1.4.2.dist-info/METADATA,sha256=j6HullcLZAkwHSYnpcXew6VVsL6nzFkyoXwpuSBDy1E,8395
131
- iatoolkit-1.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
132
- iatoolkit-1.4.2.dist-info/top_level.txt,sha256=V_w4QvDx0b1RXiy8zTCrD1Bp7AZkFe3_O0-9fMiwogg,10
133
- iatoolkit-1.4.2.dist-info/RECORD,,
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
-