iatoolkit 0.71.2__py3-none-any.whl → 0.91.1__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/__init__.py +15 -5
- iatoolkit/base_company.py +4 -58
- iatoolkit/cli_commands.py +6 -7
- iatoolkit/common/exceptions.py +1 -0
- iatoolkit/common/routes.py +12 -28
- iatoolkit/common/util.py +7 -1
- iatoolkit/company_registry.py +50 -14
- iatoolkit/{iatoolkit.py → core.py} +54 -55
- iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
- iatoolkit/infra/llm_client.py +9 -5
- iatoolkit/locales/en.yaml +10 -2
- iatoolkit/locales/es.yaml +171 -162
- iatoolkit/repositories/database_manager.py +59 -14
- iatoolkit/repositories/llm_query_repo.py +34 -22
- iatoolkit/repositories/models.py +16 -18
- iatoolkit/repositories/profile_repo.py +5 -10
- iatoolkit/repositories/vs_repo.py +9 -4
- iatoolkit/services/auth_service.py +1 -1
- iatoolkit/services/branding_service.py +1 -1
- iatoolkit/services/company_context_service.py +19 -11
- iatoolkit/services/configuration_service.py +219 -46
- iatoolkit/services/dispatcher_service.py +31 -225
- iatoolkit/services/document_service.py +10 -1
- iatoolkit/services/embedding_service.py +43 -41
- iatoolkit/services/excel_service.py +50 -2
- iatoolkit/services/history_manager_service.py +189 -0
- iatoolkit/services/jwt_service.py +1 -1
- iatoolkit/services/language_service.py +8 -2
- iatoolkit/services/license_service.py +82 -0
- iatoolkit/services/mail_service.py +171 -25
- iatoolkit/services/profile_service.py +37 -32
- iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +110 -1
- iatoolkit/services/query_service.py +192 -191
- iatoolkit/services/sql_service.py +63 -12
- iatoolkit/services/tool_service.py +231 -0
- iatoolkit/services/user_feedback_service.py +18 -6
- iatoolkit/services/user_session_context_service.py +18 -0
- iatoolkit/static/images/iatoolkit_core.png +0 -0
- iatoolkit/static/images/iatoolkit_logo.png +0 -0
- iatoolkit/static/js/chat_feedback_button.js +1 -1
- iatoolkit/static/js/chat_help_content.js +4 -4
- iatoolkit/static/js/chat_main.js +17 -5
- iatoolkit/static/js/chat_onboarding_button.js +1 -1
- iatoolkit/static/styles/chat_iatoolkit.css +1 -1
- iatoolkit/static/styles/chat_public.css +28 -0
- iatoolkit/static/styles/documents.css +598 -0
- iatoolkit/static/styles/landing_page.css +223 -7
- iatoolkit/system_prompts/__init__.py +0 -0
- iatoolkit/system_prompts/query_main.prompt +2 -1
- iatoolkit/system_prompts/sql_rules.prompt +47 -12
- iatoolkit/templates/_company_header.html +30 -5
- iatoolkit/templates/_login_widget.html +3 -3
- iatoolkit/templates/chat.html +1 -1
- iatoolkit/templates/forgot_password.html +3 -2
- iatoolkit/templates/onboarding_shell.html +1 -1
- iatoolkit/templates/signup.html +3 -0
- iatoolkit/views/base_login_view.py +1 -1
- iatoolkit/views/change_password_view.py +1 -1
- iatoolkit/views/forgot_password_view.py +9 -4
- iatoolkit/views/history_api_view.py +3 -3
- iatoolkit/views/home_view.py +4 -2
- iatoolkit/views/init_context_api_view.py +1 -1
- iatoolkit/views/llmquery_api_view.py +4 -3
- iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +1 -1
- iatoolkit/views/login_view.py +17 -5
- iatoolkit/views/logout_api_view.py +10 -2
- iatoolkit/views/prompt_api_view.py +1 -1
- iatoolkit/views/root_redirect_view.py +22 -0
- iatoolkit/views/signup_view.py +12 -4
- iatoolkit/views/static_page_view.py +27 -0
- iatoolkit/views/verify_user_view.py +1 -1
- iatoolkit-0.91.1.dist-info/METADATA +268 -0
- iatoolkit-0.91.1.dist-info/RECORD +125 -0
- iatoolkit-0.91.1.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
- iatoolkit/services/history_service.py +0 -37
- iatoolkit/templates/about.html +0 -13
- iatoolkit/templates/index.html +0 -145
- iatoolkit/templates/login_simulation.html +0 -45
- iatoolkit/views/external_login_view.py +0 -73
- iatoolkit/views/index_view.py +0 -14
- iatoolkit/views/login_simulation_view.py +0 -93
- iatoolkit-0.71.2.dist-info/METADATA +0 -276
- iatoolkit-0.71.2.dist-info/RECORD +0 -122
- {iatoolkit-0.71.2.dist-info → iatoolkit-0.91.1.dist-info}/WHEEL +0 -0
- {iatoolkit-0.71.2.dist-info → iatoolkit-0.91.1.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-0.71.2.dist-info → iatoolkit-0.91.1.dist-info}/top_level.txt +0 -0
|
@@ -9,9 +9,11 @@ from iatoolkit.services.i18n_service import I18nService
|
|
|
9
9
|
from iatoolkit.repositories.models import User, Company, ApiKey
|
|
10
10
|
from flask_bcrypt import check_password_hash
|
|
11
11
|
from iatoolkit.common.session_manager import SessionManager
|
|
12
|
+
from iatoolkit.services.language_service import LanguageService
|
|
12
13
|
from iatoolkit.services.user_session_context_service import UserSessionContextService
|
|
14
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
13
15
|
from flask_bcrypt import Bcrypt
|
|
14
|
-
from iatoolkit.
|
|
16
|
+
from iatoolkit.services.mail_service import MailService
|
|
15
17
|
import random
|
|
16
18
|
import re
|
|
17
19
|
import secrets
|
|
@@ -26,13 +28,17 @@ class ProfileService:
|
|
|
26
28
|
i18n_service: I18nService,
|
|
27
29
|
profile_repo: ProfileRepo,
|
|
28
30
|
session_context_service: UserSessionContextService,
|
|
31
|
+
config_service: ConfigurationService,
|
|
32
|
+
lang_service: LanguageService,
|
|
29
33
|
dispatcher: Dispatcher,
|
|
30
|
-
|
|
34
|
+
mail_service: MailService):
|
|
31
35
|
self.i18n_service = i18n_service
|
|
32
36
|
self.profile_repo = profile_repo
|
|
33
37
|
self.dispatcher = dispatcher
|
|
34
38
|
self.session_context = session_context_service
|
|
35
|
-
self.
|
|
39
|
+
self.config_service = config_service
|
|
40
|
+
self.lang_service = lang_service
|
|
41
|
+
self.mail_service = mail_service
|
|
36
42
|
self.bcrypt = Bcrypt()
|
|
37
43
|
|
|
38
44
|
|
|
@@ -61,11 +67,12 @@ class ProfileService:
|
|
|
61
67
|
|
|
62
68
|
# 1. Build the local user profile dictionary here.
|
|
63
69
|
# the user_profile variables are used on the LLM templates also (see in query_main.prompt)
|
|
64
|
-
user_identifier = user.email
|
|
70
|
+
user_identifier = user.email
|
|
65
71
|
user_profile = {
|
|
66
72
|
"user_email": user.email,
|
|
67
73
|
"user_fullname": f'{user.first_name} {user.last_name}',
|
|
68
74
|
"user_is_local": True,
|
|
75
|
+
"user_id": user.id,
|
|
69
76
|
"extras": {}
|
|
70
77
|
}
|
|
71
78
|
|
|
@@ -79,25 +86,6 @@ class ProfileService:
|
|
|
79
86
|
logging.error(f"Error in login: {e}")
|
|
80
87
|
return {'success': False, "message": str(e)}
|
|
81
88
|
|
|
82
|
-
def create_external_user_profile_context(self, company: Company, user_identifier: str):
|
|
83
|
-
"""
|
|
84
|
-
Public method for views to create a user profile context for an external user.
|
|
85
|
-
"""
|
|
86
|
-
# 1. Fetch the external user profile via Dispatcher.
|
|
87
|
-
external_user_profile = self.dispatcher.get_user_info(
|
|
88
|
-
company_name=company.short_name,
|
|
89
|
-
user_identifier=user_identifier
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
# 2. Call the session creation helper with external_user_id as user_identifier
|
|
93
|
-
self.save_user_profile(
|
|
94
|
-
company=company,
|
|
95
|
-
user_identifier=user_identifier,
|
|
96
|
-
user_profile=external_user_profile)
|
|
97
|
-
|
|
98
|
-
# 3. make sure the flask session is clean
|
|
99
|
-
SessionManager.clear()
|
|
100
|
-
|
|
101
89
|
def save_user_profile(self, company: Company, user_identifier: str, user_profile: dict):
|
|
102
90
|
"""
|
|
103
91
|
Private helper: Takes a pre-built profile, saves it to Redis, and sets the Flask cookie.
|
|
@@ -107,6 +95,7 @@ class ProfileService:
|
|
|
107
95
|
user_profile['id'] = user_identifier
|
|
108
96
|
user_profile['company_id'] = company.id
|
|
109
97
|
user_profile['company'] = company.name
|
|
98
|
+
user_profile['language'] = self.lang_service.get_current_language()
|
|
110
99
|
|
|
111
100
|
# save user_profile in Redis session
|
|
112
101
|
self.session_context.save_profile_data(company.short_name, user_identifier, user_profile)
|
|
@@ -214,24 +203,34 @@ class ProfileService:
|
|
|
214
203
|
# encrypt the password
|
|
215
204
|
hashed_password = self.bcrypt.generate_password_hash(password).decode('utf-8')
|
|
216
205
|
|
|
206
|
+
# account verification can be skiped with this security parameter
|
|
207
|
+
verified = False
|
|
208
|
+
cfg = self.config_service.get_configuration(company_short_name, 'parameters')
|
|
209
|
+
if cfg and not cfg.get('verify_account', True):
|
|
210
|
+
verified = True
|
|
211
|
+
message = self.i18n_service.t('flash_messages.signup_success_no_verification')
|
|
212
|
+
|
|
217
213
|
# create the new user
|
|
218
214
|
new_user = User(email=email,
|
|
219
215
|
password=hashed_password,
|
|
220
216
|
first_name=first_name.lower(),
|
|
221
217
|
last_name=last_name.lower(),
|
|
222
|
-
verified=
|
|
218
|
+
verified=verified,
|
|
223
219
|
verification_url=verification_url
|
|
224
220
|
)
|
|
225
221
|
|
|
226
222
|
# associate new company to user
|
|
227
223
|
new_user.companies.append(company)
|
|
228
224
|
|
|
225
|
+
# and create in the database
|
|
229
226
|
self.profile_repo.create_user(new_user)
|
|
230
227
|
|
|
231
228
|
# send email with verification
|
|
232
|
-
|
|
229
|
+
if not cfg or cfg.get('verify_account', True):
|
|
230
|
+
self.send_verification_email(new_user, company_short_name)
|
|
231
|
+
message = self.i18n_service.t('flash_messages.signup_success')
|
|
233
232
|
|
|
234
|
-
return {"message":
|
|
233
|
+
return {"message": message}
|
|
235
234
|
except Exception as e:
|
|
236
235
|
return {"error": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}
|
|
237
236
|
|
|
@@ -275,7 +274,7 @@ class ProfileService:
|
|
|
275
274
|
except Exception as e:
|
|
276
275
|
return {"error": self.i18n_service.t('errors.general.unexpected_error')}
|
|
277
276
|
|
|
278
|
-
def forgot_password(self, email: str, reset_url: str):
|
|
277
|
+
def forgot_password(self, company_short_name: str, email: str, reset_url: str):
|
|
279
278
|
try:
|
|
280
279
|
# Verificar si el usuario existe
|
|
281
280
|
user = self.profile_repo.get_user_by_email(email)
|
|
@@ -287,7 +286,7 @@ class ProfileService:
|
|
|
287
286
|
self.profile_repo.set_temp_code(email, temp_code)
|
|
288
287
|
|
|
289
288
|
# send email to the user
|
|
290
|
-
self.send_forgot_password_email(user, reset_url)
|
|
289
|
+
self.send_forgot_password_email(company_short_name, user, reset_url)
|
|
291
290
|
|
|
292
291
|
return {"message": self.i18n_service.t('flash_messages.forgot_password_success')}
|
|
293
292
|
except Exception as e:
|
|
@@ -388,9 +387,12 @@ class ProfileService:
|
|
|
388
387
|
</body>
|
|
389
388
|
</html>
|
|
390
389
|
"""
|
|
391
|
-
self.
|
|
390
|
+
self.mail_service.send_mail(company_short_name=company_short_name,
|
|
391
|
+
recipient=new_user.email,
|
|
392
|
+
subject=subject,
|
|
393
|
+
body=body)
|
|
392
394
|
|
|
393
|
-
def send_forgot_password_email(self, user: User, reset_url: str):
|
|
395
|
+
def send_forgot_password_email(self, company_short_name: str, user: User, reset_url: str):
|
|
394
396
|
# send email to the user
|
|
395
397
|
subject = f"Recuperación de Contraseña "
|
|
396
398
|
body = f"""
|
|
@@ -441,5 +443,8 @@ class ProfileService:
|
|
|
441
443
|
</html>
|
|
442
444
|
"""
|
|
443
445
|
|
|
444
|
-
self.
|
|
445
|
-
|
|
446
|
+
self.mail_service.send_mail(company_short_name=company_short_name,
|
|
447
|
+
recipient=user.email,
|
|
448
|
+
subject=subject,
|
|
449
|
+
body=body)
|
|
450
|
+
return {"message": self.i18n_service.t('services.mail_change_password') }
|
|
@@ -14,6 +14,12 @@ from iatoolkit.common.exceptions import IAToolkitException
|
|
|
14
14
|
import importlib.resources
|
|
15
15
|
import logging
|
|
16
16
|
|
|
17
|
+
# iatoolkit system prompts definitions
|
|
18
|
+
_SYSTEM_PROMPTS = [
|
|
19
|
+
{'name': 'query_main', 'description': 'iatoolkit main prompt'},
|
|
20
|
+
{'name': 'format_styles', 'description': 'output format styles'},
|
|
21
|
+
{'name': 'sql_rules', 'description': 'instructions for SQL queries'}
|
|
22
|
+
]
|
|
17
23
|
|
|
18
24
|
class PromptService:
|
|
19
25
|
@inject
|
|
@@ -25,6 +31,106 @@ class PromptService:
|
|
|
25
31
|
self.profile_repo = profile_repo
|
|
26
32
|
self.i18n_service = i18n_service
|
|
27
33
|
|
|
34
|
+
def sync_company_prompts(self, company_instance, prompts_config: list, categories_config: list):
|
|
35
|
+
"""
|
|
36
|
+
Synchronizes prompt categories and prompts from YAML config to Database.
|
|
37
|
+
Strategies:
|
|
38
|
+
- Categories: Create or Update existing based on name.
|
|
39
|
+
- Prompts: Create or Update existing based on name. Soft-delete or Delete unused.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
# 1. Sync Categories
|
|
43
|
+
category_map = {}
|
|
44
|
+
|
|
45
|
+
for i, category_name in enumerate(categories_config):
|
|
46
|
+
category_obj = PromptCategory(
|
|
47
|
+
company_id=company_instance.company.id,
|
|
48
|
+
name=category_name,
|
|
49
|
+
order=i + 1
|
|
50
|
+
)
|
|
51
|
+
# Persist and get back the object with ID
|
|
52
|
+
persisted_cat = self.llm_query_repo.create_or_update_prompt_category(category_obj)
|
|
53
|
+
category_map[category_name] = persisted_cat
|
|
54
|
+
|
|
55
|
+
# 2. Sync Prompts
|
|
56
|
+
defined_prompt_names = set()
|
|
57
|
+
|
|
58
|
+
for prompt_data in prompts_config:
|
|
59
|
+
category_name = prompt_data.get('category')
|
|
60
|
+
if not category_name or category_name not in category_map:
|
|
61
|
+
logging.warning(
|
|
62
|
+
f"⚠️ Warning: Prompt '{prompt_data['name']}' has an invalid or missing category. Skipping.")
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
prompt_name = prompt_data['name']
|
|
66
|
+
defined_prompt_names.add(prompt_name)
|
|
67
|
+
|
|
68
|
+
category_obj = category_map[category_name]
|
|
69
|
+
filename = f"{prompt_name}.prompt"
|
|
70
|
+
|
|
71
|
+
new_prompt = Prompt(
|
|
72
|
+
company_id=company_instance.company.id,
|
|
73
|
+
name=prompt_name,
|
|
74
|
+
description=prompt_data['description'],
|
|
75
|
+
order=prompt_data['order'],
|
|
76
|
+
category_id=category_obj.id,
|
|
77
|
+
active=prompt_data.get('active', True),
|
|
78
|
+
is_system_prompt=False,
|
|
79
|
+
filename=filename,
|
|
80
|
+
custom_fields=prompt_data.get('custom_fields', [])
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
self.llm_query_repo.create_or_update_prompt(new_prompt)
|
|
84
|
+
|
|
85
|
+
# 3. Cleanup: Delete prompts present in DB but not in Config
|
|
86
|
+
existing_prompts = self.llm_query_repo.get_prompts(company_instance.company)
|
|
87
|
+
for p in existing_prompts:
|
|
88
|
+
if p.name not in defined_prompt_names:
|
|
89
|
+
# Using hard delete to keep consistent with previous "refresh" behavior
|
|
90
|
+
self.llm_query_repo.session.delete(p)
|
|
91
|
+
|
|
92
|
+
self.llm_query_repo.commit()
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
self.llm_query_repo.rollback()
|
|
96
|
+
raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
|
|
97
|
+
|
|
98
|
+
def register_system_prompts(self):
|
|
99
|
+
"""
|
|
100
|
+
Synchronizes system prompts defined in Dispatcher/Code to Database.
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
defined_names = set()
|
|
104
|
+
|
|
105
|
+
for i, prompt_data in enumerate(_SYSTEM_PROMPTS):
|
|
106
|
+
prompt_name = prompt_data['name']
|
|
107
|
+
defined_names.add(prompt_name)
|
|
108
|
+
|
|
109
|
+
new_prompt = Prompt(
|
|
110
|
+
company_id=None, # System prompts have no company
|
|
111
|
+
name=prompt_name,
|
|
112
|
+
description=prompt_data['description'],
|
|
113
|
+
order=i + 1,
|
|
114
|
+
category_id=None,
|
|
115
|
+
active=True,
|
|
116
|
+
is_system_prompt=True,
|
|
117
|
+
filename=f"{prompt_name}.prompt",
|
|
118
|
+
custom_fields=[]
|
|
119
|
+
)
|
|
120
|
+
self.llm_query_repo.create_or_update_prompt(new_prompt)
|
|
121
|
+
|
|
122
|
+
# Cleanup old system prompts
|
|
123
|
+
existing_sys_prompts = self.llm_query_repo.get_system_prompts()
|
|
124
|
+
for p in existing_sys_prompts:
|
|
125
|
+
if p.name not in defined_names:
|
|
126
|
+
self.llm_query_repo.session.delete(p)
|
|
127
|
+
|
|
128
|
+
self.llm_query_repo.commit()
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
self.llm_query_repo.rollback()
|
|
132
|
+
raise IAToolkitException(IAToolkitException.ErrorType.DATABASE_ERROR, str(e))
|
|
133
|
+
|
|
28
134
|
def create_prompt(self,
|
|
29
135
|
prompt_name: str,
|
|
30
136
|
description: str,
|
|
@@ -35,7 +141,10 @@ class PromptService:
|
|
|
35
141
|
is_system_prompt: bool = False,
|
|
36
142
|
custom_fields: list = []
|
|
37
143
|
):
|
|
38
|
-
|
|
144
|
+
"""
|
|
145
|
+
Direct creation method (used by sync or direct calls).
|
|
146
|
+
Validates file existence before creating DB entry.
|
|
147
|
+
"""
|
|
39
148
|
prompt_filename = prompt_name.lower() + '.prompt'
|
|
40
149
|
if is_system_prompt:
|
|
41
150
|
if not importlib.resources.files('iatoolkit.system_prompts').joinpath(prompt_filename).is_file():
|