@innvisor/conny-ai 9.7.0 → 9.8.2
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.
- package/CHANGELOG.md +66 -0
- package/README.md +17 -1
- package/conny_app.py +9 -3
- package/conny_cli.py +103 -11
- package/conny_core/evolution.py +112 -0
- package/conny_core/first_turn_ops.py +16 -20
- package/conny_core/prompt_ops.py +62 -0
- package/conny_demo_voice.py +1 -1
- package/conny_doctor.py +287 -2
- package/conny_domino.py +2 -2
- package/conny_generator.py +1 -1
- package/conny_i18n.py +81 -2
- package/conny_init.py +254 -41
- package/conny_runtime_ops.py +198 -6
- package/conny_tui.py +7 -0
- package/conny_ultra_config.py +25 -11
- package/conny_utils.py +21 -3
- package/ecosystem.config.js +11 -1
- package/install.sh +78 -22
- package/npm/conny.js +75 -21
- package/package.json +12 -2
- package/run.sh +7 -0
- package/src/conny/admin/dashboard.py +35 -4
- package/src/conny/admin_memory.py +93 -0
- package/src/conny/api/routes.py +26 -9
- package/src/conny/channels/cli.py +30 -9
- package/src/conny/demo/handler.py +23 -23
- package/src/conny/personas/generator.py +1 -1
- package/src/conny/production/domino.py +2 -2
- package/src/conny/production/guard.py +4 -4
- package/src/core/admin_engines.py +51 -48
- package/src/core/globals.py +110 -9
- package/src/core/production_monitor.py +63 -38
- package/src/core/runtime.py +343 -305
- package/src/domain/prompts/prospect_pitch.py +11 -11
- package/src/domain/send_guard.py +4 -4
- package/src/interfaces/web/app.py +91 -27
- package/src/interfaces/web/demo_admin_commands.py +165 -0
- package/src/interfaces/web/demo_handler.py +178 -34
- package/brand-assets/cl-nica-de-las-am-ricas/manifest.json +0 -22
- package/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt +0 -11
- package/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt +0 -11
- package/brand-assets/cl-nica-las-am-ricas/manifest.json +0 -22
- package/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt +0 -11
- package/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt +0 -11
- package/brand-assets/conny-demo/manifest.json +0 -22
- package/brand-assets/conny-demo/processed/business-identity.txt +0 -7
- package/brand-assets/conny-demo/raw/business-identity.txt +0 -7
- package/fix_init.py +0 -27
- package/verify_conversation_impl.py +0 -48
package/conny_init.py
CHANGED
|
@@ -12,13 +12,75 @@ import shutil
|
|
|
12
12
|
import urllib.request
|
|
13
13
|
import urllib.error
|
|
14
14
|
import subprocess
|
|
15
|
+
import socket
|
|
15
16
|
from deep_translator import GoogleTranslator
|
|
16
17
|
|
|
17
|
-
CURRENT_LANG =
|
|
18
|
+
CURRENT_LANG = os.environ.get("CONNY_INIT_LANG", "en").lower()
|
|
19
|
+
|
|
20
|
+
TRANSLATIONS = {
|
|
21
|
+
"en": {
|
|
22
|
+
"Selección de idioma": "Language selection",
|
|
23
|
+
"Idioma preferido para Conny": "Preferred language for Conny",
|
|
24
|
+
"¿Comenzar configuración?": "Start guided setup?",
|
|
25
|
+
"Operación cancelada.": "Setup cancelled.",
|
|
26
|
+
"Tiempo estimado: ~3 min": "Estimated time: ~3 min",
|
|
27
|
+
"Identidad": "Identity",
|
|
28
|
+
"Nombre del negocio": "Business name",
|
|
29
|
+
"Dominio e Industria": "Domain and industry",
|
|
30
|
+
"Sector Principal": "Primary sector",
|
|
31
|
+
"Canales de Conectividad": "Connectivity channels",
|
|
32
|
+
"Canal Principal de Acceso": "Primary access channel",
|
|
33
|
+
"Gateway público": "Public gateway",
|
|
34
|
+
"¿Cómo deseas configurar el enlace público (BASE_URL)?": "How do you want to configure the public link (BASE_URL)?",
|
|
35
|
+
"Generar túnel automático (localhost.run)": "Generate automatic tunnel (localhost.run)",
|
|
36
|
+
"Configurar enlace manualmente (ingresar URL personalizada)": "Configure manually (enter a custom URL)",
|
|
37
|
+
"Introduce la URL pública de tu webhook": "Enter your public webhook URL",
|
|
38
|
+
"Levantando túnel seguro hacia localhost.run...": "Starting secure tunnel through localhost.run...",
|
|
39
|
+
"Túnel activo:": "Tunnel active:",
|
|
40
|
+
"No pude obtener un túnel automático. Ingresa una URL manual.": "I could not get an automatic tunnel. Enter a manual URL.",
|
|
41
|
+
"Dashboard web": "Web dashboard",
|
|
42
|
+
"¿Cómo quieres exponer la página web de Conny?": "How do you want to expose Conny's web dashboard?",
|
|
43
|
+
"Solo local (localhost)": "Local only (localhost)",
|
|
44
|
+
"Red/IP externa de este dispositivo": "Network/external IP for this device",
|
|
45
|
+
"URL pública personalizada": "Custom public URL",
|
|
46
|
+
"No configurar dashboard ahora": "Do not configure dashboard now",
|
|
47
|
+
"Introduce la URL pública del dashboard": "Enter the public dashboard URL",
|
|
48
|
+
"Motor de Inteligencia": "Intelligence engine",
|
|
49
|
+
"Tipo de proveedor": "Provider type",
|
|
50
|
+
"Proveedor Cloud": "Cloud provider",
|
|
51
|
+
"Modelo Específico": "Specific model",
|
|
52
|
+
"Modelo de Ollama": "Ollama model",
|
|
53
|
+
"Modelo NIM": "NIM model",
|
|
54
|
+
"Humanización y Tono": "Humanization and tone",
|
|
55
|
+
"Perfil de Voz": "Voice profile",
|
|
56
|
+
"Infraestructura y Secretos": "Infrastructure and secrets",
|
|
57
|
+
"Verificación Final": "Final verification",
|
|
58
|
+
"¿Procesar e Implementar Infraestructura?": "Provision and deploy infrastructure?",
|
|
59
|
+
"Cancelado.": "Cancelled.",
|
|
60
|
+
"Creando recursos...": "Creating resources...",
|
|
61
|
+
"Resumen de Configuración": "Configuration summary",
|
|
62
|
+
"Negocio:": "Business:",
|
|
63
|
+
"Sector:": "Sector:",
|
|
64
|
+
"Canal:": "Channel:",
|
|
65
|
+
"Proveedor:": "Provider:",
|
|
66
|
+
"Modelo:": "Model:",
|
|
67
|
+
"Voz:": "Voice:",
|
|
68
|
+
"Secretos:": "Secrets:",
|
|
69
|
+
"Directorio:": "Directory:",
|
|
70
|
+
"Control:": "Control:",
|
|
71
|
+
"Check:": "Check:",
|
|
72
|
+
"Lanzar:": "Launch:",
|
|
73
|
+
"Infraestructura Desplegada Exitosamente": "Infrastructure deployed successfully",
|
|
74
|
+
"Validando API key...": "Validating API key...",
|
|
75
|
+
"✅ API key validada correctamente.": "✅ API key validated successfully.",
|
|
76
|
+
}
|
|
77
|
+
}
|
|
18
78
|
|
|
19
79
|
def _t(text):
|
|
20
80
|
if CURRENT_LANG == 'es' or not text:
|
|
21
81
|
return text
|
|
82
|
+
if CURRENT_LANG in TRANSLATIONS and text in TRANSLATIONS[CURRENT_LANG]:
|
|
83
|
+
return TRANSLATIONS[CURRENT_LANG][text]
|
|
22
84
|
try:
|
|
23
85
|
# Limpiar texto de colores si es necesario, o traducir directo
|
|
24
86
|
return GoogleTranslator(source='es', target=CURRENT_LANG).translate(text)
|
|
@@ -30,6 +92,12 @@ from datetime import datetime
|
|
|
30
92
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
31
93
|
|
|
32
94
|
from conny_tui_select import select_menu as _orig_select_menu, confirm as _orig_confirm, text_input as _orig_text_input
|
|
95
|
+
try:
|
|
96
|
+
from conny_runtime_ops import mirror_instance_env_to_base, set_active_instance, start_localhost_run_tunnel
|
|
97
|
+
except Exception:
|
|
98
|
+
mirror_instance_env_to_base = None
|
|
99
|
+
set_active_instance = None
|
|
100
|
+
start_localhost_run_tunnel = None
|
|
33
101
|
|
|
34
102
|
def select_menu(options, title="", **kwargs):
|
|
35
103
|
if title: title = _t(title)
|
|
@@ -61,7 +129,8 @@ C_ACCENT = "\033[38;5;159m" # Cyan
|
|
|
61
129
|
B1 = "\033[1m"
|
|
62
130
|
R = "\033[0m"
|
|
63
131
|
|
|
64
|
-
|
|
132
|
+
CONNY_HOME = Path(os.environ.get("CONNY_HOME", str(Path.home() / ".conny")))
|
|
133
|
+
INSTANCES_DIR = Path(os.environ.get("INSTANCES_DIR", str(CONNY_HOME / "instances")))
|
|
65
134
|
CONNY_DIR = Path(os.environ.get("CONNY_DIR", os.path.dirname(os.path.abspath(__file__))))
|
|
66
135
|
|
|
67
136
|
SECTORS = [
|
|
@@ -93,8 +162,8 @@ TONES = [
|
|
|
93
162
|
]
|
|
94
163
|
|
|
95
164
|
LANGUAGES = [
|
|
96
|
-
("es", "Español"),
|
|
97
165
|
("en", "English"),
|
|
166
|
+
("es", "Español"),
|
|
98
167
|
("pt", "Português"),
|
|
99
168
|
]
|
|
100
169
|
|
|
@@ -138,13 +207,124 @@ def check_gpu():
|
|
|
138
207
|
|
|
139
208
|
def validate_api_key(provider, key):
|
|
140
209
|
# Dummy validation with spinner
|
|
141
|
-
sys.stdout.write(f" {C_MUTED}Validando API key...{R}")
|
|
210
|
+
sys.stdout.write(f" {C_MUTED}{_t('Validando API key...')}{R}")
|
|
142
211
|
sys.stdout.flush()
|
|
143
212
|
time.sleep(1.5)
|
|
144
|
-
sys.stdout.write(f"\r {C_SUCCESS}✅ API key validada correctamente.{R} \n")
|
|
213
|
+
sys.stdout.write(f"\r {C_SUCCESS}{_t('✅ API key validada correctamente.')}{R} \n")
|
|
145
214
|
return True
|
|
146
215
|
|
|
147
216
|
|
|
217
|
+
def _persist_language(lang: str) -> None:
|
|
218
|
+
try:
|
|
219
|
+
workspace_config_path = Path(os.environ.get("CONNY_WORKSPACE_CONFIG", str(CONNY_HOME / "config.json")))
|
|
220
|
+
workspace_config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
221
|
+
payload = {}
|
|
222
|
+
if workspace_config_path.exists():
|
|
223
|
+
try:
|
|
224
|
+
payload = json.loads(workspace_config_path.read_text(encoding="utf-8"))
|
|
225
|
+
if not isinstance(payload, dict):
|
|
226
|
+
payload = {}
|
|
227
|
+
except Exception:
|
|
228
|
+
payload = {}
|
|
229
|
+
payload["language"] = lang
|
|
230
|
+
payload["ui_language"] = lang
|
|
231
|
+
workspace_config_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _next_available_port() -> int:
|
|
237
|
+
existing_ports = []
|
|
238
|
+
if INSTANCES_DIR.exists():
|
|
239
|
+
for d in INSTANCES_DIR.iterdir():
|
|
240
|
+
env = d / ".env"
|
|
241
|
+
if not env.exists():
|
|
242
|
+
continue
|
|
243
|
+
for line in env.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
244
|
+
if line.startswith("PORT="):
|
|
245
|
+
try:
|
|
246
|
+
existing_ports.append(int(line.split("=", 1)[1].strip()))
|
|
247
|
+
except Exception:
|
|
248
|
+
pass
|
|
249
|
+
return max(existing_ports + [8003]) + 1
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _configure_gateway(port: int) -> dict:
|
|
253
|
+
options = [
|
|
254
|
+
"Generar túnel automático (localhost.run)",
|
|
255
|
+
"Configurar enlace manualmente (ingresar URL personalizada)",
|
|
256
|
+
]
|
|
257
|
+
choice = select_menu(options, title="¿Cómo deseas configurar el enlace público (BASE_URL)?")
|
|
258
|
+
if choice == 0 and start_localhost_run_tunnel:
|
|
259
|
+
print(f"\n {C_MUTED}{_t('Levantando túnel seguro hacia localhost.run...')}{R}")
|
|
260
|
+
result = start_localhost_run_tunnel(port)
|
|
261
|
+
if result.get("ok") and result.get("url"):
|
|
262
|
+
print(f" {C_SUCCESS}✓ {_t('Túnel activo:')} {result['url']}{R}")
|
|
263
|
+
return {
|
|
264
|
+
"mode": "localhost.run",
|
|
265
|
+
"base_url": str(result["url"]).rstrip("/"),
|
|
266
|
+
"tunnel_pid": str(result.get("pid") or ""),
|
|
267
|
+
"tunnel_command": str(result.get("command") or ""),
|
|
268
|
+
}
|
|
269
|
+
print(f" {C_WARNING}⚠ {_t('No pude obtener un túnel automático. Ingresa una URL manual.')}{R}")
|
|
270
|
+
|
|
271
|
+
base_url = text_input(
|
|
272
|
+
"Introduce la URL pública de tu webhook",
|
|
273
|
+
default=os.environ.get("BASE_URL", ""),
|
|
274
|
+
required=False,
|
|
275
|
+
).strip()
|
|
276
|
+
return {
|
|
277
|
+
"mode": "manual",
|
|
278
|
+
"base_url": base_url.rstrip("/"),
|
|
279
|
+
"tunnel_pid": "",
|
|
280
|
+
"tunnel_command": "",
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _local_ip() -> str:
|
|
285
|
+
try:
|
|
286
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
|
287
|
+
sock.connect(("8.8.8.8", 80))
|
|
288
|
+
return sock.getsockname()[0]
|
|
289
|
+
except Exception:
|
|
290
|
+
return "127.0.0.1"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _configure_dashboard(port: int) -> dict:
|
|
294
|
+
options = [
|
|
295
|
+
"Solo local (localhost)",
|
|
296
|
+
"Red/IP externa de este dispositivo",
|
|
297
|
+
"URL pública personalizada",
|
|
298
|
+
"No configurar dashboard ahora",
|
|
299
|
+
]
|
|
300
|
+
choice = select_menu(options, title="¿Cómo quieres exponer la página web de Conny?")
|
|
301
|
+
if choice == 0:
|
|
302
|
+
return {
|
|
303
|
+
"host": "127.0.0.1",
|
|
304
|
+
"dashboard_url": f"http://localhost:{port}/dashboard",
|
|
305
|
+
"public_dashboard_url": "",
|
|
306
|
+
}
|
|
307
|
+
if choice == 1:
|
|
308
|
+
ip = _local_ip()
|
|
309
|
+
return {
|
|
310
|
+
"host": "0.0.0.0",
|
|
311
|
+
"dashboard_url": f"http://{ip}:{port}/dashboard",
|
|
312
|
+
"public_dashboard_url": f"http://{ip}:{port}/dashboard",
|
|
313
|
+
}
|
|
314
|
+
if choice == 2:
|
|
315
|
+
url = text_input("Introduce la URL pública del dashboard", default="", required=False).strip().rstrip("/")
|
|
316
|
+
return {
|
|
317
|
+
"host": "0.0.0.0",
|
|
318
|
+
"dashboard_url": url,
|
|
319
|
+
"public_dashboard_url": url,
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
"host": "127.0.0.1",
|
|
323
|
+
"dashboard_url": "",
|
|
324
|
+
"public_dashboard_url": "",
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
148
328
|
def run_wizard():
|
|
149
329
|
|
|
150
330
|
clear()
|
|
@@ -198,27 +378,27 @@ def run_wizard():
|
|
|
198
378
|
print(f" {line}")
|
|
199
379
|
|
|
200
380
|
print()
|
|
201
|
-
print(f" {C_PRIMARY}{B1}Conny CLI {VERSION}{R} · {C_ACCENT}Autonomous Dynamic Receptionist{R} · {C_MUTED}⏱ Tiempo estimado: ~3 min{R}")
|
|
381
|
+
print(f" {C_PRIMARY}{B1}Conny CLI {VERSION}{R} · {C_ACCENT}Autonomous Dynamic Receptionist{R} · {C_MUTED}⏱ {_t('Tiempo estimado: ~3 min')}{R}")
|
|
202
382
|
print(f" {C_MUTED}{'─' * 70}{R}")
|
|
203
383
|
print()
|
|
204
384
|
|
|
205
385
|
|
|
206
386
|
|
|
207
387
|
if not confirm("¿Comenzar configuración?"):
|
|
208
|
-
print(f"\n {C_MUTED}Operación cancelada.{R}\n")
|
|
388
|
+
print(f"\n {C_MUTED}{_t('Operación cancelada.')}{R}\n")
|
|
209
389
|
return
|
|
210
390
|
|
|
211
|
-
TOTAL_STEPS =
|
|
391
|
+
TOTAL_STEPS = 9
|
|
212
392
|
ctx = ""
|
|
213
393
|
|
|
214
394
|
# 0. Language (Optional pre-step)
|
|
215
395
|
clear()
|
|
216
396
|
print(f"\n {C_PRIMARY}🌐 {_t('Selección de idioma')}{R}")
|
|
217
397
|
i = select_menu([l[1] for l in LANGUAGES], title="Idioma preferido para Conny")
|
|
218
|
-
i = select_menu([l[1] for l in LANGUAGES], title="Idioma preferido para Conny")
|
|
219
398
|
lang = LANGUAGES[i][0]
|
|
220
399
|
global CURRENT_LANG
|
|
221
400
|
CURRENT_LANG = lang
|
|
401
|
+
_persist_language(lang)
|
|
222
402
|
|
|
223
403
|
# 1. Identity
|
|
224
404
|
clear()
|
|
@@ -241,9 +421,20 @@ def run_wizard():
|
|
|
241
421
|
i = select_menu([c[1] for c in CHANNELS], title="Canal Principal de Acceso")
|
|
242
422
|
channel = CHANNELS[i][0]
|
|
243
423
|
|
|
244
|
-
# 4.
|
|
424
|
+
# 4. Gateway / Webhook
|
|
425
|
+
port = _next_available_port()
|
|
245
426
|
clear()
|
|
246
|
-
step_header(4, TOTAL_STEPS, "
|
|
427
|
+
step_header(4, TOTAL_STEPS, "Gateway público", f"{ctx}Canal: {channel} | Puerto: {port}")
|
|
428
|
+
gateway = _configure_gateway(port)
|
|
429
|
+
|
|
430
|
+
# 5. Web dashboard
|
|
431
|
+
clear()
|
|
432
|
+
step_header(5, TOTAL_STEPS, "Dashboard web", f"{ctx}Puerto: {port}")
|
|
433
|
+
dashboard = _configure_dashboard(port)
|
|
434
|
+
|
|
435
|
+
# 6. Intelligence
|
|
436
|
+
clear()
|
|
437
|
+
step_header(6, TOTAL_STEPS, "Motor de Inteligencia", ctx)
|
|
247
438
|
|
|
248
439
|
# Level 1
|
|
249
440
|
llm_types = [
|
|
@@ -322,15 +513,15 @@ def run_wizard():
|
|
|
322
513
|
provider_id = "manual"
|
|
323
514
|
model_id = text_input("Model ID")
|
|
324
515
|
|
|
325
|
-
#
|
|
516
|
+
# 7. Personality
|
|
326
517
|
clear()
|
|
327
|
-
step_header(
|
|
518
|
+
step_header(7, TOTAL_STEPS, "Humanización y Tono", ctx)
|
|
328
519
|
i = select_menu([t[1] for t in TONES], title="Perfil de Voz")
|
|
329
520
|
tone = TONES[i][0]
|
|
330
521
|
|
|
331
|
-
#
|
|
522
|
+
# 8. Credentials
|
|
332
523
|
clear()
|
|
333
|
-
step_header(
|
|
524
|
+
step_header(8, TOTAL_STEPS, "Infraestructura y Secretos", ctx)
|
|
334
525
|
|
|
335
526
|
secrets_map = {}
|
|
336
527
|
|
|
@@ -371,9 +562,9 @@ def run_wizard():
|
|
|
371
562
|
secrets_map["WA_CLOUD_TOKEN"] = text_input("WA Cloud API Token", is_password=True)
|
|
372
563
|
secrets_map["WA_PHONE_NUMBER_ID"] = text_input("Phone Number ID")
|
|
373
564
|
|
|
374
|
-
#
|
|
565
|
+
# 9. Confirmation
|
|
375
566
|
clear()
|
|
376
|
-
step_header(
|
|
567
|
+
step_header(9, TOTAL_STEPS, "Verificación Final")
|
|
377
568
|
|
|
378
569
|
print(f" ┌────────────────────────────────────────────────────────┐")
|
|
379
570
|
print(f" │ {C_PRIMARY}{B1}Resumen de Configuración{R} │")
|
|
@@ -385,56 +576,58 @@ def run_wizard():
|
|
|
385
576
|
print(f" │ {C_MUTED}Modelo:{R} {model_id.ljust(44)}│")
|
|
386
577
|
print(f" │ {C_MUTED}Voz:{R} {tone.ljust(44)}│")
|
|
387
578
|
print(f" │ {C_MUTED}Secretos:{R} {str(len(secrets_map)).ljust(44)}│")
|
|
579
|
+
print(f" │ {C_MUTED}BASE_URL:{R} {(gateway.get('base_url') or 'pending').ljust(44)[:44]}│")
|
|
580
|
+
print(f" │ {C_MUTED}Dashboard:{R} {(dashboard.get('dashboard_url') or 'local only').ljust(44)[:44]}│")
|
|
388
581
|
print(f" └────────────────────────────────────────────────────────┘\n")
|
|
389
582
|
|
|
390
583
|
if not confirm("¿Procesar e Implementar Infraestructura?"):
|
|
391
584
|
print(f"\n {C_MUTED}Cancelado.{R}\n")
|
|
392
585
|
return
|
|
393
586
|
|
|
394
|
-
print(f"\n {C_PRIMARY}Creando recursos...{R}")
|
|
395
|
-
_create(name, instance_id, sector, channel, provider_id, model_id, tone, secrets_map, lang)
|
|
587
|
+
print(f"\n {C_PRIMARY}{_t('Creando recursos...')}{R}")
|
|
588
|
+
_create(name, instance_id, sector, channel, provider_id, model_id, tone, secrets_map, lang, port, gateway, dashboard)
|
|
396
589
|
|
|
397
590
|
print(f"""
|
|
398
|
-
{C_SUCCESS}{B1}🚀 Infraestructura Desplegada Exitosamente{R}
|
|
591
|
+
{C_SUCCESS}{B1}🚀 {_t('Infraestructura Desplegada Exitosamente')}{R}
|
|
399
592
|
|
|
400
|
-
{C_MUTED}Directorio:{R} {INSTANCES_DIR / instance_id}
|
|
401
|
-
{C_MUTED}Control:{R} {C_PRIMARY}conny status {instance_id}{R}
|
|
402
|
-
{C_MUTED}Check:{R} {C_PRIMARY}conny doctor{R}
|
|
403
|
-
{C_MUTED}Lanzar:{R} {C_PRIMARY}pm2 start {INSTANCES_DIR / instance_id}/run.sh --name conny-{instance_id}{R}
|
|
593
|
+
{C_MUTED}{_t('Directorio:')}{R} {INSTANCES_DIR / instance_id}
|
|
594
|
+
{C_MUTED}{_t('Control:')}{R} {C_PRIMARY}conny status {instance_id}{R}
|
|
595
|
+
{C_MUTED}{_t('Check:')}{R} {C_PRIMARY}conny doctor{R}
|
|
596
|
+
{C_MUTED}{_t('Lanzar:')}{R} {C_PRIMARY}pm2 start {INSTANCES_DIR / instance_id}/run.sh --name conny-{instance_id}{R}
|
|
404
597
|
""")
|
|
405
598
|
|
|
406
599
|
# Post-onboarding commands
|
|
407
600
|
if is_local and provider_id == "ollama":
|
|
408
601
|
print(f" {C_WARNING}💡 Asegúrate de descargar el modelo: `ollama run {model_id}`{R}\n")
|
|
409
602
|
|
|
410
|
-
def _create(name, iid, sector, channel, provider, model_id, tone, secrets_map, lang):
|
|
603
|
+
def _create(name, iid, sector, channel, provider, model_id, tone, secrets_map, lang, port, gateway, dashboard):
|
|
411
604
|
idir = INSTANCES_DIR / iid
|
|
412
605
|
idir.mkdir(parents=True, exist_ok=True)
|
|
413
606
|
|
|
414
|
-
existing_ports = []
|
|
415
|
-
if INSTANCES_DIR.exists():
|
|
416
|
-
for d in INSTANCES_DIR.iterdir():
|
|
417
|
-
env = d / ".env"
|
|
418
|
-
if env.exists():
|
|
419
|
-
for line in env.read_text().splitlines():
|
|
420
|
-
if line.startswith("PORT="):
|
|
421
|
-
try:
|
|
422
|
-
parts = line.split("=")
|
|
423
|
-
if len(parts) > 1:
|
|
424
|
-
existing_ports.append(int(parts[1]))
|
|
425
|
-
except: pass
|
|
426
|
-
|
|
427
|
-
port = max(existing_ports + [8003]) + 1
|
|
428
607
|
webhook_secret = f"conny_{iid}_{secrets.token_hex(6)}"
|
|
608
|
+
base_url = str((gateway or {}).get("base_url") or "").rstrip("/")
|
|
609
|
+
tunnel_command = str((gateway or {}).get("tunnel_command") or "").replace('"', '\\"')
|
|
610
|
+
dashboard = dashboard or {}
|
|
611
|
+
host = str(dashboard.get("host") or "127.0.0.1").strip()
|
|
612
|
+
dashboard_url = str(dashboard.get("dashboard_url") or "").rstrip("/")
|
|
613
|
+
public_dashboard_url = str(dashboard.get("public_dashboard_url") or "").rstrip("/")
|
|
429
614
|
|
|
430
615
|
env_lines = [
|
|
431
616
|
f"INSTANCE_ID={iid}",
|
|
432
617
|
f"PORT={port}",
|
|
618
|
+
f"HOST={host}",
|
|
619
|
+
f"BASE_URL={base_url}",
|
|
620
|
+
f"PUBLIC_BASE_URL={base_url}",
|
|
621
|
+
f"DASHBOARD_URL={dashboard_url}",
|
|
622
|
+
f"PUBLIC_DASHBOARD_URL={public_dashboard_url}",
|
|
433
623
|
"DEMO_MODE=false",
|
|
434
624
|
f"PLATFORM={channel}",
|
|
435
625
|
f"SECTOR={sector}",
|
|
436
626
|
f"BUSINESS_NAME=\"{name}\"",
|
|
437
627
|
f"WEBHOOK_SECRET={webhook_secret}",
|
|
628
|
+
f"TUNNEL_PROVIDER={(gateway or {}).get('mode', '')}",
|
|
629
|
+
f"TUNNEL_PID={(gateway or {}).get('tunnel_pid', '')}",
|
|
630
|
+
f"TUNNEL_COMMAND=\"{tunnel_command}\"",
|
|
438
631
|
f"LLM_PROVIDER={provider}",
|
|
439
632
|
f"LLM_MODEL={model_id}",
|
|
440
633
|
"DEBUG=false"
|
|
@@ -473,7 +666,10 @@ llm:
|
|
|
473
666
|
|
|
474
667
|
for f in core:
|
|
475
668
|
src = CONNY_DIR / f
|
|
476
|
-
if src.exists():
|
|
669
|
+
if src.exists():
|
|
670
|
+
dst = idir / f
|
|
671
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
672
|
+
shutil.copy2(src, dst)
|
|
477
673
|
|
|
478
674
|
for d in ["soul", "teachings", "memory_store", "knowledge_gaps", "integrations/vault", "logs"]:
|
|
479
675
|
(idir / d).mkdir(parents=True, exist_ok=True)
|
|
@@ -486,10 +682,27 @@ llm:
|
|
|
486
682
|
"status": "configured",
|
|
487
683
|
"timestamp": datetime.now().isoformat(),
|
|
488
684
|
"instance": iid,
|
|
489
|
-
"provider": provider
|
|
685
|
+
"provider": provider,
|
|
686
|
+
"port": port,
|
|
687
|
+
"base_url": base_url,
|
|
688
|
+
"dashboard": {
|
|
689
|
+
"host": host,
|
|
690
|
+
"url": dashboard_url,
|
|
691
|
+
"public_url": public_dashboard_url,
|
|
692
|
+
},
|
|
693
|
+
"tunnel": {
|
|
694
|
+
"provider": (gateway or {}).get("mode", ""),
|
|
695
|
+
"pid": (gateway or {}).get("tunnel_pid", ""),
|
|
696
|
+
"command": (gateway or {}).get("tunnel_command", ""),
|
|
697
|
+
},
|
|
490
698
|
}
|
|
491
699
|
(idir / "conny.state.json").write_text(json.dumps(state, indent=2))
|
|
492
700
|
|
|
701
|
+
if set_active_instance:
|
|
702
|
+
set_active_instance(iid)
|
|
703
|
+
if mirror_instance_env_to_base:
|
|
704
|
+
mirror_instance_env_to_base(iid)
|
|
705
|
+
|
|
493
706
|
def _slug(name):
|
|
494
707
|
s = name.lower().strip()
|
|
495
708
|
for a, b in [("á","a"),("é","e"),("í","i"),("ó","o"),("ú","u"),("ñ","n")]:
|