iatoolkit 0.66.4__tar.gz → 0.67.0__tar.gz
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-0.67.0/LICENSE +21 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/PKG-INFO +40 -22
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/pyproject.toml +1 -1
- iatoolkit-0.67.0/readme.md +65 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/__init__.py +2 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/base_company.py +1 -20
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/company_registry.py +1 -2
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/iatoolkit.py +7 -3
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/repositories/database_manager.py +2 -2
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/repositories/models.py +0 -3
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/repositories/profile_repo.py +0 -4
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/branding_service.py +6 -3
- iatoolkit-0.67.0/src/iatoolkit/services/configuration_service.py +140 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/dispatcher_service.py +12 -9
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/i18n_service.py +2 -2
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/language_service.py +22 -20
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/onboarding_service.py +10 -4
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/profile_service.py +2 -2
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +37 -47
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/chat.html +11 -4
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/help_content_api_view.py +8 -8
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit.egg-info/PKG-INFO +40 -22
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit.egg-info/SOURCES.txt +2 -1
- iatoolkit-0.66.4/readme.md +0 -49
- iatoolkit-0.66.4/src/iatoolkit/services/help_content_service.py +0 -30
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/requirements.txt +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/setup.cfg +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/cli_commands.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/common/__init__.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/common/exceptions.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/common/routes.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/common/session_manager.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/common/util.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/__init__.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/call_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/gemini_adapter.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/llm_client.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/llm_response.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/mail_app.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/openai_adapter.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/locales/en.yaml +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/locales/es.yaml +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/repositories/__init__.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/repositories/document_repo.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/repositories/tasks_repo.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/repositories/vs_repo.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/__init__.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/auth_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/benchmark_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/document_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/excel_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/file_processor_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/history_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/jwt_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/load_documents_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/mail_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/prompt_manager_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/query_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/search_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/sql_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/tasks_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/images/fernando.jpeg +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/js/chat_filepond.js +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/js/chat_help_content.js +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/js/chat_history_button.js +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/js/chat_main.js +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/styles/chat_public.css +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/styles/landing_page.css +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/static/styles/onboarding.css +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/system_prompts/format_styles.prompt +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/system_prompts/query_main.prompt +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/system_prompts/sql_rules.prompt +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/_company_header.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/_login_widget.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/about.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/base.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/change_password.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/chat_modals.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/error.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/forgot_password.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/index.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/login_simulation.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/onboarding_shell.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/templates/signup.html +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/__init__.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/base_login_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/change_password_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/external_login_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/file_store_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/history_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/home_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/index_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/init_context_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/llmquery_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/login_simulation_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/login_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/logout_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/profile_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/prompt_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/signup_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/tasks_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/tasks_review_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit/views/verify_user_view.py +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit.egg-info/requires.txt +0 -0
- {iatoolkit-0.66.4 → iatoolkit-0.67.0}/src/iatoolkit.egg-info/top_level.txt +0 -0
iatoolkit-0.67.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Fernando Libedinsky
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iatoolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.67.0
|
|
4
4
|
Summary: IAToolkit
|
|
5
5
|
Author: Fernando Libedinsky
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Requires-Python: >=3.12
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
9
10
|
Requires-Dist: aiohappyeyeballs==2.4.4
|
|
10
11
|
Requires-Dist: aiohttp==3.11.9
|
|
11
12
|
Requires-Dist: aiosignal==1.3.1
|
|
@@ -206,47 +207,64 @@ Requires-Dist: wrapt==1.17.0
|
|
|
206
207
|
Requires-Dist: yarl==1.18.3
|
|
207
208
|
Requires-Dist: zipp==3.21.0
|
|
208
209
|
Requires-Dist: zstandard==0.23.0
|
|
210
|
+
Dynamic: license-file
|
|
209
211
|
|
|
210
212
|
|
|
211
213
|
<div align="center">
|
|
212
|
-
|
|
214
|
+
<h1>
|
|
215
|
+
IAToolkit
|
|
216
|
+
</h1>
|
|
217
|
+
|
|
213
218
|
<p><strong>The Open-Source Framework for Building AI Chatbots on Your Private Data.</strong></p>
|
|
219
|
+
<h4>
|
|
220
|
+
<a href="https://www.iatoolkit.com" target="_blank" style="text-decoration: none; color: inherit;">
|
|
221
|
+
www.iatoolkit.com
|
|
222
|
+
</a>
|
|
223
|
+
</h4>
|
|
214
224
|
</div>
|
|
215
225
|
|
|
216
|
-
IAToolkit is a comprehensive, open-source framework designed for building enterprise-grade
|
|
217
|
-
AI chatbots and conversational applications.
|
|
226
|
+
IAToolkit is a comprehensive, Python open-source framework designed for building enterprise-grade
|
|
227
|
+
AI chatbots and conversational applications. It bridges the gap between the power of
|
|
228
|
+
Large Language Models (LLMs) and the valuable,
|
|
229
|
+
private data locked within your organization's databases and documents.
|
|
230
|
+
|
|
218
231
|
With IAToolkit, you can build production-ready, context-aware chatbots and agents that
|
|
219
232
|
can query relational databases, perform semantic searches on documents,
|
|
220
233
|
and connect to your internal APIs in minutes.
|
|
221
234
|
|
|
222
|
-
|
|
235
|
+
Create secure, branded chat interfaces that can reason over your data, answer questions, and execute custom business logic,
|
|
236
|
+
all powered by leading models from OpenAI, Google Gemini, and more.
|
|
223
237
|
|
|
224
238
|
|
|
225
239
|
## 🚀 Key Features
|
|
226
240
|
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
241
|
+
* **🔗 Unified Data Connection**
|
|
242
|
+
* **Natural Language to SQL**: Let your chatbot query relational databases (PostgreSQL, MySQL, SQLite) using everyday language.
|
|
243
|
+
* **Semantic Document Search**: Automatically chunk, embed, and search across your private documents (PDFs, Word, etc.) to provide contextually accurate answers.
|
|
230
244
|
|
|
231
|
-
*
|
|
232
|
-
*
|
|
245
|
+
* **🏢 Enterprise-Ready Multi-Tenancy**
|
|
246
|
+
* Deploy isolated "Company" modules, each with its own data, tools, and context.
|
|
247
|
+
* Perfect for SaaS products or internal departmental agents.
|
|
233
248
|
|
|
234
|
-
*
|
|
235
|
-
*
|
|
249
|
+
* **🎨 Fully Brandable UI**
|
|
250
|
+
* Customize the look and feel for each "Company" with its own logos, colors, and even language settings (i18n).
|
|
251
|
+
* Provides a white-labeled experience for your users.
|
|
236
252
|
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* Powerful Flask-based **CLI** for database setup, API key generation, and more.
|
|
253
|
+
* **🧠 LLM Agnostic**
|
|
254
|
+
* Switch between **OpenAI (GPT-*)** and **Google (Gemini-*)** with a single line change in your configuration.
|
|
255
|
+
* No code refactoring needed.
|
|
241
256
|
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
257
|
+
* **🛠️ Developer-First Experience**
|
|
258
|
+
* Built with a clean **Dependency Injection** architecture.
|
|
259
|
+
* High-quality code base with **90%+ test coverage**.
|
|
245
260
|
|
|
246
|
-
|
|
261
|
+
* **🔒 Security & Observability Built-In**
|
|
262
|
+
* Comes with integrated user authentication, API keys, and secure session handling out of the box.
|
|
263
|
+
* Full traceability with detailed logging of all queries, function calls, token usage, and costs.
|
|
264
|
+
## ⚡ Quick Start: Try our 'hello world' example
|
|
247
265
|
|
|
248
|
-
|
|
249
|
-
|
|
266
|
+
Ready to see it in action? Our Quickstart Guide will walk you through downloading, configuring, and launching your first AI assistant in just a few minutes.
|
|
267
|
+
It's the best way to experience the toolkit's capabilities firsthand.
|
|
250
268
|
|
|
251
269
|
## 🤝 Contributing
|
|
252
270
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
<div align="center">
|
|
3
|
+
<h1>
|
|
4
|
+
IAToolkit
|
|
5
|
+
</h1>
|
|
6
|
+
|
|
7
|
+
<p><strong>The Open-Source Framework for Building AI Chatbots on Your Private Data.</strong></p>
|
|
8
|
+
<h4>
|
|
9
|
+
<a href="https://www.iatoolkit.com" target="_blank" style="text-decoration: none; color: inherit;">
|
|
10
|
+
www.iatoolkit.com
|
|
11
|
+
</a>
|
|
12
|
+
</h4>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
IAToolkit is a comprehensive, Python open-source framework designed for building enterprise-grade
|
|
16
|
+
AI chatbots and conversational applications. It bridges the gap between the power of
|
|
17
|
+
Large Language Models (LLMs) and the valuable,
|
|
18
|
+
private data locked within your organization's databases and documents.
|
|
19
|
+
|
|
20
|
+
With IAToolkit, you can build production-ready, context-aware chatbots and agents that
|
|
21
|
+
can query relational databases, perform semantic searches on documents,
|
|
22
|
+
and connect to your internal APIs in minutes.
|
|
23
|
+
|
|
24
|
+
Create secure, branded chat interfaces that can reason over your data, answer questions, and execute custom business logic,
|
|
25
|
+
all powered by leading models from OpenAI, Google Gemini, and more.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## 🚀 Key Features
|
|
29
|
+
|
|
30
|
+
* **🔗 Unified Data Connection**
|
|
31
|
+
* **Natural Language to SQL**: Let your chatbot query relational databases (PostgreSQL, MySQL, SQLite) using everyday language.
|
|
32
|
+
* **Semantic Document Search**: Automatically chunk, embed, and search across your private documents (PDFs, Word, etc.) to provide contextually accurate answers.
|
|
33
|
+
|
|
34
|
+
* **🏢 Enterprise-Ready Multi-Tenancy**
|
|
35
|
+
* Deploy isolated "Company" modules, each with its own data, tools, and context.
|
|
36
|
+
* Perfect for SaaS products or internal departmental agents.
|
|
37
|
+
|
|
38
|
+
* **🎨 Fully Brandable UI**
|
|
39
|
+
* Customize the look and feel for each "Company" with its own logos, colors, and even language settings (i18n).
|
|
40
|
+
* Provides a white-labeled experience for your users.
|
|
41
|
+
|
|
42
|
+
* **🧠 LLM Agnostic**
|
|
43
|
+
* Switch between **OpenAI (GPT-*)** and **Google (Gemini-*)** with a single line change in your configuration.
|
|
44
|
+
* No code refactoring needed.
|
|
45
|
+
|
|
46
|
+
* **🛠️ Developer-First Experience**
|
|
47
|
+
* Built with a clean **Dependency Injection** architecture.
|
|
48
|
+
* High-quality code base with **90%+ test coverage**.
|
|
49
|
+
|
|
50
|
+
* **🔒 Security & Observability Built-In**
|
|
51
|
+
* Comes with integrated user authentication, API keys, and secure session handling out of the box.
|
|
52
|
+
* Full traceability with detailed logging of all queries, function calls, token usage, and costs.
|
|
53
|
+
## ⚡ Quick Start: Try our 'hello world' example
|
|
54
|
+
|
|
55
|
+
Ready to see it in action? Our Quickstart Guide will walk you through downloading, configuring, and launching your first AI assistant in just a few minutes.
|
|
56
|
+
It's the best way to experience the toolkit's capabilities firsthand.
|
|
57
|
+
|
|
58
|
+
## 🤝 Contributing
|
|
59
|
+
|
|
60
|
+
We welcome contributions! Whether it's adding a new feature, improving documentation, or fixing a bug,
|
|
61
|
+
please feel free to open a pull request.
|
|
62
|
+
|
|
63
|
+
## 📄 License
|
|
64
|
+
|
|
65
|
+
IAToolkit is open-source and licensed under the [MIT License](LICENSE).
|
|
@@ -13,6 +13,7 @@ from .base_company import BaseCompany
|
|
|
13
13
|
from iatoolkit.repositories.database_manager import DatabaseManager
|
|
14
14
|
|
|
15
15
|
# --- Services ---
|
|
16
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
16
17
|
from iatoolkit.services.query_service import QueryService
|
|
17
18
|
from iatoolkit.services.sql_service import SqlService
|
|
18
19
|
from iatoolkit.services.document_service import DocumentService
|
|
@@ -29,6 +30,7 @@ __all__ = [
|
|
|
29
30
|
'BaseCompany',
|
|
30
31
|
'DatabaseManager',
|
|
31
32
|
'QueryService',
|
|
33
|
+
'ConfigurationService',
|
|
32
34
|
'SqlService',
|
|
33
35
|
'ExcelService',
|
|
34
36
|
'DocumentService',
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
9
9
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
10
|
-
|
|
11
10
|
from iatoolkit.services.prompt_manager_service import PromptService
|
|
12
11
|
from iatoolkit.repositories.models import Company, Function, PromptCategory
|
|
13
12
|
from iatoolkit import IAToolkit
|
|
@@ -22,22 +21,14 @@ class BaseCompany(ABC):
|
|
|
22
21
|
self.prompt_service: PromptService = injector.get(PromptService)
|
|
23
22
|
self.company: Company | None = None
|
|
24
23
|
|
|
25
|
-
def _load_company_by_short_name(self, short_name: str) -> Company:
|
|
26
|
-
self.company = self.profile_repo.get_company_by_short_name(short_name)
|
|
27
|
-
return self.company
|
|
28
|
-
|
|
29
24
|
def _create_company(self,
|
|
30
25
|
short_name: str,
|
|
31
26
|
name: str,
|
|
32
27
|
parameters: dict | None = None,
|
|
33
|
-
branding: dict | None = None,
|
|
34
|
-
onboarding_cards: dict | None = None,
|
|
35
28
|
) -> Company:
|
|
36
29
|
company_obj = Company(short_name=short_name,
|
|
37
30
|
name=name,
|
|
38
|
-
parameters=parameters
|
|
39
|
-
branding=branding,
|
|
40
|
-
onboarding_cards=onboarding_cards)
|
|
31
|
+
parameters=parameters)
|
|
41
32
|
self.company = self.profile_repo.create_company(company_obj)
|
|
42
33
|
return self.company
|
|
43
34
|
|
|
@@ -78,11 +69,6 @@ class BaseCompany(ABC):
|
|
|
78
69
|
)
|
|
79
70
|
|
|
80
71
|
|
|
81
|
-
@abstractmethod
|
|
82
|
-
# initialize all the database tables needed
|
|
83
|
-
def register_company(self):
|
|
84
|
-
raise NotImplementedError("La subclase debe implementar el método create_company()")
|
|
85
|
-
|
|
86
72
|
@abstractmethod
|
|
87
73
|
# get context specific for this company
|
|
88
74
|
def get_company_context(self, **kwargs) -> str:
|
|
@@ -98,11 +84,6 @@ class BaseCompany(ABC):
|
|
|
98
84
|
def handle_request(self, tag: str, params: dict) -> dict:
|
|
99
85
|
raise NotImplementedError("La subclase debe implementar el método handle_request()")
|
|
100
86
|
|
|
101
|
-
@abstractmethod
|
|
102
|
-
# get context specific for the query
|
|
103
|
-
def start_execution(self):
|
|
104
|
-
raise NotImplementedError("La subclase debe implementar el método start_execution()")
|
|
105
|
-
|
|
106
87
|
@abstractmethod
|
|
107
88
|
# get context specific for the query
|
|
108
89
|
def get_metadata_from_filename(self, filename: str) -> dict:
|
|
@@ -31,10 +31,9 @@ class CompanyRegistry:
|
|
|
31
31
|
|
|
32
32
|
# save the created instance in the registry
|
|
33
33
|
self._company_instances[company_key] = company_instance
|
|
34
|
-
logging.info(f"company '{company_key}' instantiated")
|
|
35
34
|
|
|
36
35
|
except Exception as e:
|
|
37
|
-
logging.error(f"Error
|
|
36
|
+
logging.error(f"Error while creating company instance for {company_key}: {e}")
|
|
38
37
|
logging.exception(e)
|
|
39
38
|
raise
|
|
40
39
|
|
|
@@ -19,7 +19,7 @@ from werkzeug.middleware.proxy_fix import ProxyFix
|
|
|
19
19
|
from injector import Binder, Injector, singleton
|
|
20
20
|
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
21
21
|
|
|
22
|
-
IATOOLKIT_VERSION = "0.
|
|
22
|
+
IATOOLKIT_VERSION = "0.67.0"
|
|
23
23
|
|
|
24
24
|
# global variable for the unique instance of IAToolkit
|
|
25
25
|
_iatoolkit_instance: Optional['IAToolkit'] = None
|
|
@@ -318,6 +318,8 @@ class IAToolkit:
|
|
|
318
318
|
from iatoolkit.services.branding_service import BrandingService
|
|
319
319
|
from iatoolkit.services.i18n_service import I18nService
|
|
320
320
|
from iatoolkit.services.language_service import LanguageService
|
|
321
|
+
from iatoolkit.services.onboarding_service import OnboardingService
|
|
322
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
321
323
|
|
|
322
324
|
binder.bind(QueryService, to=QueryService)
|
|
323
325
|
binder.bind(TaskService, to=TaskService)
|
|
@@ -331,8 +333,10 @@ class IAToolkit:
|
|
|
331
333
|
binder.bind(JWTService, to=JWTService)
|
|
332
334
|
binder.bind(Dispatcher, to=Dispatcher)
|
|
333
335
|
binder.bind(BrandingService, to=BrandingService)
|
|
336
|
+
binder.bind(OnboardingService, to=OnboardingService)
|
|
334
337
|
binder.bind(I18nService, to=I18nService)
|
|
335
338
|
binder.bind(LanguageService, to=LanguageService)
|
|
339
|
+
binder.bind(ConfigurationService, to=ConfigurationService)
|
|
336
340
|
|
|
337
341
|
def _bind_infrastructure(self, binder: Binder):
|
|
338
342
|
from iatoolkit.infra.llm_client import llmClient
|
|
@@ -359,9 +363,9 @@ class IAToolkit:
|
|
|
359
363
|
# instantiate all the registered companies
|
|
360
364
|
get_company_registry().instantiate_companies(self._injector)
|
|
361
365
|
|
|
362
|
-
# use the dispatcher to
|
|
366
|
+
# use the dispatcher to load the config and prepare the execution
|
|
363
367
|
dispatcher = self._injector.get(Dispatcher)
|
|
364
|
-
dispatcher.
|
|
368
|
+
dispatcher.load_company_configs()
|
|
365
369
|
|
|
366
370
|
def _setup_cli_commands(self):
|
|
367
371
|
from iatoolkit.cli_commands import register_core_commands
|
|
@@ -58,9 +58,6 @@ class Company(Base):
|
|
|
58
58
|
# encrypted api-key
|
|
59
59
|
openai_api_key = Column(String, nullable=True)
|
|
60
60
|
gemini_api_key = Column(String, nullable=True)
|
|
61
|
-
|
|
62
|
-
branding = Column(JSON, nullable=True)
|
|
63
|
-
onboarding_cards = Column(JSON, nullable=True)
|
|
64
61
|
parameters = Column(JSON, nullable=True)
|
|
65
62
|
created_at = Column(DateTime, default=datetime.now)
|
|
66
63
|
allow_jwt = Column(Boolean, default=True, nullable=True)
|
|
@@ -74,10 +74,6 @@ class ProfileRepo:
|
|
|
74
74
|
if company:
|
|
75
75
|
if company.parameters != new_company.parameters:
|
|
76
76
|
company.parameters = new_company.parameters
|
|
77
|
-
if company.branding != new_company.branding:
|
|
78
|
-
company.branding = new_company.branding
|
|
79
|
-
if company.onboarding_cards != new_company.onboarding_cards:
|
|
80
|
-
company.onboarding_cards = new_company.onboarding_cards
|
|
81
77
|
else:
|
|
82
78
|
# Si la compañía no existe, la añade a la sesión.
|
|
83
79
|
self.session.add(new_company)
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# IAToolkit is open source software.
|
|
5
5
|
|
|
6
6
|
from iatoolkit.repositories.models import Company
|
|
7
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
7
8
|
from injector import inject
|
|
8
9
|
|
|
9
10
|
|
|
@@ -12,7 +13,8 @@ class BrandingService:
|
|
|
12
13
|
Branding configuration for IAToolkit
|
|
13
14
|
"""
|
|
14
15
|
@inject
|
|
15
|
-
def __init__(self):
|
|
16
|
+
def __init__(self, config_service: ConfigurationService):
|
|
17
|
+
self.config_service = config_service
|
|
16
18
|
"""
|
|
17
19
|
Define los estilos de branding por defecto para la aplicación.
|
|
18
20
|
"""
|
|
@@ -74,8 +76,9 @@ class BrandingService:
|
|
|
74
76
|
"""
|
|
75
77
|
final_branding_values = self._default_branding.copy()
|
|
76
78
|
|
|
77
|
-
if company
|
|
78
|
-
|
|
79
|
+
if company:
|
|
80
|
+
branding_data = self.config_service.get_company_content(company.short_name, 'branding')
|
|
81
|
+
final_branding_values.update(branding_data)
|
|
79
82
|
|
|
80
83
|
# Función para convertir HEX a RGB
|
|
81
84
|
def hex_to_rgb(hex_color):
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# iatoolkit/services/configuration_service.py
|
|
2
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
3
|
+
# Product: IAToolkit
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from iatoolkit import BaseCompany
|
|
7
|
+
from iatoolkit.repositories.profile_repo import ProfileRepo
|
|
8
|
+
from iatoolkit.repositories.models import Company
|
|
9
|
+
from iatoolkit.common.util import Utility
|
|
10
|
+
from injector import inject
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
class ConfigurationService:
|
|
14
|
+
"""
|
|
15
|
+
Orchestrates the configuration of a Company by reading its YAML files
|
|
16
|
+
and using the BaseCompany's protected methods to register settings.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@inject
|
|
20
|
+
def __init__(self,
|
|
21
|
+
profile_repo: ProfileRepo,
|
|
22
|
+
utility: Utility):
|
|
23
|
+
self.profile_repo = profile_repo
|
|
24
|
+
self.utility = utility
|
|
25
|
+
self._loaded_configs = {} # cache for store loaded configurations
|
|
26
|
+
|
|
27
|
+
def get_company_content(self, company_short_name: str, content_key: str) -> dict | list | None:
|
|
28
|
+
"""
|
|
29
|
+
Public method to provide a specific section of a company's configuration.
|
|
30
|
+
It uses a cache to avoid reading files from disk on every call.
|
|
31
|
+
"""
|
|
32
|
+
self._ensure_config_loaded(company_short_name)
|
|
33
|
+
return self._loaded_configs[company_short_name].get(content_key)
|
|
34
|
+
|
|
35
|
+
def register_company(self, company_short_name: str, company_instance: BaseCompany):
|
|
36
|
+
"""
|
|
37
|
+
Main entry point for configuring a company instance.
|
|
38
|
+
This method is invoked by the dispatcher for each registered company.
|
|
39
|
+
"""
|
|
40
|
+
logging.info(f"⚙️ Starting configuration for company '{company_short_name}'...")
|
|
41
|
+
|
|
42
|
+
# 1. identify the instance with his name and load info from database
|
|
43
|
+
company_instance.id = company_short_name
|
|
44
|
+
company_instance.company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
45
|
+
|
|
46
|
+
# 2. Load the main configuration file and supplementary content files
|
|
47
|
+
config = self._load_and_merge_configs(company_short_name)
|
|
48
|
+
|
|
49
|
+
# 3. Register core company details and get the database object
|
|
50
|
+
company_db_object = self._register_core_details(company_instance, config)
|
|
51
|
+
|
|
52
|
+
# 4. Register tools (functions)
|
|
53
|
+
self._register_tools(company_instance, config.get('tools', []))
|
|
54
|
+
|
|
55
|
+
# 5. Register prompt categories and prompts
|
|
56
|
+
self._register_prompts(company_instance, config)
|
|
57
|
+
|
|
58
|
+
# 6. Link the persisted Company object back to the running instance
|
|
59
|
+
company_instance.company = company_db_object
|
|
60
|
+
|
|
61
|
+
logging.info(f"✅ Company '{company_short_name}' configured successfully.")
|
|
62
|
+
|
|
63
|
+
def _ensure_config_loaded(self, company_short_name: str):
|
|
64
|
+
"""
|
|
65
|
+
Checks if the configuration for a company is in the cache.
|
|
66
|
+
If not, it loads it from files and stores it.
|
|
67
|
+
"""
|
|
68
|
+
if company_short_name not in self._loaded_configs:
|
|
69
|
+
self._loaded_configs[company_short_name] = self._load_and_merge_configs(company_short_name)
|
|
70
|
+
|
|
71
|
+
def _load_and_merge_configs(self, company_short_name: str) -> dict:
|
|
72
|
+
"""
|
|
73
|
+
Loads the main company.yaml and merges data from supplementary files
|
|
74
|
+
specified in the 'content_files' section.
|
|
75
|
+
"""
|
|
76
|
+
config_dir = Path("companies") / company_short_name / "config"
|
|
77
|
+
main_config_path = config_dir / "company.yaml"
|
|
78
|
+
|
|
79
|
+
if not main_config_path.exists():
|
|
80
|
+
raise FileNotFoundError(f"Main configuration file not found: {main_config_path}")
|
|
81
|
+
|
|
82
|
+
config = self.utility.load_schema_from_yaml(main_config_path)
|
|
83
|
+
|
|
84
|
+
# Load and merge supplementary content files (e.g., onboarding_cards)
|
|
85
|
+
for key, file_path in config.get('help_files', {}).items():
|
|
86
|
+
supplementary_path = config_dir / file_path
|
|
87
|
+
if supplementary_path.exists():
|
|
88
|
+
config[key] = self.utility.load_schema_from_yaml(supplementary_path)
|
|
89
|
+
else:
|
|
90
|
+
logging.warning(f"⚠️ Warning: Content file not found: {supplementary_path}")
|
|
91
|
+
config[key] = None # Ensure the key exists but is empty
|
|
92
|
+
|
|
93
|
+
return config
|
|
94
|
+
|
|
95
|
+
def _register_core_details(self, company_instance: BaseCompany, config: dict) -> Company:
|
|
96
|
+
"""Calls _create_company with data from the merged YAML config."""
|
|
97
|
+
return company_instance._create_company(
|
|
98
|
+
name=config['name'],
|
|
99
|
+
short_name=config['id'],
|
|
100
|
+
parameters=config.get('parameters', {})
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def _register_tools(self, company_instance: BaseCompany, tools_config: list):
|
|
104
|
+
"""Calls _create_function for each tool defined in the YAML."""
|
|
105
|
+
for tool in tools_config:
|
|
106
|
+
company_instance._create_function(
|
|
107
|
+
function_name=tool['function_name'],
|
|
108
|
+
description=tool['description'],
|
|
109
|
+
params=tool['params']
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def _register_prompts(self, company_instance: BaseCompany, config: dict):
|
|
113
|
+
"""
|
|
114
|
+
Creates prompt categories first, then creates each prompt and assigns
|
|
115
|
+
it to its respective category.
|
|
116
|
+
"""
|
|
117
|
+
prompts_config = config.get('prompts', [])
|
|
118
|
+
categories_config = config.get('prompt_categories', [])
|
|
119
|
+
|
|
120
|
+
created_categories = {}
|
|
121
|
+
for i, category_name in enumerate(categories_config):
|
|
122
|
+
category_obj = company_instance._create_prompt_category(name=category_name, order=i + 1)
|
|
123
|
+
created_categories[category_name] = category_obj
|
|
124
|
+
|
|
125
|
+
for prompt_data in prompts_config:
|
|
126
|
+
category_name = prompt_data.get('category')
|
|
127
|
+
if not category_name or category_name not in created_categories:
|
|
128
|
+
logging.info(f"⚠️ Warning: Prompt '{prompt_data['name']}' has an invalid or missing category. Skipping.")
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
category_obj = created_categories[category_name]
|
|
132
|
+
|
|
133
|
+
company_instance._create_prompt(
|
|
134
|
+
prompt_name=prompt_data['name'],
|
|
135
|
+
description=prompt_data['description'],
|
|
136
|
+
order=prompt_data['order'],
|
|
137
|
+
category=category_obj,
|
|
138
|
+
active=prompt_data.get('active', True),
|
|
139
|
+
custom_fields=prompt_data.get('custom_fields', [])
|
|
140
|
+
)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
from iatoolkit.common.exceptions import IAToolkitException
|
|
7
7
|
from iatoolkit.services.prompt_manager_service import PromptService
|
|
8
8
|
from iatoolkit.repositories.llm_query_repo import LLMQueryRepo
|
|
9
|
-
|
|
9
|
+
from iatoolkit.services.configuration_service import ConfigurationService
|
|
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
|
|
@@ -19,11 +19,13 @@ import os
|
|
|
19
19
|
class Dispatcher:
|
|
20
20
|
@inject
|
|
21
21
|
def __init__(self,
|
|
22
|
+
config_service: ConfigurationService,
|
|
22
23
|
prompt_service: PromptService,
|
|
23
24
|
llmquery_repo: LLMQueryRepo,
|
|
24
25
|
util: Utility,
|
|
25
26
|
excel_service: ExcelService,
|
|
26
27
|
mail_service: MailService):
|
|
28
|
+
self.config_service = config_service
|
|
27
29
|
self.prompt_service = prompt_service
|
|
28
30
|
self.llmquery_repo = llmquery_repo
|
|
29
31
|
self.util = util
|
|
@@ -55,14 +57,18 @@ class Dispatcher:
|
|
|
55
57
|
self._company_instances = self.company_registry.get_all_company_instances()
|
|
56
58
|
return self._company_instances
|
|
57
59
|
|
|
58
|
-
def
|
|
60
|
+
def load_company_configs(self):
|
|
59
61
|
# initialize the system functions and prompts
|
|
60
62
|
self.setup_iatoolkit_system()
|
|
61
63
|
|
|
62
|
-
"""
|
|
63
|
-
for
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
"""Loads the configuration of every company"""
|
|
65
|
+
for company_name, company_instance in self.company_instances.items():
|
|
66
|
+
try:
|
|
67
|
+
# register the company configuration
|
|
68
|
+
self.config_service.register_company(company_name, company_instance)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logging.error(f"❌ Failed to register configuration for '{company_name}': {e}")
|
|
71
|
+
continue
|
|
66
72
|
|
|
67
73
|
return True
|
|
68
74
|
|
|
@@ -90,9 +96,6 @@ class Dispatcher:
|
|
|
90
96
|
)
|
|
91
97
|
i += 1
|
|
92
98
|
|
|
93
|
-
# register in the database every company class
|
|
94
|
-
for company in self.company_instances.values():
|
|
95
|
-
company.register_company()
|
|
96
99
|
|
|
97
100
|
def dispatch(self, company_name: str, action: str, **kwargs) -> dict:
|
|
98
101
|
company_key = company_name.lower()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# iatoolkit/services/i18n_service.py
|
|
2
2
|
import os
|
|
3
3
|
import logging
|
|
4
|
-
from injector import inject
|
|
4
|
+
from injector import inject, singleton
|
|
5
5
|
from iatoolkit.common.util import Utility
|
|
6
6
|
from iatoolkit.services.language_service import LanguageService
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
@singleton
|
|
9
9
|
class I18nService:
|
|
10
10
|
"""
|
|
11
11
|
Servicio centralizado para manejar la internacionalización (i18n).
|