iatoolkit 0.11.0__py3-none-any.whl → 0.66.2__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.
- iatoolkit/base_company.py +11 -3
- iatoolkit/common/routes.py +92 -52
- iatoolkit/common/session_manager.py +0 -1
- iatoolkit/common/util.py +17 -27
- iatoolkit/iatoolkit.py +91 -47
- iatoolkit/infra/llm_client.py +7 -8
- iatoolkit/infra/openai_adapter.py +1 -1
- iatoolkit/infra/redis_session_manager.py +48 -2
- iatoolkit/locales/en.yaml +144 -0
- iatoolkit/locales/es.yaml +140 -0
- iatoolkit/repositories/database_manager.py +17 -2
- iatoolkit/repositories/models.py +31 -4
- iatoolkit/repositories/profile_repo.py +7 -2
- iatoolkit/services/auth_service.py +193 -0
- iatoolkit/services/branding_service.py +59 -18
- iatoolkit/services/dispatcher_service.py +10 -40
- iatoolkit/services/excel_service.py +15 -15
- iatoolkit/services/help_content_service.py +30 -0
- iatoolkit/services/history_service.py +2 -11
- iatoolkit/services/i18n_service.py +104 -0
- iatoolkit/services/jwt_service.py +15 -24
- iatoolkit/services/language_service.py +77 -0
- iatoolkit/services/onboarding_service.py +43 -0
- iatoolkit/services/profile_service.py +148 -75
- iatoolkit/services/query_service.py +124 -81
- iatoolkit/services/tasks_service.py +1 -1
- iatoolkit/services/user_feedback_service.py +68 -32
- iatoolkit/services/user_session_context_service.py +112 -54
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/chat_feedback_button.js +80 -0
- iatoolkit/static/js/chat_help_content.js +124 -0
- iatoolkit/static/js/chat_history_button.js +112 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +148 -220
- iatoolkit/static/js/chat_onboarding_button.js +97 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +35 -0
- iatoolkit/static/styles/chat_iatoolkit.css +367 -199
- iatoolkit/static/styles/chat_modal.css +98 -76
- iatoolkit/static/styles/chat_public.css +107 -0
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/onboarding.css +169 -0
- iatoolkit/system_prompts/query_main.prompt +3 -12
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/base.html +40 -20
- iatoolkit/templates/change_password.html +57 -36
- iatoolkit/templates/chat.html +169 -83
- iatoolkit/templates/chat_modals.html +134 -68
- iatoolkit/templates/error.html +44 -8
- iatoolkit/templates/forgot_password.html +40 -23
- iatoolkit/templates/index.html +145 -0
- iatoolkit/templates/login_simulation.html +34 -0
- iatoolkit/templates/onboarding_shell.html +104 -0
- iatoolkit/templates/signup.html +63 -65
- iatoolkit/views/base_login_view.py +92 -0
- iatoolkit/views/change_password_view.py +56 -30
- iatoolkit/views/external_login_view.py +61 -28
- iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
- iatoolkit/views/forgot_password_view.py +27 -19
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +50 -23
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +73 -0
- iatoolkit/views/llmquery_api_view.py +57 -0
- iatoolkit/views/login_simulation_view.py +81 -0
- iatoolkit/views/login_view.py +130 -37
- iatoolkit/views/logout_api_view.py +49 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/{prompt_view.py → prompt_api_view.py} +10 -10
- iatoolkit/views/signup_view.py +42 -35
- iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/verify_user_view.py +35 -28
- {iatoolkit-0.11.0.dist-info → iatoolkit-0.66.2.dist-info}/METADATA +2 -2
- iatoolkit-0.66.2.dist-info/RECORD +119 -0
- iatoolkit/common/auth.py +0 -200
- iatoolkit/static/images/arrow_up.png +0 -0
- iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
- iatoolkit/static/images/logo_clinica.png +0 -0
- iatoolkit/static/images/logo_iatoolkit.png +0 -0
- iatoolkit/static/images/logo_maxxa.png +0 -0
- iatoolkit/static/images/logo_notaria.png +0 -0
- iatoolkit/static/images/logo_tarjeta.png +0 -0
- iatoolkit/static/images/logo_umayor.png +0 -0
- iatoolkit/static/images/upload.png +0 -0
- iatoolkit/static/js/chat_feedback.js +0 -115
- iatoolkit/static/js/chat_history.js +0 -117
- iatoolkit/static/styles/chat_info.css +0 -53
- iatoolkit/templates/header.html +0 -31
- iatoolkit/templates/home.html +0 -199
- iatoolkit/templates/login.html +0 -43
- iatoolkit/templates/test.html +0 -9
- iatoolkit/views/chat_token_request_view.py +0 -98
- iatoolkit/views/chat_view.py +0 -58
- iatoolkit/views/download_file_view.py +0 -58
- iatoolkit/views/external_chat_login_view.py +0 -95
- iatoolkit/views/history_view.py +0 -57
- iatoolkit/views/llmquery_view.py +0 -65
- iatoolkit/views/tasks_review_view.py +0 -83
- iatoolkit/views/user_feedback_view.py +0 -74
- iatoolkit-0.11.0.dist-info/RECORD +0 -110
- {iatoolkit-0.11.0.dist-info → iatoolkit-0.66.2.dist-info}/WHEEL +0 -0
- {iatoolkit-0.11.0.dist-info → iatoolkit-0.66.2.dist-info}/top_level.txt +0 -0
iatoolkit/base_company.py
CHANGED
|
@@ -26,10 +26,18 @@ class BaseCompany(ABC):
|
|
|
26
26
|
self.company = self.profile_repo.get_company_by_short_name(short_name)
|
|
27
27
|
return self.company
|
|
28
28
|
|
|
29
|
-
def _create_company(self,
|
|
29
|
+
def _create_company(self,
|
|
30
|
+
short_name: str,
|
|
31
|
+
name: str,
|
|
32
|
+
parameters: dict | None = None,
|
|
33
|
+
branding: dict | None = None,
|
|
34
|
+
onboarding_cards: dict | None = None,
|
|
35
|
+
) -> Company:
|
|
30
36
|
company_obj = Company(short_name=short_name,
|
|
31
37
|
name=name,
|
|
32
|
-
|
|
38
|
+
parameters=parameters,
|
|
39
|
+
branding=branding,
|
|
40
|
+
onboarding_cards=onboarding_cards)
|
|
33
41
|
self.company = self.profile_repo.create_company(company_obj)
|
|
34
42
|
return self.company
|
|
35
43
|
|
|
@@ -82,7 +90,7 @@ class BaseCompany(ABC):
|
|
|
82
90
|
|
|
83
91
|
@abstractmethod
|
|
84
92
|
# get context specific for this company
|
|
85
|
-
def get_user_info(self,
|
|
93
|
+
def get_user_info(self, user_identifier: str) -> dict:
|
|
86
94
|
raise NotImplementedError("La subclase debe implementar el método get_user_info()")
|
|
87
95
|
|
|
88
96
|
@abstractmethod
|
iatoolkit/common/routes.py
CHANGED
|
@@ -3,80 +3,102 @@
|
|
|
3
3
|
#
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
|
-
from flask import render_template, redirect,
|
|
7
|
-
from iatoolkit.common.session_manager import SessionManager
|
|
6
|
+
from flask import render_template, redirect, url_for,send_from_directory, current_app, abort
|
|
8
7
|
from flask import jsonify
|
|
9
|
-
from iatoolkit.views.history_view import HistoryView
|
|
10
|
-
import os
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def logout(company_short_name: str):
|
|
14
|
-
SessionManager.clear()
|
|
15
|
-
flash("Has cerrado sesión correctamente", "info")
|
|
16
|
-
if company_short_name:
|
|
17
|
-
return redirect(url_for('login', company_short_name=company_short_name))
|
|
18
|
-
else:
|
|
19
|
-
return redirect(url_for('home'))
|
|
20
8
|
|
|
21
9
|
|
|
22
10
|
# this function register all the views
|
|
23
11
|
def register_views(injector, app):
|
|
24
12
|
|
|
25
|
-
from iatoolkit.views.
|
|
26
|
-
from iatoolkit.views.
|
|
27
|
-
from iatoolkit.views.
|
|
28
|
-
from iatoolkit.views.
|
|
29
|
-
from iatoolkit.views.
|
|
30
|
-
from iatoolkit.views.
|
|
31
|
-
from iatoolkit.views.external_chat_login_view import ExternalChatLoginView
|
|
13
|
+
from iatoolkit.views.index_view import IndexView
|
|
14
|
+
from iatoolkit.views.init_context_api_view import InitContextApiView
|
|
15
|
+
from iatoolkit.views.llmquery_api_view import LLMQueryApiView
|
|
16
|
+
from iatoolkit.views.tasks_api_view import TaskApiView
|
|
17
|
+
from iatoolkit.views.tasks_review_api_view import TaskReviewApiView
|
|
18
|
+
from iatoolkit.views.login_simulation_view import LoginSimulationView
|
|
32
19
|
from iatoolkit.views.signup_view import SignupView
|
|
33
20
|
from iatoolkit.views.verify_user_view import VerifyAccountView
|
|
34
21
|
from iatoolkit.views.forgot_password_view import ForgotPasswordView
|
|
35
22
|
from iatoolkit.views.change_password_view import ChangePasswordView
|
|
36
|
-
from iatoolkit.views.
|
|
37
|
-
from iatoolkit.views.
|
|
38
|
-
from iatoolkit.views.
|
|
39
|
-
from iatoolkit.views.
|
|
40
|
-
from iatoolkit.views.
|
|
41
|
-
from iatoolkit.views.
|
|
42
|
-
|
|
43
|
-
|
|
23
|
+
from iatoolkit.views.file_store_api_view import FileStoreApiView
|
|
24
|
+
from iatoolkit.views.user_feedback_api_view import UserFeedbackApiView
|
|
25
|
+
from iatoolkit.views.prompt_api_view import PromptApiView
|
|
26
|
+
from iatoolkit.views.history_api_view import HistoryApiView
|
|
27
|
+
from iatoolkit.views.help_content_api_view import HelpContentApiView
|
|
28
|
+
from iatoolkit.views.profile_api_view import UserLanguageApiView # <-- Importa la nueva vista
|
|
29
|
+
|
|
30
|
+
from iatoolkit.views.login_view import LoginView, FinalizeContextView
|
|
31
|
+
from iatoolkit.views.external_login_view import ExternalLoginView, RedeemTokenApiView
|
|
32
|
+
from iatoolkit.views.logout_api_view import LogoutApiView
|
|
33
|
+
from iatoolkit.views.home_view import HomeView
|
|
44
34
|
|
|
45
|
-
#
|
|
46
|
-
app.add_url_rule('
|
|
35
|
+
# iatoolkit home page
|
|
36
|
+
app.add_url_rule('/', view_func=IndexView.as_view('index'))
|
|
47
37
|
|
|
48
|
-
#
|
|
49
|
-
app.add_url_rule('/<company_short_name>/
|
|
50
|
-
app.add_url_rule('/<company_short_name>/external_login/<external_user_id>', view_func=ExternalLoginView.as_view('external_login'))
|
|
51
|
-
app.add_url_rule('/auth/chat_token', view_func=ChatTokenRequestView.as_view('chat-token'))
|
|
38
|
+
# company home view
|
|
39
|
+
app.add_url_rule('/<company_short_name>/home', view_func=HomeView.as_view('home'))
|
|
52
40
|
|
|
53
|
-
#
|
|
41
|
+
# login for the iatoolkit integrated frontend
|
|
54
42
|
app.add_url_rule('/<company_short_name>/login', view_func=LoginView.as_view('login'))
|
|
43
|
+
|
|
44
|
+
# this is the login for external users
|
|
45
|
+
app.add_url_rule('/<company_short_name>/external_login',
|
|
46
|
+
view_func=ExternalLoginView.as_view('external_login'))
|
|
47
|
+
|
|
48
|
+
# this endpoint is called when onboarding_shell finish the context load
|
|
49
|
+
app.add_url_rule(
|
|
50
|
+
'/<company_short_name>/finalize',
|
|
51
|
+
view_func=FinalizeContextView.as_view('finalize_no_token')
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
app.add_url_rule(
|
|
55
|
+
'/<company_short_name>/finalize/<token>',
|
|
56
|
+
view_func=FinalizeContextView.as_view('finalize_with_token')
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
app.add_url_rule(
|
|
60
|
+
'/api/profile/language',
|
|
61
|
+
view_func=UserLanguageApiView.as_view('user_language_api')
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# logout
|
|
65
|
+
app.add_url_rule('/<company_short_name>/api/logout',
|
|
66
|
+
view_func=LogoutApiView.as_view('logout'))
|
|
67
|
+
|
|
68
|
+
# this endpoint is called by the JS for changing the token for a session
|
|
69
|
+
app.add_url_rule('/<string:company_short_name>/api/redeem_token',
|
|
70
|
+
view_func = RedeemTokenApiView.as_view('redeem_token'))
|
|
71
|
+
|
|
72
|
+
# init (reset) the company context
|
|
73
|
+
app.add_url_rule('/<company_short_name>/api/init-context',
|
|
74
|
+
view_func=InitContextApiView.as_view('init-context'),
|
|
75
|
+
methods=['POST', 'OPTIONS'])
|
|
76
|
+
|
|
77
|
+
# register new user, account verification and forgot password
|
|
55
78
|
app.add_url_rule('/<company_short_name>/signup',view_func=SignupView.as_view('signup'))
|
|
56
|
-
app.add_url_rule('/<company_short_name>/logout', 'logout', logout)
|
|
57
|
-
app.add_url_rule('/logout', 'logout', logout)
|
|
58
79
|
app.add_url_rule('/<company_short_name>/verify/<token>', view_func=VerifyAccountView.as_view('verify_account'))
|
|
59
80
|
app.add_url_rule('/<company_short_name>/forgot-password', view_func=ForgotPasswordView.as_view('forgot_password'))
|
|
60
81
|
app.add_url_rule('/<company_short_name>/change-password/<token>', view_func=ChangePasswordView.as_view('change_password'))
|
|
61
82
|
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
app.add_url_rule('/<company_short_name>/
|
|
65
|
-
app.add_url_rule('/<company_short_name>/prompts', view_func=PromptView.as_view('prompt'))
|
|
66
|
-
app.add_url_rule('/<company_short_name>/history', view_func=HistoryView.as_view('history'))
|
|
67
|
-
app.add_url_rule('/tasks', view_func=TaskView.as_view('tasks'))
|
|
68
|
-
app.add_url_rule('/tasks/review/<int:task_id>', view_func=TaskReviewView.as_view('tasks-review'))
|
|
69
|
-
app.add_url_rule('/load', view_func=FileStoreView.as_view('load'))
|
|
83
|
+
# main chat query, used by the JS in the browser (with credentials)
|
|
84
|
+
# can be used also for executing iatoolkit prompts
|
|
85
|
+
app.add_url_rule('/<company_short_name>/api/llm_query', view_func=LLMQueryApiView.as_view('llm_query_api'))
|
|
70
86
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
view_func=lambda: render_template('about.html'))
|
|
87
|
+
# open the promt directory
|
|
88
|
+
app.add_url_rule('/<company_short_name>/api/prompts', view_func=PromptApiView.as_view('prompt'))
|
|
74
89
|
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
# toolbar buttons
|
|
91
|
+
app.add_url_rule('/<company_short_name>/api/feedback', view_func=UserFeedbackApiView.as_view('feedback'))
|
|
92
|
+
app.add_url_rule('/<company_short_name>/api/history', view_func=HistoryApiView.as_view('history'))
|
|
93
|
+
app.add_url_rule('/<company_short_name>/api/help-content', view_func=HelpContentApiView.as_view('help-content'))
|
|
94
|
+
|
|
95
|
+
# tasks management endpoints: create task, and review answer
|
|
96
|
+
app.add_url_rule('/tasks', view_func=TaskApiView.as_view('tasks'))
|
|
97
|
+
app.add_url_rule('/tasks/review/<int:task_id>', view_func=TaskReviewApiView.as_view('tasks-review'))
|
|
98
|
+
|
|
99
|
+
# this endpoint is for upload documents into the vector store (api-key)
|
|
100
|
+
app.add_url_rule('/api/load', view_func=FileStoreApiView.as_view('load_api'))
|
|
77
101
|
|
|
78
|
-
app.add_url_rule('/<company_short_name>/<external_user_id>/download-file/<path:filename>',
|
|
79
|
-
view_func=DownloadFileView.as_view('download-file'))
|
|
80
102
|
|
|
81
103
|
@app.route('/download/<path:filename>')
|
|
82
104
|
def download_file(filename):
|
|
@@ -99,3 +121,21 @@ def register_views(injector, app):
|
|
|
99
121
|
except FileNotFoundError:
|
|
100
122
|
abort(404)
|
|
101
123
|
|
|
124
|
+
# login testing
|
|
125
|
+
app.add_url_rule('/<company_short_name>/login_test',
|
|
126
|
+
view_func=LoginSimulationView.as_view('login_test'))
|
|
127
|
+
|
|
128
|
+
app.add_url_rule(
|
|
129
|
+
'/about', # URL de la ruta
|
|
130
|
+
view_func=lambda: render_template('about.html'))
|
|
131
|
+
|
|
132
|
+
app.add_url_rule('/version', 'version',
|
|
133
|
+
lambda: jsonify({"iatoolkit_version": current_app.config.get('VERSION', 'N/A')}))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# hacer que la raíz '/' vaya al home de iatoolkit
|
|
137
|
+
@app.route('/')
|
|
138
|
+
def root_redirect():
|
|
139
|
+
return redirect(url_for('index'))
|
|
140
|
+
|
|
141
|
+
|
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,30 +21,8 @@ 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
|
-
|
|
46
24
|
def render_prompt_from_template(self,
|
|
47
25
|
template_pathname: str,
|
|
48
|
-
query: str = None,
|
|
49
26
|
client_data: dict = {},
|
|
50
27
|
**kwargs) -> str:
|
|
51
28
|
|
|
@@ -58,8 +35,6 @@ class Utility:
|
|
|
58
35
|
env = Environment(loader=FileSystemLoader(template_dir))
|
|
59
36
|
template = env.get_template(template_file)
|
|
60
37
|
|
|
61
|
-
kwargs["query"] = query
|
|
62
|
-
|
|
63
38
|
# add all the keys in client_data to kwargs
|
|
64
39
|
kwargs.update(client_data)
|
|
65
40
|
|
|
@@ -74,7 +49,6 @@ class Utility:
|
|
|
74
49
|
def render_prompt_from_string(self,
|
|
75
50
|
template_string: str,
|
|
76
51
|
searchpath: str | list[str] = None,
|
|
77
|
-
query: str = None,
|
|
78
52
|
client_data: dict = {},
|
|
79
53
|
**kwargs) -> str:
|
|
80
54
|
"""
|
|
@@ -97,7 +71,6 @@ class Utility:
|
|
|
97
71
|
env = Environment(loader=loader)
|
|
98
72
|
template = env.from_string(template_string)
|
|
99
73
|
|
|
100
|
-
kwargs["query"] = query
|
|
101
74
|
kwargs.update(client_data)
|
|
102
75
|
|
|
103
76
|
prompt = template.render(**kwargs)
|
|
@@ -108,6 +81,23 @@ class Utility:
|
|
|
108
81
|
f'No se pudo renderizar el template desde el string, error: {str(e)}') from e
|
|
109
82
|
|
|
110
83
|
|
|
84
|
+
def get_company_template(self, company_short_name: str, template_name: str) -> str:
|
|
85
|
+
# 1. get the path to the company specific template
|
|
86
|
+
template_path = os.path.join(os.getcwd(), f'companies/{company_short_name}/templates/{template_name}')
|
|
87
|
+
if not os.path.exists(template_path):
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
# 2. read the file
|
|
91
|
+
try:
|
|
92
|
+
with open(template_path, 'r') as f:
|
|
93
|
+
template_string = f.read()
|
|
94
|
+
|
|
95
|
+
return template_string
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logging.exception(e)
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
111
101
|
def serialize(self, obj):
|
|
112
102
|
if isinstance(obj, datetime) or isinstance(obj, date):
|
|
113
103
|
return obj.isoformat()
|
iatoolkit/iatoolkit.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
|
-
from flask import Flask, url_for
|
|
6
|
+
from flask import Flask, url_for, get_flashed_messages
|
|
7
7
|
from flask_session import Session
|
|
8
8
|
from flask_injector import FlaskInjector
|
|
9
9
|
from flask_bcrypt import Bcrypt
|
|
@@ -15,10 +15,11 @@ import logging
|
|
|
15
15
|
import os
|
|
16
16
|
from typing import Optional, Dict, Any
|
|
17
17
|
from iatoolkit.repositories.database_manager import DatabaseManager
|
|
18
|
-
|
|
19
|
-
from injector import Binder,
|
|
18
|
+
from werkzeug.middleware.proxy_fix import ProxyFix
|
|
19
|
+
from injector import Binder, Injector, singleton
|
|
20
20
|
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
21
21
|
|
|
22
|
+
IATOOLKIT_VERSION = "0.66.2"
|
|
22
23
|
|
|
23
24
|
# global variable for the unique instance of IAToolkit
|
|
24
25
|
_iatoolkit_instance: Optional['IAToolkit'] = None
|
|
@@ -51,7 +52,7 @@ class IAToolkit:
|
|
|
51
52
|
self.app = None
|
|
52
53
|
self.db_manager = None
|
|
53
54
|
self._injector = None
|
|
54
|
-
self.version =
|
|
55
|
+
self.version = IATOOLKIT_VERSION # default version
|
|
55
56
|
|
|
56
57
|
@classmethod
|
|
57
58
|
def get_instance(cls) -> 'IAToolkit':
|
|
@@ -87,7 +88,7 @@ class IAToolkit:
|
|
|
87
88
|
# and other integrations, as views are handled manually.
|
|
88
89
|
FlaskInjector(app=self.app, injector=self._injector)
|
|
89
90
|
|
|
90
|
-
# Step 6: initialize dispatcher and registered
|
|
91
|
+
# Step 6: initialize dispatcher and registered companies
|
|
91
92
|
self._init_dispatcher_and_company_instances()
|
|
92
93
|
|
|
93
94
|
# Step 7: Finalize setup within the application context
|
|
@@ -95,16 +96,12 @@ class IAToolkit:
|
|
|
95
96
|
self._setup_cors()
|
|
96
97
|
self._setup_additional_services()
|
|
97
98
|
self._setup_cli_commands()
|
|
99
|
+
self._setup_request_globals()
|
|
98
100
|
self._setup_context_processors()
|
|
99
101
|
|
|
100
102
|
# Step 8: define the download_dir for excel's
|
|
101
103
|
self._setup_download_dir()
|
|
102
104
|
|
|
103
|
-
try:
|
|
104
|
-
self.version = _pkg_version("iatoolkit")
|
|
105
|
-
except PackageNotFoundError:
|
|
106
|
-
pass
|
|
107
|
-
|
|
108
105
|
logging.info(f"🎉 IAToolkit v{self.version} inicializado correctamente")
|
|
109
106
|
self._initialized = True
|
|
110
107
|
return self.app
|
|
@@ -113,9 +110,26 @@ class IAToolkit:
|
|
|
113
110
|
# get a value from the config dict or the environment variable
|
|
114
111
|
return self.config.get(key, os.getenv(key, default))
|
|
115
112
|
|
|
113
|
+
def _setup_request_globals(self):
|
|
114
|
+
"""
|
|
115
|
+
Configures functions to run before each request to set up
|
|
116
|
+
request-global variables, such as language.
|
|
117
|
+
"""
|
|
118
|
+
injector = self._injector
|
|
119
|
+
|
|
120
|
+
@self.app.before_request
|
|
121
|
+
def set_request_language():
|
|
122
|
+
"""
|
|
123
|
+
Determines and caches the language for the current request in g.lang.
|
|
124
|
+
"""
|
|
125
|
+
from iatoolkit.services.language_service import LanguageService
|
|
126
|
+
language_service = injector.get(LanguageService)
|
|
127
|
+
language_service.get_current_language()
|
|
128
|
+
|
|
116
129
|
def _setup_logging(self):
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
# Lee el nivel de log desde una variable de entorno, con 'INFO' como valor por defecto.
|
|
131
|
+
log_level_name = os.getenv('LOG_LEVEL', 'INFO').upper()
|
|
132
|
+
log_level = getattr(logging, log_level_name, logging.INFO)
|
|
119
133
|
|
|
120
134
|
logging.basicConfig(
|
|
121
135
|
level=log_level,
|
|
@@ -144,11 +158,21 @@ class IAToolkit:
|
|
|
144
158
|
is_https = self._get_config_value('USE_HTTPS', 'false').lower() == 'true'
|
|
145
159
|
is_dev = self._get_config_value('FLASK_ENV') == 'development'
|
|
146
160
|
|
|
161
|
+
# get the iatoolkit domain
|
|
162
|
+
parsed_url = urlparse(os.getenv('IATOOLKIT_BASE_URL'))
|
|
163
|
+
domain = parsed_url.netloc
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
self.version = _pkg_version("iatoolkit")
|
|
167
|
+
except PackageNotFoundError:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
147
171
|
self.app.config.update({
|
|
148
172
|
'VERSION': self.version,
|
|
149
173
|
'SECRET_KEY': self._get_config_value('FLASK_SECRET_KEY', 'iatoolkit-default-secret'),
|
|
150
|
-
'SESSION_COOKIE_SAMESITE': "None"
|
|
151
|
-
'SESSION_COOKIE_SECURE':
|
|
174
|
+
'SESSION_COOKIE_SAMESITE': "None",
|
|
175
|
+
'SESSION_COOKIE_SECURE': True,
|
|
152
176
|
'SESSION_PERMANENT': False,
|
|
153
177
|
'SESSION_USE_SIGNER': True,
|
|
154
178
|
'JWT_SECRET_KEY': self._get_config_value('JWT_SECRET_KEY', 'iatoolkit-jwt-secret'),
|
|
@@ -156,6 +180,12 @@ class IAToolkit:
|
|
|
156
180
|
'JWT_EXPIRATION_SECONDS_CHAT': int(self._get_config_value('JWT_EXPIRATION_SECONDS_CHAT', 3600))
|
|
157
181
|
})
|
|
158
182
|
|
|
183
|
+
if parsed_url.scheme == 'https':
|
|
184
|
+
self.app.config['PREFERRED_URL_SCHEME'] = 'https'
|
|
185
|
+
|
|
186
|
+
# 2. ProxyFix para no tener problemas con iframes y rutas
|
|
187
|
+
self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
|
|
188
|
+
|
|
159
189
|
# Configuración para tokenizers en desarrollo
|
|
160
190
|
if is_dev:
|
|
161
191
|
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
@@ -172,6 +202,15 @@ class IAToolkit:
|
|
|
172
202
|
self.db_manager.create_all()
|
|
173
203
|
logging.info("✅ Base de datos configurada correctamente")
|
|
174
204
|
|
|
205
|
+
@self.app.teardown_appcontext
|
|
206
|
+
def remove_session(exception=None):
|
|
207
|
+
"""
|
|
208
|
+
Flask calls this after each request.
|
|
209
|
+
It ensures the SQLAlchemy session is properly closed
|
|
210
|
+
and the DB connection is returned to the pool.
|
|
211
|
+
"""
|
|
212
|
+
self.db_manager.scoped_session.remove()
|
|
213
|
+
|
|
175
214
|
def _setup_redis_sessions(self):
|
|
176
215
|
redis_url = self._get_config_value('REDIS_URL')
|
|
177
216
|
if not redis_url:
|
|
@@ -202,19 +241,19 @@ class IAToolkit:
|
|
|
202
241
|
|
|
203
242
|
def _setup_cors(self):
|
|
204
243
|
"""🌐 Configura CORS"""
|
|
205
|
-
|
|
244
|
+
from iatoolkit.company_registry import get_company_registry
|
|
245
|
+
|
|
246
|
+
# default CORS origin
|
|
206
247
|
default_origins = [
|
|
207
|
-
"http://localhost:5001",
|
|
208
|
-
"http://127.0.0.1:5001",
|
|
209
248
|
os.getenv('IATOOLKIT_BASE_URL')
|
|
210
249
|
]
|
|
211
250
|
|
|
212
|
-
#
|
|
251
|
+
# Iterate through the registered company names
|
|
213
252
|
extra_origins = []
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
253
|
+
all_company_instances = get_company_registry().get_all_company_instances()
|
|
254
|
+
for company_name, company_instance in all_company_instances.items():
|
|
255
|
+
cors_origin = company_instance.company.parameters.get('cors_origin', [])
|
|
256
|
+
extra_origins += cors_origin
|
|
218
257
|
|
|
219
258
|
all_origins = default_origins + extra_origins
|
|
220
259
|
|
|
@@ -229,7 +268,6 @@ class IAToolkit:
|
|
|
229
268
|
|
|
230
269
|
logging.info(f"✅ CORS configurado para: {all_origins}")
|
|
231
270
|
|
|
232
|
-
|
|
233
271
|
def _configure_core_dependencies(self, binder: Binder):
|
|
234
272
|
"""⚙️ Configures all system dependencies."""
|
|
235
273
|
try:
|
|
@@ -241,7 +279,6 @@ class IAToolkit:
|
|
|
241
279
|
self._bind_repositories(binder)
|
|
242
280
|
self._bind_services(binder)
|
|
243
281
|
self._bind_infrastructure(binder)
|
|
244
|
-
self._bind_views(binder)
|
|
245
282
|
|
|
246
283
|
logging.info("✅ Dependencias configuradas correctamente")
|
|
247
284
|
|
|
@@ -279,6 +316,8 @@ class IAToolkit:
|
|
|
279
316
|
from iatoolkit.services.jwt_service import JWTService
|
|
280
317
|
from iatoolkit.services.dispatcher_service import Dispatcher
|
|
281
318
|
from iatoolkit.services.branding_service import BrandingService
|
|
319
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
320
|
+
from iatoolkit.services.language_service import LanguageService
|
|
282
321
|
|
|
283
322
|
binder.bind(QueryService, to=QueryService)
|
|
284
323
|
binder.bind(TaskService, to=TaskService)
|
|
@@ -292,39 +331,24 @@ class IAToolkit:
|
|
|
292
331
|
binder.bind(JWTService, to=JWTService)
|
|
293
332
|
binder.bind(Dispatcher, to=Dispatcher)
|
|
294
333
|
binder.bind(BrandingService, to=BrandingService)
|
|
295
|
-
|
|
334
|
+
binder.bind(I18nService, to=I18nService)
|
|
335
|
+
binder.bind(LanguageService, to=LanguageService)
|
|
296
336
|
|
|
297
337
|
def _bind_infrastructure(self, binder: Binder):
|
|
298
338
|
from iatoolkit.infra.llm_client import llmClient
|
|
299
339
|
from iatoolkit.infra.llm_proxy import LLMProxy
|
|
300
340
|
from iatoolkit.infra.google_chat_app import GoogleChatApp
|
|
301
341
|
from iatoolkit.infra.mail_app import MailApp
|
|
302
|
-
from iatoolkit.
|
|
342
|
+
from iatoolkit.services.auth_service import AuthService
|
|
303
343
|
from iatoolkit.common.util import Utility
|
|
304
344
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
binder.bind(LLMProxy, to=LLMProxy, scope=singleton)
|
|
308
|
-
binder.bind(llmClient, to=llmClient, scope=singleton)
|
|
345
|
+
binder.bind(LLMProxy, to=LLMProxy)
|
|
346
|
+
binder.bind(llmClient, to=llmClient)
|
|
309
347
|
binder.bind(GoogleChatApp, to=GoogleChatApp)
|
|
310
348
|
binder.bind(MailApp, to=MailApp)
|
|
311
|
-
binder.bind(
|
|
349
|
+
binder.bind(AuthService, to=AuthService)
|
|
312
350
|
binder.bind(Utility, to=Utility)
|
|
313
351
|
|
|
314
|
-
def _bind_views(self, binder: Binder):
|
|
315
|
-
"""Vincula las vistas después de que el injector ha sido creado"""
|
|
316
|
-
from iatoolkit.views.llmquery_view import LLMQueryView
|
|
317
|
-
from iatoolkit.views.home_view import HomeView
|
|
318
|
-
from iatoolkit.views.chat_view import ChatView
|
|
319
|
-
from iatoolkit.views.change_password_view import ChangePasswordView
|
|
320
|
-
|
|
321
|
-
binder.bind(HomeView, to=HomeView)
|
|
322
|
-
binder.bind(ChatView, to=ChatView)
|
|
323
|
-
binder.bind(ChangePasswordView, to=ChangePasswordView)
|
|
324
|
-
binder.bind(LLMQueryView, to=LLMQueryView)
|
|
325
|
-
|
|
326
|
-
logging.info("✅ Views configuradas correctamente")
|
|
327
|
-
|
|
328
352
|
def _setup_additional_services(self):
|
|
329
353
|
Bcrypt(self.app)
|
|
330
354
|
|
|
@@ -362,12 +386,32 @@ class IAToolkit:
|
|
|
362
386
|
@self.app.context_processor
|
|
363
387
|
def inject_globals():
|
|
364
388
|
from iatoolkit.common.session_manager import SessionManager
|
|
389
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
390
|
+
from iatoolkit.services.i18n_service import I18nService
|
|
391
|
+
|
|
392
|
+
# Get services from the injector
|
|
393
|
+
profile_service = self._injector.get(ProfileService)
|
|
394
|
+
i18n_service = self._injector.get(I18nService)
|
|
395
|
+
|
|
396
|
+
# The 't' function wrapper no longer needs to determine the language itself.
|
|
397
|
+
# It will be automatically handled by the refactored I18nService.
|
|
398
|
+
def translate_for_template(key: str, **kwargs):
|
|
399
|
+
return i18n_service.t(key, **kwargs)
|
|
400
|
+
|
|
401
|
+
# Get user profile if a session exists
|
|
402
|
+
user_profile = profile_service.get_current_session_info().get('profile', {})
|
|
403
|
+
|
|
365
404
|
return {
|
|
366
405
|
'url_for': url_for,
|
|
367
406
|
'iatoolkit_version': self.version,
|
|
368
407
|
'app_name': 'IAToolkit',
|
|
369
|
-
'
|
|
370
|
-
'
|
|
408
|
+
'user_identifier': SessionManager.get('user_identifier'),
|
|
409
|
+
'company_short_name': SessionManager.get('company_short_name'),
|
|
410
|
+
'user_is_local': user_profile.get('user_is_local'),
|
|
411
|
+
'user_email': user_profile.get('user_email'),
|
|
412
|
+
'iatoolkit_base_url': os.environ.get('IATOOLKIT_BASE_URL', ''),
|
|
413
|
+
'flashed_messages': get_flashed_messages(with_categories=True),
|
|
414
|
+
't': translate_for_template
|
|
371
415
|
}
|
|
372
416
|
|
|
373
417
|
def _get_default_static_folder(self) -> str:
|
iatoolkit/infra/llm_client.py
CHANGED
|
@@ -21,6 +21,7 @@ import tiktoken
|
|
|
21
21
|
from typing import Dict, Optional, List
|
|
22
22
|
from iatoolkit.services.dispatcher_service import Dispatcher
|
|
23
23
|
|
|
24
|
+
CONTEXT_ERROR_MESSAGE = 'Tu consulta supera el límite de contexto, utiliza el boton de recarga de contexto.'
|
|
24
25
|
|
|
25
26
|
class llmClient:
|
|
26
27
|
_llm_clients_cache = {} # class attribute, for the clients cache
|
|
@@ -116,7 +117,7 @@ class llmClient:
|
|
|
116
117
|
|
|
117
118
|
# in case of context error
|
|
118
119
|
if "context_length_exceeded" in str(e):
|
|
119
|
-
error_message =
|
|
120
|
+
error_message = CONTEXT_ERROR_MESSAGE
|
|
120
121
|
|
|
121
122
|
raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, error_message)
|
|
122
123
|
|
|
@@ -256,25 +257,23 @@ class llmClient:
|
|
|
256
257
|
|
|
257
258
|
# in case of context error
|
|
258
259
|
if "context_length_exceeded" in str(e):
|
|
259
|
-
error_message =
|
|
260
|
+
error_message = CONTEXT_ERROR_MESSAGE
|
|
260
261
|
elif "string_above_max_length" in str(e):
|
|
261
|
-
error_message = 'La respuesta es muy
|
|
262
|
+
error_message = 'La respuesta es muy extensa, trata de filtrar/restringuir tu consulta'
|
|
262
263
|
|
|
263
264
|
raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, error_message)
|
|
264
265
|
|
|
265
266
|
def set_company_context(self,
|
|
266
267
|
company: Company,
|
|
267
268
|
company_base_context: str,
|
|
268
|
-
model
|
|
269
|
+
model) -> str:
|
|
269
270
|
|
|
270
|
-
|
|
271
|
-
self.model = model
|
|
272
|
-
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...")
|
|
273
272
|
|
|
274
273
|
llm_proxy = self.llm_proxy_factory.create_for_company(company)
|
|
275
274
|
try:
|
|
276
275
|
response = llm_proxy.create_response(
|
|
277
|
-
model=
|
|
276
|
+
model=model,
|
|
278
277
|
input=[{
|
|
279
278
|
"role": "system",
|
|
280
279
|
"content": company_base_context
|
|
@@ -55,7 +55,7 @@ class OpenAIAdapter:
|
|
|
55
55
|
|
|
56
56
|
# En caso de error de contexto
|
|
57
57
|
if "context_length_exceeded" in str(e):
|
|
58
|
-
error_message = 'Tu consulta supera el limite de contexto
|
|
58
|
+
error_message = 'Tu consulta supera el limite de contexto. Reinicia el contexto con el boton de la barra superior.'
|
|
59
59
|
|
|
60
60
|
raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, error_message)
|
|
61
61
|
|