@innvisor/conny-ai 9.7.0 → 9.8.0
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 +54 -0
- package/README.md +17 -1
- package/conny_app.py +8 -2
- 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_init.py +234 -41
- package/conny_runtime_ops.py +198 -6
- 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 +73 -17
- package/package.json +13 -3
- 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,105 @@ 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 _next_available_port() -> int:
|
|
218
|
+
existing_ports = []
|
|
219
|
+
if INSTANCES_DIR.exists():
|
|
220
|
+
for d in INSTANCES_DIR.iterdir():
|
|
221
|
+
env = d / ".env"
|
|
222
|
+
if not env.exists():
|
|
223
|
+
continue
|
|
224
|
+
for line in env.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
225
|
+
if line.startswith("PORT="):
|
|
226
|
+
try:
|
|
227
|
+
existing_ports.append(int(line.split("=", 1)[1].strip()))
|
|
228
|
+
except Exception:
|
|
229
|
+
pass
|
|
230
|
+
return max(existing_ports + [8003]) + 1
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _configure_gateway(port: int) -> dict:
|
|
234
|
+
options = [
|
|
235
|
+
"Generar túnel automático (localhost.run)",
|
|
236
|
+
"Configurar enlace manualmente (ingresar URL personalizada)",
|
|
237
|
+
]
|
|
238
|
+
choice = select_menu(options, title="¿Cómo deseas configurar el enlace público (BASE_URL)?")
|
|
239
|
+
if choice == 0 and start_localhost_run_tunnel:
|
|
240
|
+
print(f"\n {C_MUTED}{_t('Levantando túnel seguro hacia localhost.run...')}{R}")
|
|
241
|
+
result = start_localhost_run_tunnel(port)
|
|
242
|
+
if result.get("ok") and result.get("url"):
|
|
243
|
+
print(f" {C_SUCCESS}✓ {_t('Túnel activo:')} {result['url']}{R}")
|
|
244
|
+
return {
|
|
245
|
+
"mode": "localhost.run",
|
|
246
|
+
"base_url": str(result["url"]).rstrip("/"),
|
|
247
|
+
"tunnel_pid": str(result.get("pid") or ""),
|
|
248
|
+
"tunnel_command": str(result.get("command") or ""),
|
|
249
|
+
}
|
|
250
|
+
print(f" {C_WARNING}⚠ {_t('No pude obtener un túnel automático. Ingresa una URL manual.')}{R}")
|
|
251
|
+
|
|
252
|
+
base_url = text_input(
|
|
253
|
+
"Introduce la URL pública de tu webhook",
|
|
254
|
+
default=os.environ.get("BASE_URL", ""),
|
|
255
|
+
required=False,
|
|
256
|
+
).strip()
|
|
257
|
+
return {
|
|
258
|
+
"mode": "manual",
|
|
259
|
+
"base_url": base_url.rstrip("/"),
|
|
260
|
+
"tunnel_pid": "",
|
|
261
|
+
"tunnel_command": "",
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _local_ip() -> str:
|
|
266
|
+
try:
|
|
267
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
|
268
|
+
sock.connect(("8.8.8.8", 80))
|
|
269
|
+
return sock.getsockname()[0]
|
|
270
|
+
except Exception:
|
|
271
|
+
return "127.0.0.1"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _configure_dashboard(port: int) -> dict:
|
|
275
|
+
options = [
|
|
276
|
+
"Solo local (localhost)",
|
|
277
|
+
"Red/IP externa de este dispositivo",
|
|
278
|
+
"URL pública personalizada",
|
|
279
|
+
"No configurar dashboard ahora",
|
|
280
|
+
]
|
|
281
|
+
choice = select_menu(options, title="¿Cómo quieres exponer la página web de Conny?")
|
|
282
|
+
if choice == 0:
|
|
283
|
+
return {
|
|
284
|
+
"host": "127.0.0.1",
|
|
285
|
+
"dashboard_url": f"http://localhost:{port}/dashboard",
|
|
286
|
+
"public_dashboard_url": "",
|
|
287
|
+
}
|
|
288
|
+
if choice == 1:
|
|
289
|
+
ip = _local_ip()
|
|
290
|
+
return {
|
|
291
|
+
"host": "0.0.0.0",
|
|
292
|
+
"dashboard_url": f"http://{ip}:{port}/dashboard",
|
|
293
|
+
"public_dashboard_url": f"http://{ip}:{port}/dashboard",
|
|
294
|
+
}
|
|
295
|
+
if choice == 2:
|
|
296
|
+
url = text_input("Introduce la URL pública del dashboard", default="", required=False).strip().rstrip("/")
|
|
297
|
+
return {
|
|
298
|
+
"host": "0.0.0.0",
|
|
299
|
+
"dashboard_url": url,
|
|
300
|
+
"public_dashboard_url": url,
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
"host": "127.0.0.1",
|
|
304
|
+
"dashboard_url": "",
|
|
305
|
+
"public_dashboard_url": "",
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
148
309
|
def run_wizard():
|
|
149
310
|
|
|
150
311
|
clear()
|
|
@@ -198,24 +359,23 @@ def run_wizard():
|
|
|
198
359
|
print(f" {line}")
|
|
199
360
|
|
|
200
361
|
print()
|
|
201
|
-
print(f" {C_PRIMARY}{B1}Conny CLI {VERSION}{R} · {C_ACCENT}Autonomous Dynamic Receptionist{R} · {C_MUTED}⏱ Tiempo estimado: ~3 min{R}")
|
|
362
|
+
print(f" {C_PRIMARY}{B1}Conny CLI {VERSION}{R} · {C_ACCENT}Autonomous Dynamic Receptionist{R} · {C_MUTED}⏱ {_t('Tiempo estimado: ~3 min')}{R}")
|
|
202
363
|
print(f" {C_MUTED}{'─' * 70}{R}")
|
|
203
364
|
print()
|
|
204
365
|
|
|
205
366
|
|
|
206
367
|
|
|
207
368
|
if not confirm("¿Comenzar configuración?"):
|
|
208
|
-
print(f"\n {C_MUTED}Operación cancelada.{R}\n")
|
|
369
|
+
print(f"\n {C_MUTED}{_t('Operación cancelada.')}{R}\n")
|
|
209
370
|
return
|
|
210
371
|
|
|
211
|
-
TOTAL_STEPS =
|
|
372
|
+
TOTAL_STEPS = 9
|
|
212
373
|
ctx = ""
|
|
213
374
|
|
|
214
375
|
# 0. Language (Optional pre-step)
|
|
215
376
|
clear()
|
|
216
377
|
print(f"\n {C_PRIMARY}🌐 {_t('Selección de idioma')}{R}")
|
|
217
378
|
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
379
|
lang = LANGUAGES[i][0]
|
|
220
380
|
global CURRENT_LANG
|
|
221
381
|
CURRENT_LANG = lang
|
|
@@ -241,9 +401,20 @@ def run_wizard():
|
|
|
241
401
|
i = select_menu([c[1] for c in CHANNELS], title="Canal Principal de Acceso")
|
|
242
402
|
channel = CHANNELS[i][0]
|
|
243
403
|
|
|
244
|
-
# 4.
|
|
404
|
+
# 4. Gateway / Webhook
|
|
405
|
+
port = _next_available_port()
|
|
406
|
+
clear()
|
|
407
|
+
step_header(4, TOTAL_STEPS, "Gateway público", f"{ctx}Canal: {channel} | Puerto: {port}")
|
|
408
|
+
gateway = _configure_gateway(port)
|
|
409
|
+
|
|
410
|
+
# 5. Web dashboard
|
|
245
411
|
clear()
|
|
246
|
-
step_header(
|
|
412
|
+
step_header(5, TOTAL_STEPS, "Dashboard web", f"{ctx}Puerto: {port}")
|
|
413
|
+
dashboard = _configure_dashboard(port)
|
|
414
|
+
|
|
415
|
+
# 6. Intelligence
|
|
416
|
+
clear()
|
|
417
|
+
step_header(6, TOTAL_STEPS, "Motor de Inteligencia", ctx)
|
|
247
418
|
|
|
248
419
|
# Level 1
|
|
249
420
|
llm_types = [
|
|
@@ -322,15 +493,15 @@ def run_wizard():
|
|
|
322
493
|
provider_id = "manual"
|
|
323
494
|
model_id = text_input("Model ID")
|
|
324
495
|
|
|
325
|
-
#
|
|
496
|
+
# 7. Personality
|
|
326
497
|
clear()
|
|
327
|
-
step_header(
|
|
498
|
+
step_header(7, TOTAL_STEPS, "Humanización y Tono", ctx)
|
|
328
499
|
i = select_menu([t[1] for t in TONES], title="Perfil de Voz")
|
|
329
500
|
tone = TONES[i][0]
|
|
330
501
|
|
|
331
|
-
#
|
|
502
|
+
# 8. Credentials
|
|
332
503
|
clear()
|
|
333
|
-
step_header(
|
|
504
|
+
step_header(8, TOTAL_STEPS, "Infraestructura y Secretos", ctx)
|
|
334
505
|
|
|
335
506
|
secrets_map = {}
|
|
336
507
|
|
|
@@ -371,9 +542,9 @@ def run_wizard():
|
|
|
371
542
|
secrets_map["WA_CLOUD_TOKEN"] = text_input("WA Cloud API Token", is_password=True)
|
|
372
543
|
secrets_map["WA_PHONE_NUMBER_ID"] = text_input("Phone Number ID")
|
|
373
544
|
|
|
374
|
-
#
|
|
545
|
+
# 9. Confirmation
|
|
375
546
|
clear()
|
|
376
|
-
step_header(
|
|
547
|
+
step_header(9, TOTAL_STEPS, "Verificación Final")
|
|
377
548
|
|
|
378
549
|
print(f" ┌────────────────────────────────────────────────────────┐")
|
|
379
550
|
print(f" │ {C_PRIMARY}{B1}Resumen de Configuración{R} │")
|
|
@@ -385,56 +556,58 @@ def run_wizard():
|
|
|
385
556
|
print(f" │ {C_MUTED}Modelo:{R} {model_id.ljust(44)}│")
|
|
386
557
|
print(f" │ {C_MUTED}Voz:{R} {tone.ljust(44)}│")
|
|
387
558
|
print(f" │ {C_MUTED}Secretos:{R} {str(len(secrets_map)).ljust(44)}│")
|
|
559
|
+
print(f" │ {C_MUTED}BASE_URL:{R} {(gateway.get('base_url') or 'pending').ljust(44)[:44]}│")
|
|
560
|
+
print(f" │ {C_MUTED}Dashboard:{R} {(dashboard.get('dashboard_url') or 'local only').ljust(44)[:44]}│")
|
|
388
561
|
print(f" └────────────────────────────────────────────────────────┘\n")
|
|
389
562
|
|
|
390
563
|
if not confirm("¿Procesar e Implementar Infraestructura?"):
|
|
391
564
|
print(f"\n {C_MUTED}Cancelado.{R}\n")
|
|
392
565
|
return
|
|
393
566
|
|
|
394
|
-
print(f"\n {C_PRIMARY}Creando recursos...{R}")
|
|
395
|
-
_create(name, instance_id, sector, channel, provider_id, model_id, tone, secrets_map, lang)
|
|
567
|
+
print(f"\n {C_PRIMARY}{_t('Creando recursos...')}{R}")
|
|
568
|
+
_create(name, instance_id, sector, channel, provider_id, model_id, tone, secrets_map, lang, port, gateway, dashboard)
|
|
396
569
|
|
|
397
570
|
print(f"""
|
|
398
|
-
{C_SUCCESS}{B1}🚀 Infraestructura Desplegada Exitosamente{R}
|
|
571
|
+
{C_SUCCESS}{B1}🚀 {_t('Infraestructura Desplegada Exitosamente')}{R}
|
|
399
572
|
|
|
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}
|
|
573
|
+
{C_MUTED}{_t('Directorio:')}{R} {INSTANCES_DIR / instance_id}
|
|
574
|
+
{C_MUTED}{_t('Control:')}{R} {C_PRIMARY}conny status {instance_id}{R}
|
|
575
|
+
{C_MUTED}{_t('Check:')}{R} {C_PRIMARY}conny doctor{R}
|
|
576
|
+
{C_MUTED}{_t('Lanzar:')}{R} {C_PRIMARY}pm2 start {INSTANCES_DIR / instance_id}/run.sh --name conny-{instance_id}{R}
|
|
404
577
|
""")
|
|
405
578
|
|
|
406
579
|
# Post-onboarding commands
|
|
407
580
|
if is_local and provider_id == "ollama":
|
|
408
581
|
print(f" {C_WARNING}💡 Asegúrate de descargar el modelo: `ollama run {model_id}`{R}\n")
|
|
409
582
|
|
|
410
|
-
def _create(name, iid, sector, channel, provider, model_id, tone, secrets_map, lang):
|
|
583
|
+
def _create(name, iid, sector, channel, provider, model_id, tone, secrets_map, lang, port, gateway, dashboard):
|
|
411
584
|
idir = INSTANCES_DIR / iid
|
|
412
585
|
idir.mkdir(parents=True, exist_ok=True)
|
|
413
586
|
|
|
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
587
|
webhook_secret = f"conny_{iid}_{secrets.token_hex(6)}"
|
|
588
|
+
base_url = str((gateway or {}).get("base_url") or "").rstrip("/")
|
|
589
|
+
tunnel_command = str((gateway or {}).get("tunnel_command") or "").replace('"', '\\"')
|
|
590
|
+
dashboard = dashboard or {}
|
|
591
|
+
host = str(dashboard.get("host") or "127.0.0.1").strip()
|
|
592
|
+
dashboard_url = str(dashboard.get("dashboard_url") or "").rstrip("/")
|
|
593
|
+
public_dashboard_url = str(dashboard.get("public_dashboard_url") or "").rstrip("/")
|
|
429
594
|
|
|
430
595
|
env_lines = [
|
|
431
596
|
f"INSTANCE_ID={iid}",
|
|
432
597
|
f"PORT={port}",
|
|
598
|
+
f"HOST={host}",
|
|
599
|
+
f"BASE_URL={base_url}",
|
|
600
|
+
f"PUBLIC_BASE_URL={base_url}",
|
|
601
|
+
f"DASHBOARD_URL={dashboard_url}",
|
|
602
|
+
f"PUBLIC_DASHBOARD_URL={public_dashboard_url}",
|
|
433
603
|
"DEMO_MODE=false",
|
|
434
604
|
f"PLATFORM={channel}",
|
|
435
605
|
f"SECTOR={sector}",
|
|
436
606
|
f"BUSINESS_NAME=\"{name}\"",
|
|
437
607
|
f"WEBHOOK_SECRET={webhook_secret}",
|
|
608
|
+
f"TUNNEL_PROVIDER={(gateway or {}).get('mode', '')}",
|
|
609
|
+
f"TUNNEL_PID={(gateway or {}).get('tunnel_pid', '')}",
|
|
610
|
+
f"TUNNEL_COMMAND=\"{tunnel_command}\"",
|
|
438
611
|
f"LLM_PROVIDER={provider}",
|
|
439
612
|
f"LLM_MODEL={model_id}",
|
|
440
613
|
"DEBUG=false"
|
|
@@ -473,7 +646,10 @@ llm:
|
|
|
473
646
|
|
|
474
647
|
for f in core:
|
|
475
648
|
src = CONNY_DIR / f
|
|
476
|
-
if src.exists():
|
|
649
|
+
if src.exists():
|
|
650
|
+
dst = idir / f
|
|
651
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
652
|
+
shutil.copy2(src, dst)
|
|
477
653
|
|
|
478
654
|
for d in ["soul", "teachings", "memory_store", "knowledge_gaps", "integrations/vault", "logs"]:
|
|
479
655
|
(idir / d).mkdir(parents=True, exist_ok=True)
|
|
@@ -486,10 +662,27 @@ llm:
|
|
|
486
662
|
"status": "configured",
|
|
487
663
|
"timestamp": datetime.now().isoformat(),
|
|
488
664
|
"instance": iid,
|
|
489
|
-
"provider": provider
|
|
665
|
+
"provider": provider,
|
|
666
|
+
"port": port,
|
|
667
|
+
"base_url": base_url,
|
|
668
|
+
"dashboard": {
|
|
669
|
+
"host": host,
|
|
670
|
+
"url": dashboard_url,
|
|
671
|
+
"public_url": public_dashboard_url,
|
|
672
|
+
},
|
|
673
|
+
"tunnel": {
|
|
674
|
+
"provider": (gateway or {}).get("mode", ""),
|
|
675
|
+
"pid": (gateway or {}).get("tunnel_pid", ""),
|
|
676
|
+
"command": (gateway or {}).get("tunnel_command", ""),
|
|
677
|
+
},
|
|
490
678
|
}
|
|
491
679
|
(idir / "conny.state.json").write_text(json.dumps(state, indent=2))
|
|
492
680
|
|
|
681
|
+
if set_active_instance:
|
|
682
|
+
set_active_instance(iid)
|
|
683
|
+
if mirror_instance_env_to_base:
|
|
684
|
+
mirror_instance_env_to_base(iid)
|
|
685
|
+
|
|
493
686
|
def _slug(name):
|
|
494
687
|
s = name.lower().strip()
|
|
495
688
|
for a, b in [("á","a"),("é","e"),("í","i"),("ó","o"),("ú","u"),("ñ","n")]:
|