iatoolkit 0.71.4__py3-none-any.whl → 1.4.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.
Files changed (114) hide show
  1. iatoolkit/__init__.py +19 -7
  2. iatoolkit/base_company.py +1 -71
  3. iatoolkit/cli_commands.py +9 -21
  4. iatoolkit/common/exceptions.py +2 -0
  5. iatoolkit/common/interfaces/__init__.py +0 -0
  6. iatoolkit/common/interfaces/asset_storage.py +34 -0
  7. iatoolkit/common/interfaces/database_provider.py +38 -0
  8. iatoolkit/common/model_registry.py +159 -0
  9. iatoolkit/common/routes.py +53 -32
  10. iatoolkit/common/util.py +17 -12
  11. iatoolkit/company_registry.py +55 -14
  12. iatoolkit/{iatoolkit.py → core.py} +102 -72
  13. iatoolkit/infra/{mail_app.py → brevo_mail_app.py} +15 -37
  14. iatoolkit/infra/llm_providers/__init__.py +0 -0
  15. iatoolkit/infra/llm_providers/deepseek_adapter.py +278 -0
  16. iatoolkit/infra/{gemini_adapter.py → llm_providers/gemini_adapter.py} +11 -17
  17. iatoolkit/infra/{openai_adapter.py → llm_providers/openai_adapter.py} +41 -7
  18. iatoolkit/infra/llm_proxy.py +235 -134
  19. iatoolkit/infra/llm_response.py +5 -0
  20. iatoolkit/locales/en.yaml +134 -4
  21. iatoolkit/locales/es.yaml +293 -162
  22. iatoolkit/repositories/database_manager.py +92 -22
  23. iatoolkit/repositories/document_repo.py +7 -0
  24. iatoolkit/repositories/filesystem_asset_repository.py +36 -0
  25. iatoolkit/repositories/llm_query_repo.py +36 -22
  26. iatoolkit/repositories/models.py +86 -95
  27. iatoolkit/repositories/profile_repo.py +64 -13
  28. iatoolkit/repositories/vs_repo.py +31 -28
  29. iatoolkit/services/auth_service.py +1 -1
  30. iatoolkit/services/branding_service.py +1 -1
  31. iatoolkit/services/company_context_service.py +96 -39
  32. iatoolkit/services/configuration_service.py +329 -67
  33. iatoolkit/services/dispatcher_service.py +51 -227
  34. iatoolkit/services/document_service.py +10 -1
  35. iatoolkit/services/embedding_service.py +9 -6
  36. iatoolkit/services/excel_service.py +50 -2
  37. iatoolkit/services/file_processor_service.py +0 -5
  38. iatoolkit/services/history_manager_service.py +208 -0
  39. iatoolkit/services/jwt_service.py +1 -1
  40. iatoolkit/services/knowledge_base_service.py +412 -0
  41. iatoolkit/services/language_service.py +8 -2
  42. iatoolkit/services/license_service.py +82 -0
  43. iatoolkit/{infra/llm_client.py → services/llm_client_service.py} +42 -29
  44. iatoolkit/services/load_documents_service.py +18 -47
  45. iatoolkit/services/mail_service.py +171 -25
  46. iatoolkit/services/profile_service.py +69 -36
  47. iatoolkit/services/{prompt_manager_service.py → prompt_service.py} +136 -25
  48. iatoolkit/services/query_service.py +229 -203
  49. iatoolkit/services/sql_service.py +116 -34
  50. iatoolkit/services/tool_service.py +246 -0
  51. iatoolkit/services/user_feedback_service.py +18 -6
  52. iatoolkit/services/user_session_context_service.py +121 -51
  53. iatoolkit/static/images/iatoolkit_core.png +0 -0
  54. iatoolkit/static/images/iatoolkit_logo.png +0 -0
  55. iatoolkit/static/js/chat_feedback_button.js +1 -1
  56. iatoolkit/static/js/chat_help_content.js +4 -4
  57. iatoolkit/static/js/chat_main.js +61 -9
  58. iatoolkit/static/js/chat_model_selector.js +227 -0
  59. iatoolkit/static/js/chat_onboarding_button.js +1 -1
  60. iatoolkit/static/js/chat_reload_button.js +4 -1
  61. iatoolkit/static/styles/chat_iatoolkit.css +59 -3
  62. iatoolkit/static/styles/chat_public.css +28 -0
  63. iatoolkit/static/styles/documents.css +598 -0
  64. iatoolkit/static/styles/landing_page.css +223 -7
  65. iatoolkit/static/styles/llm_output.css +34 -1
  66. iatoolkit/system_prompts/__init__.py +0 -0
  67. iatoolkit/system_prompts/query_main.prompt +28 -3
  68. iatoolkit/system_prompts/sql_rules.prompt +47 -12
  69. iatoolkit/templates/_company_header.html +30 -5
  70. iatoolkit/templates/_login_widget.html +3 -3
  71. iatoolkit/templates/base.html +13 -0
  72. iatoolkit/templates/chat.html +45 -3
  73. iatoolkit/templates/forgot_password.html +3 -2
  74. iatoolkit/templates/onboarding_shell.html +1 -2
  75. iatoolkit/templates/signup.html +3 -0
  76. iatoolkit/views/base_login_view.py +8 -3
  77. iatoolkit/views/change_password_view.py +1 -1
  78. iatoolkit/views/chat_view.py +76 -0
  79. iatoolkit/views/forgot_password_view.py +9 -4
  80. iatoolkit/views/history_api_view.py +3 -3
  81. iatoolkit/views/home_view.py +4 -2
  82. iatoolkit/views/init_context_api_view.py +1 -1
  83. iatoolkit/views/llmquery_api_view.py +4 -3
  84. iatoolkit/views/load_company_configuration_api_view.py +49 -0
  85. iatoolkit/views/{file_store_api_view.py → load_document_api_view.py} +15 -11
  86. iatoolkit/views/login_view.py +25 -8
  87. iatoolkit/views/logout_api_view.py +10 -2
  88. iatoolkit/views/prompt_api_view.py +1 -1
  89. iatoolkit/views/rag_api_view.py +216 -0
  90. iatoolkit/views/root_redirect_view.py +22 -0
  91. iatoolkit/views/signup_view.py +12 -4
  92. iatoolkit/views/static_page_view.py +27 -0
  93. iatoolkit/views/users_api_view.py +33 -0
  94. iatoolkit/views/verify_user_view.py +1 -1
  95. iatoolkit-1.4.2.dist-info/METADATA +268 -0
  96. iatoolkit-1.4.2.dist-info/RECORD +133 -0
  97. iatoolkit-1.4.2.dist-info/licenses/LICENSE_COMMUNITY.md +15 -0
  98. iatoolkit/repositories/tasks_repo.py +0 -52
  99. iatoolkit/services/history_service.py +0 -37
  100. iatoolkit/services/search_service.py +0 -55
  101. iatoolkit/services/tasks_service.py +0 -188
  102. iatoolkit/templates/about.html +0 -13
  103. iatoolkit/templates/index.html +0 -145
  104. iatoolkit/templates/login_simulation.html +0 -45
  105. iatoolkit/views/external_login_view.py +0 -73
  106. iatoolkit/views/index_view.py +0 -14
  107. iatoolkit/views/login_simulation_view.py +0 -93
  108. iatoolkit/views/tasks_api_view.py +0 -72
  109. iatoolkit/views/tasks_review_api_view.py +0 -55
  110. iatoolkit-0.71.4.dist-info/METADATA +0 -276
  111. iatoolkit-0.71.4.dist-info/RECORD +0 -122
  112. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/WHEEL +0 -0
  113. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/licenses/LICENSE +0 -0
  114. {iatoolkit-0.71.4.dist-info → iatoolkit-1.4.2.dist-info}/top_level.txt +0 -0
