iatoolkit 0.22.1__py3-none-any.whl → 0.50.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (46) hide show
  1. iatoolkit/common/routes.py +31 -31
  2. iatoolkit/common/session_manager.py +0 -1
  3. iatoolkit/common/util.py +0 -21
  4. iatoolkit/iatoolkit.py +5 -18
  5. iatoolkit/infra/llm_client.py +3 -5
  6. iatoolkit/infra/redis_session_manager.py +48 -2
  7. iatoolkit/repositories/models.py +1 -2
  8. iatoolkit/services/auth_service.py +74 -0
  9. iatoolkit/services/dispatcher_service.py +12 -21
  10. iatoolkit/services/excel_service.py +15 -15
  11. iatoolkit/services/history_service.py +2 -11
  12. iatoolkit/services/profile_service.py +83 -25
  13. iatoolkit/services/query_service.py +132 -82
  14. iatoolkit/services/tasks_service.py +1 -1
  15. iatoolkit/services/user_feedback_service.py +3 -6
  16. iatoolkit/services/user_session_context_service.py +112 -54
  17. iatoolkit/static/js/chat_feedback.js +1 -1
  18. iatoolkit/static/js/chat_history.js +1 -5
  19. iatoolkit/static/js/chat_main.js +1 -1
  20. iatoolkit/static/styles/landing_page.css +62 -2
  21. iatoolkit/system_prompts/query_main.prompt +3 -12
  22. iatoolkit/templates/_login_widget.html +6 -8
  23. iatoolkit/templates/chat.html +78 -4
  24. iatoolkit/templates/error.html +1 -1
  25. iatoolkit/templates/index.html +38 -11
  26. iatoolkit/templates/{home.html → login_test.html} +11 -51
  27. iatoolkit/views/external_login_view.py +50 -111
  28. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +4 -4
  29. iatoolkit/views/history_api_view.py +52 -0
  30. iatoolkit/views/init_context_api_view.py +62 -0
  31. iatoolkit/views/llmquery_api_view.py +50 -0
  32. iatoolkit/views/llmquery_web_view.py +38 -0
  33. iatoolkit/views/{home_view.py → login_test_view.py} +2 -5
  34. iatoolkit/views/login_view.py +79 -56
  35. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +4 -4
  36. iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -19
  37. {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.0.dist-info}/METADATA +2 -2
  38. {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.0.dist-info}/RECORD +40 -41
  39. iatoolkit/common/auth.py +0 -200
  40. iatoolkit/templates/login.html +0 -43
  41. iatoolkit/views/download_file_view.py +0 -58
  42. iatoolkit/views/history_view.py +0 -57
  43. iatoolkit/views/init_context_view.py +0 -35
  44. iatoolkit/views/llmquery_view.py +0 -65
  45. {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.0.dist-info}/WHEEL +0 -0
  46. {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.0.dist-info}/top_level.txt +0 -0
@@ -5,18 +5,18 @@
5
5
 
6
6
  from injector import inject
7
7
  from iatoolkit.repositories.profile_repo import ProfileRepo
8
+ from iatoolkit.services.dispatcher_service import Dispatcher
8
9
  from iatoolkit.repositories.models import User, Company, ApiKey
9
10
  from flask_bcrypt import check_password_hash
10
11
  from iatoolkit.common.session_manager import SessionManager
12
+ from iatoolkit.services.user_session_context_service import UserSessionContextService
11
13
  from flask_bcrypt import Bcrypt
12
14
  from iatoolkit.infra.mail_app import MailApp
13
15
  import random
14
- import logging
15
16
  import re
16
17
  import secrets
17
18
  import string
18
- from datetime import datetime, timezone
19
- from iatoolkit.services.user_session_context_service import UserSessionContextService
19
+ from iatoolkit.services.dispatcher_service import Dispatcher
20
20
 
21
21
 
22
22
  class ProfileService:
@@ -24,8 +24,10 @@ class ProfileService:
24
24
  def __init__(self,
25
25
  profile_repo: ProfileRepo,
26
26
  session_context_service: UserSessionContextService,
27
+ dispatcher: Dispatcher,
27
28
  mail_app: MailApp):
28
29
  self.profile_repo = profile_repo
30
+ self.dispatcher = dispatcher
29
31
  self.session_context = session_context_service
30
32
  self.mail_app = mail_app
31
33
  self.bcrypt = Bcrypt()
@@ -42,7 +44,7 @@ class ProfileService:
42
44
  if not check_password_hash(user.password, password):
