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.
- iatoolkit/common/routes.py +31 -31
- iatoolkit/common/session_manager.py +0 -1
- iatoolkit/common/util.py +0 -21
- iatoolkit/iatoolkit.py +5 -18
- iatoolkit/infra/llm_client.py +3 -5
- iatoolkit/infra/redis_session_manager.py +48 -2
- iatoolkit/repositories/models.py +1 -2
- iatoolkit/services/auth_service.py +74 -0
- iatoolkit/services/dispatcher_service.py +12 -21
- iatoolkit/services/excel_service.py +15 -15
- iatoolkit/services/history_service.py +2 -11
- iatoolkit/services/profile_service.py +83 -25
- iatoolkit/services/query_service.py +132 -82
- iatoolkit/services/tasks_service.py +1 -1
- iatoolkit/services/user_feedback_service.py +3 -6
- iatoolkit/services/user_session_context_service.py +112 -54
- iatoolkit/static/js/chat_feedback.js +1 -1
- iatoolkit/static/js/chat_history.js +1 -5
- iatoolkit/static/js/chat_main.js +1 -1
- iatoolkit/static/styles/landing_page.css +62 -2
- iatoolkit/system_prompts/query_main.prompt +3 -12
- iatoolkit/templates/_login_widget.html +6 -8
- iatoolkit/templates/chat.html +78 -4
- iatoolkit/templates/error.html +1 -1
- iatoolkit/templates/index.html +38 -11
- iatoolkit/templates/{home.html → login_test.html} +11 -51
- iatoolkit/views/external_login_view.py +50 -111
- iatoolkit/views/{file_store_view.py → file_store_api_view.py} +4 -4
- iatoolkit/views/history_api_view.py +52 -0
- iatoolkit/views/init_context_api_view.py +62 -0
- iatoolkit/views/llmquery_api_view.py +50 -0
- iatoolkit/views/llmquery_web_view.py +38 -0
- iatoolkit/views/{home_view.py → login_test_view.py} +2 -5
- iatoolkit/views/login_view.py +79 -56
- iatoolkit/views/{prompt_view.py → prompt_api_view.py} +4 -4
- iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -19
- {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.0.dist-info}/METADATA +2 -2
- {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.0.dist-info}/RECORD +40 -41
- iatoolkit/common/auth.py +0 -200
- iatoolkit/templates/login.html +0 -43
- iatoolkit/views/download_file_view.py +0 -58
- iatoolkit/views/history_view.py +0 -57
- iatoolkit/views/init_context_view.py +0 -35
- iatoolkit/views/llmquery_view.py +0 -65
- {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.0.dist-info}/WHEEL +0 -0
- {iatoolkit-0.22.1.dist-info → iatoolkit-0.50.0.dist-info}/top_level.txt +0 -0
iatoolkit/common/routes.py
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
from flask import render_template, redirect, flash, url_for,send_from_directory, current_app, abort
|
|
7
7
|
from iatoolkit.common.session_manager import SessionManager
|
|
8
8
|
from flask import jsonify
|
|
9
|
-
from iatoolkit.views.
|
|
9
|
+
from iatoolkit.views.history_api_view import HistoryApiView
|
|
10
10
|
import os
|
|
11
11
|
|
|
12
12
|
|
|
@@ -20,36 +20,34 @@ def logout(company_short_name: str):
|
|
|
20
20
|
def register_views(injector, app):
|
|
21
21
|
|
|
22
22
|
from iatoolkit.views.index_view import IndexView
|
|
23
|
-
from iatoolkit.views.
|
|
24
|
-
from iatoolkit.views.
|
|
23
|
+
from iatoolkit.views.init_context_api_view import InitContextApiView
|
|
24
|
+
from iatoolkit.views.llmquery_web_view import LLMQueryWebView
|
|
25
|
+
from iatoolkit.views.llmquery_api_view import LLMQueryApiView
|
|
25
26
|
from iatoolkit.views.tasks_view import TaskView
|
|
26
27
|
from iatoolkit.views.tasks_review_view import TaskReviewView
|
|
27
|
-
from iatoolkit.views.
|
|
28
|
+
from iatoolkit.views.login_test_view import LoginTest
|
|
28
29
|
from iatoolkit.views.login_view import LoginView, InitiateLoginView
|
|
29
|
-
from iatoolkit.views.external_login_view import InitiateExternalChatView
|
|
30
|
+
from iatoolkit.views.external_login_view import InitiateExternalChatView
|
|
30
31
|
from iatoolkit.views.signup_view import SignupView
|
|
31
32
|
from iatoolkit.views.verify_user_view import VerifyAccountView
|
|
32
33
|
from iatoolkit.views.forgot_password_view import ForgotPasswordView
|
|
33
34
|
from iatoolkit.views.change_password_view import ChangePasswordView
|
|
34
|
-
from iatoolkit.views.
|
|
35
|
-
from iatoolkit.views.
|
|
36
|
-
from iatoolkit.views.
|
|
35
|
+
from iatoolkit.views.file_store_api_view import FileStoreApiView
|
|
36
|
+
from iatoolkit.views.user_feedback_api_view import UserFeedbackApiView
|
|
37
|
+
from iatoolkit.views.prompt_api_view import PromptApiView
|
|
37
38
|
from iatoolkit.views.chat_token_request_view import ChatTokenRequestView
|
|
38
|
-
from iatoolkit.views.download_file_view import DownloadFileView
|
|
39
39
|
|
|
40
40
|
# iatoolkit home page
|
|
41
41
|
app.add_url_rule('/<company_short_name>', view_func=IndexView.as_view('index'))
|
|
42
42
|
|
|
43
43
|
# init (reset) the company context (with api-key)
|
|
44
|
-
app.add_url_rule('/<company_short_name
|
|
45
|
-
|
|
44
|
+
app.add_url_rule('/<company_short_name>/api/init_context_api',
|
|
45
|
+
view_func=InitContextApiView.as_view('init_context_api'))
|
|
46
46
|
|
|
47
47
|
# this functions are for login external users (with api-key)
|
|
48
48
|
# only the first one should be used from an external app
|
|
49
49
|
app.add_url_rule('/<company_short_name>/initiate_external_chat',
|
|
50
50
|
view_func=InitiateExternalChatView.as_view('initiate_external_chat'))
|
|
51
|
-
app.add_url_rule('/<company_short_name>/external_login',
|
|
52
|
-
view_func=ExternalChatLoginView.as_view('external_login'))
|
|
53
51
|
|
|
54
52
|
# this endpoint is for requesting a chat token for external users
|
|
55
53
|
app.add_url_rule('/auth/chat_token',
|
|
@@ -70,36 +68,27 @@ def register_views(injector, app):
|
|
|
70
68
|
|
|
71
69
|
# main chat query, used by the JS in the browser (with credentials)
|
|
72
70
|
# can be used also for executing iatoolkit prompts
|
|
73
|
-
app.add_url_rule('/<company_short_name>/llm_query', view_func=
|
|
71
|
+
app.add_url_rule('/<company_short_name>/llm_query', view_func=LLMQueryWebView.as_view('llm_query_web'))
|
|
74
72
|
|
|
75
|
-
#
|
|
73
|
+
# this is the same function as above, but with api-key
|
|
74
|
+
app.add_url_rule('/<company_short_name>/api/llm_query', view_func=LLMQueryApiView.as_view('llm_query_api'))
|
|
75
|
+
|
|
76
|
+
# chat buttons are here on
|
|
76
77
|
|
|
77
78
|
# open the promt directory
|
|
78
|
-
app.add_url_rule('/<company_short_name>/prompts', view_func=
|
|
79
|
+
app.add_url_rule('/<company_short_name>/api/prompts', view_func=PromptApiView.as_view('prompt'))
|
|
79
80
|
|
|
80
81
|
# feedback and history
|
|
81
|
-
app.add_url_rule('/<company_short_name>/feedback', view_func=
|
|
82
|
-
app.add_url_rule('/<company_short_name>/history', view_func=
|
|
82
|
+
app.add_url_rule('/<company_short_name>/api/feedback', view_func=UserFeedbackApiView.as_view('feedback'))
|
|
83
|
+
app.add_url_rule('/<company_short_name>/api/history', view_func=HistoryApiView.as_view('history'))
|
|
83
84
|
|
|
84
85
|
# tasks management endpoints: create task, and review answer
|
|
85
86
|
app.add_url_rule('/tasks', view_func=TaskView.as_view('tasks'))
|
|
86
87
|
app.add_url_rule('/tasks/review/<int:task_id>', view_func=TaskReviewView.as_view('tasks-review'))
|
|
87
88
|
|
|
88
89
|
# this endpoint is for upload documents into the vector store (api-key)
|
|
89
|
-
app.add_url_rule('/load', view_func=
|
|
90
|
-
|
|
91
|
-
# login testing (old home page)
|
|
92
|
-
app.add_url_rule('/login_testing', view_func=HomeView.as_view('home'))
|
|
93
|
-
|
|
94
|
-
app.add_url_rule(
|
|
95
|
-
'/about', # URL de la ruta
|
|
96
|
-
view_func=lambda: render_template('about.html'))
|
|
97
|
-
|
|
98
|
-
app.add_url_rule('/version', 'version',
|
|
99
|
-
lambda: jsonify({"iatoolkit_version": current_app.config.get('VERSION', 'N/A')}))
|
|
90
|
+
app.add_url_rule('/api/load', view_func=FileStoreApiView.as_view('load_api'))
|
|
100
91
|
|
|
101
|
-
app.add_url_rule('/<company_short_name>/<external_user_id>/download-file/<path:filename>',
|
|
102
|
-
view_func=DownloadFileView.as_view('download-file'))
|
|
103
92
|
|
|
104
93
|
@app.route('/download/<path:filename>')
|
|
105
94
|
def download_file(filename):
|
|
@@ -122,6 +111,17 @@ def register_views(injector, app):
|
|
|
122
111
|
except FileNotFoundError:
|
|
123
112
|
abort(404)
|
|
124
113
|
|
|
114
|
+
# login testing (old home page)
|
|
115
|
+
app.add_url_rule('/login_test', view_func=LoginTest.as_view('login_test'))
|
|
116
|
+
|
|
117
|
+
app.add_url_rule(
|
|
118
|
+
'/about', # URL de la ruta
|
|
119
|
+
view_func=lambda: render_template('about.html'))
|
|
120
|
+
|
|
121
|
+
app.add_url_rule('/version', 'version',
|
|
122
|
+
lambda: jsonify({"iatoolkit_version": current_app.config.get('VERSION', 'N/A')}))
|
|
123
|
+
|
|
124
|
+
|
|
125
125
|
# hacer que la raíz '/' vaya al home de iatoolkit
|
|
126
126
|
@app.route('/')
|
|
127
127
|
def root_redirect():
|
iatoolkit/common/util.py
CHANGED
|
@@ -9,7 +9,6 @@ from iatoolkit.common.exceptions import IAToolkitException
|
|
|
9
9
|
from injector import inject
|
|
10
10
|
import os
|
|
11
11
|
from jinja2 import Environment, FileSystemLoader
|
|
12
|
-
from iatoolkit.common.session_manager import SessionManager
|
|
13
12
|
from datetime import datetime, date
|
|
14
13
|
from decimal import Decimal
|
|
15
14
|
import yaml
|
|
@@ -22,26 +21,6 @@ class Utility:
|
|
|
22
21
|
def __init__(self):
|
|
23
22
|
self.encryption_key = os.getenv('FERNET_KEY')
|
|
24
23
|
|
|
25
|
-
@staticmethod
|
|
26
|
-
def resolve_user_identifier(external_user_id: str = None, local_user_id: int = 0) -> tuple[str, bool]:
|
|
27
|
-
"""
|
|
28
|
-
Resuelve un identificador único de usuario desde external_user_id o local_user_id.
|
|
29
|
-
|
|
30
|
-
Lógica:
|
|
31
|
-
- Si external_user_id existe y no está vacío: usar external_user_id
|
|
32
|
-
- Si no, y local_user_id > 0: obtener email de la sesión actual y retornarlo como ID
|
|
33
|
-
- Si ninguno: retornar string vacío
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
if external_user_id and external_user_id.strip():
|
|
37
|
-
return external_user_id.strip(), False
|
|
38
|
-
elif local_user_id and local_user_id > 0:
|
|
39
|
-
# get the user information from the session
|
|
40
|
-
user_data = SessionManager.get('user')
|
|
41
|
-
if user_data:
|
|
42
|
-
return user_data.get('email', ''), True
|
|
43
|
-
|
|
44
|
-
return "", False
|
|
45
24
|
|
|
46
25
|
def render_prompt_from_template(self,
|
|
47
26
|
template_pathname: str,
|
iatoolkit/iatoolkit.py
CHANGED
|
@@ -257,7 +257,6 @@ class IAToolkit:
|
|
|
257
257
|
self._bind_repositories(binder)
|
|
258
258
|
self._bind_services(binder)
|
|
259
259
|
self._bind_infrastructure(binder)
|
|
260
|
-
self._bind_views(binder)
|
|
261
260
|
|
|
262
261
|
logging.info("✅ Dependencias configuradas correctamente")
|
|
263
262
|
|
|
@@ -309,34 +308,21 @@ class IAToolkit:
|
|
|
309
308
|
binder.bind(Dispatcher, to=Dispatcher)
|
|
310
309
|
binder.bind(BrandingService, to=BrandingService)
|
|
311
310
|
|
|
312
|
-
|
|
313
311
|
def _bind_infrastructure(self, binder: Binder):
|
|
314
312
|
from iatoolkit.infra.llm_client import llmClient
|
|
315
313
|
from iatoolkit.infra.llm_proxy import LLMProxy
|
|
316
314
|
from iatoolkit.infra.google_chat_app import GoogleChatApp
|
|
317
315
|
from iatoolkit.infra.mail_app import MailApp
|
|
318
|
-
from iatoolkit.
|
|
316
|
+
from iatoolkit.services.auth_service import AuthService
|
|
319
317
|
from iatoolkit.common.util import Utility
|
|
320
318
|
|
|
321
|
-
|
|
322
|
-
|
|
323
319
|
binder.bind(LLMProxy, to=LLMProxy, scope=singleton)
|
|
324
320
|
binder.bind(llmClient, to=llmClient, scope=singleton)
|
|
325
321
|
binder.bind(GoogleChatApp, to=GoogleChatApp)
|
|
326
322
|
binder.bind(MailApp, to=MailApp)
|
|
327
|
-
binder.bind(
|
|
323
|
+
binder.bind(AuthService, to=AuthService)
|
|
328
324
|
binder.bind(Utility, to=Utility)
|
|
329
325
|
|
|
330
|
-
def _bind_views(self, binder: Binder):
|
|
331
|
-
"""Vincula las vistas después de que el injector ha sido creado"""
|
|
332
|
-
from iatoolkit.views.llmquery_view import LLMQueryView
|
|
333
|
-
from iatoolkit.views.home_view import HomeView
|
|
334
|
-
|
|
335
|
-
binder.bind(HomeView, to=HomeView)
|
|
336
|
-
binder.bind(LLMQueryView, to=LLMQueryView)
|
|
337
|
-
|
|
338
|
-
logging.info("✅ Views configuradas correctamente")
|
|
339
|
-
|
|
340
326
|
def _setup_additional_services(self):
|
|
341
327
|
Bcrypt(self.app)
|
|
342
328
|
|
|
@@ -374,12 +360,13 @@ class IAToolkit:
|
|
|
374
360
|
@self.app.context_processor
|
|
375
361
|
def inject_globals():
|
|
376
362
|
from iatoolkit.common.session_manager import SessionManager
|
|
363
|
+
|
|
377
364
|
return {
|
|
378
365
|
'url_for': url_for,
|
|
379
366
|
'iatoolkit_version': self.version,
|
|
380
367
|
'app_name': 'IAToolkit',
|
|
381
|
-
'
|
|
382
|
-
'
|
|
368
|
+
'user_identifier': SessionManager.get('user_identifier'),
|
|
369
|
+
'company_short_name': SessionManager.get('company_short_name'),
|
|
383
370
|
'iatoolkit_base_url': os.environ.get('IATOOLKIT_BASE_URL', ''),
|
|
384
371
|
}
|
|
385
372
|
|
iatoolkit/infra/llm_client.py
CHANGED
|
@@ -266,16 +266,14 @@ class llmClient:
|
|
|
266
266
|
def set_company_context(self,
|
|
267
267
|
company: Company,
|
|
268
268
|
company_base_context: str,
|
|
269
|
-
model
|
|
269
|
+
model) -> str:
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
self.model = model
|
|
273
|
-
logging.info(f"initializing model '{self.model}' with company context: {self.count_tokens(company_base_context)} tokens...")
|
|
271
|
+
logging.info(f"initializing model '{model}' with company context: {self.count_tokens(company_base_context)} tokens...")
|
|
274
272
|
|
|
275
273
|
llm_proxy = self.llm_proxy_factory.create_for_company(company)
|
|
276
274
|
try:
|
|
277
275
|
response = llm_proxy.create_response(
|
|
278
|
-
model=
|
|
276
|
+
model=model,
|
|
279
277
|
input=[{
|
|
280
278
|
"role": "system",
|
|
281
279
|
"content": company_base_context
|
|
@@ -37,9 +37,14 @@ class RedisSessionManager:
|
|
|
37
37
|
return cls._client
|
|
38
38
|
|
|
39
39
|
@classmethod
|
|
40
|
-
def set(cls, key: str, value: str,
|
|
40
|
+
def set(cls, key: str, value: str, **kwargs):
|
|
41
|
+
"""
|
|
42
|
+
Método set flexible que pasa argumentos opcionales (como ex, nx)
|
|
43
|
+
directamente al cliente de redis.
|
|
44
|
+
"""
|
|
41
45
|
client = cls._get_client()
|
|
42
|
-
|
|
46
|
+
# Pasa todos los argumentos de palabra clave adicionales al cliente real
|
|
47
|
+
result = client.set(key, value, **kwargs)
|
|
43
48
|
return result
|
|
44
49
|
|
|
45
50
|
@classmethod
|
|
@@ -49,12 +54,53 @@ class RedisSessionManager:
|
|
|
49
54
|
result = value if value is not None else default
|
|
50
55
|
return result
|
|
51
56
|
|
|
57
|
+
@classmethod
|
|
58
|
+
def hset(cls, key: str, field: str, value: str):
|
|
59
|
+
"""
|
|
60
|
+
Establece un campo en un Hash de Redis.
|
|
61
|
+
"""
|
|
62
|
+
client = cls._get_client()
|
|
63
|
+
return client.hset(key, field, value)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def hget(cls, key: str, field: str):
|
|
67
|
+
"""
|
|
68
|
+
Obtiene el valor de un campo de un Hash de Redis.
|
|
69
|
+
Devuelve None si la clave o el campo no existen.
|
|
70
|
+
"""
|
|
71
|
+
client = cls._get_client()
|
|
72
|
+
return client.hget(key, field)
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def hdel(cls, key: str, *fields):
|
|
76
|
+
"""
|
|
77
|
+
Elimina uno o más campos de un Hash de Redis.
|
|
78
|
+
"""
|
|
79
|
+
client = cls._get_client()
|
|
80
|
+
return client.hdel(key, *fields)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def pipeline(cls):
|
|
84
|
+
"""
|
|
85
|
+
Inicia una transacción (pipeline) de Redis.
|
|
86
|
+
"""
|
|
87
|
+
client = cls._get_client()
|
|
88
|
+
return client.pipeline()
|
|
89
|
+
|
|
90
|
+
|
|
52
91
|
@classmethod
|
|
53
92
|
def remove(cls, key: str):
|
|
54
93
|
client = cls._get_client()
|
|
55
94
|
result = client.delete(key)
|
|
56
95
|
return result
|
|
57
96
|
|
|
97
|
+
@classmethod
|
|
98
|
+
def exists(cls, key: str) -> bool:
|
|
99
|
+
"""Verifica si una clave existe en Redis."""
|
|
100
|
+
client = cls._get_client()
|
|
101
|
+
# El comando EXISTS de Redis devuelve un entero (0 o 1). Lo convertimos a booleano.
|
|
102
|
+
return bool(client.exists(key))
|
|
103
|
+
|
|
58
104
|
@classmethod
|
|
59
105
|
def set_json(cls, key: str, value: dict, ex: int = None):
|
|
60
106
|
json_str = json.dumps(value)
|
iatoolkit/repositories/models.py
CHANGED
|
@@ -265,8 +265,7 @@ class UserFeedback(Base):
|
|
|
265
265
|
id = Column(Integer, primary_key=True)
|
|
266
266
|
company_id = Column(Integer, ForeignKey('iat_companies.id',
|
|
267
267
|
ondelete='CASCADE'), nullable=False)
|
|
268
|
-
|
|
269
|
-
external_user_id = Column(String(128), default='', nullable=True)
|
|
268
|
+
user_identifier = Column(String(128), default='', nullable=True)
|
|
270
269
|
message = Column(Text, nullable=False)
|
|
271
270
|
rating = Column(Integer, nullable=False)
|
|
272
271
|
created_at = Column(DateTime, default=datetime.now)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
from flask import request
|
|
7
|
+
from injector import inject
|
|
8
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
9
|
+
from flask import request
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AuthService:
|
|
13
|
+
"""
|
|
14
|
+
Centralized service for handling authentication for all incoming requests.
|
|
15
|
+
It determines the user's identity based on either a Flask session cookie or an API Key.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@inject
|
|
19
|
+
def __init__(self, profile_service: ProfileService):
|
|
20
|
+
"""
|
|
21
|
+
Injects ProfileService to access session information and validate API keys.
|
|
22
|
+
"""
|
|
23
|
+
self.profile_service = profile_service
|
|
24
|
+
|
|
25
|
+
def verify(self) -> dict:
|
|
26
|
+
"""
|
|
27
|
+
Verifies the current request and identifies the user.
|
|
28
|
+
|
|
29
|
+
Returns a dictionary with:
|
|
30
|
+
- success: bool
|
|
31
|
+
- user_identifier: str (if successful)
|
|
32
|
+
- company_short_name: str (if successful)
|
|
33
|
+
- error_message: str (on failure)
|
|
34
|
+
- status_code: int (on failure)
|
|
35
|
+
"""
|
|
36
|
+
# --- Priority 1: Check for a valid Flask web session ---
|
|
37
|
+
session_info = self.profile_service.get_current_session_info()
|
|
38
|
+
if session_info and session_info.get('user_identifier'):
|
|
39
|
+
# User is authenticated via a web session cookie.
|
|
40
|
+
return {
|
|
41
|
+
"success": True,
|
|
42
|
+
"company_short_name": session_info['company_short_name'],
|
|
43
|
+
"user_identifier": session_info['user_identifier']
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# --- Priority 2: Check for a valid API Key in headers ---
|
|
47
|
+
api_key = None
|
|
48
|
+
auth = request.headers.get('Authorization', '')
|
|
49
|
+
if isinstance(auth, str) and auth.lower().startswith('bearer '):
|
|
50
|
+
api_key = auth.split(' ', 1)[1].strip()
|
|
51
|
+
|
|
52
|
+
if api_key:
|
|
53
|
+
api_key_entry = self.profile_service.get_active_api_key_entry(api_key)
|
|
54
|
+
if not api_key_entry:
|
|
55
|
+
return {"success": False, "error": "Invalid or inactive API Key", "status_code": 401}
|
|
56
|
+
|
|
57
|
+
# obtain the company from the api_key_entry
|
|
58
|
+
company = api_key_entry.company
|
|
59
|
+
|
|
60
|
+
# For API calls, the external_user_id must be provided in the request.
|
|
61
|
+
user_identifier = ''
|
|
62
|
+
if request.is_json:
|
|
63
|
+
data = request.get_json() or {}
|
|
64
|
+
user_identifier = data.get('external_user_id', '')
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
"success": True,
|
|
68
|
+
"company_short_name": company.short_name,
|
|
69
|
+
"user_identifier": user_identifier
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# --- Failure: No valid credentials found ---
|
|
73
|
+
return {"success": False, "error": "Authentication required. No session cookie or API Key provided.",
|
|
74
|
+
"status_code": 401}
|
|
@@ -10,7 +10,6 @@ from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
|
10
10
|
from iatoolkit.repositories.models import Company, Function
|
|
11
11
|
from iatoolkit.services.excel_service import ExcelService
|
|
12
12
|
from iatoolkit.services.mail_service import MailService
|
|
13
|
-
from iatoolkit.common.session_manager import SessionManager
|
|
14
13
|
from iatoolkit.common.util import Utility
|
|
15
14
|
from injector import inject
|
|
16
15
|
import logging
|
|
@@ -171,29 +170,24 @@ class Dispatcher:
|
|
|
171
170
|
tools.append(ai_tool)
|
|
172
171
|
return tools
|
|
173
172
|
|
|
174
|
-
def get_user_info(self, company_name: str, user_identifier: str
|
|
173
|
+
def get_user_info(self, company_name: str, user_identifier: str) -> dict:
|
|
175
174
|
if company_name not in self.company_instances:
|
|
176
175
|
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
177
176
|
f"Empresa no configurada: {company_name}")
|
|
178
177
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
raw_user_data =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
raw_user_data = company_instance.get_user_info(user_identifier)
|
|
188
|
-
except Exception as e:
|
|
189
|
-
logging.exception(e)
|
|
190
|
-
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
191
|
-
f"Error en get_user_info de {company_name}: {str(e)}") from e
|
|
178
|
+
# source 2: external company user
|
|
179
|
+
company_instance = self.company_instances[company_name]
|
|
180
|
+
try:
|
|
181
|
+
raw_user_data = company_instance.get_user_info(user_identifier)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logging.exception(e)
|
|
184
|
+
raise IAToolkitException(IAToolkitException.ErrorType.EXTERNAL_SOURCE_ERROR,
|
|
185
|
+
f"Error en get_user_info de {company_name}: {str(e)}") from e
|
|
192
186
|
|
|
193
187
|
# always normalize the data for consistent structure
|
|
194
|
-
return self._normalize_user_data(raw_user_data
|
|
188
|
+
return self._normalize_user_data(raw_user_data)
|
|
195
189
|
|
|
196
|
-
def _normalize_user_data(self, raw_data: dict
|
|
190
|
+
def _normalize_user_data(self, raw_data: dict) -> dict:
|
|
197
191
|
"""
|
|
198
192
|
Asegura que los datos del usuario siempre tengan una estructura consistente.
|
|
199
193
|
"""
|
|
@@ -203,10 +197,7 @@ class Dispatcher:
|
|
|
203
197
|
"username": raw_data.get("id", 0),
|
|
204
198
|
"user_email": raw_data.get("email", ""),
|
|
205
199
|
"user_fullname": raw_data.get("user_fullname", ""),
|
|
206
|
-
"
|
|
207
|
-
"company_name": raw_data.get("company", ""),
|
|
208
|
-
"company_short_name": raw_data.get("company_short_name", ""),
|
|
209
|
-
"is_local": is_local,
|
|
200
|
+
"is_local": False,
|
|
210
201
|
"extras": raw_data.get("extras", {})
|
|
211
202
|
}
|
|
212
203
|
|
|
@@ -23,21 +23,21 @@ class ExcelService:
|
|
|
23
23
|
|
|
24
24
|
def excel_generator(self, **kwargs) -> str:
|
|
25
25
|
"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
41
|
try:
|
|
42
42
|
# get the parameters
|
|
43
43
|
fname = kwargs.get('filename')
|
|
@@ -5,29 +5,20 @@
|
|
|
5
5
|
|
|
6
6
|
from injector import inject
|
|
7
7
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
8
|
-
|
|
9
8
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
10
|
-
from iatoolkit.common.util import Utility
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
class HistoryService:
|
|
14
12
|
@inject
|
|
15
13
|
def __init__(self, llm_query_repo: LLMQueryRepo,
|
|
16
|
-
profile_repo: ProfileRepo
|
|
17
|
-
util: Utility):
|
|
14
|
+
profile_repo: ProfileRepo):
|
|
18
15
|
self.llm_query_repo = llm_query_repo
|
|
19
16
|
self.profile_repo = profile_repo
|
|
20
|
-
self.util = util
|
|
21
17
|
|
|
22
18
|
def get_history(self,
|
|
23
19
|
company_short_name: str,
|
|
24
|
-
|
|
25
|
-
local_user_id: int = 0) -> dict:
|
|
20
|
+
user_identifier: str) -> dict:
|
|
26
21
|
try:
|
|
27
|
-
user_identifier, _ = self.util.resolve_user_identifier(external_user_id, local_user_id)
|
|
28
|
-
if not user_identifier:
|
|
29
|
-
return {'error': "No se pudo resolver el identificador del usuario"}
|
|
30
|
-
|
|
31
22
|
# validate company
|
|
32
23
|
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
33
24
|
if not company:
|