iatoolkit/__init__.py CHANGED
@@ -1,34 +1,46 @@
1
- """
2
- IAToolkit Package
3
- """
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ __version__ = "1.4.2"
4
7
 
5
8
  # Expose main classes and functions at the top level of the package
6
9
 
7
10
  # main IAToolkit class
8
- from .iatoolkit import IAToolkit, create_app, current_iatoolkit
11
+ from iatoolkit.core import IAToolkit, create_app, current_iatoolkit
9
12
 
10
13
  # for registering the client companies
11
- from .company_registry import register_company
14
+ from .company_registry import register_company, set_company_registry
12
15
  from .base_company import BaseCompany
13
16
 
14
17
  # --- Services ---
15
18
  from iatoolkit.services.query_service import QueryService
16
19
  from iatoolkit.services.document_service import DocumentService
17
- from iatoolkit.services.search_service import SearchService
20
+ from iatoolkit.services.knowledge_base_service import KnowledgeBaseService
18
21
  from iatoolkit.services.sql_service import SqlService
19
22
  from iatoolkit.services.load_documents_service import LoadDocumentsService
20
23
  from iatoolkit.infra.call_service import CallServiceClient
24
+ from iatoolkit.services.profile_service import ProfileService
25
+ from iatoolkit.services.mail_service import MailService
26
+ from iatoolkit.repositories.models import Base as OrmModel
27
+ from iatoolkit.base_company import BaseCompany
21
28
 
