iatoolkit 1.9.0__py3-none-any.whl → 1.15.3__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 +1 -1
- iatoolkit/common/routes.py +1 -1
- iatoolkit/common/util.py +8 -123
- iatoolkit/core.py +1 -0
- iatoolkit/infra/connectors/file_connector.py +10 -2
- iatoolkit/infra/connectors/google_drive_connector.py +3 -0
- iatoolkit/infra/connectors/local_file_connector.py +3 -0
- iatoolkit/infra/connectors/s3_connector.py +24 -1
- iatoolkit/infra/llm_providers/deepseek_adapter.py +17 -1
- iatoolkit/infra/llm_providers/gemini_adapter.py +117 -18
- iatoolkit/infra/llm_providers/openai_adapter.py +175 -18
- iatoolkit/infra/llm_response.py +13 -0
- iatoolkit/locales/en.yaml +47 -2
- iatoolkit/locales/es.yaml +45 -1
- iatoolkit/repositories/llm_query_repo.py +44 -33
- iatoolkit/services/company_context_service.py +294 -133
- iatoolkit/services/dispatcher_service.py +1 -1
- iatoolkit/services/knowledge_base_service.py +26 -4
- iatoolkit/services/llm_client_service.py +58 -2
- iatoolkit/services/prompt_service.py +236 -330
- iatoolkit/services/query_service.py +37 -18
- iatoolkit/services/storage_service.py +92 -0
- iatoolkit/static/js/chat_filepond.js +188 -63
- iatoolkit/static/js/chat_main.js +105 -52
- iatoolkit/static/styles/chat_iatoolkit.css +96 -0
- iatoolkit/system_prompts/query_main.prompt +24 -41
- iatoolkit/templates/chat.html +15 -6
- iatoolkit/views/base_login_view.py +1 -1
- iatoolkit/views/categories_api_view.py +43 -3
- iatoolkit/views/chat_view.py +1 -1
- iatoolkit/views/login_view.py +1 -1
- iatoolkit/views/prompt_api_view.py +1 -1
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/METADATA +1 -1
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/RECORD +38 -37
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/WHEEL +0 -0
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/licenses/LICENSE +0 -0
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/licenses/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-1.9.0.dist-info → iatoolkit-1.15.3.dist-info}/top_level.txt +0 -0
|
@@ -7,8 +7,10 @@ import logging
|
|
|
7
7
|
from typing import Dict, List, Optional
|
|
8
8
|
from iatoolkit.infra.llm_response import LLMResponse, ToolCall, Usage
|
|
9
9
|
from iatoolkit.common.exceptions import IAToolkitException
|
|
10
|
-
import html
|
|
11
10
|
from typing import List
|
|
11
|
+
import mimetypes
|
|
12
|
+
import re
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class OpenAIAdapter:
|
|
14
16
|
"""Adaptador para la API de OpenAI"""
|
|
@@ -24,9 +26,14 @@ class OpenAIAdapter:
|
|
|
24
26
|
tools: Optional[List[Dict]] = None,
|
|
25
27
|
text: Optional[Dict] = None,
|
|
26
28
|
reasoning: Optional[Dict] = None,
|
|
27
|
-
tool_choice: str = "auto"
|
|
29
|
+
tool_choice: str = "auto",
|
|
30
|
+
images: Optional[List[Dict]] = None) -> LLMResponse:
|
|
28
31
|
"""Llamada a la API de OpenAI y mapeo a estructura común"""
|
|
29
32
|
try:
|
|
33
|
+
# Handle multimodal input if images are present
|
|
34
|
+
if images:
|
|
35
|
+
input = self._prepare_multimodal_input(input, images)
|
|
36
|
+
|
|
30
37
|
# Preparar parámetros para OpenAI
|
|
31
38
|
params = {
|
|
32
39
|
'model': model,
|
|
@@ -56,28 +63,177 @@ class OpenAIAdapter:
|
|
|
56
63
|
|
|
57
64
|
raise IAToolkitException(IAToolkitException.ErrorType.LLM_ERROR, error_message)
|
|
58
65
|
|
|
66
|
+
def _prepare_multimodal_input(self, messages: List[Dict], images: List[Dict]) -> List[Dict]:
|
|
67
|
+
"""
|
|
68
|
+
Transforma el mensaje del usuario de texto simple a contenido multimodal (texto + imágenes)
|
|
69
|
+
usando el formato de Responses API (input_text/input_image).
|
|
70
|
+
"""
|
|
71
|
+
# Encontrar el último mensaje del usuario
|
|
72
|
+
target_message = None
|
|
73
|
+
for msg in reversed(messages):
|
|
74
|
+
if msg.get('role') == 'user':
|
|
75
|
+
target_message = msg
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
if not target_message:
|
|
79
|
+
return messages
|
|
80
|
+
|
|
81
|
+
text_content = target_message.get('content', '')
|
|
82
|
+
content_parts = []
|
|
83
|
+
|
|
84
|
+
# Agregar parte de texto (Responses API)
|
|
85
|
+
if text_content:
|
|
86
|
+
content_parts.append({"type": "input_text", "text": text_content})
|
|
87
|
+
|
|
88
|
+
# Agregar partes de imagen (Responses API)
|
|
89
|
+
for img in images:
|
|
90
|
+
filename = img.get('name', '')
|
|
91
|
+
mime_type, _ = mimetypes.guess_type(filename)
|
|
92
|
+
if not mime_type:
|
|
93
|
+
mime_type = 'image/jpeg'
|
|
94
|
+
|
|
95
|
+
base64_data = img.get('base64', '')
|
|
96
|
+
url = f"data:{mime_type};base64,{base64_data}"
|
|
97
|
+
|
|
98
|
+
content_parts.append({
|
|
99
|
+
"type": "input_image",
|
|
100
|
+
"image_url": url
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
# Construir nueva lista de mensajes con el contenido actualizado
|
|
104
|
+
final_messages = []
|
|
105
|
+
for msg in messages:
|
|
106
|
+
if msg is target_message:
|
|
107
|
+
new_msg = msg.copy()
|
|
108
|
+
new_msg['content'] = content_parts
|
|
109
|
+
final_messages.append(new_msg)
|
|
110
|
+
else:
|
|
111
|
+
final_messages.append(msg)
|
|
112
|
+
|
|
113
|
+
return final_messages
|
|
114
|
+
|
|
59
115
|
def _map_openai_response(self, openai_response) -> LLMResponse:
|
|
60
116
|
"""Mapear respuesta de OpenAI a estructura común"""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
117
|
+
tool_calls: List[ToolCall] = []
|
|
118
|
+
content_parts: List[Dict] = []
|
|
119
|
+
output_text = ""
|
|
120
|
+
|
|
121
|
+
print(f'openai_response.output: {openai_response.output}')
|
|
122
|
+
output_items = getattr(openai_response, 'output', []) or []
|
|
123
|
+
|
|
124
|
+
def _extract_markdown_images(text: str) -> None:
|
|
125
|
+
# Pattern: 
|
|
126
|
+
markdown_images = re.findall(r'!\[([^\]]*)\]\((https?://[^)]+)\)', text or "")
|
|
127
|
+
for _alt_text, url in markdown_images:
|
|
128
|
+
content_parts.append({
|
|
129
|
+
"type": "image",
|
|
130
|
+
"source": {
|
|
131
|
+
"type": "url",
|
|
132
|
+
"media_type": "image/webp",
|
|
133
|
+
"url": url
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
for item in output_items:
|
|
138
|
+
item_type = getattr(item, 'type', '')
|
|
139
|
+
|
|
140
|
+
# 1) Tool calls (Responses API)
|
|
141
|
+
if item_type == "function_call":
|
|
142
|
+
tool_calls.append(ToolCall(
|
|
143
|
+
call_id=getattr(item, 'call_id', ''),
|
|
144
|
+
type=item_type,
|
|
145
|
+
name=getattr(item, 'name', ''),
|
|
146
|
+
arguments=getattr(item, 'arguments', '{}')
|
|
147
|
+
))
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# 2) Mensajes (lo más común en Responses API)
|
|
151
|
+
if item_type == "message":
|
|
152
|
+
msg_content = getattr(item, "content", None) or []
|
|
153
|
+
for part in msg_content:
|
|
154
|
+
part_type = getattr(part, "type", "") or ""
|
|
155
|
+
|
|
156
|
+
# 2.A) Texto
|
|
157
|
+
if part_type in ("output_text", "text"):
|
|
158
|
+
text_content = getattr(part, "text", "") or ""
|
|
159
|
+
if text_content:
|
|
160
|
+
_extract_markdown_images(text_content)
|
|
161
|
+
output_text += text_content
|
|
162
|
+
content_parts.append({"type": "text", "text": text_content})
|
|
163
|
+
|
|
164
|
+
# 2.B) Imagen (puede venir como URL o base64 según el SDK/endpoint)
|
|
165
|
+
elif part_type in ("output_image", "image"):
|
|
166
|
+
# Algunas variantes comunes:
|
|
167
|
+
# - part.image_url (string URL)
|
|
168
|
+
# - part.url
|
|
169
|
+
# - part.b64_json (base64)
|
|
170
|
+
image_url = getattr(part, "image_url", None) or getattr(part, "url", None)
|
|
171
|
+
b64 = getattr(part, "b64_json", None) or getattr(part, "image", None) or getattr(part, "data", None)
|
|
172
|
+
|
|
173
|
+
# mime_type a veces viene, a veces no
|
|
174
|
+
mime_type = getattr(part, "media_type", None) or getattr(part, "mime_type", None) or "image/png"
|
|
175
|
+
|
|
176
|
+
if image_url:
|
|
177
|
+
content_parts.append({
|
|
178
|
+
"type": "image",
|
|
179
|
+
"source": {
|
|
180
|
+
"type": "url",
|
|
181
|
+
"media_type": mime_type,
|
|
182
|
+
"url": image_url
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
output_text += "\n[Imagen Generada]\n"
|
|
186
|
+
elif b64:
|
|
187
|
+
content_parts.append({
|
|
188
|
+
"type": "image",
|
|
189
|
+
"source": {
|
|
190
|
+
"type": "base64",
|
|
191
|
+
"media_type": mime_type,
|
|
192
|
+
"data": b64
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
output_text += "\n[Imagen Generada]\n"
|
|
196
|
+
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
# 3) Compatibilidad hacia atrás: ítems planos "text"
|
|
200
|
+
if item_type == "text":
|
|
201
|
+
text_content = getattr(item, 'text', '') or ""
|
|
202
|
+
if text_content:
|
|
203
|
+
_extract_markdown_images(text_content)
|
|
204
|
+
output_text += text_content
|
|
205
|
+
content_parts.append({"type": "text", "text": text_content})
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
# 4) Compatibilidad hacia atrás: ítems planos "image"
|
|
209
|
+
if item_type == "image":
|
|
210
|
+
base64_data = getattr(item, 'image', '') or getattr(item, 'data', '')
|
|
211
|
+
mime_type = getattr(item, 'media_type', 'image/png')
|
|
212
|
+
if base64_data:
|
|
213
|
+
content_parts.append({
|
|
214
|
+
"type": "image",
|
|
215
|
+
"source": {
|
|
216
|
+
"type": "base64",
|
|
217
|
+
"media_type": mime_type,
|
|
218
|
+
"data": base64_data
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
output_text += "\n[Imagen Generada]\n"
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
# Fallback: Si no se extrajo texto, probamos output_text directo
|
|
225
|
+
if not output_text:
|
|
226
|
+
output_text = getattr(openai_response, 'output_text', '') or ""
|
|
227
|
+
if output_text and not content_parts:
|
|
228
|
+
_extract_markdown_images(output_text)
|
|
229
|
+
content_parts.append({"type": "text", "text": output_text})
|
|
230
|
+
|
|
74
231
|
usage = Usage(
|
|
75
232
|
input_tokens=openai_response.usage.input_tokens if openai_response.usage else 0,
|
|
76
233
|
output_tokens=openai_response.usage.output_tokens if openai_response.usage else 0,
|
|
77
234
|
total_tokens=openai_response.usage.total_tokens if openai_response.usage else 0
|
|
78
235
|
)
|
|
79
236
|
|
|
80
|
-
# Reasoning content extracted from Responses output items (type="reasoning")
|
|
81
237
|
reasoning_list = self._extract_reasoning_content(openai_response)
|
|
82
238
|
reasoning_str = "\n".join(reasoning_list)
|
|
83
239
|
|
|
@@ -85,10 +241,11 @@ class OpenAIAdapter:
|
|
|
85
241
|
id=openai_response.id,
|
|
86
242
|
model=openai_response.model,
|
|
87
243
|
status=openai_response.status,
|
|
88
|
-
output_text=
|
|
244
|
+
output_text=output_text,
|
|
89
245
|
output=tool_calls,
|
|
90
246
|
usage=usage,
|
|
91
|
-
reasoning_content=reasoning_str
|
|
247
|
+
reasoning_content=reasoning_str,
|
|
248
|
+
content_parts=content_parts
|
|
92
249
|
)
|
|
93
250
|
|
|
94
251
|
def _extract_reasoning_content(self, openai_response) -> List[str]:
|
iatoolkit/infra/llm_response.py
CHANGED
|
@@ -34,6 +34,9 @@ class LLMResponse:
|
|
|
34
34
|
usage: Usage
|
|
35
35
|
reasoning_content: str = None # campo opcional para Chain of Thought
|
|
36
36
|
|
|
37
|
+
# ordered list of content blocks (text and image mixed)
|
|
38
|
+
# Example: [{"type": "text", "text": "..."}, {"type": "image", "source": {"type": "base64", "data": "..."}}]
|
|
39
|
+
content_parts: List[Dict] = None
|
|
37
40
|
|
|
38
41
|
def __post_init__(self):
|
|
39
42
|
"""Asegura que output sea una lista"""
|
|
@@ -43,3 +46,13 @@ class LLMResponse:
|
|
|
43
46
|
if self.reasoning_content is None:
|
|
44
47
|
self.reasoning_content = ""
|
|
45
48
|
|
|
49
|
+
if self.content_parts is None:
|
|
50
|
+
self.content_parts = []
|
|
51
|
+
|
|
52
|
+
# if the response has legacy text and no content parts, create a default text part
|
|
53
|
+
if self.output_text:
|
|
54
|
+
self.content_parts.append({
|
|
55
|
+
"type": "text",
|
|
56
|
+
"text": self.output_text
|
|
57
|
+
})
|
|
58
|
+
|
iatoolkit/locales/en.yaml
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Language: English
|
|
2
2
|
ui:
|
|
3
|
+
common:
|
|
4
|
+
actions: "Actions"
|
|
5
|
+
save: "Save"
|
|
6
|
+
cancel: "Cancel"
|
|
7
|
+
apply: "Apply Changes"
|
|
8
|
+
discard_changes: "Discard Changes"
|
|
9
|
+
order: "Order"
|
|
10
|
+
|
|
11
|
+
|
|
3
12
|
login_widget:
|
|
4
13
|
title: "Sign In"
|
|
5
14
|
welcome_message: "Enter your credentials or register to access this platform."
|
|
@@ -88,7 +97,15 @@ ui:
|
|
|
88
97
|
add: "Add"
|
|
89
98
|
create: "Create"
|
|
90
99
|
delete: "Delete"
|
|
91
|
-
|
|
100
|
+
add_new: "Add new"
|
|
101
|
+
category_name_placeholder: "Category name"
|
|
102
|
+
items: "Items"
|
|
103
|
+
drag_drop_hint: "Use arrows to reorder items."
|
|
104
|
+
current_items: "Current items"
|
|
105
|
+
manage: "Manage"
|
|
106
|
+
unsaved_changes_title: "Unsaved Changes"
|
|
107
|
+
unsaved_changes_message: "You have unsaved changes. Do you want to save them before proceeding?"
|
|
108
|
+
discard_changes: "Discard Changes"
|
|
92
109
|
|
|
93
110
|
db_explorer:
|
|
94
111
|
data_explorer: "Data Explorer"
|
|
@@ -181,10 +198,32 @@ ui:
|
|
|
181
198
|
delete_cancel: "Cancel"
|
|
182
199
|
target_collection: "Collection"
|
|
183
200
|
select_collection_placeholder: "Select a collection"
|
|
201
|
+
select_collection: "Select a collection"
|
|
184
202
|
collection_required: "Collection is required"
|
|
185
203
|
collection: "Collection"
|
|
204
|
+
manage_collections: "Manage collections"
|
|
186
205
|
all_collections: "All collections"
|
|
187
206
|
|
|
207
|
+
json_editor:
|
|
208
|
+
title: "JSON Schema Editor"
|
|
209
|
+
subtitle: "Define the structure and metadata for this JSON field"
|
|
210
|
+
field_name: "Field Name"
|
|
211
|
+
type: "Data Type"
|
|
212
|
+
description: "Description"
|
|
213
|
+
empty_schema: "No fields defined yet."
|
|
214
|
+
add_root: "Add Root Field"
|
|
215
|
+
add_field: "Add Field"
|
|
216
|
+
types:
|
|
217
|
+
string: "String"
|
|
218
|
+
integer: "Integer"
|
|
219
|
+
number: "Number"
|
|
220
|
+
boolean: "Boolean"
|
|
221
|
+
object: "Object (Map)"
|
|
222
|
+
array: "Array (List)"
|
|
223
|
+
jsonb: "JSON Raw"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
188
227
|
tooltips:
|
|
189
228
|
history: "History of my queries"
|
|
190
229
|
reload_context: "Force Context Reload"
|
|
@@ -350,7 +389,6 @@ js_messages:
|
|
|
350
389
|
db_created: "Created"
|
|
351
390
|
db_last_access: "Last access"
|
|
352
391
|
db_filename: "Filename"
|
|
353
|
-
db_user: "User"
|
|
354
392
|
db_status: "Status"
|
|
355
393
|
db_collection: "Collection"
|
|
356
394
|
editor_no_file_selected: "No file selected"
|
|
@@ -358,6 +396,13 @@ js_messages:
|
|
|
358
396
|
cant_load_company: "Could not load company.yaml"
|
|
359
397
|
config_saved: "Configuration saved successfully."
|
|
360
398
|
config_error: "Error saving configuration."
|
|
399
|
+
all_collections: "All collections"
|
|
400
|
+
no_variables_found: "No variables found in prompt template."
|
|
401
|
+
manage_collections: "Manage collections"
|
|
402
|
+
edit: "Edit"
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
|
|
361
406
|
|
|
362
407
|
|
|
363
408
|
|
iatoolkit/locales/es.yaml
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# locales/es.yaml
|
|
2
2
|
ui:
|
|
3
|
+
common:
|
|
4
|
+
actions: "Acciones"
|
|
5
|
+
save: "Guardar"
|
|
6
|
+
cancel: "Cancelar"
|
|
7
|
+
apply: "Aplicar Cambios"
|
|
8
|
+
discard_changes: "Descartar Cambios"
|
|
9
|
+
order: "Orden"
|
|
10
|
+
|
|
3
11
|
login_widget:
|
|
4
12
|
title: "Iniciar Sesión"
|
|
5
13
|
welcome_message: "Ingresa tus credenciales o registrate para acceder a la plataforma."
|
|
@@ -85,6 +93,16 @@ ui:
|
|
|
85
93
|
add: "Agregar"
|
|
86
94
|
create: "Crear"
|
|
87
95
|
delete: "Borrar"
|
|
96
|
+
add_new: "Agregar nuevo"
|
|
97
|
+
category_name_placeholder: "nombre de categoria"
|
|
98
|
+
items: "Items"
|
|
99
|
+
drag_drop_hint: "Utiliza las flechas para ordenar items.."
|
|
100
|
+
current_items: "items actuales"
|
|
101
|
+
manage: "Administrar"
|
|
102
|
+
unsaved_changes_title: "Cambios sin guardar"
|
|
103
|
+
unsaved_changes_message: "Tienes modificaciones sin guardar. Quieres grabarlos antes de salir?"
|
|
104
|
+
discard_changes: "Descartar cambios"
|
|
105
|
+
|
|
88
106
|
|
|
89
107
|
db_explorer:
|
|
90
108
|
data_explorer: "Explorador de datos"
|
|
@@ -136,6 +154,7 @@ ui:
|
|
|
136
154
|
category_label: "Categoría"
|
|
137
155
|
delete_confirmation: "Eliminar el prompt?"
|
|
138
156
|
|
|
157
|
+
|
|
139
158
|
config:
|
|
140
159
|
editor_description: "Editor de configuración"
|
|
141
160
|
title: "Editor de configuraciones"
|
|
@@ -179,7 +198,26 @@ ui:
|
|
|
179
198
|
collection_required: "Debe seleccionar una categoría"
|
|
180
199
|
all_collections: "Todas las categorías"
|
|
181
200
|
collection: "Categoría"
|
|
201
|
+
manage_collections: "Administra collections"
|
|
202
|
+
select_collection: "Selecciona una categorìa"
|
|
182
203
|
|
|
204
|
+
json_editor:
|
|
205
|
+
title: "Editor de Esquema JSON"
|
|
206
|
+
subtitle: "Define la estructura y metadatos para este campo JSON"
|
|
207
|
+
field_name: "Nombre del Campo"
|
|
208
|
+
type: "Tipo de Dato"
|
|
209
|
+
description: "Descripción"
|
|
210
|
+
empty_schema: "Aún no hay campos definidos."
|
|
211
|
+
add_root: "Agregar Campo Raíz"
|
|
212
|
+
add_field: "Agregar Campo"
|
|
213
|
+
types:
|
|
214
|
+
string: "Texto (String)"
|
|
215
|
+
integer: "Entero"
|
|
216
|
+
number: "Número (Decimal)"
|
|
217
|
+
boolean: "Booleano"
|
|
218
|
+
object: "Objeto (Mapa)"
|
|
219
|
+
array: "Lista (Array)"
|
|
220
|
+
jsonb: "JSON Crudo"
|
|
183
221
|
|
|
184
222
|
tooltips:
|
|
185
223
|
history: "Historial con mis consultas"
|
|
@@ -340,7 +378,6 @@ js_messages:
|
|
|
340
378
|
search_placeholder: "Buscar usuarios..."
|
|
341
379
|
showing: "Mostrando"
|
|
342
380
|
records: "Registros"
|
|
343
|
-
db_user: "Usuario"
|
|
344
381
|
db_role: "Rol"
|
|
345
382
|
db_verified: "Verificado"
|
|
346
383
|
db_collection: "Colección"
|
|
@@ -354,6 +391,13 @@ js_messages:
|
|
|
354
391
|
cant_load_company: "No puede cargarcompany.yaml"
|
|
355
392
|
config_saved: "Configuración guardada correctamente."
|
|
356
393
|
config_error: "Error guardando configuración"
|
|
394
|
+
all_collections: "Todas las colecciones"
|
|
395
|
+
no_variables_found: "El template no contiene variables."
|
|
396
|
+
manage_collections: "Administra collections"
|
|
397
|
+
edit: "Editar"
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
|
|
357
401
|
|
|
358
402
|
|
|
359
403
|
|
|
@@ -22,12 +22,20 @@ class LLMQueryRepo:
|
|
|
22
22
|
def rollback(self):
|
|
23
23
|
self.session.rollback()
|
|
24
24
|
|
|
25
|
+
# save new query result in the database
|
|
25
26
|
def add_query(self, query: LLMQuery):
|
|
26
27
|
self.session.add(query)
|
|
27
28
|
self.session.commit()
|
|
28
29
|
return query
|
|
29
30
|
|
|
31
|
+
# get user query history
|
|
32
|
+
def get_history(self, company: Company, user_identifier: str) -> list[LLMQuery]:
|
|
33
|
+
return self.session.query(LLMQuery).filter(
|
|
34
|
+
LLMQuery.user_identifier == user_identifier,
|
|
35
|
+
).filter_by(company_id=company.id).order_by(LLMQuery.created_at.desc()).limit(100).all()
|
|
30
36
|
|
|
37
|
+
|
|
38
|
+
## --- Tools related methods
|
|
31
39
|
def get_company_tools(self, company: Company) -> list[Tool]:
|
|
32
40
|
return (
|
|
33
41
|
self.session.query(Tool)
|
|
@@ -63,6 +71,30 @@ class LLMQueryRepo:
|
|
|
63
71
|
def delete_tool(self, tool: Tool):
|
|
64
72
|
self.session.query(Tool).filter_by(id=tool.id).delete(synchronize_session=False)
|
|
65
73
|
|
|
74
|
+
# -- Prompt related methods
|
|
75
|
+
|
|
76
|
+
def get_prompt_by_name(self, company: Company, prompt_name: str):
|
|
77
|
+
return self.session.query(Prompt).filter_by(company_id=company.id, name=prompt_name).first()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_prompts(self, company: Company, include_all: bool = False) -> list[Prompt]:
|
|
81
|
+
if include_all:
|
|
82
|
+
# Include all prompts (for the prompt admin dashboard)
|
|
83
|
+
return self.session.query(Prompt).filter(
|
|
84
|
+
Prompt.company_id == company.id,
|
|
85
|
+
).all()
|
|
86
|
+
else:
|
|
87
|
+
# Only active company prompts (default behavior for end users)
|
|
88
|
+
return self.session.query(Prompt).filter(
|
|
89
|
+
Prompt.company_id == company.id,
|
|
90
|
+
Prompt.prompt_type == PromptType.COMPANY.value,
|
|
91
|
+
Prompt.active == True
|
|
92
|
+
).all()
|
|
93
|
+
|
|
94
|
+
def get_system_prompts(self) -> list[Prompt]:
|
|
95
|
+
return self.session.query(Prompt).filter_by(prompt_type=PromptType.SYSTEM.value, active=True).order_by(
|
|
96
|
+
Prompt.order).all()
|
|
97
|
+
|
|
66
98
|
def create_or_update_prompt(self, new_prompt: Prompt):
|
|
67
99
|
prompt = self.session.query(Prompt).filter_by(company_id=new_prompt.company_id,
|
|
68
100
|
name=new_prompt.name).first()
|
|
@@ -80,6 +112,18 @@ class LLMQueryRepo:
|
|
|
80
112
|
self.session.commit()
|
|
81
113
|
return prompt
|
|
82
114
|
|
|
115
|
+
def delete_prompt(self, prompt: Prompt):
|
|
116
|
+
self.session.delete(prompt)
|
|
117
|
+
self.session.commit()
|
|
118
|
+
|
|
119
|
+
# -- Prompt category methods
|
|
120
|
+
|
|
121
|
+
def get_category_by_name(self, company_id: int, name: str) -> PromptCategory:
|
|
122
|
+
return self.session.query(PromptCategory).filter_by(company_id=company_id, name=name).first()
|
|
123
|
+
|
|
124
|
+
def get_all_categories(self, company_id: int) -> List[PromptCategory]:
|
|
125
|
+
return self.session.query(PromptCategory).filter_by(company_id=company_id).order_by(PromptCategory.order).all()
|
|
126
|
+
|
|
83
127
|
def create_or_update_prompt_category(self, new_category: PromptCategory):
|
|
84
128
|
category = self.session.query(PromptCategory).filter_by(company_id=new_category.company_id,
|
|
85
129
|
name=new_category.name).first()
|
|
@@ -92,36 +136,3 @@ class LLMQueryRepo:
|
|
|
92
136
|
self.session.flush()
|
|
93
137
|
return category
|
|
94
138
|
|
|
95
|
-
def get_history(self, company: Company, user_identifier: str) -> list[LLMQuery]:
|
|
96
|
-
return self.session.query(LLMQuery).filter(
|
|
97
|
-
LLMQuery.user_identifier == user_identifier,
|
|
98
|
-
).filter_by(company_id=company.id).order_by(LLMQuery.created_at.desc()).limit(100).all()
|
|
99
|
-
|
|
100
|
-
def get_prompts(self, company: Company, include_all: bool = False) -> list[Prompt]:
|
|
101
|
-
if include_all:
|
|
102
|
-
# Include all prompts: company, system, agent
|
|
103
|
-
return self.session.query(Prompt).filter(
|
|
104
|
-
Prompt.company_id == company.id,
|
|
105
|
-
).all()
|
|
106
|
-
else:
|
|
107
|
-
# Only company prompts, excluding system (default behavior for end users)
|
|
108
|
-
return self.session.query(Prompt).filter(
|
|
109
|
-
Prompt.company_id == company.id,
|
|
110
|
-
Prompt.prompt_type == PromptType.COMPANY.value
|
|
111
|
-
).all()
|
|
112
|
-
|
|
113
|
-
def get_prompt_by_name(self, company: Company, prompt_name: str):
|
|
114
|
-
return self.session.query(Prompt).filter_by(company_id=company.id, name=prompt_name).first()
|
|
115
|
-
|
|
116
|
-
def get_category_by_name(self, company_id: int, name: str) -> PromptCategory:
|
|
117
|
-
return self.session.query(PromptCategory).filter_by(company_id=company_id, name=name).first()
|
|
118
|
-
|
|
119
|
-
def get_all_categories(self, company_id: int) -> List[PromptCategory]:
|
|
120
|
-
return self.session.query(PromptCategory).filter_by(company_id=company_id).order_by(PromptCategory.order).all()
|
|
121
|
-
|
|
122
|
-
def get_system_prompts(self) -> list[Prompt]:
|
|
123
|
-
return self.session.query(Prompt).filter_by(prompt_type=PromptType.SYSTEM.value, active=True).order_by(Prompt.order).all()
|
|
124
|
-
|
|
125
|
-
def delete_prompt(self, prompt: Prompt):
|
|
126
|
-
self.session.delete(prompt)
|
|
127
|
-
self.session.commit()
|