iatoolkit 0.56.0__py3-none-any.whl → 0.58.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.

@@ -21,7 +21,6 @@ def register_views(injector, app):
21
21
 
22
22
  from iatoolkit.views.index_view import IndexView
23
23
  from iatoolkit.views.init_context_api_view import InitContextApiView
24
- from iatoolkit.views.llmquery_web_view import LLMQueryWebView
25
24
  from iatoolkit.views.llmquery_api_view import LLMQueryApiView
26
25
  from iatoolkit.views.tasks_view import TaskView
27
26
  from iatoolkit.views.tasks_review_view import TaskReviewView
@@ -67,8 +66,8 @@ def register_views(injector, app):
67
66
  view_func=ChatTokenRequestView.as_view('chat-token'))
68
67
 
69
68
  # init (reset) the company context (with api-key)
70
- app.add_url_rule('/<company_short_name>/api/init_context_api',
71
- view_func=InitContextApiView.as_view('init_context_api'))
69
+ app.add_url_rule('/<company_short_name>/api/init-context',
70
+ view_func=InitContextApiView.as_view('init-context'))
72
71
 
73
72
  # register new user, account verification and forgot password
74
73
  app.add_url_rule('/<company_short_name>/signup',view_func=SignupView.as_view('signup'))
@@ -80,13 +79,8 @@ def register_views(injector, app):
80
79
 
81
80
  # main chat query, used by the JS in the browser (with credentials)
82
81
  # can be used also for executing iatoolkit prompts
83
- app.add_url_rule('/<company_short_name>/llm_query', view_func=LLMQueryWebView.as_view('llm_query_web'))
84
-
85
- # this is the same function as above, but with api-key
86
82
  app.add_url_rule('/<company_short_name>/api/llm_query', view_func=LLMQueryApiView.as_view('llm_query_api'))
87
83
 
88
- # chat buttons are here on
89
-
90
84
  # open the promt directory
91
85
  app.add_url_rule('/<company_short_name>/api/prompts', view_func=PromptApiView.as_view('prompt'))
92
86
 
iatoolkit/iatoolkit.py CHANGED
@@ -19,7 +19,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix
19
19
  from injector import Binder, singleton, Injector
20
20
  from importlib.metadata import version as _pkg_version, PackageNotFoundError
21
21
 
22
- IATOOLKIT_VERSION = "0.56.0"
22
+ IATOOLKIT_VERSION = "0.57.0"
23
23
 
24
24
  # global variable for the unique instance of IAToolkit
25
25
  _iatoolkit_instance: Optional['IAToolkit'] = None
@@ -3,9 +3,10 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- from sqlalchemy import Column, Integer, String, DateTime, Enum, Text, JSON, Boolean, ForeignKey, Table
6
+ from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Enum, Text, JSON, Boolean, ForeignKey, Table
7
7
  from sqlalchemy.orm import DeclarativeBase
8
8
  from sqlalchemy.orm import relationship, class_mapper, declarative_base
9
+ from sqlalchemy.sql import func
9
10
  from datetime import datetime
10
11
  from pgvector.sqlalchemy import Vector
11
12
  from enum import Enum as PyEnum
@@ -307,3 +308,27 @@ class Prompt(Base):
307
308
 
308
309
  company = relationship("Company", back_populates="prompts")
309
310
  category = relationship("PromptCategory", back_populates="prompts")
311
+
312
+ class AccessLog(Base):
313
+ # Modelo ORM para registrar cada intento de acceso a la plataforma.
314
+ __tablename__ = 'iat_access_log'
315
+
316
+ id = Column(BigInteger, primary_key=True)
317
+
318
+ timestamp = Column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)
319
+ company_short_name = Column(String(100), nullable=False, index=True)
320
+ user_identifier = Column(String(255), index=True)
321
+
322
+ # Cómo y el Resultado
323
+ auth_type = Column(String(20), nullable=False) # 'local', 'external_api', 'redeem_token', etc.
324
+ outcome = Column(String(10), nullable=False) # 'success' o 'failure'
325
+ reason_code = Column(String(50)) # Causa de fallo, ej: 'INVALID_CREDENTIALS'
326
+
327
+ # Contexto de la Petición
328
+ source_ip = Column(String(45), nullable=False)
329
+ user_agent_hash = Column(String(16)) # Hash corto del User-Agent
330
+ request_path = Column(String(255), nullable=False)
331
+
332
+ def __repr__(self):
333
+ return (f"<AccessLog(id={self.id}, company='{self.company_short_name}', "
334
+ f"user='{self.user_identifier}', outcome='{self.outcome}')>")
@@ -6,8 +6,12 @@
6
6
  from flask import request
7
7
  from injector import inject
8
8
  from iatoolkit.services.profile_service import ProfileService
9
+ from iatoolkit.services.jwt_service import JWTService
10
+ from iatoolkit.repositories.database_manager import DatabaseManager
11
+ from iatoolkit.repositories.models import AccessLog
9
12
  from flask import request
10
13
  import logging
14
+ import hashlib
11
15
 
12
16
 
13
17
  class AuthService:
@@ -17,11 +21,75 @@ class AuthService:
17
21
  """
18
22
 
19
23
  @inject
20
- def __init__(self, profile_service: ProfileService):
21
- """
22
- Injects ProfileService to access session information and validate API keys.
23
- """
24
+ def __init__(self, profile_service: ProfileService,
25
+ jwt_service: JWTService,
26
+ db_manager: DatabaseManager
27
+ ):
24
28
  self.profile_service = profile_service
29
+ self.jwt_service = jwt_service
30
+ self.db_manager = db_manager
31
+
32
+ def login_local_user(self, company_short_name: str, email: str, password: str) -> dict:
33
+ # try to autenticate a local user, register the event and return the result
34
+ auth_response = self.profile_service.login(
35
+ company_short_name=company_short_name,
36
+ email=email,
37
+ password=password
38
+ )
39
+
40
+ if not auth_response.get('success'):
41
+ self.log_access(
42
+ company_short_name=company_short_name,
43
+ user_identifier=email,
44
+ auth_type='local',
45
+ outcome='failure',
46
+ reason_code='INVALID_CREDENTIALS',
47
+ )
48
+ else:
49
+ self.log_access(
50
+ company_short_name=company_short_name,
51
+ auth_type='local',
52
+ outcome='success',
53
+ user_identifier=auth_response.get('user_identifier')
54
+ )
55
+
56
+ return auth_response
57
+
58
+ def redeem_token_for_session(self, company_short_name: str, token: str) -> dict:
59
+ # redeem a token for a session, register the event and return the result
60
+ payload = self.jwt_service.validate_chat_jwt(token)
61
+
62
+ if not payload:
63
+ self.log_access(
64
+ company_short_name=company_short_name,
65
+ auth_type='redeem_token',
66
+ outcome='failure',
67
+ reason_code='JWT_INVALID'
68
+ )
69
+ return {'success': False, 'error': 'Token inválido o expirado'}
70
+
71
+ # 2. if token is valid, extract the user_identifier
72
+ user_identifier = payload.get('user_identifier')
73
+ try:
74
+ # create the Flask session
75
+ self.profile_service.set_session_for_user(company_short_name, user_identifier)
76
+ self.log_access(
77
+ company_short_name=company_short_name,
78
+ auth_type='redeem_token',
79
+ outcome='success',
80
+ user_identifier=user_identifier
81
+ )
82
+ return {'success': True, 'user_identifier': user_identifier}
83
+ except Exception as e:
84
+ logging.error(f"Error al crear la sesión desde token para {user_identifier}: {e}")
85
+ self.log_access(
86
+ company_short_name=company_short_name,
87
+ auth_type='redeem_token',
88
+ outcome='failure',
89
+ reason_code='SESSION_CREATION_FAILED',
90
+ user_identifier=user_identifier
91
+ )
92
+ return {'success': False, 'error': 'No se pudo crear la sesión del usuario'}
25
93
 
26
94
  def verify(self) -> dict:
27
95
  """
@@ -74,4 +142,40 @@ class AuthService:
74
142
  # --- Failure: No valid credentials found ---
75
143
  logging.info(f"Authentication required. No session cookie or API Key provided.")
76
144
  return {"success": False, "error": "Authentication required. No session cookie or API Key provided.",
77
- "status_code": 402}
145
+ "status_code": 402}
146
+
147
+ def log_access(self,
148
+ company_short_name: str,
149
+ auth_type: str,
150
+ outcome: str,
151
+ user_identifier: str = None,
152
+ reason_code: str = None):
153
+ """
154
+ Registra un intento de acceso en la base de datos.
155
+ Es "best-effort" y no debe interrumpir el flujo de autenticación.
156
+ """
157
+ session = self.db_manager.scoped_session()
158
+ try:
159
+ # Capturar datos del contexto de la petición de Flask
160
+ source_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
161
+ path = request.path
162
+ ua = request.headers.get('User-Agent', '')
163
+ ua_hash = hashlib.sha256(ua.encode()).hexdigest()[:16] if ua else None
164
+
165
+ # Crear la entrada de log
166
+ log_entry = AccessLog(
167
+ company_short_name=company_short_name,
168
+ user_identifier=user_identifier,
169
+ auth_type=auth_type,
170
+ outcome=outcome,
171
+ reason_code=reason_code,
172
+ source_ip=source_ip,
173
+ user_agent_hash=ua_hash,
174
+ request_path=path,
175
+ )
176
+ session.add(log_entry)
177
+ session.commit()
178
+
179
+ except Exception as e:
180
+ logging.error(f"Fallo al escribir en AccessLog: {e}", exc_info=False)
181
+ session.rollback()
@@ -66,16 +66,18 @@ class ProfileService:
66
66
  "extras": {}
67
67
  }
68
68
 
69
- # 2. Call the session creation helper with the pre-built profile.
70
- # user_identifier = str(user.id)
71
- self.create_web_session(company, user_identifier, user_profile)
69
+ # 2. create user_profile in context
70
+ self.save_user_profile(company, user_identifier, user_profile)
71
+
72
+ # 3. create the web session
73
+ self.set_session_for_user(company.short_name, user_identifier)
72
74
  return {'success': True, "user_identifier": user_identifier, "message": "Login exitoso"}
73
75
  except Exception as e:
74
76
  return {'success': False, "message": str(e)}
75
77
 
76
- def create_external_user_session(self, company: Company, user_identifier: str):
78
+ def create_external_user_profile_context(self, company: Company, user_identifier: str):
77
79
  """
78
- Public method for views to create a web session for an external user.
80
+ Public method for views to create a user profile context for an external user.
79
81
  """
80
82
  # 1. Fetch the external user profile via Dispatcher.
81
83
  external_user_profile = self.dispatcher.get_user_info(
@@ -84,12 +86,12 @@ class ProfileService:
84
86
  )
85
87
 
86
88
  # 2. Call the session creation helper with external_user_id as user_identifier