22
29
  __all__ = [
23
30
  'IAToolkit',
24
31
  'create_app',
25
32
  'current_iatoolkit',
26
33
  'register_company',
34
+ 'set_company_registry',
27
35
  'BaseCompany',
28
36
  'QueryService',
29
37
  'SqlService',
30
38
  'DocumentService',
31
- 'SearchService',
39
+ 'KnowledgeBaseService',
32
40
  'LoadDocumentsService',
33
41
  'CallServiceClient',
42
+ 'ProfileService',
43
+ 'MailService',
44
+ 'OrmModel',
45
+ 'BaseCompany',
34
46
  ]
iatoolkit/base_company.py CHANGED
@@ -5,87 +5,17 @@
5
5
 
6
6
  # companies/base_company.py
7
7
  from abc import ABC, abstractmethod
8
- from iatoolkit.repositories.profile_repo import ProfileRepo
9
- from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
10
- from iatoolkit.services.prompt_manager_service import PromptService
11
- from iatoolkit.repositories.models import Company, Function, PromptCategory
12
- from .iatoolkit import IAToolkit
13
-
14
8
 
15
9
  class BaseCompany(ABC):
16
- def __init__(self):
17
- # Obtener el inyector global y resolver las dependencias internamente
18
- injector = IAToolkit.get_instance().get_injector()
19
- self.profile_repo: ProfileRepo = injector.get(ProfileRepo)
20
- self.llm_query_repo: LLMQueryRepo = injector.get(LLMQueryRepo)
21
- self.prompt_service: PromptService = injector.get(PromptService)
22
- self.company: Company | None = None
23
- self.company_short_name: str
24
-
25
- def _create_company(self,
26
- short_name: str,
27
- name: str,
28
- parameters: dict | None = None,
29
- ) -> Company:
30
- company_obj = Company(short_name=short_name,
31
- name=name,
32
- parameters=parameters)
33
- self.company = self.profile_repo.create_company(company_obj)
34
- return self.company
35
-
36
- def _create_function(self, function_name: str, description: str, params: dict, **kwargs):
37
- if not self.company:
38
- raise ValueError("La compañía debe estar definida antes de crear una función.")
39
-
40
- self.llm_query_repo.create_or_update_function(
41
- Function(
42
- company_id=self.company.id,
43
- name=function_name,
44
- description=description,
45
- parameters=params,
46
- system_function=False,
47
- **kwargs
48
- )
49
- )
50
-
51
- def _create_prompt_category(self, name: str, order: int) -> PromptCategory:
52
- if not self.company:
53
- raise ValueError("La compañía debe estar definida antes de crear una categoría.")
54
-
55
- return self.llm_query_repo.create_or_update_prompt_category(
56
- PromptCategory(name=name, order=order, company_id=self.company.id)
57
- )
58
-
59
- def _create_prompt(self, prompt_name: str, description: str, category: PromptCategory, order: int, **kwargs):
60
- if not self.company:
61
- raise ValueError("La compañía debe estar definida antes de crear un prompt.")
62
-
63
- self.prompt_service.create_prompt(
64
- prompt_name=prompt_name,
65
- description=description,
66
- order=order,
67
- company=self.company,
68
- category=category,
69
- **kwargs
70
- )
71
-
72
- @abstractmethod
73
- # get context specific for this company
74
- def get_user_info(self, user_identifier: str) -> dict:
75
- raise NotImplementedError("La subclase debe implementar el método get_user_info()")
76
10
 
77
11
  @abstractmethod
78
12
  # execute the specific action configured in the intent table
79
13
  def handle_request(self, tag: str, params: dict) -> dict:
80
14
  raise NotImplementedError("La subclase debe implementar el método handle_request()")
81
15
 
82
-
16
+ @abstractmethod
83
17
  def register_cli_commands(self, app):
84
- """
85
- optional method for a company definition of it's cli commands
86
- """
87
18
  pass
88
19
 
89
-
90
20
  def unsupported_operation(self, tag):
91
21
  raise NotImplementedError(f"La operación '{tag}' no está soportada por esta empresa.")
