iatoolkit 0.3.9__py3-none-any.whl → 0.107.4__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.

Potentially problematic release.


This version of iatoolkit might be problematic. Click here for more details.

Files changed (150) hide show
  1. iatoolkit/__init__.py +27 -35
  2. iatoolkit/base_company.py +3 -35
  3. iatoolkit/cli_commands.py +18 -47
  4. iatoolkit/common/__init__.py +0 -0
  5. iatoolkit/common/exceptions.py +48 -0
  6. iatoolkit/common/interfaces/__init__.py +0 -0
  7. iatoolkit/common/interfaces/asset_storage.py +34 -0
  8. iatoolkit/common/interfaces/database_provider.py +39 -0
  9. iatoolkit/common/model_registry.py +159 -0
  10. iatoolkit/common/routes.py +138 -0
  11. iatoolkit/common/session_manager.py +26 -0
  12. iatoolkit/common/util.py +353 -0
  13. iatoolkit/company_registry.py +66 -29
  14. iatoolkit/core.py +514 -0
  15. iatoolkit/infra/__init__.py +5 -0
  16. iatoolkit/infra/brevo_mail_app.py +123 -0
  17. iatoolkit/infra/call_service.py +140 -0
  18. iatoolkit/infra/connectors/__init__.py +5 -0
  19. iatoolkit/infra/connectors/file_connector.py +17 -0
  20. iatoolkit/infra/connectors/file_connector_factory.py +57 -0
  21. iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
  22. iatoolkit/infra/connectors/google_drive_connector.py +68 -0
  23. iatoolkit/infra/connectors/local_file_connector.py +46 -0
  24. iatoolkit/infra/connectors/s3_connector.py +33 -0
  25. iatoolkit/infra/google_chat_app.py +57 -0
  26. iatoolkit/infra/llm_providers/__init__.py +0 -0
  27. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  28. iatoolkit/infra/llm_providers/gemini_adapter.py +350 -0
  29. iatoolkit/infra/llm_providers/openai_adapter.py +124 -0
  30. iatoolkit/infra/llm_proxy.py +268 -0
  31. iatoolkit/infra/llm_response.py +45 -0
  32. iatoolkit/infra/redis_session_manager.py +122 -0
  33. iatoolkit/locales/en.yaml +222 -0
  34. iatoolkit/locales/es.yaml +225 -0
  35. iatoolkit/repositories/__init__.py +5 -0
  36. iatoolkit/repositories/database_manager.py +187 -0
  37. iatoolkit/repositories/document_repo.py +33 -0
  38. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  39. iatoolkit/repositories/llm_query_repo.py +105 -0
  40. iatoolkit/repositories/models.py +279 -0
  41. iatoolkit/repositories/profile_repo.py +171 -0
  42. iatoolkit/repositories/vs_repo.py +150 -0
  43. iatoolkit/services/__init__.py +5 -0
  44. iatoolkit/services/auth_service.py +193 -0
  45. {services → iatoolkit/services}/benchmark_service.py +7 -7
  46. iatoolkit/services/branding_service.py +153 -0
  47. iatoolkit/services/company_context_service.py +214 -0
  48. iatoolkit/services/configuration_service.py +375 -0
  49. iatoolkit/services/dispatcher_service.py +134 -0
  50. {services → iatoolkit/services}/document_service.py +20 -8
  51. iatoolkit/services/embedding_service.py +148 -0
  52. iatoolkit/services/excel_service.py +156 -0
  53. {services → iatoolkit/services}/file_processor_service.py +36 -21
  54. iatoolkit/services/history_manager_service.py +208 -0
  55. iatoolkit/services/i18n_service.py +104 -0
  56. iatoolkit/services/jwt_service.py +80 -0
  57. iatoolkit/services/language_service.py +89 -0
  58. iatoolkit/services/license_service.py +82 -0
  59. iatoolkit/services/llm_client_service.py +438 -0
  60. iatoolkit/services/load_documents_service.py +174 -0
  61. iatoolkit/services/mail_service.py +213 -0
  62. {services → iatoolkit/services}/profile_service.py +200 -101
  63. iatoolkit/services/prompt_service.py +303 -0
  64. iatoolkit/services/query_service.py +467 -0
  65. iatoolkit/services/search_service.py +55 -0
  66. iatoolkit/services/sql_service.py +169 -0
  67. iatoolkit/services/tool_service.py +246 -0
  68. iatoolkit/services/user_feedback_service.py +117 -0
  69. iatoolkit/services/user_session_context_service.py +213 -0
  70. iatoolkit/static/images/fernando.jpeg +0 -0
  71. iatoolkit/static/images/iatoolkit_core.png +0 -0
  72. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  73. iatoolkit/static/js/chat_feedback_button.js +80 -0
  74. iatoolkit/static/js/chat_filepond.js +85 -0
  75. iatoolkit/static/js/chat_help_content.js +124 -0
  76. iatoolkit/static/js/chat_history_button.js +110 -0
  77. iatoolkit/static/js/chat_logout_button.js +36 -0
  78. iatoolkit/static/js/chat_main.js +401 -0
  79. iatoolkit/static/js/chat_model_selector.js +227 -0
  80. iatoolkit/static/js/chat_onboarding_button.js +103 -0
  81. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  82. iatoolkit/static/js/chat_reload_button.js +38 -0
  83. iatoolkit/static/styles/chat_iatoolkit.css +559 -0
  84. iatoolkit/static/styles/chat_modal.css +133 -0
  85. iatoolkit/static/styles/chat_public.css +135 -0
  86. iatoolkit/static/styles/documents.css +598 -0
  87. iatoolkit/static/styles/landing_page.css +398 -0
  88. iatoolkit/static/styles/llm_output.css +148 -0
  89. iatoolkit/static/styles/onboarding.css +176 -0
  90. iatoolkit/system_prompts/__init__.py +0 -0
  91. iatoolkit/system_prompts/query_main.prompt +30 -23
  92. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  93. iatoolkit/templates/_company_header.html +45 -0
  94. iatoolkit/templates/_login_widget.html +42 -0
  95. iatoolkit/templates/base.html +78 -0
  96. iatoolkit/templates/change_password.html +66 -0
  97. iatoolkit/templates/chat.html +337 -0
  98. iatoolkit/templates/chat_modals.html +185 -0
  99. iatoolkit/templates/error.html +51 -0
  100. iatoolkit/templates/forgot_password.html +51 -0
  101. iatoolkit/templates/onboarding_shell.html +106 -0
  102. iatoolkit/templates/signup.html +79 -0
  103. iatoolkit/views/__init__.py +5 -0
  104. iatoolkit/views/base_login_view.py +96 -0
  105. iatoolkit/views/change_password_view.py +116 -0
  106. iatoolkit/views/chat_view.py +76 -0
  107. iatoolkit/views/embedding_api_view.py +65 -0
  108. iatoolkit/views/forgot_password_view.py +75 -0
  109. iatoolkit/views/help_content_api_view.py +54 -0
  110. iatoolkit/views/history_api_view.py +56 -0
  111. iatoolkit/views/home_view.py +63 -0
  112. iatoolkit/views/init_context_api_view.py +74 -0
  113. iatoolkit/views/llmquery_api_view.py +59 -0
  114. iatoolkit/views/load_company_configuration_api_view.py +49 -0
  115. iatoolkit/views/load_document_api_view.py +65 -0
  116. iatoolkit/views/login_view.py +170 -0
  117. iatoolkit/views/logout_api_view.py +57 -0
  118. iatoolkit/views/profile_api_view.py +46 -0
  119. iatoolkit/views/prompt_api_view.py +37 -0
  120. iatoolkit/views/root_redirect_view.py +22 -0
  121. iatoolkit/views/signup_view.py +100 -0
  122. iatoolkit/views/static_page_view.py +27 -0
  123. iatoolkit/views/user_feedback_api_view.py +60 -0
  124. iatoolkit/views/users_api_view.py +33 -0
  125. iatoolkit/views/verify_user_view.py +60 -0
  126. iatoolkit-0.107.4.dist-info/METADATA +268 -0
  127. iatoolkit-0.107.4.dist-info/RECORD +132 -0
  128. iatoolkit-0.107.4.dist-info/licenses/LICENSE +21 -0
  129. iatoolkit-0.107.4.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  130. {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/top_level.txt +0 -1
  131. iatoolkit/iatoolkit.py +0 -413
  132. iatoolkit/system_prompts/arquitectura.prompt +0 -32
  133. iatoolkit-0.3.9.dist-info/METADATA +0 -252
  134. iatoolkit-0.3.9.dist-info/RECORD +0 -32
  135. services/__init__.py +0 -5
  136. services/api_service.py +0 -75
  137. services/dispatcher_service.py +0 -351
  138. services/excel_service.py +0 -98
  139. services/history_service.py +0 -45
  140. services/jwt_service.py +0 -91
  141. services/load_documents_service.py +0 -212
  142. services/mail_service.py +0 -62
  143. services/prompt_manager_service.py +0 -172
  144. services/query_service.py +0 -334
  145. services/search_service.py +0 -32
  146. services/sql_service.py +0 -42
  147. services/tasks_service.py +0 -188
  148. services/user_feedback_service.py +0 -67
  149. services/user_session_context_service.py +0 -85
  150. {iatoolkit-0.3.9.dist-info → iatoolkit-0.107.4.dist-info}/WHEEL +0 -0
@@ -1,351 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Producto: IAToolkit
3
- # Todos los derechos reservados.
4
- # En trámite de registro en el Registro de Propiedad Intelectual de Chile.
5
-
6
- from iatoolkit import current_iatoolkit
7
- from common.exceptions import IAToolkitException
8
- from services.prompt_manager_service import PromptService
9
- from services.api_service import ApiService
10
- from repositories.llm_query_repo import LLMQueryRepo
11
- from repositories.models import Company, Function
12
- from services.excel_service import ExcelService
13
- from services.mail_service import MailService
14
- from iatoolkit.company_registry import get_company_registry
15
- from common.session_manager import SessionManager
16
- from common.util import Utility
17
- from injector import inject
18
- import logging
19
- import os
20
-
21
-
22
- class Dispatcher:
23
- @inject
24
- def __init__(self,
25
- prompt_service: PromptService,
26
- llmquery_repo: LLMQueryRepo,
27
- util: Utility,
28
- api_service: ApiService,
29
- excel_service: ExcelService,
30
- mail_service: MailService):
31
- self.prompt_service = prompt_service
32
- self.llmquery_repo = llmquery_repo
33
- self.api_service = api_service
34
- self.util = util
35
- self.excel_service = excel_service
36
- self.mail_service = mail_service
37
- self.system_functions = _FUNCTION_LIST
38
- self.system_prompts = _SYSTEM_PROMPT
39
-
40
- # Use the global registry
41
- self.company_registry = get_company_registry()
42
-
43
- # load into the dispatcher the configured companies
44
- self.company_classes = {}
45
- self.initialize_companies()
46
-
47
- # run the statrtup logic for all companies
48
- self.start_execution()
49
-
50
- self.tool_handlers = {
51
- "iat_generate_excel": self.excel_service.excel_generator,
52
- "iat_send_email": self.mail_service.send_mail,
53
- "iat_api_call": self.api_service.call_api
54
- }
55
-
56
- def initialize_companies(self):
57
- """
58
- Initializes and instantiates all registered company classes.
59
- This method should be called *after* the main injector is fully configured
60
- and the company registry is populated.
61
- """
62
- if self.company_classes: # Prevent re-initialization
63
- return
64
-
65
- # ✅ NOW it is safe to get the injector and instantiate companies.
66
- injector = current_iatoolkit().get_injector()
67
- self.company_registry.set_injector(injector)
68
- self.company_classes = self.company_registry.instantiate_companies()
69
-
70
- def start_execution(self):
71
- """Runs the startup logic for all registered companies."""
72
- for company_name, company_instance in self.company_classes.items():
73
- logging.info(f'Starting execution for company: {company_name}')
74
- company_instance.start_execution()
75
-
76
- return True
77
-
78
- def setup_all_companies(self):
79
- # create system functions
80
- for function in self.system_functions:
81
- self.llmquery_repo.create_or_update_function(
82
- Function(
83
- company_id=None,
84
- system_function=True,
85
- name=function['function_name'],
86
- description= function['description'],
87
- parameters=function['parameters']
88
- )
89
- )
90
-
91
- # create the system prompts
92
- i = 1
93
- for prompt in self.system_prompts:
94
- self.prompt_service.create_prompt(
95
- prompt_name=prompt['name'],
96
- description=prompt['description'],
97
- order=1,
98
- is_system_prompt=True
99
- )
100
- i += 1
101
-
102
- # register in the database every company class
103
- for company in self.company_classes.values():
104
- company.register_company()
105
-
106
- def dispatch(self, company_name: str, action: str, **kwargs) -> str:
107
- company_key = company_name.lower()
108
-
109
- if company_key not in self.company_classes:
110
- available_companies = list(self.company_classes.keys())
111
- raise IAToolkitException(
112
- IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
113
- f"Empresa '{company_name}' no configurada. Empresas disponibles: {available_companies}"
114
- )
115
-
116
- # check if action is a system function
117
- if action in self.tool_handlers:
118
- return self.tool_handlers[action](**kwargs)
119
-
120
- company_instance = self.company_classes[company_name]
121
- try:
122
- return company_instance.handle_request(action, **kwargs)
123
- except IAToolkitException as e:
124
- # Si ya es una IAToolkitException, la relanzamos para preservar el tipo de error original.
125
- raise e
126
-
127
- except Exception as e:
128
- logging.exception(e)
129
- raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
130
- f"Error en function call '{action}': {str(e)}") from e
131
-
132
- def get_company_context(self, company_name: str, **kwargs) -> str:
133
- if company_name not in self.company_classes:
134
- raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
135
- f"Empresa no configurada: {company_name}")
136
-
137
- company_context = ''
138
-
139
- # read the company context from this list of markdown files,
140
- # company brief, credits, operation description, etc.
141
- context_dir = os.path.join(os.getcwd(), f'companies/{company_name}/context')
142
- context_files = self.util.get_files_by_extension(context_dir, '.md', return_extension=True)
143
- for file in context_files:
144
- filepath = os.path.join(context_dir, file)
145
- company_context += self.util.load_markdown_context(filepath)
146
-
147
- # add the schemas for every table or function call responses
148
- schema_dir = os.path.join(os.getcwd(), f'companies/{company_name}/schema')
149
- schema_files = self.util.get_files_by_extension(schema_dir, '.yaml', return_extension=True)
150
- for file in schema_files:
151
- schema_name = file.split('_')[0]
152
- filepath = os.path.join(schema_dir, file)
153
- company_context += self.util.generate_context_for_schema(schema_name, filepath)
154
-
155
- company_instance = self.company_classes[company_name]
156
- try:
157
- return company_context + company_instance.get_company_context(**kwargs)
158
- except Exception as e:
159
- logging.exception(e)
160
- raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
161
- f"Error en get_company_context de {company_name}: {str(e)}") from e
162
-
163
- def get_company_services(self, company: Company) -> list[dict]:
164
- # create the syntax with openai response syntax, for the company function list
165
- tools = []
166
- functions = self.llmquery_repo.get_company_functions(company)
167
-
168
- for function in functions:
169
- # make sure is always on
170
- function.parameters["additionalProperties"] = False
171
-
172
- ai_tool = {
173
- "type": "function",
174
- "name": function.name,
175
- "description": function.description,
176
- "parameters": function.parameters,
177
- "strict": True
178
- }
179
- tools.append(ai_tool)
180
- return tools
181
-
182
- def get_user_info(self, company_name: str, user_identifier: str, is_local_user: bool) -> dict:
183
- if company_name not in self.company_classes:
184
- raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
185
- f"Empresa no configurada: {company_name}")
186
-
187
- raw_user_data = {}
188
- if is_local_user:
189
- # source 1: local user login into IAToolkit
190
- raw_user_data = SessionManager.get('user', {})
191
- else:
192
- # source 2: external company user
193
- company_instance = self.company_classes[company_name]
194
- try:
195
- raw_user_data = company_instance.get_user_info(user_identifier)
196
- except Exception as e:
197
- logging.exception(e)
198
- raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
199
- f"Error en get_user_info de {company_name}: {str(e)}") from e
200
-
201
- # always normalize the data for consistent structure
202
- return self._normalize_user_data(raw_user_data, is_local_user)
203
-
204
- def _normalize_user_data(self, raw_data: dict, is_local: bool) -> dict:
205
- """
206
- Asegura que los datos del usuario siempre tengan una estructura consistente.
207
- """
208
- # Valores por defecto para un perfil robusto
209
- normalized_user = {
210
- "id": raw_data.get("id", 0),
211
- "user_email": raw_data.get("email", ""),
212
- "user_fullname": raw_data.get("user_fullname", ""),
213
- "super_user": raw_data.get("super_user", False),
214
- "company_id": raw_data.get("company_id", 0),
215
- "company_name": raw_data.get("company", ""),
216
- "company_short_name": raw_data.get("company_short_name", ""),
217
- "is_local": is_local,
218
- "extras": raw_data.get("extras", {})
219
- }
220
-
221
- # get the extras from the raw data, if any
222
- extras = raw_data.get("extras", {})
223
- if isinstance(extras, dict):
224
- normalized_user.update(extras)
225
-
226
- return normalized_user
227
-
228
- def get_metadata_from_filename(self, company_name: str, filename: str) -> dict:
229
- if company_name not in self.company_classes:
230
- raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
231
- f"Empresa no configurada: {company_name}")
232
-
233
- company_instance = self.company_classes[company_name]
234
- try:
235
- return company_instance.get_metadata_from_filename(filename)
236
- except Exception as e:
237
- logging.exception(e)
238
- raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
239
- f"Error en get_metadata_from_filename de {company_name}: {str(e)}") from e
240
-
241
- def get_company_instance(self, company_name: str):
242
- """Returns the instance for a given company name."""
243
- return self.company_classes.get(company_name)
244
-
245
- def get_registered_companies(self) -> dict:
246
- """Obtiene todas las empresas registradas (para debugging/admin)"""
247
- return {
248
- "registered_classes": list(self.company_registry.get_registered_companies().keys()),
249
- "instantiated": list(self.company_classes.keys()),
250
- "count": len(self.company_classes)
251
- }
252
-
253
-
254
- # iatoolkit system prompts
255
- _SYSTEM_PROMPT = [
256
- {'name': 'query_main', 'description':'main prompt de iatoolkit'},
257
- {'name': 'format_styles', 'description':'formatos y estilos de salida'},
258
- {'name': 'sql_rules', 'description':'instrucciones para generar sql'}
259
- ]
260
-
261
-
262
- # iatoolkit function calls
263
- _FUNCTION_LIST = [
264
- {
265
- "name": "iat_generate_excel",
266
- "description": "Generador de Excel."
267
- "Genera un archivo Excel (.xlsx) a partir de una lista de diccionarios. "
268
- "Cada diccionario representa una fila del archivo. "
269
- "el archivo se guarda en directorio de descargas."
270
- "retorna diccionario con filename, attachment_token (para enviar archivo por mail)"
271
- "content_type y download_link",
272
- "function_name": "iat_generate_excel",
273
- "parameters": {
274
- "type": "object",
275
- "properties": {
276
- "filename": {
277
- "type": "string",
278
- "description": "Nombre del archivo de salida (ejemplo: 'reporte.xlsx')",
279
- "pattern": "^.+\\.xlsx?$"
280
- },
281
- "sheet_name": {
282
- "type": "string",
283
- "description": "Nombre de la hoja dentro del Excel",
284
- "minLength": 1
285
- },
286
- "data": {
287
- "type": "array",
288
- "description": "Lista de diccionarios. Cada diccionario representa una fila.",
289
- "minItems": 1,
290
- "items": {
291
- "type": "object",
292
- "properties": {},
293
- "additionalProperties": {
294
- "anyOf": [
295
- {"type": "string"},
296
- {"type": "number"},
297
- {"type": "boolean"},
298
- {"type": "null"},
299
- {
300
- "type": "string",
301
- "format": "date"
302
- }
303
- ]
304
- }
305
- }
306
- }
307
- },
308
- "required": ["filename", "sheet_name", "data"]
309
- }
310
- },
311
- {
312
- 'name': 'Envio de mails',
313
- 'description': "iatoolkit mail system. "
314
- "envia mails cuando un usuario lo solicita."
315
- "Si no te indican quien envia el correo utiliza la dirección iatoolkit@iatoolkit.com",
316
- 'function_name': "iat_send_email",
317
- 'parameters': {
318
- "type": "object",
319
- "properties": {
320
- "from_email": {"type": "string","description": "dirección de correo electrónico que esta enviando el email."},
321
- "recipient": {"type": "string", "description": "email del destinatario"},
322
- "subject": {"type": "string", "description": "asunto del email"},
323
- "body": {"type": "string", "description": "HTML del email"},
324
- "attachments": {
325
- "type": "array",
326
- "description": "Lista de archivos adjuntos codificados en base64",
327
- "items": {
328
- "type": "object",
329
- "properties": {
330
- "filename": {
331
- "type": "string",
332
- "description": "Nombre del archivo con su extensión (ej. informe.pdf)"
333
- },
334
- "content": {
335
- "type": "string",
336
- "description": "Contenido del archivo en b64."
337
- },
338
- "attachment_token": {
339
- "type": "string",
340
- "description": "token para descargar el archivo."
341
- }
342
- },
343
- "required": ["filename", "content", "attachment_token"],
344
- "additionalProperties": False
345
- }
346
- }
347
- },
348
- "required": ["from_email","recipient", "subject", "body", "attachments"]
349
- }
350
- }
351
- ]
services/excel_service.py DELETED
@@ -1,98 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Producto: IAToolkit
3
- # Todos los derechos reservados.
4
- # En trámite de registro en el Registro de Propiedad Intelectual de Chile.
5
-
6
- from common.util import Utility
7
- import pandas as pd
8
- from uuid import uuid4
9
- from pathlib import Path
10
- from common.exceptions import IAToolkitException
11
- from injector import inject
12
- import os
13
- import logging
14
- from flask import current_app, jsonify
15
-
16
- EXCEL_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
17
-
18
-
19
- class ExcelService:
20
- @inject
21
- def __init__(self,util: Utility):
22
- self.util = util
23
-
24
- def excel_generator(self, **kwargs) -> str:
25
- """
26
- Genera un Excel a partir de una lista de diccionarios.
27
-
28
- Parámetros esperados en kwargs:
29
- - filename: str (nombre lógico a mostrar, ej. "reporte_clientes.xlsx") [obligatorio]
30
- - data: list[dict] (filas del excel) [obligatorio]
31
- - sheet_name: str = "hoja 1"
32
-
33
- Retorna:
34
- {
35
- "filename": "reporte.xlsx",
36
- "attachment_token": "8b7f8a66-...-c1c3.xlsx",
37
- "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
38
- "download_link": "/download/8b7f8a66-...-c1c3.xlsx"
39
- }
40
- """
41
- try:
42
- # get the parameters
43
- fname = kwargs.get('filename')
44
- if not fname:
45
- return 'falta el nombre del archivo de salida'
46
-
47
- data = kwargs.get('data')
48
- if not data or not isinstance(data, list):
49
- return 'faltan los datos o no es una lista de diccionarios'
50
-
51
- sheet_name = kwargs.get('sheet_name', 'hoja 1')
52
-
53
- # 1. convert dictionary to dataframe
54
- df = pd.DataFrame(data)
55
-
56
- # 3. create temporary name
57
- token = f"{uuid4()}.xlsx"
58
- filepath = Path("static/temp") / token
59
- filepath.parent.mkdir(parents=True, exist_ok=True)
60
-
61
- # 4. save excel file in temporary directory
62
- df.to_excel(filepath, index=False, sheet_name=sheet_name)
63
-
64
- # 5. return the link to the LLM
65
- return {
66
- "filename": fname,
67
- "attachment_token": token,
68
- "content_type": EXCEL_MIME,
69
- "download_link": f"/download/{token}"
70
- }
71
-
72
- except Exception as e:
73
- raise IAToolkitException(IAToolkitException.ErrorType.CALL_ERROR,
74
- 'error generating excel file') from e
75
-
76
- def validate_file_access(self, filename):
77
- try:
78
- if not filename:
79
- return jsonify({"error": "Nombre de archivo inválido"})
80
- # Prevent path traversal attacks
81
- if '..' in filename or filename.startswith('/') or '\\' in filename:
82
- return jsonify({"error": "Nombre de archivo inválido"})
83
-
84
- temp_dir = os.path.join(current_app.root_path, 'static', 'temp')
85
- file_path = os.path.join(temp_dir, filename)
86
-
87
- if not os.path.exists(file_path):
88
- return jsonify({"error": "Archivo no encontrado"})
89
-
90
- if not os.path.isfile(file_path):
91
- return jsonify({"error": "La ruta no corresponde a un archivo"})
92
-
93
- return None
94
-
95
- except Exception as e:
96
- error_msg = f"Error validando acceso al archivo {filename}: {str(e)}"
97
- logging.error(error_msg)
98
- return jsonify({"error": "Error validando archivo"})
@@ -1,45 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Producto: IAToolkit
3
- # Todos los derechos reservados.
4
- # En trámite de registro en el Registro de Propiedad Intelectual de Chile.
5
-
6
- from injector import inject
7
- from repositories.llm_query_repo import LLMQueryRepo
8
- from repositories.profile_repo import ProfileRepo
9
- from common.util import Utility
10
-
11
-
12
- class HistoryService:
13
- @inject
14
- def __init__(self, llm_query_repo: LLMQueryRepo,
15
- profile_repo: ProfileRepo,
16
- util: Utility):
17
- self.llm_query_repo = llm_query_repo
18
- self.profile_repo = profile_repo
19
- self.util = util
20
-
21
- def get_history(self,
22
- company_short_name: str,
23
- external_user_id: str = None,
24
- local_user_id: int = 0) -> dict:
25
- try:
26
- user_identifier, _ = self.util.resolve_user_identifier(external_user_id, local_user_id)
27
- if not user_identifier:
28
- return {'error': "No se pudo resolver el identificador del usuario"}
29
-
30
- # validate company
31
- company = self.profile_repo.get_company_by_short_name(company_short_name)
32
- if not company:
33
- return {'error': f'No existe la empresa: {company_short_name}'}
34
-
35
- history = self.llm_query_repo.get_history(company, user_identifier)
36
-
37
- if not history:
38
- return {'error': 'No se pudo obtener el historial'}
39
-
40
- history_list = [query.to_dict() for query in history]
41
-
42
- return {'message': 'Historial obtenido correctamente', 'history': history_list}
43
-
44
- except Exception as e:
45
- return {'error': str(e)}
services/jwt_service.py DELETED
@@ -1,91 +0,0 @@
1
- # Copyright (c) 2024 Fernando Libedinsky
2
- # Producto: IAToolkit
3
- # Todos los derechos reservados.
4
- # En trámite de registro en el Registro de Propiedad Intelectual de Chile.
5
-
6
- import jwt
7
- import time
8
- import logging
9
- from injector import singleton, inject
10
- from typing import Optional, Dict, Any
11
- from flask import Flask
12
-
13
-
14
- @singleton
15
- class JWTService:
16
- @inject
17
- def __init__(self, app: Flask):
18
- # Acceder a la configuración directamente desde app.config
19
- try:
20
- self.secret_key = app.config['JWT_SECRET_KEY']
21
- self.algorithm = app.config['JWT_ALGORITHM']
22
- except KeyError as e:
23
- logging.error(f"Configuración JWT faltante en app.config: {e}. JWTService no funcionará correctamente.")
24
- raise RuntimeError(f"Configuración JWT esencial faltante: {e}")
25
-
26
- def generate_chat_jwt(self,
27
- company_id: int,
28
- company_short_name: str,
29
- external_user_id: str,
30
- expires_delta_seconds: int) -> Optional[str]:
31
- # generate a JWT for a chat session
32
- try:
33
- payload = {
34
- 'company_id': company_id,
35
- 'company_short_name': company_short_name,
36
- 'external_user_id': external_user_id,
37
- 'exp': time.time() + expires_delta_seconds,
38
- 'iat': time.time(),
39
- 'type': 'chat_session' # Identificador del tipo de token
40
- }
41
- token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
42
- return token
43
- except Exception as e:
44
- logging.error(f"Error al generar JWT para company {company_id}, user {external_user_id}: {e}")
45
- return None
46
-
47
- def validate_chat_jwt(self, token: str, expected_company_short_name: str) -> Optional[Dict[str, Any]]:
48
- """
49
- Valida un JWT de sesión de chat.
50
- Retorna el payload decodificado si es válido y coincide con la empresa, o None.
51
- """
52
- if not token:
53
- return None
54
- try:
55
- payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
56
-
57
- # Validaciones adicionales
58
- if payload.get('type') != 'chat_session':
59
- logging.warning(f"Validación JWT fallida: tipo incorrecto '{payload.get('type')}'")
60
- return None
61
-
62
- if payload.get('company_short_name') != expected_company_short_name:
63
- logging.warning(
64
- f"Validación JWT fallida: company_short_name no coincide. "
65
- f"Esperado: {expected_company_short_name}, Obtenido: {payload.get('company_short_name')}"
66
- )
67
- return None
68
-
69
- # external_user_id debe estar presente
70
- if 'external_user_id' not in payload or not payload['external_user_id']:
71
- logging.warning(f"Validación JWT fallida: external_user_id ausente o vacío.")
72
- return None
73
-
74
- # company_id debe estar presente
75
- if 'company_id' not in payload or not isinstance(payload['company_id'], int):
76
- logging.warning(f"Validación JWT fallida: company_id ausente o tipo incorrecto.")
77
- return None
78
-
79
- logging.debug(
80
- f"JWT validado exitosamente para company: {payload.get('company_short_name')}, user: {payload.get('external_user_id')}")
81
- return payload
82
-
83
- except jwt.ExpiredSignatureError:
84
- logging.info(f"Validación JWT fallida: token expirado para {expected_company_short_name}")
85
- return None
86
- except jwt.InvalidTokenError as e:
87
- logging.warning(f"Validación JWT fallida: token inválido para {expected_company_short_name}. Error: {e}")
88
- return None
89
- except Exception as e:
90
- logging.error(f"Error inesperado durante validación de JWT para {expected_company_short_name}: {e}")
91
- return None