iatoolkit 0.66.2__tar.gz → 0.66.8__tar.gz
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-0.66.2 → iatoolkit-0.66.8}/PKG-INFO +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/pyproject.toml +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/common/session_manager.py +2 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/iatoolkit.py +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/locales/en.yaml +23 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/locales/es.yaml +25 -2
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/repositories/database_manager.py +3 -3
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/repositories/document_repo.py +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/auth_service.py +2 -2
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/branding_service.py +1 -2
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/dispatcher_service.py +8 -9
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/document_service.py +5 -2
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/excel_service.py +15 -11
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/file_processor_service.py +4 -12
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/help_content_service.py +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/history_service.py +8 -7
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/i18n_service.py +4 -4
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/jwt_service.py +7 -9
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/language_service.py +22 -20
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/load_documents_service.py +4 -4
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/mail_service.py +9 -4
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/profile_service.py +7 -7
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/prompt_manager_service.py +20 -16
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/query_service.py +15 -14
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/sql_service.py +6 -2
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/user_feedback_service.py +15 -13
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/js/chat_history_button.js +3 -5
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/js/chat_main.js +2 -17
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/styles/chat_iatoolkit.css +69 -158
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/styles/chat_modal.css +1 -37
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/chat.html +11 -4
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/chat_modals.html +3 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/login_simulation.html +16 -5
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/help_content_api_view.py +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/history_api_view.py +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/init_context_api_view.py +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/llmquery_api_view.py +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/login_simulation_view.py +14 -2
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/verify_user_view.py +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit.egg-info/PKG-INFO +1 -1
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/readme.md +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/requirements.txt +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/setup.cfg +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/__init__.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/base_company.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/cli_commands.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/common/__init__.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/common/exceptions.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/common/routes.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/common/util.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/company_registry.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/__init__.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/call_service.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/connectors/__init__.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/gemini_adapter.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/google_chat_app.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/llm_client.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/llm_proxy.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/llm_response.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/mail_app.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/openai_adapter.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/infra/redis_session_manager.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/repositories/__init__.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/repositories/models.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/repositories/profile_repo.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/repositories/tasks_repo.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/repositories/vs_repo.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/__init__.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/benchmark_service.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/onboarding_service.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/search_service.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/tasks_service.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/services/user_session_context_service.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/images/fernando.jpeg +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/js/chat_filepond.js +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/js/chat_help_content.js +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/styles/chat_public.css +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/styles/landing_page.css +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/styles/llm_output.css +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/static/styles/onboarding.css +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/system_prompts/format_styles.prompt +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/system_prompts/query_main.prompt +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/system_prompts/sql_rules.prompt +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/_company_header.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/_login_widget.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/about.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/base.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/change_password.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/error.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/forgot_password.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/index.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/onboarding_shell.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/templates/signup.html +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/__init__.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/base_login_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/change_password_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/external_login_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/file_store_api_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/forgot_password_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/home_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/index_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/login_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/logout_api_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/profile_api_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/prompt_api_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/signup_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/tasks_api_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/tasks_review_api_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit.egg-info/SOURCES.txt +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit.egg-info/requires.txt +0 -0
- {iatoolkit-0.66.2 → iatoolkit-0.66.8}/src/iatoolkit.egg-info/top_level.txt +0 -0
|
@@ -19,7 +19,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix
|
|
|
19
19
|
from injector import Binder, Injector, singleton
|
|
20
20
|
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
21
21
|
|
|
22
|
-
IATOOLKIT_VERSION = "0.66.
|
|
22
|
+
IATOOLKIT_VERSION = "0.66.8"
|
|
23
23
|
|
|
24
24
|
# global variable for the unique instance of IAToolkit
|
|
25
25
|
_iatoolkit_instance: Optional['IAToolkit'] = None
|
|
@@ -77,6 +77,8 @@ ui:
|
|
|
77
77
|
stop: "Stop"
|
|
78
78
|
|
|
79
79
|
errors:
|
|
80
|
+
company_not_found: "The company {company_short_name} does not exist."
|
|
81
|
+
timeout: "timeout expired."
|
|
80
82
|
auth:
|
|
81
83
|
invalid_password: "The provided password is incorrect."
|
|
82
84
|
user_not_found: "A user with that email address was not found."
|
|
@@ -115,9 +117,29 @@ errors:
|
|
|
115
117
|
password_no_digit: "Password must contain at least one number."
|
|
116
118
|
password_no_special_char: "Password must contain at least one special character."
|
|
117
119
|
|
|
120
|
+
services:
|
|
121
|
+
no_text_file: "The file is not text or the encoding is not UTF-8"
|
|
122
|
+
no_output_file: "Missing output file name"
|
|
123
|
+
no_data_for_excel: "Missing data or it is not a list of dictionaries"
|
|
124
|
+
no_download_directory: "Temporary directory for saving Excel files is not configured"
|
|
125
|
+
cannot_create_excel: "Could not create the Excel file"
|
|
126
|
+
invalid_filename: "Invalid filename"
|
|
127
|
+
file_not_exist: "File not found"
|
|
128
|
+
path_is_not_a_file: "The path does not correspond to a file"
|
|
129
|
+
file_validation_error: "Error validating file"
|
|
130
|
+
user_not_authorized: "user is not authorized for this company"
|
|
131
|
+
account_not_verified: "Your account has not been verified. Please check your email."
|
|
132
|
+
missing_response_id: "Can not found 'previous_response_id' for '{company_short_name}/{user_identifier}'. Reinit context"
|
|
133
|
+
|
|
134
|
+
|
|
118
135
|
api_responses:
|
|
119
136
|
context_reloaded_success: "The context has been successfully reloaded."
|
|
120
137
|
|
|
138
|
+
services:
|
|
139
|
+
mail_sent: "Email sent successfully."
|
|
140
|
+
start_query: "Hello, what can I help you with today?"
|
|
141
|
+
|
|
142
|
+
|
|
121
143
|
flash_messages:
|
|
122
144
|
password_changed_success: "Your password has been successfully reset. You can now log in."
|
|
123
145
|
login_required: "Please log in to continue."
|
|
@@ -142,3 +164,4 @@ js_messages:
|
|
|
142
164
|
unknown_server_error: "Unknown server error."
|
|
143
165
|
loading: "Loading..."
|
|
144
166
|
reload_init: "init reloading context in background..."
|
|
167
|
+
no_history_found: "No query history found."
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
stop: "Detener"
|
|
74
74
|
|
|
75
75
|
errors:
|
|
76
|
+
company_not_found: "La empresa {company_short_name} no existe."
|
|
77
|
+
timeout: "El tiempo de espera ha expirado."
|
|
78
|
+
|
|
79
|
+
|
|
76
80
|
auth:
|
|
77
81
|
invalid_password: "La contraseña proporcionada es incorrecta."
|
|
78
82
|
user_not_found: "No se encontró un usuario con ese correo."
|
|
@@ -84,11 +88,11 @@
|
|
|
84
88
|
templates:
|
|
85
89
|
company_not_found: "Empresa no encontrada."
|
|
86
90
|
home_template_not_found: "La plantilla de la página de inicio para la empresa '{company_name}' no está configurada."
|
|
91
|
+
processing_error: "Error al procesar el template: {error}"
|
|
87
92
|
template_not_found: "No se encontro el template: '{template_name}'."
|
|
88
|
-
processing_error: "Ocurrió un error al procesar la plantilla personalizada de la página de inicio: {error}"
|
|
89
93
|
|
|
90
94
|
general:
|
|
91
|
-
unexpected_error: "Ha ocurrido un error inesperado
|
|
95
|
+
unexpected_error: "Ha ocurrido un error inesperado: {error}."
|
|
92
96
|
unsupported_language: "El idioma seleccionado no es válido."
|
|
93
97
|
signup:
|
|
94
98
|
company_not_found: "La empresa {company_name} no existe."
|
|
@@ -111,9 +115,27 @@
|
|
|
111
115
|
password_no_digit: "La contraseña debe tener al menos un número."
|
|
112
116
|
password_no_special_char: "La contraseña debe tener al menos un carácter especial."
|
|
113
117
|
|
|
118
|
+
services:
|
|
119
|
+
no_text_file: "El archivo no es texto o la codificación no es UTF-8"
|
|
120
|
+
no_output_file: "falta el nombre del archivo de salida"
|
|
121
|
+
no_data_for_excel: "faltan los datos o no es una lista de diccionarios"
|
|
122
|
+
no_download_directory: "no esta configurado el directorio temporal para guardar excels"
|
|
123
|
+
cannot_create_excel: "no se pudo crear el archivo excel"
|
|
124
|
+
invalid_filename: "Nombre de archivo inválido"
|
|
125
|
+
file_not_exist : "Archivo no encontrado"
|
|
126
|
+
path_is_not_a_file : "La ruta no corresponde a un archivo"
|
|
127
|
+
file_validation_error : "Error validando archivo"
|
|
128
|
+
user_not_authorized: "Usuario no esta autorizado para esta empresa"
|
|
129
|
+
account_not_verified: "Tu cuenta no ha sido verificada. Por favor, revisa tu correo."
|
|
130
|
+
missing_response_id: "No se encontró 'previous_response_id' para '{company_short_name}/{user_identifier}'. Reinicia el contexto."
|
|
131
|
+
|
|
114
132
|
api_responses:
|
|
115
133
|
context_reloaded_success: "El contexto se ha recargado con éxito."
|
|
116
134
|
|
|
135
|
+
services:
|
|
136
|
+
mail_sent: "mail enviado exitosamente."
|
|
137
|
+
start_query: "Hola, cual es tu pregunta?"
|
|
138
|
+
|
|
117
139
|
flash_messages:
|
|
118
140
|
password_changed_success: "Tu contraseña ha sido restablecida exitosamente. Ahora puedes iniciar sesión."
|
|
119
141
|
signup_success: "Registro exitoso. Por favor, revisa tu correo para verificar tu cuenta."
|
|
@@ -137,4 +159,5 @@
|
|
|
137
159
|
unknown_server_error: "Error desconocido del servidor."
|
|
138
160
|
loading: "Cargando..."
|
|
139
161
|
reload_init: "Iniciando recarga de contexto en segundo plano..."
|
|
162
|
+
no_history_found: "No existe historial de consultas."
|
|
140
163
|
|
|
@@ -27,8 +27,8 @@ class DatabaseManager:
|
|
|
27
27
|
self._engine = create_engine(
|
|
28
28
|
database_url,
|
|
29
29
|
echo=False,
|
|
30
|
-
pool_size=
|
|
31
|
-
max_overflow=
|
|
30
|
+
pool_size=10, # per worker
|
|
31
|
+
max_overflow=20,
|
|
32
32
|
pool_timeout=30,
|
|
33
33
|
pool_recycle=1800,
|
|
34
34
|
pool_pre_ping=True,
|
|
@@ -77,7 +77,7 @@ class DatabaseManager:
|
|
|
77
77
|
inspector = inspect(self._engine)
|
|
78
78
|
|
|
79
79
|
if table_name not in inspector.get_table_names():
|
|
80
|
-
raise RuntimeError(f"
|
|
80
|
+
raise RuntimeError(f"Table '{table_name}' does not exist.")
|
|
81
81
|
|
|
82
82
|
if exclude_columns is None:
|
|
83
83
|
exclude_columns = []
|
|
@@ -22,7 +22,7 @@ class DocumentRepo:
|
|
|
22
22
|
def get(self, company_id, filename: str ) -> Document:
|
|
23
23
|
if not company_id or not filename:
|
|
24
24
|
raise IAToolkitException(IAToolkitException.ErrorType.PARAM_NOT_FILLED,
|
|
25
|
-
'
|
|
25
|
+
'missing company_id or filename')
|
|
26
26
|
|
|
27
27
|
return self.session.query(Document).filter_by(company_id=company_id, filename=filename).first()
|
|
28
28
|
|
|
@@ -84,7 +84,7 @@ class AuthService:
|
|
|
84
84
|
)
|
|
85
85
|
return {'success': True, 'user_identifier': user_identifier}
|
|
86
86
|
except Exception as e:
|
|
87
|
-
logging.error(f"
|
|
87
|
+
logging.error(f"error creeating session for Token of {user_identifier}: {e}")
|
|
88
88
|
self.log_access(
|
|
89
89
|
company_short_name=company_short_name,
|
|
90
90
|
auth_type='redeem_token',
|
|
@@ -189,5 +189,5 @@ class AuthService:
|
|
|
189
189
|
session.commit()
|
|
190
190
|
|
|
191
191
|
except Exception as e:
|
|
192
|
-
logging.error(f"
|
|
192
|
+
logging.error(f"error writting to AccessLog: {e}", exc_info=False)
|
|
193
193
|
session.rollback()
|
|
@@ -149,7 +149,7 @@ class Dispatcher:
|
|
|
149
149
|
except Exception as e:
|
|
150
150
|
logging.exception(e)
|
|
151
151
|
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
152
|
-
f"Error
|
|
152
|
+
f"Error getting company context of: {company_name}: {str(e)}") from e
|
|
153
153
|
|
|
154
154
|
def get_company_services(self, company: Company) -> list[dict]:
|
|
155
155
|
# create the syntax with openai response syntax, for the company function list
|
|
@@ -173,7 +173,7 @@ class Dispatcher:
|
|
|
173
173
|
def get_user_info(self, company_name: str, user_identifier: str) -> dict:
|
|
174
174
|
if company_name not in self.company_instances:
|
|
175
175
|
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
176
|
-
f"
|
|
176
|
+
f"company not configured: {company_name}")
|
|
177
177
|
|
|
178
178
|
# source 2: external company user
|
|
179
179
|
company_instance = self.company_instances[company_name]
|
|
@@ -182,14 +182,14 @@ class Dispatcher:
|
|
|
182
182
|
except Exception as e:
|
|
183
183
|
logging.exception(e)
|
|
184
184
|
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
185
|
-
f"Error
|
|
185
|
+
f"Error in get_user_info: {company_name}: {str(e)}") from e
|
|
186
186
|
|
|
187
187
|
return external_user_profile
|
|
188
188
|
|
|
189
189
|
def get_metadata_from_filename(self, company_name: str, filename: str) -> dict:
|
|
190
190
|
if company_name not in self.company_instances:
|
|
191
191
|
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
192
|
-
f"
|
|
192
|
+
f"company not configured: {company_name}")
|
|
193
193
|
|
|
194
194
|
company_instance = self.company_instances[company_name]
|
|
195
195
|
try:
|
|
@@ -197,7 +197,7 @@ class Dispatcher:
|
|
|
197
197
|
except Exception as e:
|
|
198
198
|
logging.exception(e)
|
|
199
199
|
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
200
|
-
f"Error
|
|
200
|
+
f"Error in get_metadata_from_filename: {company_name}: {str(e)}") from e
|
|
201
201
|
|
|
202
202
|
def get_company_instance(self, company_name: str):
|
|
203
203
|
"""Returns the instance for a given company name."""
|
|
@@ -207,12 +207,11 @@ class Dispatcher:
|
|
|
207
207
|
|
|
208
208
|
# iatoolkit system prompts
|
|
209
209
|
_SYSTEM_PROMPT = [
|
|
210
|
-
{'name': 'query_main', 'description':'main prompt
|
|
211
|
-
{'name': 'format_styles', 'description':'
|
|
212
|
-
{'name': 'sql_rules', 'description':'
|
|
210
|
+
{'name': 'query_main', 'description':'iatoolkit main prompt'},
|
|
211
|
+
{'name': 'format_styles', 'description':'output format styles'},
|
|
212
|
+
{'name': 'sql_rules', 'description':'instructions for SQL queries'}
|
|
213
213
|
]
|
|
214
214
|
|
|
215
|
-
|
|
216
215
|
# iatoolkit function calls
|
|
217
216
|
_FUNCTION_LIST = [
|
|
218
217
|
{
|
|
@@ -11,10 +11,13 @@ import os
|
|
|
11
11
|
import pytesseract
|
|
12
12
|
from injector import inject
|
|
13
13
|
from iatoolkit.common.exceptions import IAToolkitException
|
|
14
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
14
15
|
|
|
15
16
|
class DocumentService:
|
|
16
17
|
@inject
|
|
17
|
-
def __init__(self):
|
|
18
|
+
def __init__(self, i18n_service: I18nService):
|
|
19
|
+
self.i18n_service = i18n_service
|
|
20
|
+
|
|
18
21
|
# max number of pages to load
|
|
19
22
|
self.max_doc_pages = int(os.getenv("MAX_DOC_PAGES", "200"))
|
|
20
23
|
|
|
@@ -29,7 +32,7 @@ class DocumentService:
|
|
|
29
32
|
file_content = file_content.decode('utf-8')
|
|
30
33
|
except UnicodeDecodeError:
|
|
31
34
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_FORMAT_ERROR,
|
|
32
|
-
|
|
35
|
+
self.i18n_service.t('errors.services.no_text_file'))
|
|
33
36
|
|
|
34
37
|
return file_content
|
|
35
38
|
elif filename.lower().endswith('.pdf'):
|
|
@@ -8,6 +8,7 @@ import pandas as pd
|
|
|
8
8
|
from uuid import uuid4
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from iatoolkit.common.exceptions import IAToolkitException
|
|
11
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
11
12
|
from injector import inject
|
|
12
13
|
import os
|
|
13
14
|
import logging
|
|
@@ -18,8 +19,11 @@ EXCEL_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
|
18
19
|
|
|
19
20
|
class ExcelService:
|
|
20
21
|
@inject
|
|
21
|
-
def __init__(self,
|
|
22
|
+
def __init__(self,
|
|
23
|
+
util: Utility,
|
|
24
|
+
i18n_service: I18nService):
|
|
22
25
|
self.util = util
|
|
26
|
+
self.i18n_service = i18n_service
|
|
23
27
|
|
|
24
28
|
def excel_generator(self, **kwargs) -> str:
|
|
25
29
|
"""
|
|
@@ -42,11 +46,11 @@ class ExcelService:
|
|
|
42
46
|
# get the parameters
|
|
43
47
|
fname = kwargs.get('filename')
|
|
44
48
|
if not fname:
|
|
45
|
-
return '
|
|
49
|
+
return self.i18n_service.t('errors.services.no_output_file')
|
|
46
50
|
|
|
47
51
|
data = kwargs.get('data')
|
|
48
52
|
if not data or not isinstance(data, list):
|
|
49
|
-
return '
|
|
53
|
+
return self.i18n_service.t('errors.services.no_data_for_excel')
|
|
50
54
|
|
|
51
55
|
sheet_name = kwargs.get('sheet_name', 'hoja 1')
|
|
52
56
|
|
|
@@ -58,7 +62,7 @@ class ExcelService:
|
|
|
58
62
|
|
|
59
63
|
# 4. check that download directory is configured
|
|
60
64
|
if 'IATOOLKIT_DOWNLOAD_DIR' not in current_app.config:
|
|
61
|
-
return '
|
|
65
|
+
return self.i18n_service.t('errors.services.no_download_directory')
|
|
62
66
|
|
|
63
67
|
download_dir = current_app.config['IATOOLKIT_DOWNLOAD_DIR']
|
|
64
68
|
filepath = Path(download_dir) / token
|
|
@@ -77,28 +81,28 @@ class ExcelService:
|
|
|
77
81
|
|
|
78
82
|
except Exception as e:
|
|
79
83
|
raise IAToolkitException(IAToolkitException.ErrorType.CALL_ERROR,
|
|
80
|
-
'
|
|
84
|
+
self.i18n_service.t('errors.services.cannot_create_excel')) from e
|
|
81
85
|
|
|
82
86
|
def validate_file_access(self, filename):
|
|
83
87
|
try:
|
|
84
88
|
if not filename:
|
|
85
|
-
return jsonify({"error":
|
|
89
|
+
return jsonify({"error": self.i18n_service.t('errors.services.invalid_filename')})
|
|
86
90
|
# Prevent path traversal attacks
|
|
87
91
|
if '..' in filename or filename.startswith('/') or '\\' in filename:
|
|
88
|
-
return jsonify({"error":
|
|
92
|
+
return jsonify({"error": self.i18n_service.t('errors.services.invalid_filename')})
|
|
89
93
|
|
|
90
94
|
temp_dir = os.path.join(current_app.root_path, 'static', 'temp')
|
|
91
95
|
file_path = os.path.join(temp_dir, filename)
|
|
92
96
|
|
|
93
97
|
if not os.path.exists(file_path):
|
|
94
|
-
return jsonify({"error":
|
|
98
|
+
return jsonify({"error": self.i18n_service.t('errors.services.file_not_exist')})
|
|
95
99
|
|
|
96
100
|
if not os.path.isfile(file_path):
|
|
97
|
-
return jsonify({"error":
|
|
101
|
+
return jsonify({"error": self.i18n_service.t('errors.services.path_is_not_a_file')})
|
|
98
102
|
|
|
99
103
|
return None
|
|
100
104
|
|
|
101
105
|
except Exception as e:
|
|
102
|
-
error_msg = f"
|
|
106
|
+
error_msg = f"File validation error {filename}: {str(e)}"
|
|
103
107
|
logging.error(error_msg)
|
|
104
|
-
return jsonify({"error":
|
|
108
|
+
return jsonify({"error": self.i18n_service.t('errors.services.file_validation_error')})
|
|
@@ -52,27 +52,19 @@ class FileProcessor:
|
|
|
52
52
|
logger: Optional[logging.Logger] = None):
|
|
53
53
|
self.connector = connector
|
|
54
54
|
self.config = config
|
|
55
|
-
self.logger = logger or self._setup_logger()
|
|
56
55
|
self.processed_files = 0
|
|
57
56
|
|
|
58
|
-
def _setup_logger(self):
|
|
59
|
-
logging.basicConfig(
|
|
60
|
-
filename=self.config.log_file,
|
|
61
|
-
level=logging.INFO,
|
|
62
|
-
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
63
|
-
)
|
|
64
|
-
return logging.getLogger(__name__)
|
|
65
57
|
|
|
66
58
|
def process_files(self):
|
|
67
59
|
# Fetches files from the connector, filters them, and processes them.
|
|
68
60
|
try:
|
|
69
61
|
files = self.connector.list_files()
|
|
70
62
|
except Exception as e:
|
|
71
|
-
|
|
63
|
+
logging.error(f"Error fetching files: {e}")
|
|
72
64
|
return False
|
|
73
65
|
|
|
74
66
|
if self.config.echo:
|
|
75
|
-
print(f'
|
|
67
|
+
print(f'loading {len(files)} files')
|
|
76
68
|
|
|
77
69
|
for file_info in files:
|
|
78
70
|
file_path = file_info['path']
|
|
@@ -95,10 +87,10 @@ class FileProcessor:
|
|
|
95
87
|
context=self.config.context)
|
|
96
88
|
self.processed_files += 1
|
|
97
89
|
|
|
98
|
-
|
|
90
|
+
logging.info(f"Successfully processed file: {file_path}")
|
|
99
91
|
|
|
100
92
|
except Exception as e:
|
|
101
|
-
|
|
93
|
+
logging.error(f"Error processing {file_path}: {e}")
|
|
102
94
|
if not self.config.continue_on_error:
|
|
103
95
|
raise e
|
|
104
96
|
|
|
@@ -27,4 +27,4 @@ class HelpContentService:
|
|
|
27
27
|
except Exception as e:
|
|
28
28
|
logging.exception(e)
|
|
29
29
|
raise IAToolkitException(IAToolkitException.ErrorType.CONFIG_ERROR,
|
|
30
|
-
f"Error
|
|
30
|
+
f"Error getting help file for {company_short_name}: {str(e)}") from e
|
|
@@ -6,32 +6,33 @@
|
|
|
6
6
|
from injector import inject
|
|
7
7
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
8
8
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
9
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class HistoryService:
|
|
12
14
|
@inject
|
|
13
15
|
def __init__(self, llm_query_repo: LLMQueryRepo,
|
|
14
|
-
profile_repo: ProfileRepo
|
|
16
|
+
profile_repo: ProfileRepo,
|
|
17
|
+
i18n_service: I18nService):
|
|
15
18
|
self.llm_query_repo = llm_query_repo
|
|
16
19
|
self.profile_repo = profile_repo
|
|
20
|
+
self.i18n_service = i18n_service
|
|
17
21
|
|
|
18
22
|
def get_history(self,
|
|
19
23
|
company_short_name: str,
|
|
20
24
|
user_identifier: str) -> dict:
|
|
21
25
|
try:
|
|
22
|
-
# validate company
|
|
23
26
|
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
24
27
|
if not company:
|
|
25
|
-
return {
|
|
28
|
+
return {"error": self.i18n_service.t('errors.company_not_found', company_short_name=company_short_name)}
|
|
26
29
|
|
|
27
30
|
history = self.llm_query_repo.get_history(company, user_identifier)
|
|
28
|
-
|
|
29
31
|
if not history:
|
|
30
|
-
return {'message': '
|
|
32
|
+
return {'message': 'empty history', 'history': []}
|
|
31
33
|
|
|
32
34
|
history_list = [query.to_dict() for query in history]
|
|
33
|
-
|
|
34
|
-
return {'message': 'Historial obtenido correctamente', 'history': history_list}
|
|
35
|
+
return {'message': 'history loaded ok', 'history': history_list}
|
|
35
36
|
|
|
36
37
|
except Exception as e:
|
|
37
38
|
return {'error': str(e)}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# iatoolkit/services/i18n_service.py
|
|
2
2
|
import os
|
|
3
3
|
import logging
|
|
4
|
-
from injector import inject
|
|
4
|
+
from injector import inject, singleton
|
|
5
5
|
from iatoolkit.common.util import Utility
|
|
6
6
|
from iatoolkit.services.language_service import LanguageService
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
@singleton
|
|
9
9
|
class I18nService:
|
|
10
10
|
"""
|
|
11
11
|
Servicio centralizado para manejar la internacionalización (i18n).
|
|
@@ -27,7 +27,7 @@ class I18nService:
|
|
|
27
27
|
"""
|
|
28
28
|
locales_dir = os.path.join(os.path.dirname(__file__), '..', 'locales')
|
|
29
29
|
if not os.path.exists(locales_dir):
|
|
30
|
-
logging.error("
|
|
30
|
+
logging.error("Directory 'locales' not found.")
|
|
31
31
|
return
|
|
32
32
|
|
|
33
33
|
for filename in os.listdir(locales_dir):
|
|
@@ -37,7 +37,7 @@ class I18nService:
|
|
|
37
37
|
try:
|
|
38
38
|
self.translations[lang_code] = self.util.load_schema_from_yaml(filepath)
|
|
39
39
|
except Exception as e:
|
|
40
|
-
logging.error(f"
|
|
40
|
+
logging.error(f"Error while loading the translation file {filepath}: {e}")
|
|
41
41
|
|
|
42
42
|
def _get_nested_key(self, lang: str, key: str):
|
|
43
43
|
"""
|
|
@@ -20,8 +20,8 @@ class JWTService:
|
|
|
20
20
|
self.secret_key = app.config['JWT_SECRET_KEY']
|
|
21
21
|
self.algorithm = app.config['JWT_ALGORITHM']
|
|
22
22
|
except KeyError as e:
|
|
23
|
-
logging.error(f"
|
|
24
|
-
raise RuntimeError(f"
|
|
23
|
+
logging.error(f"missing JWT configuration: {e}.")
|
|
24
|
+
raise RuntimeError(f"missing JWT configuration variables: {e}")
|
|
25
25
|
|
|
26
26
|
def generate_chat_jwt(self,
|
|
27
27
|
company_short_name: str,
|
|
@@ -58,25 +58,23 @@ class JWTService:
|
|
|
58
58
|
|
|
59
59
|
# Validaciones adicionales
|
|
60
60
|
if payload.get('type') != 'chat_session':
|
|
61
|
-
logging.warning(f"
|
|
61
|
+
logging.warning(f"Invalid JWT type '{payload.get('type')}'")
|
|
62
62
|
return None
|
|
63
63
|
|
|
64
64
|
# user_identifier debe estar presente
|
|
65
65
|
if not payload.get('user_identifier'):
|
|
66
|
-
logging.warning(f"
|
|
66
|
+
logging.warning(f"missing user_identifier in JWT payload.")
|
|
67
67
|
return None
|
|
68
68
|
|
|
69
69
|
if not payload.get('company_short_name'):
|
|
70
|
-
logging.warning(f"
|
|
70
|
+
logging.warning(f"missing company_short_name in JWT payload.")
|
|
71
71
|
return None
|
|
72
72
|
|
|
73
|
-
logging.debug(
|
|
74
|
-
f"JWT validado exitosamente para company: {payload.get('company_short_name')}, user: {payload.get('external_user_id')}")
|
|
75
73
|
return payload
|
|
76
74
|
|
|
77
75
|
except jwt.InvalidTokenError as e:
|
|
78
|
-
logging.warning(f"
|
|
76
|
+
logging.warning(f"Invalid JWT token:: {e}")
|
|
79
77
|
return None
|
|
80
78
|
except Exception as e:
|
|
81
|
-
logging.error(f"
|
|
79
|
+
logging.error(f"unexpected error during JWT validation: {e}")
|
|
82
80
|
return None
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# iatoolkit/services/language_service.py
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from injector import inject
|
|
4
|
+
from injector import inject, singleton
|
|
5
5
|
from flask import g, request
|
|
6
6
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
7
7
|
from iatoolkit.common.session_manager import SessionManager
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
@singleton
|
|
10
10
|
class LanguageService:
|
|
11
11
|
"""
|
|
12
12
|
Determines the correct language for the current request
|
|
@@ -14,6 +14,8 @@ class LanguageService:
|
|
|
14
14
|
and caches it in the Flask 'g' object for the request's lifecycle.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
FALLBACK_LANGUAGE = 'es'
|
|
18
|
+
|
|
17
19
|
@inject
|
|
18
20
|
def __init__(self, profile_repo: ProfileRepo):
|
|
19
21
|
self.profile_repo = profile_repo
|
|
@@ -49,29 +51,29 @@ class LanguageService:
|
|
|
49
51
|
if 'lang' in g:
|
|
50
52
|
return g.lang
|
|
51
53
|
|
|
52
|
-
from iatoolkit.services.i18n_service import I18nService
|
|
53
|
-
lang = I18nService.FALLBACK_LANGUAGE
|
|
54
|
-
|
|
55
54
|
try:
|
|
55
|
+
# Priority 1: User's preferred language
|
|
56
|
+
user_identifier = SessionManager.get('user_identifier')
|
|
57
|
+
if user_identifier:
|
|
58
|
+
user = self.profile_repo.get_user_by_email(user_identifier)
|
|
59
|
+
if user and user.preferred_language:
|
|
60
|
+
logging.debug(f"Language determined by user preference: {user.preferred_language}")
|
|
61
|
+
g.lang = user.preferred_language
|
|
62
|
+
return g.lang
|
|
63
|
+
|
|
64
|
+
# Priority 2: Company's default language
|
|
56
65
|
company_short_name = self._get_company_short_name()
|
|
57
66
|
if company_short_name:
|
|
58
|
-
# Prioridad 1: Preferencia del Usuario
|
|
59
|
-
user_identifier = SessionManager.get('user_identifier')
|
|
60
|
-
if user_identifier:
|
|
61
|
-
# Usamos el repositorio para obtener el objeto User
|
|
62
|
-
user = self.profile_repo.get_user_by_email(
|
|
63
|
-
user_identifier) # Asumiendo que el email es el identificador
|
|
64
|
-
if user and user.preferred_language:
|
|
65
|
-
g.lang = user.preferred_language
|
|
66
|
-
return g.lang
|
|
67
|
-
|
|
68
|
-
# Prioridad 2: Idioma por defecto de la Compañía (si no se encontró preferencia de usuario)
|
|
69
67
|
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
70
68
|
if company and company.default_language:
|
|
71
|
-
|
|
69
|
+
logging.debug(f"Language determined by company default: {company.default_language}")
|
|
70
|
+
g.lang = company.default_language
|
|
71
|
+
return g.lang
|
|
72
72
|
except Exception as e:
|
|
73
|
-
logging.
|
|
73
|
+
logging.info(f"Could not determine language, falling back to default. Reason: {e}")
|
|
74
74
|
pass
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
# Priority 3: System-wide fallback
|
|
77
|
+
logging.info(f"Language determined by system fallback: {self.FALLBACK_LANGUAGE}")
|
|
78
|
+
g.lang = self.FALLBACK_LANGUAGE
|
|
79
|
+
return g.lang
|
|
@@ -72,7 +72,7 @@ class LoadDocumentsService:
|
|
|
72
72
|
"""
|
|
73
73
|
if not connector_config:
|
|
74
74
|
raise IAToolkitException(IAToolkitException.ErrorType.MISSING_PARAMETER,
|
|
75
|
-
f"
|
|
75
|
+
f"Missing connector config")
|
|
76
76
|
|
|
77
77
|
try:
|
|
78
78
|
if not filters:
|
|
@@ -123,7 +123,7 @@ class LoadDocumentsService:
|
|
|
123
123
|
|
|
124
124
|
if not company:
|
|
125
125
|
raise IAToolkitException(IAToolkitException.ErrorType.MISSING_PARAMETER,
|
|
126
|
-
f"
|
|
126
|
+
f"missing company")
|
|
127
127
|
|
|
128
128
|
# check if file exist in repositories
|
|
129
129
|
if self.doc_repo.get(company_id=company.id,filename=filename):
|
|
@@ -182,6 +182,6 @@ class LoadDocumentsService:
|
|
|
182
182
|
self.doc_repo.session.rollback()
|
|
183
183
|
|
|
184
184
|
# if something fails, throw exception
|
|
185
|
-
logging.exception("Error
|
|
185
|
+
logging.exception("Error processing file %s: %s", filename, str(e))
|
|
186
186
|
raise IAToolkitException(IAToolkitException.ErrorType.LOAD_DOCUMENT_ERROR,
|
|
187
|
-
f"Error
|
|
187
|
+
f"Error while processing file: {filename}")
|