iatoolkit/cli_commands.py CHANGED
@@ -5,38 +5,38 @@
5
5
 
6
6
  import click
7
7
  import logging
8
- from .iatoolkit import IAToolkit
8
+ from iatoolkit.core import IAToolkit
9
9
  from iatoolkit.services.profile_service import ProfileService
10
10
 
11
+
11
12
  def register_core_commands(app):
12
13
  """Registra los comandos CLI del núcleo de IAToolkit."""
13
14
 
14
15
  @app.cli.command("api-key")
15
16
  @click.argument("company_short_name")
16
- def api_key(company_short_name: str):
17
+ @click.argument("key_name")
18
+ def api_key(company_short_name: str, key_name: str):
17
19
  """⚙️ Genera una nueva API key para una compañía ya registrada."""
18
20
  try:
19
21
  profile_service = IAToolkit.get_instance().get_injector().get(ProfileService)
20
- click.echo(f"🔑 Generando API key para '{company_short_name}'...")
21
- result = profile_service.new_api_key(company_short_name)
22
+ click.echo(f"🔑 Generating API-KEY for company: '{company_short_name}'...")
23
+ result = profile_service.new_api_key(company_short_name, key_name)
22
24
 
23
25
  if 'error' in result:
24
26
  click.echo(f"❌ Error: {result['error']}")
25
- click.echo("👉 Asegúrate de que el nombre de la compañía es correcto y está registrada.")
27
+ click.echo("👉 Make sure the company is registered and valid.")
26
28
  else:
27
- click.echo("✅ ¡Configuración lista! Agrega esta variable a tu entorno:")
29
+ click.echo("✅ ¡Api-key is ready! add this variable to your environment:")
28
30
  click.echo(f"IATOOLKIT_API_KEY='{result['api-key']}'")
29
31
  except Exception as e:
30
32
  logging.exception(e)
31
- click.echo(f"❌ Ocurrió un error inesperado durante la configuración: {e}")
33
+ click.echo(f"❌ unexpectd error during the configuration: {e}")
32
34
 
33
35
  @app.cli.command("encrypt-key")
34
36
  @click.argument("key")
35
37
  def encrypt_llm_api_key(key: str):
36
38
  from iatoolkit.common.util import Utility
37
39
 
38
-
39
-
40
40
  util = IAToolkit.get_instance().get_injector().get(Utility)
41
41
  try:
42
42
  encrypt_key = util.encrypt_key(key)
@@ -45,17 +45,5 @@ def register_core_commands(app):
45
45
  logging.exception(e)
46
46
  click.echo(f"Error: {str(e)}")
47
47
 
48
- @app.cli.command("exec-tasks")
49
- @click.argument("company_short_name")
50
- def exec_pending_tasks(company_short_name: str):
51
- from iatoolkit.services.tasks_service import TaskService
52
- task_service = IAToolkit.get_instance().get_injector().get(TaskService)
53
-
54
- try:
55
- result = task_service.trigger_pending_tasks(company_short_name)
56
- click.echo(result['message'])
57
- except Exception as e:
58
- logging.exception(e)
59
- click.echo(f"Error: {str(e)}")
60
48
 
61
49
 
@@ -37,6 +37,8 @@ class IAToolkitException(Exception):
37
37
  LOAD_DOCUMENT_ERROR = 25
38
38
  INVALID_USER = 26
39
39
  VECTOR_STORE_ERROR = 27
40
+ EMBEDDING_ERROR = 28
41
+ MODEL = 29
40
42
 
41
43
 
42
44
 