43
45
  return {'success': False, "message": "Contraseña inválida"}
44
46
 
45
- company = self.get_company_by_short_name(company_short_name)
47
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
46
48
  if not company:
47
49
  return {'success': False, "message": "Empresa no encontrada"}
48
50
 
@@ -54,34 +56,87 @@ class ProfileService:
54
56
  return {'success': False,
55
57
  "message": "Tu cuenta no ha sido verificada. Por favor, revisa tu correo."}
56
58
 
57
- # save user data into session manager
58
- self.set_user_session(user=user, company=company)
59
-
60
- return {'success': True, "user": user, "message": "Login exitoso"}
59
+ # 1. Build the local user profile dictionary here.
60
+ # the user_profile variables are used on the LLM templates also (see in query_main.prompt)
61
+ user_identifier = user.email # no longer de ID
62
+ user_profile = {
63
+ "id": user_identifier,
64
+ "user_email": user.email,
65
+ "user_fullname": f'{user.first_name} {user.last_name}',
66
+ "user_is_local": True,
67
+ "extras": {}
68
+ }
69
+
70
+ # 2. Call the session creation helper with the pre-built profile.
71
+ # user_identifier = str(user.id)
72
+ self.create_web_session(company, user_identifier, user_profile)
73
+ return {'success': True, "user_identifier": user_identifier, "message": "Login exitoso"}
61
74
  except Exception as e:
62
75
  return {'success': False, "message": str(e)}
63
76
 
77
+ def create_external_user_session(self, company: Company, external_user_id: str):
78
+ """
79
+ Public method for views to create a web session for an external user.
80
+ """
81
+ # 1. Fetch the profile from the external system via Dispatcher.
82
+ user_profile = self.dispatcher.get_user_info(
83
+ company_name=company.short_name,
84
+ user_identifier=external_user_id
85
+ )
86
+
87
+ # 2. Call the session creation helper with external_user_id as user_identifier
88
+ self.create_web_session(
89
+ company=company,
90
+ user_identifier=external_user_id,
91
+ user_profile=user_profile)
92
+
93
+ def create_web_session(self, company: Company, user_identifier: str, user_profile: dict):
94
+ """
95
+ Private helper: Takes a pre-built profile, saves it to Redis, and sets the Flask cookie.
96
+ """
97
+ user_profile['company_short_name'] = company.short_name
98
+ user_profile['user_identifier'] = user_identifier
99
+ user_profile['user_id'] = user_identifier
100
+ user_profile['company_id'] = company.id
101
+ user_profile['company'] = company.name
102
+
103
+ # user_profile['last_activity'] = datetime.now(timezone.utc).timestamp()
104
+ self.session_context.save_profile_data(company.short_name, user_identifier, user_profile)
64
105
 
65
- def set_user_session(self, user: User, company: Company):
66
- SessionManager.set('user_id', user.id)
67
- SessionManager.set('company_id', company.id)
106
+ # save this values into Flask session cookie
107
+ SessionManager.set('user_identifier', user_identifier)
68
108
  SessionManager.set('company_short_name', company.short_name)
69
109
 