87
- self.create_web_session(
89
+ self.save_user_profile(
88
90
  company=company,
89
91
  user_identifier=user_identifier,
90
92
  user_profile=external_user_profile)
91
93
 
92
- def create_web_session(self, company: Company, user_identifier: str, user_profile: dict):
94
+ def save_user_profile(self, company: Company, user_identifier: str, user_profile: dict):
93
95
  """
94
96
  Private helper: Takes a pre-built profile, saves it to Redis, and sets the Flask cookie.
95
97
  """
@@ -102,10 +104,8 @@ class ProfileService:
102
104
  # save user_profile in Redis session
103
105
  self.session_context.save_profile_data(company.short_name, user_identifier, user_profile)
104
106
 
105
- # save a min Flask session cookie for this user
106
- self.set_session_for_user(company.short_name, user_identifier)
107
-
108
107
  def set_session_for_user(self, company_short_name: str, user_identifier:str ):
108
+ # save a min Flask session cookie for this user
109
109
  SessionManager.set('company_short_name', company_short_name)
110
110
  SessionManager.set('user_identifier', user_identifier)
111
111
 
@@ -66,9 +66,6 @@ class QueryService:
66
66
 
67
67
  # Get the user profile from the single source of truth.
68
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
69
 
73
70
  # render the iatoolkit main system prompt with the company/user information
74
71
  system_prompt_template = self.prompt_service.get_system_prompt()
@@ -23,7 +23,7 @@ class UserSessionContextService:
23
23
  return f"session:{company_short_name}/{user_identifier}"
24
24
 
25
25
  def clear_all_context(self, company_short_name: str, user_identifier: str):
26
- """Limpia el contexto de sesión para un usuario de forma atómica."""
26
+ """Limpia el contexto del LLM en la sesión para un usuario de forma atómica."""
27
27
  session_key = self._get_session_key(company_short_name, user_identifier)
28
28
  if session_key:
29
29
  # RedisSessionManager.remove(session_key)
@@ -6,7 +6,7 @@ let selectedPrompt = null; // Will hold a lightweight prompt object
6
6
 
7
7
  $(document).ready(function () {
8
8
  // Gatilla el redeem sin esperar ni manejar respuesta aquí
9
- if (window.redeemToken !== '') {
9
+ if (window.redeemToken) {
10
10
  const url = `/api/redeem_token`;
11
11
  // No await: dejamos que callToolkit maneje todo internamente
12
12
  callToolkit(url, {'token': window.redeemToken}, "POST").catch(() => {});
@@ -183,7 +183,7 @@ const handleChatMessage = async function () {
183
183
  user_identifier: window.user_identifier
184
184
  };
185
185
 
186
- const responseData = await callToolkit("/llm_query", data, "POST");
186
+ const responseData = await callToolkit("/api/llm_query", data, "POST");
187
187
  if (responseData && responseData.answer) {
188
188
  const answerSection = $('<div>').addClass('answer-section llm-output').append(responseData.answer);
189
189
  displayBotMessage(answerSection);
@@ -14,7 +14,8 @@
14
14
  method="post">
15
15
  <div class="mb-3">
16
16
  <label for="email" class="form-label d-block">Correo Electrónico</label>
17
- <input type="email" id="email" name="email" class="form-control" required>
17
+ <input type="email" id="email" name="email" class="form-control"
18
+ required value="{{ form_data.email or '' }}">
18
19
  </div>
19
20
  <div class="mb-3">
20
21
  <label for="password" class="form-label d-block">Contraseña</label>
@@ -183,11 +183,12 @@
183
183
  // --- Global Configuration from Backend ---
184
184
  window.companyShortName = "{{ company_short_name }}";
185
185
  window.user_identifier = "{{ user_identifier }}";
186
- window.redeemToken = "{{ redeem_token }}";
186
+ window.redeemToken = {{ redeem_token | tojson | default('null') }};
187
187
  window.iatoolkit_base_url = "{{ iatoolkit_base_url }}";
188
188
  window.availablePrompts = {{ prompts.message | tojson }};
189
189
  window.onboardingCards = {{ onboarding_cards | tojson }};
190
190
  window.sendButtonColor = "{{ branding.send_button_color }}";
191
+
191
192
  </script>
192
193
 
193
194
  <!-- Carga de los scripts JS externos después de definir las variables globales -->
@@ -195,7 +196,7 @@
195
196
  <script src="{{ url_for('static', filename='js/chat_filepond.js', _external=True) }}"></script>
196
197
  <script src="{{ url_for('static', filename='js/chat_history.js', _external=True) }}"></script>
197
198
  <script src="{{ url_for('static', filename='js/chat_feedback.js', _external=True) }}"></script>
198
- <script src="{{ url_for('static', filename='js/chat_main.js', _external=True) }}"></script>
199
+ <script src="{{ url_for('static', filename='js/chat_context_reload.js', _external=True) }}"></script><script src="{{ url_for('static', filename='js/chat_main.js', _external=True) }}"></script>
199
200
 
200
201
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css">
201
202
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
@@ -223,67 +224,6 @@
223
224
  })
224
225
  });
225
226
 
226
- document.addEventListener('DOMContentLoaded', function() {
227
- const reloadButton = document.getElementById('force-reload-button');
228
- if (!reloadButton) return;
229
-
230
- const originalIconClass = 'bi bi-arrow-clockwise';
231
- const spinnerIconClass = 'spinner-border spinner-border-sm';
232
-
233
- // Configuración de Toastr para que aparezca abajo a la derecha
234
- toastr.options = { "positionClass": "toast-bottom-right", "preventDuplicates": true };
235
-
236
- reloadButton.addEventListener('click', function(event) {
237
- event.preventDefault();
238
-
239
- if (reloadButton.disabled) return; // Prevenir doble clic
240
-
241
- // 1. Deshabilitar y mostrar spinner
242
- reloadButton.disabled = true;
243
- const icon = reloadButton.querySelector('i');
244
- icon.className = spinnerIconClass;
245
- toastr.info('Iniciando recarga de contexto en segundo plano...');
246
-
247
- // 2. Construir la URL dinámicamente
248
- const company = window.companyShortName;
249
- const reloadUrl = `/${company}/api/init_context_api`;
250
-
251
- // 3. Hacer la llamada AJAX con POST
252
- fetch(reloadUrl, {
253
- method: 'POST',
254
- headers: {
255
- 'Content-Type': 'application/json'
256
- },
257
- // Envía un cuerpo vacío o los datos necesarios
258
- body: JSON.stringify({})
259
- })
260
- .then(response => {
261
- if (!response.ok) {
262
- return response.json().then(err => {
263
- throw new Error(err.error_message || `Error del servidor: ${response.status}`);
264
- });
265
- }
266
- return response.json();
267
- })
268
- .then(data => {
269
- if (data.status === 'OK') {
270
- toastr.success(data.message || 'Contexto recargado exitosamente.');
271
- } else {
272
- toastr.error(data.error_message || 'Ocurrió un error desconocido.');
273
- }
274
- })
275
- .catch(error => {
276
- console.error('Error durante la recarga del contexto:', error);
277
- toastr.error(error.message || 'Error de red al intentar recargar.');
278
- })
279
- .finally(() => {
280
- // 4. Restaurar el botón
281
- reloadButton.disabled = false;
282
- icon.className = originalIconClass;
283
- });
284
- });
285
- });
286
-
287
227
  // Inicialización del modal de onboarding
288
228
  document.addEventListener('DOMContentLoaded', function () {
289
229
  const btn = document.getElementById('onboarding-button');
@@ -8,11 +8,13 @@ from flask.views import MethodView
8
8
  from flask import render_template, url_for
9
9
  from injector import inject
10
10
  from iatoolkit.services.profile_service import ProfileService
11
+ from iatoolkit.services.auth_service import AuthService
11
12
  from iatoolkit.services.query_service import QueryService
12
13
  from iatoolkit.services.branding_service import BrandingService
13
14
  from iatoolkit.services.onboarding_service import OnboardingService
14
15
  from iatoolkit.services.prompt_manager_service import PromptService
15
16
  from iatoolkit.services.jwt_service import JWTService
17
+ from iatoolkit.repositories.models import Company
16
18
 
17
19
 
18
20
  class BaseLoginView(MethodView):
@@ -23,6 +25,7 @@ class BaseLoginView(MethodView):
23
25
  @inject
24
26
  def __init__(self,
25
27
  profile_service: ProfileService,
28
+ auth_service: AuthService,
26
29
  jwt_service: JWTService,
27
30
  branding_service: BrandingService,
28
31
  prompt_service: PromptService,
@@ -30,6 +33,7 @@ class BaseLoginView(MethodView):
30
33
  query_service: QueryService
31
34
  ):
32
35
  self.profile_service = profile_service
36
+ self.auth_service = auth_service
33
37
  self.jwt_service = jwt_service
34
38
  self.branding_service = branding_service
35
39
  self.prompt_service = prompt_service
@@ -37,44 +41,26 @@ class BaseLoginView(MethodView):
37
41
  self.query_service = query_service
38
42
 
39
43
 
40
- def _handle_login_path(self, company_short_name: str, user_identifier: str, company):
44
+ def _handle_login_path(self,
45
+ company: Company,
46
+ user_identifier: str,
47
+ target_url: str,
48
+ redeem_token: str = None):
41
49
  """
42
50
  Centralized logic to decide between the fast path and the slow path.
43
51
  """
44
- # --- Get the company branding ---
52
+ # --- Get the company branding and onboarding_cards
45
53
  branding_data = self.branding_service.get_company_branding(company)
54
+ onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
55
+ company_short_name = company.short_name
46
56
 
47
57
  # this service decides is the context needs to be rebuilt or not
48
58
  prep_result = self.query_service.prepare_context(
49
- company_short_name=company_short_name, user_identifier=user_identifier
59
+ company_short_name=company.short_name, user_identifier=user_identifier
50
60
  )
51
61
 
52
- # generate continuation token for external login
53
- redeem_token = ''
54
- if self.__class__.__name__ == 'ExternalLoginView':
55
- redeem_token = self.jwt_service.generate_chat_jwt(
56
- company_short_name=company_short_name,
57
- user_identifier=user_identifier,
58
- expires_delta_seconds=300
59
- )
60
-
61
- if not redeem_token:
62
- return "Error al generar el redeem_token para login externo.", 500
63
-
64
62
  if prep_result.get('rebuild_needed'):
65
63
  # --- SLOW PATH: Render the loading shell ---
66
- onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
67
-
68
- # callback url to call when the context finish loading
69
- if redeem_token:
70
- target_url = url_for('finalize_with_token',
71
- company_short_name=company_short_name,
72
- token=redeem_token,
73
- _external=True)
74
- else:
75
- target_url = url_for('finalize_no_token',
76
- company_short_name=company_short_name,
77
- _external=True)
78
64
  return render_template(
79
65
  "onboarding_shell.html",
80
66
  iframe_src_url=target_url,
@@ -84,13 +70,12 @@ class BaseLoginView(MethodView):
84
70
  else:
85
71
  # --- FAST PATH: Render the chat page directly ---
86
72
  prompts = self.prompt_service.get_user_prompts(company_short_name)
87
- onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
88
73
  return render_template(
89
74
  "chat.html",
90
75
  company_short_name=company_short_name,
91
76
  user_identifier=user_identifier,
92
- branding=branding_data,
93
77
  prompts=prompts,
78
+ branding=branding_data,
94
79
  onboarding_cards=onboarding_cards,
95
80
  redeem_token=redeem_token
96
81
  )
@@ -5,46 +5,19 @@
5
5
 
6
6
  import os
7
7
  import logging
8
- from flask import request, jsonify
9
- from flask.views import MethodView
8
+ from flask import request, jsonify, url_for
10
9
  from injector import inject
11
- from iatoolkit.services.auth_service import AuthService
12
10
  from iatoolkit.views.base_login_view import BaseLoginView
13
11
 
14
12
  # Importar los servicios que necesita la clase base
15
13
  from iatoolkit.services.profile_service import ProfileService
16
14
  from iatoolkit.services.jwt_service import JWTService
17
- from iatoolkit.services.branding_service import BrandingService
18
- from iatoolkit.services.onboarding_service import OnboardingService
19
- from iatoolkit.services.query_service import QueryService
20
- from iatoolkit.services.prompt_manager_service import PromptService
21
15
 
22
16
  class ExternalLoginView(BaseLoginView):
23
17
  """
24
18
  Handles login for external users via API.
25
19
  Authenticates and then delegates the path decision (fast/slow) to the base class.
26
20
  """
27
- @inject
28
- def __init__(self,
29
- iauthentication: AuthService,
30
- jwt_service: JWTService,
31
- profile_service: ProfileService,
32
- branding_service: BrandingService,
33
- prompt_service: PromptService,
34
- onboarding_service: OnboardingService,
35
- query_service: QueryService):
36
- # Pass the dependencies for the base class to its __init__
37
- super().__init__(
38
- profile_service=profile_service,
39
- jwt_service=jwt_service,
40
- branding_service=branding_service,
41
- onboarding_service=onboarding_service,
42
- query_service=query_service,
43
- prompt_service=prompt_service,
44
- )
45
- # Handle the dependency specific to this child class
46
- self.iauthentication = iauthentication
47
-
48
21
  def post(self, company_short_name: str):
49
22
  data = request.get_json()
50
23
  if not data or 'user_identifier' not in data:
@@ -59,52 +32,52 @@ class ExternalLoginView(BaseLoginView):
59
32
  return jsonify({"error": "missing user_identifier"}), 404
60
33
 
61
34
  # 1. Authenticate the API call.
62
- iaut = self.iauthentication.verify()
63
- if not iaut.get("success"):
64
- return jsonify(iaut), 401
35
+ auth_response = self.auth_service.verify()
36
+ if not auth_response.get("success"):
37
+ return jsonify(auth_response), 401
65
38
 
66
39
  # 2. Create the external user session.
67
- self.profile_service.create_external_user_session(company, user_identifier)
40
+ self.profile_service.create_external_user_profile_context(company, user_identifier)
41
+
42
+ # 3. create a redeem_token for create session at the end of the process
43
+ redeem_token = self.jwt_service.generate_chat_jwt(
44
+ company_short_name=company_short_name,
45
+ user_identifier=user_identifier,
46
+ expires_delta_seconds=300
47
+ )
48
+
49
+ if not redeem_token:
50
+ return jsonify({"error": "Error al generar el redeem_token para login externo."}), 403
51
+
52
+ # 4. define URL to call when slow path is finished
53
+ target_url = url_for('finalize_with_token',
54
+ company_short_name=company_short_name,
55
+ token=redeem_token,
56
+ _external=True)
68
57
 
69
- # 3. Delegate the path decision to the centralized logic.
58
+ # 5. Delegate the path decision to the centralized logic.
70
59
  try:
71
- return self._handle_login_path(company_short_name, user_identifier, company)
60
+ return self._handle_login_path(company, user_identifier, target_url, redeem_token)
72
61
  except Exception as e:
73
62
  logging.exception(f"Error processing external login path for {company_short_name}/{user_identifier}: {e}")
74
63
  return jsonify({"error": f"Internal server error while starting chat. {str(e)}"}), 500
75
64
 
76
65
 
77
- class RedeemTokenApiView(MethodView):
66
+ class RedeemTokenApiView(BaseLoginView):
78
67
  # this endpoint is only used ONLY by chat_main.js to redeem a chat token
79
- @inject
80
- def __init__(self,
81
- profile_service: ProfileService,
82
- jwt_service: JWTService):
83
- self.profile_service = profile_service
84
- self.jwt_service = jwt_service
85
-
86
68
  def post(self, company_short_name: str):
87
69
  data = request.get_json()
88
70
  if not data or 'token' not in data:
89
71
  return jsonify({"error": "Falta token de validación"}), 400
90
72
 
91
- # 1. validate the token
73
+ # get the token and validate with auth service
92
74
  token = data.get('token')
93
- payload = self.jwt_service.validate_chat_jwt(token)
94
- if not payload:
95
- logging.warning("Intento de canjear un token inválido o expirado.")
96
- return {"error": "Token inválido o expirado."}, 401
97
-
98
- # 2. if token is valid, extract the user_identifier
99
- user_identifier = payload.get('user_identifier')
100
-
101
- try:
102
- # 3. create the Flask session
103
- self.profile_service.set_session_for_user(company_short_name, user_identifier)
104
- logging.info(f"Token de sesión canjeado exitosamente para {user_identifier}.")
75
+ redeem_result = self.auth_service.redeem_token_for_session(
76
+ company_short_name=company_short_name,
77
+ token=token
78
+ )
105
79
 
106
- return {"status": "ok"}, 200
80
+ if not redeem_result['success']:
81
+ return {"error": redeem_result['error']}, 401
107
82
 
108
- except Exception as e:
109
- logging.error(f"Error al crear la sesión desde token para {user_identifier}: {e}")
110
- return {"error": "No se pudo crear la sesión del usuario."}, 500
83
+ return {"status": "ok"}, 200
@@ -42,6 +42,7 @@ class InitContextApiView(MethodView):
42
42
  self.query_service.session_context.clear_all_context(company_short_name, user_identifier)
43
43
  logging.info(f"Context for {company_short_name}/{user_identifier} has been cleared.")
44
44
 
45
+ # LLM context is clean, now we can load it again
45
46
  self.query_service.prepare_context(
46
47
  company_short_name=company_short_name,
47
48
  user_identifier=user_identifier
@@ -52,7 +53,7 @@ class InitContextApiView(MethodView):
52
53
  user_identifier=user_identifier
53
54
  )
54
55
 
55
- logging.info(f"Context for {company_short_name}/{user_identifier} rebuilt successfully.")
56
+ # logging.info(f"Context for {company_short_name}/{user_identifier} rebuilt successfully.")
56
57
 
57
58
  # 3. Respond with JSON, as this is an API endpoint.
58
59
  return jsonify({'status': 'OK', 'message': 'Context has been reloaded successfully.'}), 200
@@ -32,12 +32,6 @@ class LLMQueryApiView(MethodView):
32
32
  if not data:
33
33
  return jsonify({"error": "Invalid JSON body"}), 400
34
34
 
35
- # 3. Ensure session state exists for this API user (lazy creation).
36
- profile = self.profile_service.get_profile_by_identifier(company_short_name, user_identifier)
37
- if not profile:
38
- company = self.profile_service.get_company_by_short_name(company_short_name)
39
- self.profile_service.create_external_user_session(company, user_identifier)
40
-
41
35
  # 4. Call the unified query service method.
42
36
  result = self.query_service.llm_query(
43
37
  company_short_name=company_short_name,
@@ -7,6 +7,7 @@ from flask.views import MethodView
7
7
  from flask import request, redirect, render_template, url_for
8
8
  from injector import inject
9
9
  from iatoolkit.services.profile_service import ProfileService
10
+ from iatoolkit.services.auth_service import AuthService
10
11
  from iatoolkit.services.jwt_service import JWTService
11
12
  from iatoolkit.services.query_service import QueryService
12
13
  from iatoolkit.services.prompt_manager_service import PromptService
@@ -21,7 +22,6 @@ class LoginView(BaseLoginView):
21
22
  Handles login for local users.
22
23
  Authenticates and then delegates the path decision (fast/slow) to the base class.
23
24
  """
24
-
25
25
  def post(self, company_short_name: str):
26
26
  company = self.profile_service.get_company_by_short_name(company_short_name)
27
27
  if not company:
@@ -30,8 +30,8 @@ class LoginView(BaseLoginView):
30
30
  email = request.form.get('email')
31
31
  password = request.form.get('password')
32
32
 
33
- # 1. Authenticate user and create the session for internal user
34
- auth_response = self.profile_service.login(
33
+ # 1. Authenticate internal user
34
+ auth_response = self.auth_service.login_local_user(
35
35
  company_short_name=company_short_name,
36
36
  email=email,
37
37
  password=password
@@ -51,9 +51,14 @@ class LoginView(BaseLoginView):
51
51
 
52
52
  user_identifier = auth_response['user_identifier']
53
53
 
54
+ # 3. define URL to call when slow path is finished
55
+ target_url = url_for('finalize_no_token',
56
+ company_short_name=company_short_name,
57
+ _external=True)
58
+
54
59
  # 2. Delegate the path decision to the centralized logic.
55
60
  try:
56
- return self._handle_login_path(company_short_name, user_identifier, company)
61
+ return self._handle_login_path(company, user_identifier, target_url)
57
62
  except Exception as e:
58
63
  return render_template("error.html", company=company, company_short_name=company_short_name,
59
64
  message=f"Error processing login path: {str(e)}"), 500
@@ -67,6 +72,7 @@ class FinalizeContextView(MethodView):
67
72
  @inject
68
73
  def __init__(self,
69
74
  profile_service: ProfileService,
75
+ auth_service: AuthService,
70
76
  query_service: QueryService,
71
77
  prompt_service: PromptService,
72
78
  branding_service: BrandingService,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.56.0
3
+ Version: 0.58.0
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
@@ -2,10 +2,10 @@ iatoolkit/__init__.py,sha256=4PWjMJjktixtrxF6BY405qyA50Sv967kEP2x-oil6qk,1120
2
2
  iatoolkit/base_company.py,sha256=nfF-G0h63jy3Qh9kCnvx8Ozx76IjG2p7a34HpweWhOk,4608
3
3
  iatoolkit/cli_commands.py,sha256=G5L9xQXZ0lVFXQWBaE_KEZHyfuiT6PL1nTQRoSdnBzc,2302
4
4
  iatoolkit/company_registry.py,sha256=tduqt3oV8iDX_IB1eA7KIgvIxE4edTcy-3qZIXh3Lzw,2549
5
- iatoolkit/iatoolkit.py,sha256=ve63cb-1L-m_RLPsU7KRG9IPhhMREb1fDQjqOIgHx9w,17583
5
+ iatoolkit/iatoolkit.py,sha256=tFx-D1Q0usCdOc3vSNdtz94mTCWhUGcyRkDISI2QMMs,17583
6
6
  iatoolkit/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  iatoolkit/common/exceptions.py,sha256=EXx40n5htp7UiOM6P1xfJ9U6NMcADqm62dlFaKz7ICU,1154
8
- iatoolkit/common/routes.py,sha256=c8kRk8pEK_nV-2P1LiYXRERmW_QoW21Hk6CZkkizv6Y,6419
8
+ iatoolkit/common/routes.py,sha256=vwPGZExbyYNM_-HXHRInSf8Py6uWjiOxifv2hbn_sGo,6145
9
9
  iatoolkit/common/session_manager.py,sha256=UeKfD15bcEA3P5e0WSURfotLqpsiIMp3AXxAMhtgHs0,471
10
10
  iatoolkit/common/util.py,sha256=w9dTd3csK0gKtFSp-a4t7XmCPZiYDhiON92uXRbTT8A,14624
11
11
  iatoolkit/infra/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
@@ -29,12 +29,12 @@ iatoolkit/repositories/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCL
29
29
  iatoolkit/repositories/database_manager.py,sha256=QgV8hNnVv9RmeOvUdomdj_mfk0bf3Rl8Ti41a-5zIAY,3700
30
30
  iatoolkit/repositories/document_repo.py,sha256=Y7bF1kZB1HWJsAGjWdF7P2aVYeTYNufq9ngQXp7mDkY,1124
31
31
  iatoolkit/repositories/llm_query_repo.py,sha256=YT_t7cYGQk8rwzH_17-28aTzO-e2jUfa2rvXy8tugvA,3612
32
- iatoolkit/repositories/models.py,sha256=3YbIJXNMZiTkMbPyiSOiyzqUKEQF0JIfN4VSWYzwr44,13146
32
+ iatoolkit/repositories/models.py,sha256=qM95kiKm_92JoPNXiwMT4HTjr5BjalnrDiatG3Rte-M,14300
33
33
  iatoolkit/repositories/profile_repo.py,sha256=21am3GP7XCG0nq6i3pArQ7mfGsrRn8rdcWT98fsdwlU,4397
34
34
  iatoolkit/repositories/tasks_repo.py,sha256=icVO_r2oPagGnnBhwVFzznnvEEU2EAx-2dlWuWvoDC4,1745
35
35
  iatoolkit/repositories/vs_repo.py,sha256=UkpmQQiocgM5IwRBmmWhw3HHzHP6zK1nN3J3TcQgjhc,5300
36
36
  iatoolkit/services/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
37
- iatoolkit/services/auth_service.py,sha256=XHG2F0Vf2wDFjylc_OdDEIJL8I4cdhxeUIZIltgE4DU,2936
37
+ iatoolkit/services/auth_service.py,sha256=vBVKkFJWsaPhhR-LvfLXfIrR-mmR-P5Y8Ya_5p1pgC8,7126
38
38
  iatoolkit/services/benchmark_service.py,sha256=CdbFYyS3FHFhNzWQEa9ZNjUlmON10DT1nKNbZQ1EUi8,5880
39
39
  iatoolkit/services/branding_service.py,sha256=gXj9Lj6EIFNIHT6wAHia5lr4_2a2sD-ExMbewno5YD8,7505
40
40
  iatoolkit/services/dispatcher_service.py,sha256=Qdn2x4cozpgpKg2448sUxkhO6tuplzb8xPWUxdTTFBE,12772
@@ -46,19 +46,19 @@ iatoolkit/services/jwt_service.py,sha256=W2kQVNQheQSLkNLS7RZ4jd3hmySBPLbHAS5hvBr
46
46
  iatoolkit/services/load_documents_service.py,sha256=ZpB0BZ3qX1fGJGBtZtMLbFdWWx0hkPoeCS3OqJKwCTs,7291
47
47
  iatoolkit/services/mail_service.py,sha256=2h-fcF3swZDya_o7IpgXkmuj3iEVHVCiHi7oVxU99sQ,2182
48
48
  iatoolkit/services/onboarding_service.py,sha256=cMO2Ho1-G3wAeVNl-j25LwCMJjRwj3yKHpYKnZUFLDE,2001
49
- iatoolkit/services/profile_service.py,sha256=uWlCsoGAPZaJkvVTBZVBWdh6Rk5LHcpDHWgYqi5vTp4,20317
49
+ iatoolkit/services/profile_service.py,sha256=TuWV5wVR7gUYylOPKcUR0KdxHAglRv0cXxO18kCgng0,20300
50
50
  iatoolkit/services/prompt_manager_service.py,sha256=U-XmSpkeXvv1KRN4dytdMxSYBMRSB7y-UHcb18mk0nA,8342
51
- iatoolkit/services/query_service.py,sha256=J42KzthYIIsUX8cUS2E7vcXA6IZOJ28Y8I637m-9za4,17649
51
+ iatoolkit/services/query_service.py,sha256=zv2xIy-Ukppso-1UEl7e2Gq31pherrOZ-TbeuUU4oZM,17509
52
52
  iatoolkit/services/search_service.py,sha256=i1xGWu7ORKIIDH0aAQBkF86dVVbLQ0Yrooz5TiZ6aGo,1823
53
53
  iatoolkit/services/sql_service.py,sha256=MIslAtpJWnTMgSD74nnqTvQj27p-lHiyRXc6OiA2C_c,2172
54
54
  iatoolkit/services/tasks_service.py,sha256=itREO5rDnUIgsqtyCOBKDtH30QL5v1egs4qPTiBK8xU,6865
55
55
  iatoolkit/services/user_feedback_service.py,sha256=ooy750qWmYOeJi-IJQofu8pLG4svGjGU_JKpKMURZkw,2353
56
- iatoolkit/services/user_session_context_service.py,sha256=TeYi4xF04Xk-4Qnp6cVTmxuNzA4B1nMVUuFDjTHeiZQ,6764
56
+ iatoolkit/services/user_session_context_service.py,sha256=vYF_vWM37tPB_ZyPBJ6f6WTJVjT2j-4L8JfZbqbI93k,6775
57
57
  iatoolkit/static/images/fernando.jpeg,sha256=W68TYMuo5hZVpbP-evwH6Nu4xWFv2bc8pJzSKDoLTeQ,100612
58
58
  iatoolkit/static/js/chat_feedback.js,sha256=zlLEDQfEocGK7RKG2baqI-9fyQlqe6hVuAHOKTPmWek,4399
59
59
  iatoolkit/static/js/chat_filepond.js,sha256=mzXafm7a506EpM37KATTK3zvAswO1E0KSUY1vKbwuRc,3163
60
60
  iatoolkit/static/js/chat_history.js,sha256=4h6ldU7cDvgkW84fMKB8JReoxCX0NKSQAir_4CzAF9I,4382
61
- iatoolkit/static/js/chat_main.js,sha256=3ZsMy6NauobPRaYDvSJAws26l16FDslXGm8kGfXLt_E,18538
61
+ iatoolkit/static/js/chat_main.js,sha256=o1ZNkeaBgGG9d07e5BxTL9OI0aJ26z1bvca1lGoQ-Uo,18535
62
62
  iatoolkit/static/js/chat_onboarding.js,sha256=b6ofiFcPhuCaPmSFIvDQZqcMUVvbI7LpIsjZOZJUSAU,3185
63
63
  iatoolkit/static/styles/chat_iatoolkit.css,sha256=aA-PZ2TGl_k82JSVVBC2-CJT0NiZAuLOGoiaJhdeVUU,11416
64
64
  iatoolkit/static/styles/chat_info.css,sha256=17DbgoNYE21VYWfb5L9-QLCpD2R1idK4imKRLwXtJLY,1058
@@ -70,12 +70,12 @@ iatoolkit/system_prompts/format_styles.prompt,sha256=MSMe1qvR3cF_0IbFshn8R0z6Wx6
70
70
  iatoolkit/system_prompts/query_main.prompt,sha256=D2Wjf0uunQIQsQiJVrY-BTQz6PemM5En6ftmw_c5t4E,2808
71
71
  iatoolkit/system_prompts/sql_rules.prompt,sha256=y4nURVnb9AyFwt-lrbMNBHHtZlhk6kC9grYoOhRnrJo,59174
72
72
  iatoolkit/templates/_branding_styles.html,sha256=x0GJmY1WWpPxKBUoqmxh685_1c6-4uLJWNPU6r81uAc,1912
73
- iatoolkit/templates/_login_widget.html,sha256=p7Xz0P1Xd3Otn41uVQTA3GmDswMrUVMIn9doDNJojAA,1852
73
+ iatoolkit/templates/_login_widget.html,sha256=JeqVqUlFqst7_qrZ5vbE_MjmqVJC6bCKnrhJwDboxew,1903
74
74
  iatoolkit/templates/_navbar.html,sha256=o1PvZE5ueLmVpGUAmsjtu-vS_WPROTlJc2sTXl6AS4Y,360
75
75
  iatoolkit/templates/about.html,sha256=ciC08grUVz5qLzdzDDqDX31xirg5PrJIRYabWpV9oA8,294
76
76
  iatoolkit/templates/base.html,sha256=hHfBqZJsPcZGlb4BxAbHvpJFcSjckLIAVTYTmoyXrz0,2323
77
77
  iatoolkit/templates/change_password.html,sha256=G5a3hYLTpz_5Q_eZ4LNcYSqLeW-CuT4NCHD8bkhAd9k,3573
78
- iatoolkit/templates/chat.html,sha256=usmyuke0Moh6jNMWvxK2p_N15V6UzHNeXyXDd45XWIU,14087
78
+ iatoolkit/templates/chat.html,sha256=NdKj1cDqQWSLrjMjfne32IZcwBdg_DV9apm8BMeTQDI,11952
79
79
  iatoolkit/templates/chat_modals.html,sha256=NwwgPoOmVbjy4aO2eHsy1TUMXRiOfTOC5Jx_F2ehhcs,6947
80
80
  iatoolkit/templates/error.html,sha256=c3dxieMygsvdjQMiQu_sn6kqqag9zFtVu-z5FunX6so,580
81
81
  iatoolkit/templates/forgot_password.html,sha256=NRZqbNHJXSLNArF_KLbzuem-U57v07awS0ikI_DJbfM,2360
@@ -86,26 +86,25 @@ iatoolkit/templates/onboarding_shell.html,sha256=r1ivSR2ci8GrDSm1uaD-cf78rfO1bKT
86
86
  iatoolkit/templates/signup.html,sha256=9ArDvcNQgHFR2dwxy-37AXzGUOeOsT7Nz5u0y6fAB3U,4385
87
87
  iatoolkit/templates/test.html,sha256=rwNtxC83tbCl5COZFXYvmRBxxmgFJtPNuVBd_nq9KWY,133
88
88
  iatoolkit/views/__init__.py,sha256=5JqK9sZ6jBuK83zDQokUhxQ0wuJJJ9DXB8pYCLkX7X4,102
89
- iatoolkit/views/base_login_view.py,sha256=6AANVhwhs5MyT3WVjJqKZLoS1TwyErsrKnb1xhpqP4Y,3970
89
+ iatoolkit/views/base_login_view.py,sha256=qoMMrAezCJvzjcfNzIbd2vwHhksALIQfNK8f_9E8m0o,3241
90
90
  iatoolkit/views/change_password_view.py,sha256=tM0woZyKdhY4XYjS_YXg2sKq3RYkXGfcq_eVAKrNvNM,4498
91
91
  iatoolkit/views/chat_token_request_view.py,sha256=wf32_A2Sq8NHYWshCwL10Tovd1znLoD0jQjzutR3sVE,4408
92
- iatoolkit/views/external_login_view.py,sha256=nuWebrl2Opnhj6H-UW6dUOHwi8pj4s1_-dN3VEKNQdY,4483
92
+ iatoolkit/views/external_login_view.py,sha256=6EM1pfMo1yP5ofADIyq_d4m7wH-OppKgLa86KFdiyH0,3282
93
93
  iatoolkit/views/file_store_api_view.py,sha256=Uz9f6sey3_F5K8zuyQz6SwYRKAalCjD1ekf-Mkl_Kfo,2326
94
94
  iatoolkit/views/forgot_password_view.py,sha256=-qKJeeOBqJFdvDUk7rCNg1E1cDQnJQkozPpb0T0FgwA,3159
95
95
  iatoolkit/views/history_api_view.py,sha256=x-tZhB8UzqrD2n-WDIfmHK9iVhGZ9f0yncsGs9mxwt0,1953
96
96
  iatoolkit/views/index_view.py,sha256=P5aVdEWxsYOZGbzcXd6WFE733qZ7YXIoeqriUMAM6V8,1527
97
- iatoolkit/views/init_context_api_view.py,sha256=1j8NKfODfPrffbA5YO8TPMHh-ildlLNzReIxv_qO-W4,2586
98
- iatoolkit/views/llmquery_api_view.py,sha256=Rh-y-VENwwtNsDrYAD_SWKwjK16fW-pFRWlEvI-OYwY,2120
99
- iatoolkit/views/llmquery_web_view.py,sha256=WhjlA1mfsoL8hL9tlKQfjCUcaTzT43odlp_uQKmT314,1500
97
+ iatoolkit/views/init_context_api_view.py,sha256=tWlAN01nr3otXchraDHZZdMCFxAEM_GI0SwiFuhehAQ,2649
98
+ iatoolkit/views/llmquery_api_view.py,sha256=ANnspwka7Yxjf2X58tE6IippyBfjOP-RgwbJlbOO_tw,1740
100
99
  iatoolkit/views/login_simulation_view.py,sha256=0Qt-puRnltI2HZxlfdyJmOf26-hQp3xjknGV_jkwV7E,3484
101
- iatoolkit/views/login_view.py,sha256=sjM8ki_F7C9S0a9pukLd5jn5KnM2sbRabnAIrtIK8KQ,5373
100
+ iatoolkit/views/login_view.py,sha256=ESJLKHGUKQw71STHK2AoxugQUJmxnPYlI13n7ZawLLg,5663
102
101
  iatoolkit/views/prompt_api_view.py,sha256=MP0r-MiswwKcbNc_5KY7aVbHkrR218I8XCiCX1D0yTA,1244
103
102
  iatoolkit/views/signup_view.py,sha256=BCjhM2lMiDPwYrlW_eEwPl-ZLupblbFfsonWtq0E4vU,3922
104
103
  iatoolkit/views/tasks_review_view.py,sha256=keLsLCyOTTlcoIapnB_lbuSvLwrPVZVpBiFC_7ChbLg,3388
105
104
  iatoolkit/views/tasks_view.py,sha256=a3anTXrJTTvbQuc6PSpOzidLKQFL4hWa7PI2Cppcz8w,4110
106
105
  iatoolkit/views/user_feedback_api_view.py,sha256=59XB9uQLHI4Q6QA4_XhK787HzfXb-c6EY7k1Ccyr4hI,2424
107
106
  iatoolkit/views/verify_user_view.py,sha256=7XLSaxvs8LjBr3cYOUDa9B8DqW_50IGlq0IvmOQcD0Y,2340
108
- iatoolkit-0.56.0.dist-info/METADATA,sha256=Td4cobpNkkpPaIPZALITKGwwBHowzfG3klOYoGANjsk,9301
109
- iatoolkit-0.56.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
110
- iatoolkit-0.56.0.dist-info/top_level.txt,sha256=V_w4QvDx0b1RXiy8zTCrD1Bp7AZkFe3_O0-9fMiwogg,10
111
- iatoolkit-0.56.0.dist-info/RECORD,,
107
+ iatoolkit-0.58.0.dist-info/METADATA,sha256=0dlpinBIYN54AdKHdawbfpToVXPHyCKbfa9dM1Vi6dw,9301
108
+ iatoolkit-0.58.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
109
+ iatoolkit-0.58.0.dist-info/top_level.txt,sha256=V_w4QvDx0b1RXiy8zTCrD1Bp7AZkFe3_O0-9fMiwogg,10
110
+ iatoolkit-0.58.0.dist-info/RECORD,,
@@ -1,38 +0,0 @@
1
- from flask.views import MethodView
2
- from flask import request, jsonify
3
- from injector import inject
4
- from iatoolkit.services.query_service import QueryService
5
- from iatoolkit.services.auth_service import AuthService # Use AuthService for session check
6
-
7
-
8
- class LLMQueryWebView(MethodView): # Renamed for clarity
9
- """
10
- Web-only endpoint for submitting queries from the chat UI.
11
- Authenticates via Flask session cookie.
12
- """
13
-
14
- @inject
15
- def __init__(self, auth_service: AuthService, query_service: QueryService):
16
- self.auth_service = auth_service
17
- self.query_service = query_service
18
-
19
- def post(self, company_short_name: str):
20
- # 1. Authenticate the web session request.
21
- auth_result = self.auth_service.verify()
22
- if not auth_result.get("success"):
23
- return jsonify({"error": auth_result.get("error_message")}), auth_result.get("status_code", 401)
24
-
25
- # 2. Get the guaranteed user_identifier from the auth result.
26
- user_identifier = auth_result['user_identifier']
27
- data = request.get_json() or {}
28
-
29
- # 3. Call the unified query service method.
30
- result = self.query_service.llm_query(
31
- company_short_name=company_short_name,
32
- user_identifier=user_identifier,
33
- question=data.get('question', ''),
34
- prompt_name=data.get('prompt_name'),
35
- client_data=data.get('client_data', {}),
36
- files=data.get('files', [])
37
- )
38
- return jsonify(result)