File without changes
@@ -0,0 +1,34 @@
1
+ from enum import Enum
2
+ from typing import List
3
+ import abc
4
+
5
+
6
+ class AssetType(Enum):
7
+ CONFIG = "config"
8
+ PROMPT = "prompts"
9
+ SCHEMA = "schema"
10
+ CONTEXT = "context"
11
+
12
+
13
+ class AssetRepository(abc.ABC):
14
+ @abc.abstractmethod
15
+ def exists(self, company_short_name: str, asset_type: AssetType, filename: str) -> bool:
16
+ pass
17
+
18
+ @abc.abstractmethod
19
+ def read_text(self, company_short_name: str, asset_type: AssetType, filename: str) -> str:
20
+ pass
21
+
22
+ @abc.abstractmethod
23
+ def list_files(self, company_short_name: str, asset_type: AssetType, extension: str = None) -> List[str]:
24
+ pass
25
+
26
+ @abc.abstractmethod
27
+ def write_text(self, company_short_name: str, asset_type: AssetType, filename: str, content: str) -> None:
28
+ """Creates or updates a text asset."""
29
+ pass
30
+
31
+ @abc.abstractmethod
32
+ def delete(self, company_short_name: str, asset_type: AssetType, filename: str) -> None:
33
+ """Deletes an asset if it exists."""
34
+ pass
@@ -0,0 +1,38 @@
1
+ import abc
2
+ from typing import Any, List, Dict, Union
3
+
4
+ class DatabaseProvider(abc.ABC):
5
+ """
6
+ Abstract interface for interacting with a database source.
7
+ Handles both metadata introspection and query execution.
8
+ """
9
+
10
+ # --- Schema Methods ---
11
+ @abc.abstractmethod
12
+ def get_all_table_names(self) -> List[str]:
13
+ pass
14
+
15
+ @abc.abstractmethod
16
+ def get_table_description(self,
17
+ table_name: str,
18
+ schema_object_name: str | None = None,
19
+ exclude_columns: List[str] | None = None) -> str:
20
+ pass
21
+
22
+ # --- Execution Methods ---
23
+ @abc.abstractmethod
24
+ def execute_query(self, query: str, commit: bool = False) -> Union[List[Dict[str, Any]], Dict[str, int]]:
25
+ """
26
+ Executes a query and returns:
27
+ - A list of dicts for SELECT (rows).
28
+ - A dict {'rowcount': N} for INSERT/UPDATE/DELETE.
29
+ """
30
+ pass
31
+
32
+ @abc.abstractmethod
33
+ def commit(self) -> None:
34
+ pass
35
+
36
+ @abc.abstractmethod
37
+ def rollback(self) -> None:
38
+ pass
@@ -0,0 +1,159 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ # Copyright (c) 2024 Fernando Libedinsky
7
+ # Product: IAToolkit
8
+ #
9
+ # IAToolkit is open source software.
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from injector import inject, singleton
15
+ from typing import Literal
16
+
17
+
18
+ HistoryType = Literal["server_side", "client_side"]
19
+ ProviderType = Literal["openai", "gemini", "deepseek", "xai", "anthropic", "unknown"]
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class ModelMetadata:
24
+ """Static metadata for a logical family of models."""
25
+ provider: ProviderType
26
+ history_type: HistoryType
27
+
28
+
29
+ @singleton
30
+ class ModelRegistry:
31
+ """
32
+ Central registry for model metadata.
33
+
34
+ Responsibilities:
35
+ - Map a model name to its provider (openai, gemini, deepseek, etc.).
36
+ - Decide which history strategy to use for a model (server_side / client_side).
37
+ - Provide convenience helpers (is_openai, is_gemini, is_deepseek, etc.).
38
+ """
39
+
40
+ @inject
41
+ def __init__(self):
42
+ # Hardcoded rules for now; can be extended or loaded from config later.
43
+ # The order of patterns matters: first match wins.
44
+ self._provider_patterns: dict[ProviderType, tuple[str, ...]] = {
45
+ "openai": ("gpt", "gpt-5", "gpt-5-mini", "gpt-5.1"),
46
+ "gemini": ("gemini", "gemini-3"),
47
+ "deepseek": ("deepseek",),
48
+ "xai": ("grok", "grok-1", "grok-beta"),
49
+ "anthropic": ("claude", "claude-3", "claude-2"),
50
+ }
51
+
52
+ # ------------------------------------------------------------------
53
+ # Public API
54
+ # ------------------------------------------------------------------
55
+
56
+ def get_provider(self, model: str) -> ProviderType:
57
+ """
58
+ Returns the logical provider for a given model name.
59
+
60
+ Examples:
61
+ "gpt-4o" -> "openai"
62
+ "gemini-pro" -> "gemini"
63
+ "deepseek-chat" -> "deepseek"
64
+ """
65
+ if not model:
66
+ return "unknown"
67
+
68
+ model_lower = model.lower()
69
+ for provider, patterns in self._provider_patterns.items():
70
+ if any(pat in model_lower for pat in patterns):
71
+ return provider
72
+
73
+ return "unknown"
74
+
75
+ def get_request_defaults(self, model: str) -> dict:
76
+ """
77
+ Return per-model request defaults to keep model-specific policy centralized.
78
+
79
+ Notes:
80
+ - This should only include keys that are supported by the target provider.
81
+ - Callers should merge these defaults with user-provided params (do not mutate inputs).
82
+ """
83
+ model_lower = (model or "").lower()
84
+ provider = self.get_provider(model_lower)
85
+
86
+ # Conservative defaults: do not send provider-specific knobs unless we know they are supported.
87
+ defaults = {"text": {}, "reasoning": {}}
88
+
89
+ # OpenAI/xAI (OpenAI-compatible) support 'text.verbosity' and 'reasoning.effort' in our current integration.
90
+ if provider in ("openai", "xai"):
91
+ defaults["text"] = {"verbosity": "low"}
92
+
93
+ # Fine-grained per-model tuning.
94
+ if model_lower in ("gpt-5", "gpt-5-mini"):
95
+ defaults["reasoning"] = {"effort": "minimal"}
96
+ elif model_lower == "gpt-5.1":
97
+ defaults["reasoning"] = {"effort": "low", "summary": "auto"}
98
+
99
+ # Gemini/DeepSeek/unknown: keep defaults empty to avoid sending unsupported parameters.
100
+ return defaults
101
+
102
+ def resolve_request_params(self, model: str, text: dict | None = None, reasoning: dict | None = None) -> dict:
103
+ """
104
+ Resolve provider/model defaults and merge them with caller-provided overrides.
105
+
106
+ Rules:
107
+ - Defaults come from get_request_defaults(model).
108
+ - Caller overrides win over defaults.
109
+ - Input dictionaries are never mutated.
110
+ """
111
+ defaults = self.get_request_defaults(model)
112
+
113
+ merged_text: dict = {}
114
+ merged_text.update(defaults.get("text") or {})
115
+ merged_text.update(text or {})
116
+
117
+ merged_reasoning: dict = {}
118
+ merged_reasoning.update(defaults.get("reasoning") or {})
119
+ merged_reasoning.update(reasoning or {})
120
+
121
+ return {
122
+ "text": merged_text,
123
+ "reasoning": merged_reasoning,
124
+ }
125
+
126
+ def get_history_type(self, model: str) -> HistoryType:
127
+ """
128
+ Returns the history strategy for a given model.
129
+
130
+ Current rules:
131
+ - openai/xai/anthropic: server_side (API manages conversation state via ids)
132
+ - gemini/deepseek/unknown: client_side (we manage full message history)
133
+ """
134
+ provider = self.get_provider(model)
135
+
136
+ if provider in ("openai", "xai", "anthropic"):
137
+ return "server_side"
138
+
139
+ # Default for gemini, deepseek and any unknown provider
140
+ return "client_side"
141
+
142
+ # ------------------------------------------------------------------
143
+ # Convenience helpers (used during migration)
144
+ # ------------------------------------------------------------------
145
+
146
+ def is_openai_model(self, model: str) -> bool:
147
+ return self.get_provider(model) == "openai"
148
+
149
+ def is_gemini_model(self, model: str) -> bool:
150
+ return self.get_provider(model) == "gemini"
151
+
152
+ def is_deepseek_model(self, model: str) -> bool:
153
+ return self.get_provider(model) == "deepseek"
154
+
155
+ def is_xai_model(self, model: str) -> bool:
156
+ return self.get_provider(model) == "xai"
157
+
158
+ def is_anthropic_model(self, model: str) -> bool:
159
+ return self.get_provider(model) == "anthropic"
@@ -8,32 +8,33 @@ from flask import jsonify
8
8
 