70
- # save user data into session manager
71
- user_data = {
72
- "id": user.id,
73
- "email": user.email,
74
- "user_fullname": f'{user.first_name} {user.last_name}',
75
- "company_id": company.id,
76
- "company": company.name,
77
- "company_short_name": company.short_name,
78
- "user_is_local": True, # origin of data
79
- "extras": {} # company specific data
110
+ def get_current_session_info(self) -> dict:
111
+ """
112
+ Gets the current web user's profile from the unified session.
113
+ This is the standard way to access user data for web requests.
114
+ """
115
+ # 1. Get identifiers from the simple Flask session cookie.
116
+ user_identifier = SessionManager.get('user_identifier')
117
+ company_short_name = SessionManager.get('company_short_name')
118
+
119
+ if not user_identifier or not company_short_name:
120
+ # No authenticated web user.
121
+ return {}
122
+
123
+ # 2. Use the identifiers to fetch the full, authoritative profile from Redis.
124
+ profile = self.session_context.get_profile_data(company_short_name, user_identifier)
125
+
126
+ return {
127
+ "user_identifier": user_identifier,
128
+ "company_short_name": company_short_name,
129
+ "profile": profile
80
130
  }
81
- SessionManager.set('user', user_data)
82
131
 
83
- # save time session was activated (in timestamp format)
84
- SessionManager.set('last_activity', datetime.now(timezone.utc).timestamp())
132
+ def get_profile_by_identifier(self, company_short_name: str, user_identifier: str) -> dict:
133
+ """
134
+ Fetches a user profile directly by their identifier, bypassing the Flask session.
135
+ This is ideal for API-side checks.
136
+ """
137
+ if not company_short_name or not user_identifier:
138
+ return {}
139
+ return self.session_context.get_profile_data(company_short_name, user_identifier)
85
140
 
86
141
 
87
142
  def signup(self,
@@ -95,7 +150,7 @@ class ProfileService:
95
150
  try:
96
151
 
97
152
  # get company info
98
- company = self.get_company_by_short_name(company_short_name)
153
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
99
154
  if not company:
100
155
  return {"error": f"la empresa {company_short_name} no existe"}
101
156
 
@@ -240,6 +295,9 @@ class ProfileService:
240
295
  def get_company_by_short_name(self, short_name: str) -> Company:
241
296
  return self.profile_repo.get_company_by_short_name(short_name)
242
297
 
298
+ def get_active_api_key_entry(self, api_key_value: str) -> ApiKey | None:
299
+ return self.profile_repo.get_active_api_key_entry(api_key_value)
300
+
243
301
  def new_api_key(self, company_short_name: str):
244
302
  company = self.get_company_by_short_name(company_short_name)
245
303
  if not company:
@@ -4,11 +4,11 @@
4
4
  # IAToolkit is open source software.
5
5
 
6
6
  from iatoolkit.infra.llm_client import llmClient
7
+ from iatoolkit.services.profile_service import ProfileService
7
8
  from iatoolkit.repositories.document_repo import DocumentRepo
8
9
  from iatoolkit.repositories.profile_repo import ProfileRepo
9
10
  from iatoolkit.services.document_service import DocumentService
10
11
  from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
11
-
12
12
  from iatoolkit.repositories.models import Task
13
13
  from iatoolkit.services.dispatcher_service import Dispatcher
14
14
  from iatoolkit.services.prompt_manager_service import PromptService
@@ -21,6 +21,7 @@ import logging
21
21
  from typing import Optional
22
22
  import json
23
23
  import time
24
+ import hashlib
24
25
  import os
25
26
 
26
27
 
@@ -30,6 +31,7 @@ class QueryService:
30
31
  @inject
31
32
  def __init__(self,
32
33
  llm_client: llmClient,
34
+ profile_service: ProfileService,
33
35
  document_service: DocumentService,
34
36
  document_repo: DocumentRepo,
35
37
  llmquery_repo: LLMQueryRepo,
@@ -39,6 +41,7 @@ class QueryService:
39
41
  dispatcher: Dispatcher,
40
42
  session_context: UserSessionContextService
41
43
  ):
44
+ self.profile_service = profile_service
42
45
  self.document_service = document_service
43
46
  self.document_repo = document_repo
44
47
  self.llmquery_repo = llmquery_repo
@@ -55,107 +58,133 @@ class QueryService:
55
58
  raise IAToolkitException(IAToolkitException.ErrorType.API_KEY,
56
59
  "La variable de entorno 'LLM_MODEL' no está configurada.")
57
60
 
61
+ def _build_context_and_profile(self, company_short_name: str, user_identifier: str) -> tuple:
62
+ # this method read the user/company context from the database and renders the system prompt
63
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
64
+ if not company:
65
+ return None, None
66
+
67
+ # Get the user profile from the single source of truth.
68
+ user_profile = self.profile_service.get_profile_by_identifier(company_short_name, user_identifier)
69
+ if not user_profile:
70
+ # This might happen if a session exists for a user that was deleted.
71
+ return None, None
72
+
73
+ # render the iatoolkit main system prompt with the company/user information
74
+ system_prompt_template = self.prompt_service.get_system_prompt()
75
+ rendered_system_prompt = self.util.render_prompt_from_string(
76
+ template_string=system_prompt_template,
77
+ question=None,
78
+ client_data=user_profile,
79
+ company=company,
80
+ service_list=self.dispatcher.get_company_services(company)
81
+ )
82
+
83
+ # get the company context: schemas, database models, .md files
84
+ company_specific_context = self.dispatcher.get_company_context(company_name=company_short_name)
85
+
86
+ # merge context: company + user
87
+ final_system_context = f"{company_specific_context}\n{rendered_system_prompt}"
88
+
89
+ return final_system_context, user_profile
90
+
91
+ def prepare_context(self, company_short_name: str, user_identifier: str) -> dict:
92
+ # prepare the context and decide if it needs to be rebuilt
93
+ # save the generated context in the session context for later use
94
+ if not user_identifier:
95
+ return {'rebuild_needed': True, 'error': 'Invalid user identifier'}
58
96
 
59
- def llm_init_context(self,
60
- company_short_name: str,
61
- external_user_id: str = None,
62
- local_user_id: int = 0,
63
- model: str = ''):
64
- start_time = time.time()
65
- if not model:
66
- model = self.model
97
+ # create the company/user context and compute its version
98
+ final_system_context, user_profile = self._build_context_and_profile(
99
+ company_short_name, user_identifier)
67
100
 
68
- # Validate the user and company
69
- user_identifier, is_local_user = self.util.resolve_user_identifier(external_user_id, local_user_id)
70
- if not user_identifier:
71
- raise IAToolkitException(IAToolkitException.ErrorType.INVALID_USER,
72
- "No se pudo resolver el identificador del usuario")
101
+ # save the user information in the session context
102
+ # it's needed for the jinja predefined prompts (filtering)
103
+ self.session_context.save_profile_data(company_short_name, user_identifier, user_profile)
73
104
 
74
- company = self.profile_repo.get_company_by_short_name(company_short_name)
75
- if not company:
76
- raise IAToolkitException(IAToolkitException.ErrorType.INVALID_NAME,
77
- f"Empresa no encontrada: {company_short_name}")
105
+ # calculate the context version
106
+ current_version = self._compute_context_version_from_string(final_system_context)
78
107
 
79
- logging.info(f"Inicializando contexto para {company_short_name}/{user_identifier} con modelo {model} ...")
80
108
  try:
81
- # 1. clean any previous context for company/user
82
- self.session_context.clear_all_context(
83
- company_short_name=company_short_name,
84
- user_identifier=user_identifier
85
- )
109
+ prev_version = self.session_context.get_context_version(company_short_name, user_identifier)
110
+ except Exception:
111
+ prev_version = None
86
112
 
87
- # 2. get dictionary with user information from company DB
88
- # user roles are read at this point from company db
89
- user_profile = self.dispatcher.get_user_info(
90
- company_name=company_short_name,
91
- user_identifier=user_identifier,
92
- is_local_user=is_local_user
93
- )
113
+ rebuild_is_needed = not (prev_version and prev_version == current_version and
114
+ self._has_valid_cached_context(company_short_name, user_identifier))
94
115
 
95
- # add the user logged in to the user_info dictionary
96
- user_profile['user_id'] = user_identifier
116
+ if rebuild_is_needed:
117
+ logging.info(
118
+ f"Se necesita reconstrucción de contexto para {company_short_name}/{user_identifier}. Preparando...")
97
119
 
98
- # save the user information in the session context
99
- # it's needed for the jinja predefined prompts (filtering)
100
- self.session_context.save_user_session_data(company_short_name, user_identifier, user_profile)
101
120
 
102
- # 3. render the iatoolkit main system prompt with the company/user information
103
- system_prompt_template = self.prompt_service.get_system_prompt()
104
- rendered_system_prompt = self.util.render_prompt_from_string(
105
- template_string=system_prompt_template,
106
- question=None,
107
- client_data=user_profile,
108
- company=company,
109
- service_list=self.dispatcher.get_company_services(company)
110
- )
121
+ # Guardar el contexto preparado y su versión para que `finalize_context_rebuild` los use.
122
+ self.session_context.save_prepared_context(company_short_name, user_identifier, final_system_context,
123
+ current_version)
124
+
125
+ return {'rebuild_needed': rebuild_is_needed}
126
+
127
+ def finalize_context_rebuild(self, company_short_name: str, user_identifier: str, model: str = ''):
128
+
129
+ # end the initilization, if there is a prepare context send it to llm
130
+ if not model:
131
+ model = self.model
111
132
 
112
- # 4. add more company context: schemas, database models, .md files
113
- company_specific_context = self.dispatcher.get_company_context(company_name=company_short_name)
133
+ # --- Lógica de Bloqueo ---
134
+ lock_key = f"lock:context:{company_short_name}/{user_identifier}"
135
+ if not self.session_context.acquire_lock(lock_key, expire_seconds=60):
136
+ logging.warning(
137
+ f"Intento de reconstruir contexto para {user_identifier} mientras ya estaba en progreso. Se omite.")
138
+ return
139
+
140
+ try:
141
+ start_time = time.time()
142
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
143
+
144
+ # get the prepared context and version from the session cache
145
+ prepared_context, version_to_save = self.session_context.get_and_clear_prepared_context(company_short_name,
146
+ user_identifier)
147
+ if not prepared_context:
148
+ logging.info(
149
+ f"No se requiere reconstrucción de contexto para {company_short_name}/{user_identifier}. Finalización rápida.")
150
+ return
151
+
152
+ logging.info(f"Enviando contexto al LLM para {company_short_name}/{user_identifier}...")
114
153
 
115
- # 5. merge contexts
116
- final_system_context = f"{company_specific_context}\n{rendered_system_prompt}"
154
+ # Limpiar solo el historial de chat y el ID de respuesta anterior
155
+ self.session_context.clear_llm_history(company_short_name, user_identifier)
117
156
 
118
157
  if self.util.is_gemini_model(model):
119
- # save the initial context as `context_history` (list of messages)
120
- context_history = [{"role": "user", "content": final_system_context}]
158
+ context_history = [{"role": "user", "content": prepared_context}]
121
159
  self.session_context.save_context_history(company_short_name, user_identifier, context_history)
122
- logging.info(f"Contexto inicial para Gemini guardado en sesión")
123
- return "gemini-context-initialized"
124
160
 
125
161
  elif self.util.is_openai_model(model):
126
-
127
- # 6. set the company/user context as the initial context for the LLM
128
162
  response_id = self.llm_client.set_company_context(
129
- company=company,
130
- company_base_context=final_system_context,
131
- model=model
163
+ company=company, company_base_context=prepared_context, model=model
132
164
  )
133
-
134
- # 7. save response_id in the session context
135
165
  self.session_context.save_last_response_id(company_short_name, user_identifier, response_id)
136
166
 
137
- logging.info(f"Contexto inicial de company '{company_short_name}/{user_identifier}' ha sido establecido en {int(time.time() - start_time)} seg.")
138
- return response_id
167
+ if version_to_save:
168
+ self.session_context.save_context_version(company_short_name, user_identifier, version_to_save)
139
169
 
170
+ logging.info(
171
+ f"Contexto de {company_short_name}/{user_identifier} establecido en {int(time.time() - start_time)} seg.")
140
172
  except Exception as e:
141
- logging.exception(f"Error al inicializar el contexto del LLM para {company_short_name}: {e}")
173
+ logging.exception(f"Error en finalize_context_rebuild para {company_short_name}: {e}")
142
174
  raise e
175
+ finally:
176
+ # --- Liberar el Bloqueo ---
177
+ self.session_context.release_lock(lock_key)
143
178
 
144
179
  def llm_query(self,
145
180
  company_short_name: str,
146
- external_user_id: Optional[str] = None,
147
- local_user_id: int = 0,
181
+ user_identifier: str,
148
182
  task: Optional[Task] = None,
149
183
  prompt_name: str = None,
150
184
  question: str = '',
151
185
  client_data: dict = {},
152
186
  files: list = []) -> dict:
153
187
  try:
154
- user_identifier, is_local_user = self.util.resolve_user_identifier(external_user_id, local_user_id)
155
- if not user_identifier:
156
- return {"error": True,
157
- "error_message": "No se pudo identificar al usuario"}
158
-
159
188
  company = self.profile_repo.get_company_by_short_name(short_name=company_short_name)
160
189
  if not company:
161
190
  return {"error": True,
@@ -173,23 +202,19 @@ class QueryService:
173
202
  # get user context
174
203
  previous_response_id = self.session_context.get_last_response_id(company.short_name, user_identifier)
175
204
  if not previous_response_id:
176
- # try to initialize the company/user context
177
- previous_response_id = self.llm_init_context(company.short_name, external_user_id, local_user_id)
178
- if not previous_response_id:
179
- return {'error': True,
180
- "error_message": f"FATAL: No se encontró 'previous_response_id' para '{company.short_name}/{user_identifier}'. La conversación no puede continuar."
181
- }
205
+ return {'error': True,
206
+ "error_message": f"FATAL: No se encontró 'previous_response_id' para '{company.short_name}/{user_identifier}'. La conversación no puede continuar."
207
+ }
182
208
  elif self.util.is_gemini_model(self.model):
183
209
  # check the length of the context_history and remove old messages
184
210
  self._trim_context_history(context_history)
185
211
 
186
- # get the user data from the session context
187
- user_info_from_session = self.session_context.get_user_session_data(company.short_name, user_identifier)
212
+ # get the user profile data from the session context
213
+ user_profile = self.profile_service.get_profile_by_identifier(company.short_name, user_identifier)
188
214
 
189
- # Combinar datos: los datos de la tarea/request tienen prioridad sobre los de la sesión
190
- final_client_data = (user_info_from_session or {}).copy()
215
+ # combine client_data with user_profile
216
+ final_client_data = (user_profile or {}).copy()
191
217
  final_client_data.update(client_data)
192
- final_client_data['user_id'] = user_identifier
193
218
 
194
219
  # Load attached files into the context
195
220
  files_context = self.load_files_for_context(files)
@@ -207,7 +232,7 @@ class QueryService:
207
232
  template_string=prompt_content,
208
233
  question=question,
209
234
  client_data=final_client_data,
210
- external_user_id=external_user_id,
235
+ user_identifier=user_identifier,
211
236
  company=company,
212
237
  )
213
238
 
@@ -254,6 +279,31 @@ class QueryService:
254
279
  logging.exception(e)
255
280
  return {'error': True, "error_message": f"{str(e)}"}
256
281
 
282
+ def _compute_context_version_from_string(self, final_system_context: str) -> str:
283
+ # returns a hash of the context string
284
+ try:
285
+ return hashlib.sha256(final_system_context.encode("utf-8")).hexdigest()
286
+ except Exception:
287
+ return "unknown"
288
+
289
+ def _has_valid_cached_context(self, company_short_name: str, user_identifier: str) -> bool:
290
+ """
291
+ Verifica si existe un estado de contexto reutilizable en sesión.
292
+ - OpenAI: last_response_id presente.
293
+ - Gemini: context_history con al menos 1 mensaje.
294
+ """
295
+ try:
296
+ if self.util.is_openai_model(self.model):
297
+ prev_id = self.session_context.get_last_response_id(company_short_name, user_identifier)
298
+ return bool(prev_id)
299
+ if self.util.is_gemini_model(self.model):
300
+ history = self.session_context.get_context_history(company_short_name, user_identifier) or []
301
+ return len(history) >= 1
302
+ return False
303
+ except Exception as e:
304
+ logging.warning(f"Error verificando caché de contexto: {e}")
305
+ return False
306
+
257
307
  def load_files_for_context(self, files: list) -> str:
258
308
  """
259
309
  Processes a list of attached files, decodes their content,
@@ -101,7 +101,7 @@ class TaskService:
101
101
  # call the IA
102
102
  response = self.query_service.llm_query(
103
103
  task=task,
104
- local_user_id=0,
104
+ user_identifier='task-monitor',
105
105
  company_short_name=task.company.short_name,
106
106
  prompt_name=task.task_type.name,
107
107
  client_data=task.client_data,
@@ -19,8 +19,7 @@ class UserFeedbackService:
19
19
  def new_feedback(self,
20
20
  company_short_name: str,
21
21
  message: str,
22
- external_user_id: str = None,
23
- local_user_id: int = 0,
22
+ user_identifier: str,
24
23
  space: str = None,
25
24
  type: str = None,
26
25
  rating: int = None) -> dict:
@@ -31,7 +30,7 @@ class UserFeedbackService:
31
30
  return {'error': f'No existe la empresa: {company_short_name}'}
32
31
 
33
32
  # send notification to Google Chat
34
- chat_message = f"*Nuevo feedback de {company_short_name}*:\n*Usuario:* {external_user_id or local_user_id}\n*Mensaje:* {message}\n*Calificación:* {rating}"
33
+ chat_message = f"*Nuevo feedback de {company_short_name}*:\n*Usuario:* {user_identifier}\n*Mensaje:* {message}\n*Calificación:* {rating}"
35
34
 
36
35
  # TO DO: get the space and type from the input data
37
36
  chat_data = {
@@ -45,7 +44,6 @@ class UserFeedbackService:
45
44
  }
46
45
 
47
46
  chat_result = self.google_chat_app.send_message(message_data=chat_data)
48
-
49
47
  if not chat_result.get('success'):
50
48
  logging.warning(f"Error al enviar notificación a Google Chat: {chat_result.get('message')}")
51
49
 
@@ -53,8 +51,7 @@ class UserFeedbackService:
53
51
  new_feedback = UserFeedback(
54
52
  company_id=company.id,
55
53
  message=message,
56
- local_user_id=local_user_id,
57
- external_user_id=external_user_id,
54
+ user_identifier=user_identifier,
58
55
  rating=rating
59
56
  )
60
57
  new_feedback = self.profile_repo.save_feedback(new_feedback)