iatoolkit 0.71.4__py3-none-any.whl → 0.91.1__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.
Files changed (86) hide show
  1. iatoolkit/__init__.py +15 -5
  2. iatoolkit/base_company.py +4 -58
  3. iatoolkit/cli_commands.py +6 -7
  4. iatoolkit/common/exceptions.py +1 -0
  5. iatoolkit/common/routes.py +12 -28
  6. iatoolkit/common/util.py +7 -1
  7. iatoolkit/company_registry.py +50 -14
  8. iatoolkit/{iatoolkit.py → core.py} +54 -55
  9. iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
  10. iatoolkit/infra/llm_client.py +9 -5
  11. iatoolkit/locales/en.yaml +10 -2
  12. iatoolkit/locales/es.yaml +171 -162
  13. iatoolkit/repositories/database_manager.py +59 -14
  14. iatoolkit/repositories/llm_query_repo.py +34 -22
  15. iatoolkit/repositories/models.py +16 -18
  16. iatoolkit/repositories/profile_repo.py +5 -10
  17. iatoolkit/repositories/vs_repo.py +9 -4
  18. iatoolkit/services/auth_service.py +1 -1
  19. iatoolkit/services/branding_service.py +1 -1
  20. iatoolkit/services/company_context_service.py +19 -11
  21. iatoolkit/services/configuration_service.py +219 -46
  22. iatoolkit/services/dispatcher_service.py +31 -225
  23. iatoolkit/services/document_service.py +10 -1
  24. iatoolkit/services/embedding_service.py +9 -6
  25. iatoolkit/services/excel_service.py +50 -2
  26. iatoolkit/services/history_manager_service.py +189 -0
  27. iatoolkit/services/jwt_service.py +1 -1
  28. iatoolkit/services/language_service.py +8 -2
  29. iatoolkit/services/license_service.py +82 -0
  30. iatoolkit/services/mail_service.py +171 -25
  31. iatoolkit/services/profile_service.py +37 -32
  32. iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +110 -1
  33. iatoolkit/services/query_service.py +192 -191
  34. iatoolkit/services/sql_service.py +63 -12
  35. iatoolkit/services/tool_service.py +231 -0
  36. iatoolkit/services/user_feedback_service.py +18 -6
  37. iatoolkit/services/user_session_context_service.py +18 -0
  38. iatoolkit/static/images/iatoolkit_core.png +0 -0
  39. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  40. iatoolkit/static/js/chat_feedback_button.js +1 -1
  41. iatoolkit/static/js/chat_help_content.js +4 -4
  42. iatoolkit/static/js/chat_main.js +17 -5
  43. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  44. iatoolkit/static/styles/chat_iatoolkit.css +1 -1
  45. iatoolkit/static/styles/chat_public.css +28 -0
  46. iatoolkit/static/styles/documents.css +598 -0
  47. iatoolkit/static/styles/landing_page.css +223 -7
  48. iatoolkit/system_prompts/__init__.py +0 -0
  49. iatoolkit/system_prompts/query_main.prompt +2 -1
  50. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  51. iatoolkit/templates/_company_header.html +30 -5
  52. iatoolkit/templates/_login_widget.html +3 -3
  53. iatoolkit/templates/chat.html +1 -1
  54. iatoolkit/templates/forgot_password.html +3 -2
  55. iatoolkit/templates/onboarding_shell.html +1 -1
  56. iatoolkit/templates/signup.html +3 -0
  57. iatoolkit/views/base_login_view.py +1 -1
  58. iatoolkit/views/change_password_view.py +1 -1
  59. iatoolkit/views/forgot_password_view.py +9 -4
  60. iatoolkit/views/history_api_view.py +3 -3
  61. iatoolkit/views/home_view.py +4 -2
  62. iatoolkit/views/init_context_api_view.py +1 -1
  63. iatoolkit/views/llmquery_api_view.py +4 -3
  64. iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +1 -1
  65. iatoolkit/views/login_view.py +17 -5
  66. iatoolkit/views/logout_api_view.py +10 -2
  67. iatoolkit/views/prompt_api_view.py +1 -1
  68. iatoolkit/views/root_redirect_view.py +22 -0
  69. iatoolkit/views/signup_view.py +12 -4
  70. iatoolkit/views/static_page_view.py +27 -0
  71. iatoolkit/views/verify_user_view.py +1 -1
  72. iatoolkit-0.91.1.dist-info/METADATA +268 -0
  73. iatoolkit-0.91.1.dist-info/RECORD +125 -0
  74. iatoolkit-0.91.1.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  75. iatoolkit/services/history_service.py +0 -37
  76. iatoolkit/templates/about.html +0 -13
  77. iatoolkit/templates/index.html +0 -145
  78. iatoolkit/templates/login_simulation.html +0 -45
  79. iatoolkit/views/external_login_view.py +0 -73
  80. iatoolkit/views/index_view.py +0 -14
  81. iatoolkit/views/login_simulation_view.py +0 -93
  82. iatoolkit-0.71.4.dist-info/METADATA +0 -276
  83. iatoolkit-0.71.4.dist-info/RECORD +0 -122
  84. {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/WHEEL +0 -0
  85. {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/licenses/LICENSE +0 -0
  86. {iatoolkit-0.71.4.dist-info → iatoolkit-0.91.1.dist-info}/top_level.txt +0 -0
iatoolkit/locales/es.yaml CHANGED
@@ -1,163 +1,172 @@
1
- # locales/es.yaml
2
- ui:
3
- login_widget:
4
- title: "Iniciar Sesión"
5
- welcome_message: "Ingresa tus credenciales o registrate para acceder a la plataforma IA de Sample Company."
6
- email_placeholder: "Correo electrónico"
7
- login_button: "Acceder"
8
- forgot_password_link: "¿Olvidaste tu contraseña?"
9
- no_account_prompt: "¿No tienes una cuenta?"
10
- signup_link: "Regístrate"
11
-
12
- signup:
13
- title: "Crear una Cuenta"
14
- subtitle: "Comienza tu viaje con nosotros hoy."
15
- first_name_label: "Nombre"
16
- last_name_label: "Apellido"
17
- email_label: "Correo Electrónico"
18
- password_label: "Contraseña"
19
- confirm_password_label: "Confirmar Contraseña"
20
- signup_button: "Crear Cuenta"
21
- already_have_account: "¿Ya tienes una cuenta?"
22
- login_link: "Inicia Sesión"
23
- disclaimer: "🔒 Valoramos tu privacidad. Tus datos se usarán exclusivamente para el funcionamiento de la plataforma."
24
-
25
- forgot_password:
26
- title: "Recuperar Contraseña"
27
- subtitle: "Ingresa tu correo electrónico y te enviaremos un enlace para restablecer tu contraseña."
28
- submit_button: "Enviar Enlace de Recuperación"
29
- back_to_login: "Volver a Iniciar Sesión"
30
-
31
- change_password:
32
- title: "Crear Nueva Contraseña"
33
- subtitle: "Estás cambiando la contraseña para <strong>{email}</strong>."
34
- temp_code_label: "Código Temporal"
35
- temp_code_placeholder: "Revisa tu correo electrónico"
36
- new_password_label: "Nueva Contraseña"
37
- password_instructions: "Debe contener al menos 8 caracteres, mayúscula, minúscula, número y un carácter especial."
38
- confirm_password_label: "Confirmar Nueva Contraseña"
39
- save_button: "Guardar Contraseña"
40
- back_to_home: "Volver al inicio"
41
-
42
- chat:
43
- welcome_message: "¡Hola! ¿En qué te puedo ayudar hoy?"
44
- input_placeholder: "Escribe tu consulta aquí..."
45
- prompts_available: "Prompts disponibles"
46
-
47
- tooltips:
48
- history: "Historial con mis consultas"
49
- reload_context: "Forzar Recarga de Contexto"
50
- feedback: "Tu feedback es muy importante"
51
- usage_guide: "Guía de Uso"
52
- onboarding: "Cómo preguntar mejor"
53
- logout: "Cerrar sesión"
54
- use_prompt_assistant: "Usar Asistente de Prompts"
55
- attach_files: "Adjuntar archivos"
56
- view_attached_files: "Ver archivos adjuntos"
57
- modals:
58
- files_title: "Archivos Cargados"
59
- feedback_title: "Tu Opinión es Importante"
60
- feedback_prompt: "¿Qué tan útil fue la respuesta del asistente?"
61
- feedback_comment_label: "Tu comentario nos ayuda a mejorar:"
62
- feedback_comment_placeholder: "Escribe aquí tu opinión, sugerencias o comentarios..."
63
- history_title: "Historial de Consultas"
64
- history_table_date: "Fecha"
65
- history_table_query: "Consulta"
66
- loading_history: "Cargando historial..."
67
- no_history_found: "No se encontró historial de consultas."
68
- help_title: "Guía de uso del Asistente IA"
69
-
70
- buttons:
71
- cancel: "Cerrar"
72
- send: "Enviar"
73
- stop: "Detener"
74
-
75
- errors:
76
- company_not_found: "La empresa {company_short_name} no existe."
77
- timeout: "El tiempo de espera ha expirado."
78
-
79
-
80
- auth:
81
- invalid_password: "La contraseña proporcionada es incorrecta."
82
- user_not_found: "No se encontró un usuario con ese correo."
83
- invalid_or_expired_token: "Token inválido o expirado."
84
- session_creation_failed: "No se pudo crear la sesión del usuario."
85
- authentication_required: "Autenticación requerida. No se proporcionó cookie de sesión o clave de API."
86
- invalid_api_key: "Clave de API inválida o inactiva."
87
- no_user_identifier_api: "No se proporcionó user_identifier para la llamada a la API."
88
- templates:
89
- company_not_found: "Empresa no encontrada."
90
- home_template_not_found: "La plantilla de la página de inicio para la empresa '{company_short_name}' no está configurada."
91
- processing_error: "Error al procesar el template: {error}"
92
- template_not_found: "No se encontro el template: '{template_name}'."
93
-
94
- general:
95
- unexpected_error: "Ha ocurrido un error inesperado: {error}."
96
- unsupported_language: "El idioma seleccionado no es válido."
97
- signup:
98
- company_not_found: "La empresa {company_short_name} no existe."
99
- incorrect_password_for_existing_user: "La contraseña para el usuario {email} es incorrecta."
100
- user_already_registered: "El usuario con email '{email}' ya existe en esta empresa."
101
- password_mismatch: "Las contraseñas no coinciden. Por favor, inténtalo de nuevo."
102
- change_password:
103
- token_expired: "El enlace de cambio de contraseña ha expirado. Por favor, solicita uno nuevo."
104
- password_mismatch: "Las contraseñas no coinciden. Por favor, inténtalo nuevamente."
105
- invalid_temp_code: "El código temporal no es válido. Por favor, verifica o solicita uno nuevo."
106
- forgot_password:
107
- user_not_registered: "El usuario con correo {email} no está registrado."
108
- verification:
109
- token_expired: "El enlace de verificación ha expirado. Por favor, contacta a soporte si necesitas uno nuevo."
110
- user_not_found: "El usuario que intentas verificar no existe."
111
- validation:
112
- password_too_short: "La contraseña debe tener al menos 8 caracteres."
113
- password_no_uppercase: "La contraseña debe tener al menos una letra mayúscula."
114
- password_no_lowercase: "La contraseña debe tener al menos una letra minúscula."
115
- password_no_digit: "La contraseña debe tener al menos un número."
116
- password_no_special_char: "La contraseña debe tener al menos un carácter especial."
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
-
132
- api_responses:
133
- context_reloaded_success: "El contexto se ha recargado con éxito."
134
-
135
- services:
136
- mail_sent: "mail enviado exitosamente."
137
- start_query: "Hola, cual es tu pregunta?"
138
-
139
- flash_messages:
140
- password_changed_success: "Tu contraseña ha sido restablecida exitosamente. Ahora puedes iniciar sesión."
141
- signup_success: "Registro exitoso. Por favor, revisa tu correo para verificar tu cuenta."
142
- user_associated_success: "Usuario existente asociado exitosamente a la nueva empresa."
143
- account_verified_success: "Tu cuenta ha sido verificada exitosamente. ¡Bienvenido!"
144
- forgot_password_success: "Si tu correo está registrado, recibirás un enlace para restablecer tu contraseña."
145
-
146
- # Claves específicas para JavaScript
147
- js_messages:
148
- feedback_sent_success_title: "Feedback Enviado"
149
- feedback_sent_success_body: "¡Gracias por tu comentario!"
150
- feedback_sent_error: "No se pudo enviar el feedback, por favor intente nuevamente."
151
- feedback_rating_error: "Por favor, califica al asistente con las estrellas."
152
- feedback_comment_error: "Por favor, escribe tu comentario antes de enviar."
153
- context_reloaded: "El contexto ha sido recargado."
154
- error_loading_history: "Ocurrió un error al cargar el historial."
155
- request_aborted: "La generación de la respuesta ha sido detenida."
156
- processing_error: "Ocurrió un error al procesar la solicitud."
157
- server_comm_error: "Error de comunicación con el servidor ({status}). Por favor, intente de nuevo más tarde."
158
- network_error: "Ocurrió un error de red. Por favor, inténtalo de nuevo en unos momentos."
159
- unknown_server_error: "Error desconocido del servidor."
160
- loading: "Cargando..."
161
- reload_init: "Iniciando recarga de contexto en segundo plano..."
162
- no_history_found: "No existe historial de consultas."
1
+ # locales/es.yaml
2
+ ui:
3
+ login_widget:
4
+ title: "Iniciar Sesión"
5
+ welcome_message: "Ingresa tus credenciales o registrate para acceder a la plataforma."
6
+ email_placeholder: "Correo electrónico"
7
+ login_button: "Acceder"
8
+ forgot_password_link: "¿Olvidaste tu contraseña?"
9
+ no_account_prompt: "¿No tienes una cuenta?"
10
+ signup_link: "Regístrate"
11
+
12
+ signup:
13
+ title: "Crear una Cuenta"
14
+ subtitle: "Comienza tu viaje con nosotros hoy."
15
+ first_name_label: "Nombre"
16
+ last_name_label: "Apellido"
17
+ email_label: "Correo Electrónico"
18
+ password_label: "Contraseña"
19
+ confirm_password_label: "Confirmar Contraseña"
20
+ signup_button: "Crear Cuenta"
21
+ already_have_account: "¿Ya tienes una cuenta?"
22
+ login_link: "Inicia Sesión"
23
+ disclaimer: "🔒 Valoramos tu privacidad. Tus datos se usarán exclusivamente para el funcionamiento de la plataforma."
24
+
25
+ forgot_password:
26
+ title: "Recuperar Contraseña"
27
+ subtitle: "Ingresa tu correo electrónico y te enviaremos un enlace para restablecer tu contraseña."
28
+ submit_button: "Enviar Enlace de Recuperación"
29
+ back_to_login: "Volver a Iniciar Sesión"
30
+
31
+ change_password:
32
+ title: "Crear Nueva Contraseña"
33
+ subtitle: "Estás cambiando la contraseña para <strong>{email}</strong>."
34
+ temp_code_label: "Código Temporal"
35
+ temp_code_placeholder: "Revisa tu correo electrónico"
36
+ new_password_label: "Nueva Contraseña"
37
+ password_instructions: "Debe contener al menos 8 caracteres, mayúscula, minúscula, número y un carácter especial."
38
+ confirm_password_label: "Confirmar Nueva Contraseña"
39
+ save_button: "Guardar Contraseña"
40
+ back_to_home: "Volver al inicio"
41
+
42
+ chat:
43
+ welcome_message: "¡Hola! ¿En qué te puedo ayudar hoy?"
44
+ input_placeholder: "Escribe aquí..."
45
+ prompts_available: "Prompts disponibles"
46
+ init_context: "Inicializando el contexto de la IA ..."
47
+
48
+
49
+ tooltips:
50
+ history: "Historial con mis consultas"
51
+ reload_context: "Forzar Recarga de Contexto"
52
+ feedback: "Tu feedback es muy importante"
53
+ usage_guide: "Guía de Uso"
54
+ onboarding: "Cómo preguntar mejor"
55
+ logout: "Cerrar sesión"
56
+ use_prompt_assistant: "Usar Asistente de Prompts"
57
+ attach_files: "Adjuntar archivos"
58
+ view_attached_files: "Ver archivos adjuntos"
59
+ modals:
60
+ files_title: "Archivos Cargados"
61
+ feedback_title: "Tu Opinión es Importante"
62
+ feedback_prompt: "¿Qué tan útil fue la respuesta del asistente?"
63
+ feedback_comment_label: "Tu comentario nos ayuda a mejorar:"
64
+ feedback_comment_placeholder: "Escribe aquí tu opinión, sugerencias o comentarios..."
65
+ history_title: "Historial de Consultas"
66
+ history_table_date: "Fecha"
67
+ history_table_query: "Consulta"
68
+ loading_history: "Cargando historial..."
69
+ no_history_found: "No se encontró historial de consultas."
70
+ help_title: "Guía de uso del Asistente IA"
71
+
72
+ buttons:
73
+ cancel: "Cerrar"
74
+ send: "Enviar"
75
+ stop: "Detener"
76
+
77
+ errors:
78
+ company_not_found: "La empresa {company_short_name} no existe."
79
+ timeout: "El tiempo de espera ha expirado."
80
+
81
+
82
+ auth:
83
+ invalid_password: "La contraseña proporcionada es incorrecta."
84
+ user_not_found: "No se encontró un usuario con ese correo."
85
+ invalid_or_expired_token: "Token inválido o expirado."
86
+ session_creation_failed: "No se pudo crear la sesión del usuario."
87
+ authentication_required: "Autenticación requerida. No se proporcionó cookie de sesión o clave de API."
88
+ invalid_api_key: "Clave de API inválida o inactiva."
89
+ no_user_identifier_api: "No se proporcionó user_identifier para la llamada a la API."
90
+ templates:
91
+ company_not_found: "Empresa no encontrada."
92
+ home_template_not_found: "La plantilla de la página de inicio para la empresa '{company_short_name}' no está configurada."
93
+ processing_error: "Error al procesar el template: {error}"
94
+ template_not_found: "No se encontro el template: '{template_name}'."
95
+
96
+ general:
97
+ unexpected_error: "Ha ocurrido un error inesperado: {error}."
98
+ unsupported_language: "El idioma seleccionado no es válido."
99
+ signup:
100
+ company_not_found: "La empresa {company_short_name} no existe."
101
+ incorrect_password_for_existing_user: "La contraseña para el usuario {email} es incorrecta."
102
+ user_already_registered: "El usuario con email '{email}' ya existe en esta empresa."
103
+ password_mismatch: "Las contraseñas no coinciden. Por favor, inténtalo de nuevo."
104
+ change_password:
105
+ token_expired: "El enlace de cambio de contraseña ha expirado. Por favor, solicita uno nuevo."
106
+ password_mismatch: "Las contraseñas no coinciden. Por favor, inténtalo nuevamente."
107
+ invalid_temp_code: "El código temporal no es válido. Por favor, verifica o solicita uno nuevo."
108
+ forgot_password:
109
+ user_not_registered: "El usuario con correo {email} no está registrado."
110
+ verification:
111
+ token_expired: "El enlace de verificación ha expirado. Por favor, contacta a soporte si necesitas uno nuevo."
112
+ user_not_found: "El usuario que intentas verificar no existe."
113
+ validation:
114
+ password_too_short: "La contraseña debe tener al menos 8 caracteres."
115
+ password_no_uppercase: "La contraseña debe tener al menos una letra mayúscula."
116
+ password_no_lowercase: "La contraseña debe tener al menos una letra minúscula."
117
+ password_no_digit: "La contraseña debe tener al menos un número."
118
+ password_no_special_char: "La contraseña debe tener al menos un carácter especial."
119
+
120
+ services:
121
+ no_text_file: "El archivo no es texto o la codificación no es UTF-8"
122
+ no_output_file: "falta el nombre del archivo de salida"
123
+ no_data_for_excel: "faltan los datos o no es una lista de diccionarios"
124
+ no_download_directory: "no esta configurado el directorio temporal para guardar excels"
125
+ cannot_create_excel: "no se pudo crear el archivo excel"
126
+ invalid_filename: "Nombre de archivo inválido"
127
+ file_not_exist : "Archivo no encontrado"
128
+ path_is_not_a_file : "La ruta no corresponde a un archivo"
129
+ file_validation_error : "Error validando archivo"
130
+ user_not_authorized: "Usuario no esta autorizado para esta empresa"
131
+ account_not_verified: "Tu cuenta no ha sido verificada. Por favor, revisa tu correo."
132
+ missing_response_id: "No se encontró 'previous_response_id' para '{company_short_name}/{user_identifier}'. Reinicia el contexto."
133
+ context_rebuild_failed: "No se pudo reconstruir el contexto de la empresa."
134
+ cannot_read_excel: "No se pudo leer el archivo excel"
135
+ cannot_read_csv: "No se pudo leer el archivo CSV"
136
+
137
+
138
+ api_responses:
139
+ context_reloaded_success: "El contexto se ha recargado con éxito."
140
+
141
+ services:
142
+ mail_sent: "mail enviado exitosamente."
143
+ start_query: "Hola, cual es tu pregunta?"
144
+ mail_change_password: "se envio mail para cambio de clave"
145
+
146
+ flash_messages:
147
+ password_changed_success: "Tu contraseña ha sido restablecida exitosamente. Ahora puedes iniciar sesión."
148
+ signup_success: "Registro exitoso. Por favor, revisa tu correo para verificar tu cuenta."
149
+ signup_success_no_verification: "Registro exitoso."
150
+ user_associated_success: "Usuario existente asociado exitosamente a la nueva empresa."
151
+ account_verified_success: "Tu cuenta ha sido verificada exitosamente. ¡Bienvenido!"
152
+ forgot_password_success: "Si tu correo está registrado, recibirás un enlace para restablecer tu contraseña."
153
+
154
+ # Claves específicas para JavaScript
155
+ js_messages:
156
+ feedback_sent_success_title: "Feedback Enviado"
157
+ feedback_sent_success_body: "¡Gracias por tu comentario!"
158
+ feedback_sent_error: "No se pudo enviar el feedback, por favor intente nuevamente."
159
+ feedback_rating_error: "Por favor, califica al asistente con las estrellas."
160
+ feedback_comment_error: "Por favor, escribe tu comentario antes de enviar."
161
+ context_reloaded: "El contexto ha sido recargado."
162
+ error_loading_history: "Ocurrió un error al cargar el historial."
163
+ request_aborted: "La generación de la respuesta ha sido detenida."
164
+ processing_error: "Ocurrió un error al procesar la solicitud."
165
+ server_comm_error: "Error de comunicación con el servidor ({status}). Por favor, intente de nuevo más tarde."
166
+ network_error: "Ocurrió un error de red. Por favor, inténtalo de nuevo en unos momentos."
167
+ unknown_server_error: "Error desconocido del servidor."
168
+ loading: "Cargando..."
169
+ reload_init: "Iniciando recarga de contexto en segundo plano..."
170
+ no_history_found: "No existe historial de consultas."
171
+ example: "Ejemplo:"
163
172
 
@@ -4,7 +4,7 @@
4
4
  # IAToolkit is open source software.
5
5
 
6
6
  # database_manager.py
7
- from sqlalchemy import create_engine, event, inspect
7
+ from sqlalchemy import create_engine, event, inspect, text
8
8
  from sqlalchemy.orm import sessionmaker, scoped_session
9
9
  from sqlalchemy.engine.url import make_url
10
10
  from iatoolkit.repositories.models import Base
@@ -14,14 +14,26 @@ from pgvector.psycopg2 import register_vector
14
14
 
15
15
  class DatabaseManager:
16
16
  @inject
17
- def __init__(self, database_url: str, register_pgvector: bool = True):
17
+ def __init__(self,
18
+ database_url: str,
19
+ schema: str | None = None,
20
+ register_pgvector: bool = True):
18
21
  """
19
22
  Inicializa el gestor de la base de datos.
20
23
  :param database_url: URL de la base de datos.
24
+ :param schema: Esquema por defecto para la conexión (search_path).
21
25
  :param echo: Si True, habilita logs de SQL.
22
26
  """
27
+
28
+ self.schema = schema
29
+
30
+ # FIX HEROKU: replace postgres:// by postgresql:// for compatibility with SQLAlchemy 1.4+
31
+ if database_url and database_url.startswith("postgres://"):
32
+ database_url = database_url.replace("postgres://", "postgresql://", 1)
33
+
23
34
  self.url = make_url(database_url)
24
- if database_url.startswith('sqlite'): # for tests
35
+
36
+ if database_url.startswith('sqlite'):
25
37
  self._engine = create_engine(database_url, echo=False)
26
38
  else:
27
39
  self._engine = create_engine(
@@ -40,9 +52,30 @@ class DatabaseManager:
40
52
  expire_on_commit=False)
41
53
  self.scoped_session = scoped_session(self.SessionFactory)
42
54
 
43
- # REGISTRAR pgvector para cada nueva conexión solo en postgres
44
- if register_pgvector and self.url.get_backend_name() == 'postgresql':
45
- event.listen(self._engine, 'connect', self.on_connect)
55
+ # Register pgvector for each new connection
56
+ backend = self.url.get_backend_name()
57
+ if backend == 'postgresql' or backend == 'postgres':
58
+ if register_pgvector:
59
+ event.listen(self._engine, 'connect', self.on_connect)
60
+
61
+ # if there is a schema, configure the search_path for each connection
62
+ if self.schema:
63
+ event.listen(self._engine, 'checkout', self.set_search_path)
64
+
65
+ def set_search_path(self, dbapi_connection, connection_record, connection_proxy):
66
+ # Configure the search_path for this connection
67
+ cursor = dbapi_connection.cursor()
68
+
69
+ # The defined schema is first, and then public by default
70
+ try:
71
+ cursor.execute(f"SET search_path TO {self.schema}, public")
72
+ cursor.close()
73
+
74
+ # commit for persist the change in the session
75
+ dbapi_connection.commit()
76
+ except Exception:
77
+ # if failed, rollback to avoid invalidating the connection
78
+ dbapi_connection.rollback()
46
79
 
47
80
  @staticmethod
48
81
  def on_connect(dbapi_connection, connection_record):
@@ -62,6 +95,12 @@ class DatabaseManager:
62
95
  return self._engine
63
96
 
64
97
  def create_all(self):
98
+ # if there is a schema defined, make sure it exists before creating tables
99
+ backend = self.url.get_backend_name()
100
+ if self.schema and (backend == 'postgresql' or backend == 'postgres'):
101
+ with self._engine.begin() as conn:
102
+ conn.execute(text(f"CREATE SCHEMA IF NOT EXISTS {self.schema}"))
103
+
65
104
  Base.metadata.create_all(self._engine)
66
105
 
67
106
  def drop_all(self):
@@ -73,22 +112,24 @@ class DatabaseManager:
73
112
  def get_all_table_names(self) -> list[str]:
74
113
  # Returns a list of all table names in the database
75
114
  inspector = inspect(self._engine)
76
- return inspector.get_table_names()
115
+ return inspector.get_table_names(schema=self.schema)
77
116
 
78
117
  def get_table_schema(self,
79
118
  table_name: str,
80
- schema_name: str | None = None,
119
+ db_schema: str,
120
+ schema_object_name: str | None = None,
81
121
  exclude_columns: list[str] | None = None) -> str:
82
122
  inspector = inspect(self._engine)
83
123
 
84
- if table_name not in inspector.get_table_names():
85
- raise RuntimeError(f"Table '{table_name}' does not exist.")
124
+ # search the table in the specified schema
125
+ if table_name not in inspector.get_table_names(schema=db_schema):
126
+ raise RuntimeError(f"Table '{table_name}' does not exist in database schema '{db_schema}'.")
86
127
 
87
128
  if exclude_columns is None:
88
129
  exclude_columns = []
89
130
 
90
- # get all thre table columns
91
- columns = inspector.get_columns(table_name)
131
+ # get all the table columns
132
+ columns = inspector.get_columns(table_name, schema=db_schema)
92
133
 
93
134
  # construct a json dictionary with the table definition
94
135
  json_dict = {
@@ -96,8 +137,12 @@ class DatabaseManager:
96
137
  "description": f"Definición de la tabla {table_name}.",
97
138
  "fields": []
98
139
  }
99
- if schema_name:
100
- json_dict["description"] += f"Los detalles de cada campo están en el objeto **`{schema_name}`**."
140
+ if schema_object_name:
141
+ json_dict["description"] += f"Los detalles de cada campo están en el objeto **`{schema_object_name}`**."
142
+
143
+ if db_schema:
144
+ json_dict["schema"] = db_schema
145
+ json_dict["description"] += f"Pertenece al esquema **`{db_schema}`**."
101
146
 
102
147
  # now add every column to the json dictionary
103
148
  for col in columns:
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- from iatoolkit.repositories.models import LLMQuery, Function, Company, Prompt, PromptCategory
6
+ from iatoolkit.repositories.models import LLMQuery, Tool, Company, Prompt, PromptCategory
7
7
  from injector import inject
8
8
  from iatoolkit.repositories.database_manager import DatabaseManager
9
9
  from sqlalchemy import or_
@@ -13,38 +13,50 @@ class LLMQueryRepo:
13
13
  def __init__(self, db_manager: DatabaseManager):
14
14
  self.session = db_manager.get_session()
15
15
 
16
+ def commit(self):
17
+ self.session.commit()
18
+
19
+ def rollback(self):
20
+ self.session.rollback()
21
+
16
22
  def add_query(self, query: LLMQuery):
17
23
  self.session.add(query)
18
24
  self.session.commit()
19
25
  return query
20
26
 
21
27
 
22
- def get_company_functions(self, company: Company) -> list[Function]:
28
+ def get_company_tools(self, company: Company) -> list[Tool]:
23
29
  return (
24
- self.session.query(Function)
30
+ self.session.query(Tool)
25
31
  .filter(
26
- Function.is_active.is_(True),
32
+ Tool.is_active.is_(True),
27
33
  or_(
28
- Function.company_id == company.id,
29
- Function.system_function.is_(True)
34
+ Tool.company_id == company.id,
35
+ Tool.system_function.is_(True)
30
36
  )
31
37
  )
32
38
  .all()
33
39
  )
34
40
 
35
- def create_or_update_function(self, new_function: Function):
36
- function = self.session.query(Function).filter_by(company_id=new_function.company_id,
37
- name=new_function.name).first()
38
- if function:
39
- function.description = new_function.description
40
- function.parameters = new_function.parameters
41
- function.system_function = new_function.system_function
41
+ def delete_system_tools(self):
42
+ self.session.query(Tool).filter_by(system_function=True).delete(synchronize_session=False)
43
+
44
+ def create_or_update_tool(self, new_tool: Tool):
45
+ tool = self.session.query(Tool).filter_by(company_id=new_tool.company_id,
46
+ name=new_tool.name).first()
47
+ if tool:
48
+ tool.description = new_tool.description
49
+ tool.parameters = new_tool.parameters
50
+ tool.system_function = new_tool.system_function
42
51
  else:
43
- self.session.add(new_function)
44
- function = new_function
52
+ self.session.add(new_tool)
53
+ tool = new_tool
45
54
 
46
- self.session.commit()
47
- return function
55
+ self.session.flush()
56
+ return tool
57
+
58
+ def delete_tool(self, tool: Tool):
59
+ self.session.query(Tool).filter_by(id=tool.id).delete(synchronize_session=False)
48
60
 
49
61
  def create_or_update_prompt(self, new_prompt: Prompt):
50
62
  prompt = self.session.query(Prompt).filter_by(company_id=new_prompt.company_id,
@@ -53,7 +65,6 @@ class LLMQueryRepo:
53
65
  prompt.category_id = new_prompt.category_id
54
66
  prompt.description = new_prompt.description
55
67
  prompt.order = new_prompt.order
56
- prompt.active = new_prompt.active
57
68
  prompt.is_system_prompt = new_prompt.is_system_prompt
58
69
  prompt.filename = new_prompt.filename
59
70
  prompt.custom_fields = new_prompt.custom_fields
@@ -61,7 +72,7 @@ class LLMQueryRepo:
61
72
  self.session.add(new_prompt)
62
73
  prompt = new_prompt
63
74
 
64
- self.session.commit()
75
+ self.session.flush()
65
76
  return prompt
66
77
 
67
78
  def create_or_update_prompt_category(self, new_category: PromptCategory):
@@ -73,7 +84,7 @@ class LLMQueryRepo:
73
84
  self.session.add(new_category)
74
85
  category = new_category
75
86
 
76
- self.session.commit()
87
+ self.session.flush()
77
88
  return category
78
89
 
79
90
  def get_history(self, company: Company, user_identifier: str) -> list[LLMQuery]:
@@ -84,8 +95,9 @@ class LLMQueryRepo:
84
95
  def get_prompts(self, company: Company) -> list[Prompt]:
85
96
  return self.session.query(Prompt).filter_by(company_id=company.id, is_system_prompt=False).all()
86
97
 
98
+ def get_prompt_by_name(self, company: Company, prompt_name: str):
99
+ return self.session.query(Prompt).filter_by(company_id=company.id, name=prompt_name).first()
100
+
87
101
  def get_system_prompts(self) -> list[Prompt]:
88
102
  return self.session.query(Prompt).filter_by(is_system_prompt=True, active=True).order_by(Prompt.order).all()
89
103
 
90
- def get_prompt_by_name(self, company: Company, prompt_name: str):
91
- return self.session.query(Prompt).filter_by(company_id=company.id, name=prompt_name).first()