9
9
 
10
10
  # this function register all the views
11
- def register_views(injector, app):
11
+ def register_views(app):
12
12
 
13
- from iatoolkit.views.index_view import IndexView
14
13
  from iatoolkit.views.init_context_api_view import InitContextApiView
15
14
  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
19
15
  from iatoolkit.views.signup_view import SignupView
20
16
  from iatoolkit.views.verify_user_view import VerifyAccountView
21
17
  from iatoolkit.views.forgot_password_view import ForgotPasswordView
22
18
  from iatoolkit.views.change_password_view import ChangePasswordView
23
- from iatoolkit.views.file_store_api_view import FileStoreApiView
19
+ from iatoolkit.views.load_document_api_view import LoadDocumentApiView
24
20
  from iatoolkit.views.user_feedback_api_view import UserFeedbackApiView
25
21
  from iatoolkit.views.prompt_api_view import PromptApiView
26
22
  from iatoolkit.views.history_api_view import HistoryApiView
27
23
  from iatoolkit.views.help_content_api_view import HelpContentApiView
28
- from iatoolkit.views.profile_api_view import UserLanguageApiView # <-- Importa la nueva vista
24
+ from iatoolkit.views.profile_api_view import UserLanguageApiView
29
25
  from iatoolkit.views.embedding_api_view import EmbeddingApiView
30
26
  from iatoolkit.views.login_view import LoginView, FinalizeContextView
31
- from iatoolkit.views.external_login_view import ExternalLoginView, RedeemTokenApiView
27
+ from iatoolkit.views.load_company_configuration_api_view import LoadCompanyConfigurationApiView
32
28
  from iatoolkit.views.logout_api_view import LogoutApiView
33
29
  from iatoolkit.views.home_view import HomeView
30
+ from iatoolkit.views.chat_view import ChatView
31
+ from iatoolkit.views.static_page_view import StaticPageView
32
+ from iatoolkit.views.root_redirect_view import RootRedirectView
33
+ from iatoolkit.views.users_api_view import UsersApiView
34
+ from iatoolkit.views.rag_api_view import RagApiView
34
35
 
35
- # iatoolkit home page
36
- app.add_url_rule('/', view_func=IndexView.as_view('index'))
36
+ # assign root '/' to our new redirect logic
37
+ app.add_url_rule('/home', view_func=RootRedirectView.as_view('root_redirect'))
37
38
 
38
39
  # company home view
39
40
  app.add_url_rule('/<company_short_name>/home', view_func=HomeView.as_view('home'))
@@ -41,9 +42,9 @@ def register_views(injector, app):
41
42
  # login for the iatoolkit integrated frontend
42
43
  app.add_url_rule('/<company_short_name>/login', view_func=LoginView.as_view('login'))
43
44
 
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'))
45
+ # Chat Route (Direct Access)
46
+ app.add_url_rule('/<company_short_name>/chat',
47
+ view_func=ChatView.as_view('chat'))
47
48
 
48
49
  # this endpoint is called when onboarding_shell finish the context load
49
50
  app.add_url_rule(
@@ -65,10 +66,6 @@ def register_views(injector, app):
65
66
  app.add_url_rule('/<company_short_name>/api/logout',
66
67
  view_func=LogoutApiView.as_view('logout'))
67
68
 
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
69
  # init (reset) the company context
73
70
  app.add_url_rule('/<company_short_name>/api/init-context',
74
71
  view_func=InitContextApiView.as_view('init-context'),
@@ -79,6 +76,10 @@ def register_views(injector, app):
79
76
  app.add_url_rule('/<company_short_name>/verify/<token>', view_func=VerifyAccountView.as_view('verify_account'))
80
77
  app.add_url_rule('/<company_short_name>/forgot-password', view_func=ForgotPasswordView.as_view('forgot_password'))
81
78
  app.add_url_rule('/<company_short_name>/change-password/<token>', view_func=ChangePasswordView.as_view('change_password'))
79
+ app.add_url_rule(
80
+ '/<string:company_short_name>/api/company-users',
81
+ view_func=UsersApiView.as_view('company-users')
82
+ )
82
83
 
83
84
  # main chat query, used by the JS in the browser (with credentials)
84
85
  # can be used also for executing iatoolkit prompts
@@ -92,17 +93,48 @@ def register_views(injector, app):
92
93
  app.add_url_rule('/<company_short_name>/api/history', view_func=HistoryApiView.as_view('history'))
93
94
  app.add_url_rule('/<company_short_name>/api/help-content', view_func=HelpContentApiView.as_view('help-content'))
94
95
 
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'))
96
+ # --- RAG API Routes ---
97
+ rag_view = RagApiView.as_view('rag_api')
98
+
99
+ # 1. List Files (POST for filters)
100
+ app.add_url_rule('/api/rag/<company_short_name>/files',
101
+ view_func=rag_view,
102
+ methods=['POST'],
103
+ defaults={'action': 'list_files'})
104
+
105
+ # 2. Delete File
106
+ app.add_url_rule('/api/rag/<company_short_name>/files/<int:document_id>',
107
+ view_func=rag_view,
108
+ methods=['DELETE'],
109
+ defaults={'action': 'delete_file'})
110
+
111
+ # 3. Search Lab
112
+ app.add_url_rule('/api/rag/<company_short_name>/search',
113
+ view_func=rag_view,
114
+ methods=['POST'],
115
+ defaults={'action': 'search'})
116
+
117
+ # 4. Get File Content (View/Download)
118
+ app.add_url_rule('/api/rag/<company_short_name>/files/<int:document_id>/content',
119
+ view_func=rag_view,
120
+ methods=['GET'],
121
+ defaults={'action': 'get_file_content'})
98
122
 
99
123
  # 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'))
124
+ app.add_url_rule('/api/load-document', view_func=LoadDocumentApiView.as_view('load-document'), methods=['POST'])
101
125
 
102
126
  # this endpoint is for generating embeddings for a given text
103
127
  app.add_url_rule('/<company_short_name>/api/embedding',
104
128
  view_func=EmbeddingApiView.as_view('embedding_api'))
105
129
 
130
+ # company configuration
131
+ app.add_url_rule('/<company_short_name>/api/load_configuration',
132
+ view_func=LoadCompanyConfigurationApiView.as_view('load-configuration'))
133
+
134
+ # static pages
135
+ # url: /pages/foundation o /pages/implementation_plan
136
+ static_view = StaticPageView.as_view('static_pages')
137
+ app.add_url_rule('/pages/<page_name>', view_func=static_view, methods=['GET'])
106
138
 
107
139
  @app.route('/download/<path:filename>')
108
140
  def download_file(filename):
@@ -125,21 +157,10 @@ def register_views(injector, app):
125
157
  except FileNotFoundError:
126
158
  abort(404)
127
159
 
128
- # login testing
129
- app.add_url_rule('/<company_short_name>/login_test',
130
- view_func=LoginSimulationView.as_view('login_test'))
131
-
132
- app.add_url_rule(
133
- '/about', # URL de la ruta
134
- view_func=lambda: render_template('about.html'))
135
160
 
136
161
  app.add_url_rule('/version', 'version',
137
162
  lambda: jsonify({"iatoolkit_version": current_app.config.get('VERSION', 'N/A')}))
138
163
 
139
164
 
140
- # hacer que la raíz '/' vaya al home de iatoolkit
141
- @app.route('/')
142
- def root_redirect():
143
- return redirect(url_for('index'))
144
165
 
145
166
 
iatoolkit/common/util.py CHANGED
@@ -6,6 +6,7 @@
6
6
  import logging
7
7
  from typing import List
8
8
  from iatoolkit.common.exceptions import IAToolkitException
9
+ from flask import request
9
10
  from injector import inject
10
11
  import os
11
12
  from jinja2 import Environment, FileSystemLoader
@@ -16,6 +17,7 @@ from cryptography.fernet import Fernet
16
17
  import base64
17
18
 
18
19
 
20
+
19
21
  class Utility:
20
22
  @inject
21
23
  def __init__(self):
@@ -97,6 +99,10 @@ class Utility:
97
99
  logging.exception(e)
98
100
  return None
99
101
 
102
+ def get_template_by_language(self, template_name: str, default_langueage: str = 'en') -> str:
103
+ # english is default
104
+ lang = request.args.get("lang", default_langueage)
105
+ return f'{template_name}_{lang}.html'
100
106
 
101
107
  def serialize(self, obj):
102
108
  if isinstance(obj, datetime) or isinstance(obj, date):
@@ -151,6 +157,17 @@ class Utility:
151
157
  schema = yaml.safe_load(f)
152
158
  return schema
153
159
 
160
+ def load_yaml_from_string(self, yaml_content: str) -> dict:
161
+ """
162
+ Parses a YAML string into a dictionary securely.
163
+ """
164
+ try:
165
+ yaml_content = yaml_content.replace('\t', ' ')
166
+ return yaml.safe_load(yaml_content) or {}
167
+ except yaml.YAMLError as e:
168
+ logging.error(f"Error parsing YAML string: {e}")
169
+ return {}
170
+
154
171
  def generate_context_for_schema(self, entity_name: str, schema_file: str = None, schema: dict = {}) -> str:
155
172
  if not schema_file and not schema:
156
173
  raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
@@ -334,15 +351,3 @@ class Utility:
334
351
  logging.exception(e)
335
352
  raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
336
353
  f'Error al buscar archivos en el directorio {directory}: {str(e)}') from e
337
-
338
- def is_openai_model(self, model: str) -> bool:
339
- openai_models = [
340
- 'gpt-5', 'gpt'
341
- ]
342
- return any(openai_model in model.lower() for openai_model in openai_models)
343
-
344
- def is_gemini_model(self, model: str) -> bool:
345
- gemini_models = [
346
- 'gemini', 'gemini-2.5-pro'
347
- ]
348
- return any(gemini_model in model.lower() for gemini_model in gemini_models)