@innvisor/conny-ai 9.8.2 → 9.8.5
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 +12 -0
- package/conny_app.py +5 -3
- package/conny_cli.py +3 -0
- package/conny_studio.py +53 -11
- package/npm/conny.js +2 -2
- package/package.json +1 -1
- package/src/conny/channels/cli.py +3 -0
- package/src/conny/demo/handler.py +27 -97
- package/src/interfaces/web/demo_handler.py +24 -101
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 9.8.4 - 2026-06-02
|
|
4
|
+
|
|
5
|
+
- made bare `conny` route to the modern onboarding/chat surface instead of the legacy help screen
|
|
6
|
+
- kept `conny init` untouched while preserving the same banner and branding in the post-onboarding chat UI
|
|
7
|
+
- aligned the legacy Python CLI fallback so direct `conny_cli.py` launches follow the same start behavior
|
|
8
|
+
|
|
9
|
+
## 9.8.3 - 2026-06-02
|
|
10
|
+
|
|
11
|
+
- made `conny` open the real chat interface after onboarding instead of the setup flow
|
|
12
|
+
- kept `conny init` unchanged and preserved its banner/design exactly
|
|
13
|
+
- added slash-command chat shortcuts with a Codex-style launcher header
|
|
14
|
+
|
|
3
15
|
## 9.8.2 - 2026-06-02
|
|
4
16
|
|
|
5
17
|
- persisted the language selected in `conny init` so the rest of the CLI loads it automatically
|
package/conny_app.py
CHANGED
|
@@ -282,10 +282,12 @@ def _sh(*a):
|
|
|
282
282
|
|
|
283
283
|
def main():
|
|
284
284
|
signal.signal(signal.SIGINT, lambda *_: sys.exit(0))
|
|
285
|
-
if first_run() and not (len(sys.argv)>1 and sys.argv[1] in ("help","--help","-h","-v","--version")):
|
|
286
|
-
onboard()
|
|
287
285
|
if len(sys.argv) <= 1:
|
|
288
|
-
|
|
286
|
+
if first_run():
|
|
287
|
+
onboard()
|
|
288
|
+
return
|
|
289
|
+
cmd_chat()
|
|
290
|
+
return
|
|
289
291
|
else:
|
|
290
292
|
route(sys.argv[1], " ".join(sys.argv[2:]))
|
|
291
293
|
|
package/conny_cli.py
CHANGED
|
@@ -11845,6 +11845,9 @@ def main():
|
|
|
11845
11845
|
if not workspace_is_configured() or not get_instances():
|
|
11846
11846
|
cmd_init(args)
|
|
11847
11847
|
return
|
|
11848
|
+
modern_entrypoint = Path(os.environ.get("CONNY_DIR", os.path.dirname(os.path.abspath(__file__)))) / "conny_app.py"
|
|
11849
|
+
subprocess.call([sys.executable, str(modern_entrypoint)])
|
|
11850
|
+
return
|
|
11848
11851
|
|
|
11849
11852
|
# Help
|
|
11850
11853
|
if args.help or cmd in ("help", "--help", "-h", ""):
|
package/conny_studio.py
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""conny_studio.py — Interactive CLI
|
|
2
|
+
"""conny_studio.py — Interactive CLI chat with live monitoring."""
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
7
|
import time
|
|
8
8
|
import uuid
|
|
9
|
+
import subprocess
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from datetime import datetime
|
|
11
12
|
|
|
12
13
|
import httpx
|
|
14
|
+
from rich.console import Console
|
|
13
15
|
|
|
14
16
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
17
|
+
from conny_design import LOGO_FULL, SEP, ICON_BRAND
|
|
15
18
|
from conny_uncertainty import UncertaintyDetector
|
|
16
19
|
from conny_voice import ConnyVoice
|
|
17
20
|
|
|
21
|
+
CONSOLE = Console()
|
|
22
|
+
VERSION = "9.8.2"
|
|
23
|
+
try:
|
|
24
|
+
package_path = Path(__file__).resolve().parent / "package.json"
|
|
25
|
+
if package_path.exists():
|
|
26
|
+
VERSION = json.loads(package_path.read_text(encoding="utf-8")).get("version", VERSION)
|
|
27
|
+
except Exception:
|
|
28
|
+
pass
|
|
29
|
+
|
|
18
30
|
STUDIO_DIR = Path.home() / ".conny" / "studio" / "memory"
|
|
19
31
|
API_URL = "http://localhost:8001/test"
|
|
20
32
|
|
|
@@ -69,11 +81,16 @@ class ConnyStudio:
|
|
|
69
81
|
}, ensure_ascii=False) + "\n")
|
|
70
82
|
|
|
71
83
|
async def handle_command(self, cmd: str) -> str:
|
|
84
|
+
if cmd in ("/help", "/menu", "/start"):
|
|
85
|
+
return (
|
|
86
|
+
"Comandos: /help /menu /clear /history /models /config /language "
|
|
87
|
+
"/export /reload-persona /fix-last"
|
|
88
|
+
)
|
|
72
89
|
if cmd == "/clear":
|
|
73
90
|
self.history = []
|
|
74
91
|
self.chat_id = f"studio_{uuid.uuid4().hex[:8]}"
|
|
75
92
|
return "Session cleared. New conversation started."
|
|
76
|
-
elif cmd
|
|
93
|
+
elif cmd in ("/history", "/show-memory"):
|
|
77
94
|
if not self.history:
|
|
78
95
|
return "No turns in memory yet."
|
|
79
96
|
lines = []
|
|
@@ -81,6 +98,14 @@ class ConnyStudio:
|
|
|
81
98
|
role = "YOU" if h["role"] == "user" else "MEL"
|
|
82
99
|
lines.append(f" [{role}] {h['content'][:80]}")
|
|
83
100
|
return "\n".join(lines)
|
|
101
|
+
elif cmd == "/models":
|
|
102
|
+
return self._run_cli_command("modelo")
|
|
103
|
+
elif cmd == "/config":
|
|
104
|
+
return self._run_cli_command("config")
|
|
105
|
+
elif cmd == "/language":
|
|
106
|
+
return self._run_cli_command("language")
|
|
107
|
+
elif cmd in ("/new", "/init"):
|
|
108
|
+
return self._run_cli_command("init")
|
|
84
109
|
elif cmd == "/show-failures":
|
|
85
110
|
if not self.failures_file.exists():
|
|
86
111
|
return "No failures detected this session."
|
|
@@ -101,13 +126,28 @@ class ConnyStudio:
|
|
|
101
126
|
return "No previous turn to fix."
|
|
102
127
|
return f"Unknown command: {cmd}"
|
|
103
128
|
|
|
129
|
+
def _run_cli_command(self, command: str) -> str:
|
|
130
|
+
cli = Path(__file__).resolve().parent / "conny_cli.py"
|
|
131
|
+
if not cli.exists():
|
|
132
|
+
return f"CLI no disponible para /{command}"
|
|
133
|
+
try:
|
|
134
|
+
print(f"\033[90m[system] launching: conny {command}\033[0m")
|
|
135
|
+
subprocess.run([sys.executable, str(cli), command], check=False)
|
|
136
|
+
return f"/{command} closed. Back in chat."
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
return f"No pude abrir /{command}: {exc}"
|
|
139
|
+
|
|
104
140
|
def print_header(self):
|
|
105
|
-
print(
|
|
106
|
-
print(
|
|
107
|
-
print(f"
|
|
108
|
-
print(
|
|
109
|
-
print("
|
|
110
|
-
print("
|
|
141
|
+
print()
|
|
142
|
+
CONSOLE.print(LOGO_FULL)
|
|
143
|
+
CONSOLE.print(f" {ICON_BRAND} v{VERSION} · chat real")
|
|
144
|
+
CONSOLE.print(SEP)
|
|
145
|
+
print(f" Instance: {self.instance_id}")
|
|
146
|
+
print(f" Session: {self.session_id}")
|
|
147
|
+
print(" Comandos: /help /menu /clear /history /models /config /language /export")
|
|
148
|
+
print(" Atajos: 1=models 2=config 3=language 4=help")
|
|
149
|
+
CONSOLE.print(SEP)
|
|
150
|
+
print()
|
|
111
151
|
|
|
112
152
|
def print_scores(self, scores):
|
|
113
153
|
conf = scores["confidence"]
|
|
@@ -122,14 +162,16 @@ class ConnyStudio:
|
|
|
122
162
|
self.print_header()
|
|
123
163
|
while True:
|
|
124
164
|
try:
|
|
125
|
-
user_input = input("\033[1;32m[YOU]\033[0m ")
|
|
165
|
+
user_input = input("\033[1;32m[YOU]\033[0m ").strip()
|
|
126
166
|
except (EOFError, KeyboardInterrupt):
|
|
127
167
|
print("\n\033[90mSession ended.\033[0m")
|
|
128
168
|
break
|
|
129
|
-
if not user_input
|
|
169
|
+
if not user_input:
|
|
130
170
|
continue
|
|
171
|
+
if user_input in ("1", "2", "3", "4"):
|
|
172
|
+
user_input = { "1": "/models", "2": "/config", "3": "/language", "4": "/help" }[user_input]
|
|
131
173
|
if user_input.startswith("/"):
|
|
132
|
-
result = await self.handle_command(user_input
|
|
174
|
+
result = await self.handle_command(user_input)
|
|
133
175
|
print(f"\033[1;33m[SYSTEM]\033[0m {result}")
|
|
134
176
|
continue
|
|
135
177
|
try:
|
package/npm/conny.js
CHANGED
|
@@ -485,7 +485,7 @@ function execConny(argv) {
|
|
|
485
485
|
}
|
|
486
486
|
|
|
487
487
|
const args = process.argv.slice(2);
|
|
488
|
-
const isHelp = args.
|
|
488
|
+
const isHelp = args.includes("-h") || args.includes("--help") || args.includes("help");
|
|
489
489
|
const isVersion = args.includes("-v") || args.includes("--version") || args.includes("version");
|
|
490
490
|
const isBootstrapCheck = args.includes("--bootstrap-check");
|
|
491
491
|
const isJson = args.includes("--json");
|
|
@@ -518,7 +518,7 @@ if (isHelp) {
|
|
|
518
518
|
process.exit(0);
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
-
const launchArgs = args
|
|
521
|
+
const launchArgs = args;
|
|
522
522
|
|
|
523
523
|
if (!execConny(launchArgs)) {
|
|
524
524
|
fail(`No pude iniciar Conny desde ${connyHome}`);
|
package/package.json
CHANGED
|
@@ -11785,6 +11785,9 @@ def main():
|
|
|
11785
11785
|
if not workspace_is_configured() or not get_instances():
|
|
11786
11786
|
cmd_init(args)
|
|
11787
11787
|
return
|
|
11788
|
+
modern_entrypoint = Path(os.environ.get("CONNY_DIR", os.path.dirname(os.path.abspath(__file__)))) / "conny_app.py"
|
|
11789
|
+
subprocess.call([sys.executable, str(modern_entrypoint)])
|
|
11790
|
+
return
|
|
11788
11791
|
|
|
11789
11792
|
# Help
|
|
11790
11793
|
if args.help or cmd in ("help", "--help", "-h", ""):
|
|
@@ -261,17 +261,8 @@ async def _handle_demo_message(self, chat_id: str, text: str,
|
|
|
261
261
|
)
|
|
262
262
|
_save("assistant", r)
|
|
263
263
|
bubbles = self._split_bubbles(r, chat_id=chat_id, archetype=_demo_archetype)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
_greeting_tokens = (
|
|
267
|
-
"hola", "buenas", "buenas tardes", "buenos dias", "buenos días",
|
|
268
|
-
"buenas noches", "hey", "holi", "hi", "hello",
|
|
269
|
-
"good morning", "good afternoon", "good evening",
|
|
270
|
-
)
|
|
271
|
-
if any(_text_norm == token or _text_norm.startswith(token + " ") for token in _greeting_tokens):
|
|
272
|
-
lowered_bubble = _normalize_conv_text(bubbles[0] or "")
|
|
273
|
-
if not any(token in lowered_bubble for token in ("cuentame", "cuéntame", "revisar", "ayudo", "ayudar")):
|
|
274
|
-
bubbles.append(_lang_text("cuéntame qué te gustaría revisar", "what would you like to check?"))
|
|
264
|
+
# The LLM owns the follow-up. Do not append generic CTA bubbles here:
|
|
265
|
+
# that made good model answers sound like fallback.
|
|
275
266
|
tone = self._demo_sessions.get(btone_key, "GENERAL")
|
|
276
267
|
if tone in ("SALUD PREMIUM", "PREMIUM"):
|
|
277
268
|
bubbles = [b[0].upper() + b[1:] if b else b for b in bubbles]
|
|
@@ -665,6 +656,7 @@ async def _handle_demo_message(self, chat_id: str, text: str,
|
|
|
665
656
|
),
|
|
666
657
|
]
|
|
667
658
|
had_output = False
|
|
659
|
+
last_candidate = None
|
|
668
660
|
for prompt_now, temp_now, max_now, tier_now, limit_now in attempts:
|
|
669
661
|
# No lanzar repair si ya pasó demasiado tiempo desde que llegó el mensaje
|
|
670
662
|
if time.time() - _chain_start > _CHAIN_TIMEOUT_S:
|
|
@@ -679,9 +671,12 @@ async def _handle_demo_message(self, chat_id: str, text: str,
|
|
|
679
671
|
)
|
|
680
672
|
if candidate and candidate.strip():
|
|
681
673
|
had_output = True
|
|
674
|
+
last_candidate = candidate
|
|
682
675
|
if not validator(candidate):
|
|
683
676
|
return candidate, True
|
|
684
|
-
|
|
677
|
+
if had_output and last_candidate:
|
|
678
|
+
return last_candidate, True
|
|
679
|
+
return None, False
|
|
685
680
|
|
|
686
681
|
def _save(role, msg):
|
|
687
682
|
if db:
|
|
@@ -723,20 +718,7 @@ async def _handle_demo_message(self, chat_id: str, text: str,
|
|
|
723
718
|
)
|
|
724
719
|
_save("assistant", r)
|
|
725
720
|
bubbles = self._split_bubbles(r, chat_id=chat_id, archetype=_demo_archetype)
|
|
726
|
-
#
|
|
727
|
-
# solo 1 burbuja sin pregunta de seguimiento → agregar burbuja de apertura.
|
|
728
|
-
# Esta lógica existía en la primera definición de _send (línea 13772) pero
|
|
729
|
-
# se perdió cuando se redefinió _send aquí en la misma función.
|
|
730
|
-
if should_normalize_first_turn and len(bubbles) == 1:
|
|
731
|
-
_text_norm = _normalize_conv_text(text or "")
|
|
732
|
-
_greeting_tokens = (
|
|
733
|
-
"hola", "buenas", "buenas tardes", "buenos dias", "buenos días",
|
|
734
|
-
"buenas noches", "hey", "holi",
|
|
735
|
-
)
|
|
736
|
-
if any(_text_norm == token or _text_norm.startswith(token + " ") for token in _greeting_tokens):
|
|
737
|
-
lowered_bubble = _normalize_conv_text(bubbles[0] or "")
|
|
738
|
-
if not any(token in lowered_bubble for token in ("cuentame", "cuéntame", "revisar", "ayudo", "ayudar")):
|
|
739
|
-
bubbles.append("cuéntame qué te gustaría revisar")
|
|
721
|
+
# The LLM owns the follow-up. Do not append generic CTA bubbles here.
|
|
740
722
|
# Para premium/salud premium: restaurar mayúscula inicial
|
|
741
723
|
tone = self._demo_sessions.get(btone_key, "GENERAL")
|
|
742
724
|
if tone in ("SALUD PREMIUM", "PREMIUM"):
|
|
@@ -1089,6 +1071,7 @@ async def _handle_demo_message(self, chat_id: str, text: str,
|
|
|
1089
1071
|
),
|
|
1090
1072
|
]
|
|
1091
1073
|
had_output = False
|
|
1074
|
+
last_candidate = None
|
|
1092
1075
|
for prompt_now, temp_now, max_now, tier_now in attempts:
|
|
1093
1076
|
candidate = await _llm(
|
|
1094
1077
|
prompt_now,
|
|
@@ -1099,9 +1082,12 @@ async def _handle_demo_message(self, chat_id: str, text: str,
|
|
|
1099
1082
|
)
|
|
1100
1083
|
if candidate and candidate.strip():
|
|
1101
1084
|
had_output = True
|
|
1085
|
+
last_candidate = candidate
|
|
1102
1086
|
if not validator(candidate):
|
|
1103
1087
|
return candidate, True
|
|
1104
|
-
|
|
1088
|
+
if had_output and last_candidate:
|
|
1089
|
+
return last_candidate, True
|
|
1090
|
+
return None, False
|
|
1105
1091
|
|
|
1106
1092
|
def _demo_owner_last_resort(
|
|
1107
1093
|
user_text: str,
|
|
@@ -1806,63 +1792,7 @@ Máximo 1 oración por burbuja. Natural y seguro."""
|
|
|
1806
1792
|
max_t=220,
|
|
1807
1793
|
)
|
|
1808
1794
|
if not r:
|
|
1809
|
-
|
|
1810
|
-
r = _lang_text(
|
|
1811
|
-
f"ya tengo {nombre} ||| ya me ubiqué con cómo tendría que sonar esto ||| Escríbeme como si fueras un cliente y te respondo",
|
|
1812
|
-
f"I’ve got {nombre} now ||| I already know how this chat should sound ||| text me like a real client and I’ll reply in context",
|
|
1813
|
-
f"já tenho {nombre} ||| já entendi como esse chat precisa soar ||| me escreve como um cliente real e eu respondo em contexto",
|
|
1814
|
-
)
|
|
1815
|
-
else:
|
|
1816
|
-
# v12: no info → opciones naturales, sin exponer estado interno
|
|
1817
|
-
if _owner_is_english():
|
|
1818
|
-
_no_info_opts = [
|
|
1819
|
-
f"got it, {nombre} ||| tell me what the business does and I’ll shape the demo around that",
|
|
1820
|
-
f"okay, {nombre} ||| I’m not finding solid public info yet, so tell me what you offer and I’ll ground it from there",
|
|
1821
|
-
f"I’ve got the name now ||| give me a quick picture of the business and I’ll keep going",
|
|
1822
|
-
]
|
|
1823
|
-
elif _owner_is_portuguese():
|
|
1824
|
-
_no_info_opts = [
|
|
1825
|
-
f"perfeito, {nombre} ||| me conta com o que o negócio trabalha e eu monto a demo nisso",
|
|
1826
|
-
f"ok, {nombre} ||| ainda não achei informação pública forte, então me conta o que vocês oferecem e eu ajusto a demo",
|
|
1827
|
-
f"já tenho o nome ||| me dá um resumo rápido do negócio e eu sigo daqui",
|
|
1828
|
-
]
|
|
1829
|
-
else:
|
|
1830
|
-
_no_info_opts = [
|
|
1831
|
-
f"ya anoté {nombre} ||| cuéntame a qué se dedican y te muestro cómo respondería",
|
|
1832
|
-
f"listo, {nombre} ||| no los encuentro en Google todavía — cuéntame qué hacen y arrancamos",
|
|
1833
|
-
f"ya los tengo ||| igual puedo hacer la demo — escríbeme un poco de qué trata el negocio",
|
|
1834
|
-
]
|
|
1835
|
-
r = _r.choice(_no_info_opts)
|
|
1836
|
-
|
|
1837
|
-
# ── Burbuja extra: confirmación del link ─────────────────────────
|
|
1838
|
-
# Solo si encontramos info real (no cuando usamos el fallback de Google search)
|
|
1839
|
-
import urllib.parse as _up
|
|
1840
|
-
is_fallback_url = biz_url.startswith("https://www.google.com/search") or biz_url.startswith("https://www.google.com/maps/search")
|
|
1841
|
-
if biz_url and found and not is_fallback_url:
|
|
1842
|
-
# Natural: manda el link con texto corto, sin pregunta directa
|
|
1843
|
-
if _owner_is_english():
|
|
1844
|
-
_link_intros = [
|
|
1845
|
-
"I found this for you",
|
|
1846
|
-
"this looks like your business",
|
|
1847
|
-
"I found you here",
|
|
1848
|
-
"this is what I found for the business",
|
|
1849
|
-
]
|
|
1850
|
-
elif _owner_is_portuguese():
|
|
1851
|
-
_link_intros = [
|
|
1852
|
-
"achei isso de vocês",
|
|
1853
|
-
"encontrei vocês por aqui",
|
|
1854
|
-
"isso parece ser de vocês",
|
|
1855
|
-
"foi isso que eu achei do negócio",
|
|
1856
|
-
]
|
|
1857
|
-
else:
|
|
1858
|
-
_link_intros = [
|
|
1859
|
-
"mira, encontré esto de ustedes",
|
|
1860
|
-
"los encontré por acá",
|
|
1861
|
-
"esto es de ustedes",
|
|
1862
|
-
"vi esto de su negocio",
|
|
1863
|
-
]
|
|
1864
|
-
r = r.rstrip() + f" ||| {_r.choice(_link_intros)} ||| {biz_url}"
|
|
1865
|
-
|
|
1795
|
+
return _send("⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
1866
1796
|
return _send(r)
|
|
1867
1797
|
|
|
1868
1798
|
# ── Confirmación positiva del link: "sí ese es / correcto / sí" ───────────
|
|
@@ -2155,7 +2085,7 @@ Natural, sin punto al final, sin ¿¡, en minúscula.""",
|
|
|
2155
2085
|
f"perfecto, ya leí el documento de {business_name}"
|
|
2156
2086
|
f" ||| ya sé de qué se tratan — probemos, Escríbeme algo como cliente"
|
|
2157
2087
|
)
|
|
2158
|
-
return _send(r
|
|
2088
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2159
2089
|
else:
|
|
2160
2090
|
# Doc llegó pero no pudimos extraer texto (imagen, binario raro)
|
|
2161
2091
|
return _send(
|
|
@@ -2244,7 +2174,7 @@ Natural, sin punto al final, sin ¿¡, en minúscula.""",
|
|
|
2244
2174
|
f"listo, ya entendí bien lo que hace {business_name} ||| "
|
|
2245
2175
|
f"arrancamos? Escríbeme algo como cliente a ver qué pasa"
|
|
2246
2176
|
)
|
|
2247
|
-
return _send(r
|
|
2177
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2248
2178
|
|
|
2249
2179
|
elif not _has_what:
|
|
2250
2180
|
# Falta: qué hacen
|
|
@@ -2255,7 +2185,7 @@ Todavía no sabes exactamente qué servicios o productos ofrecen.
|
|
|
2255
2185
|
Haz UNA pregunta natural para entenderlo. Muy corta. Sin punto al final. En minúscula. Sin ¿ ni ¡.""",
|
|
2256
2186
|
"preguntando qué hacen", max_t=80
|
|
2257
2187
|
)
|
|
2258
|
-
return _send(r
|
|
2188
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2259
2189
|
|
|
2260
2190
|
elif not _has_where:
|
|
2261
2191
|
# Falta: dónde están
|
|
@@ -2267,7 +2197,7 @@ Haz UNA pregunta natural para saberlo. Muy corta. Sin punto al final. En minúsc
|
|
|
2267
2197
|
Ejemplo: "y dónde están ubicados?" o "en qué ciudad o barrio están" """,
|
|
2268
2198
|
"preguntando ubicación", max_t=80
|
|
2269
2199
|
)
|
|
2270
|
-
return _send(r
|
|
2200
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2271
2201
|
|
|
2272
2202
|
else:
|
|
2273
2203
|
# Seguir aprendiendo con una pregunta más
|
|
@@ -2278,7 +2208,7 @@ Haz UNA pregunta más para entender mejor al negocio (horario, qué los diferenc
|
|
|
2278
2208
|
Muy corta. Sin punto al final. En minúscula. Sin ¿ ni ¡.""",
|
|
2279
2209
|
"pregunta adicional", max_t=80
|
|
2280
2210
|
)
|
|
2281
|
-
return _send(r
|
|
2211
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2282
2212
|
|
|
2283
2213
|
# ── INTERCEPTOR: preguntas meta (soy bot? eres real? eres IA?) ─────────
|
|
2284
2214
|
# Deben responderse ANTES del flujo normal — sin buscar en web ni confundirse
|
|
@@ -2427,6 +2357,7 @@ IDENTIDAD Y CREADOR — REGLA DURA
|
|
|
2427
2357
|
"""
|
|
2428
2358
|
customer_history = sim_history[-8:]
|
|
2429
2359
|
customer_had_output = False
|
|
2360
|
+
last_candidate = None
|
|
2430
2361
|
customer_reply = None
|
|
2431
2362
|
original_history = history
|
|
2432
2363
|
history = customer_history
|
|
@@ -2574,7 +2505,7 @@ Maneja en 2 burbujas (|||). REGLAS ESTRICTAS:
|
|
|
2574
2505
|
|
|
2575
2506
|
Ejemplo del tono que quiero:
|
|
2576
2507
|
"sí, hay de todo en el mercado ||| qué presupuesto tienes más o menos, para ver qué te muestro" """, "maneja la objeción")
|
|
2577
|
-
return _send((r
|
|
2508
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2578
2509
|
|
|
2579
2510
|
if detected_cmd == "/cita":
|
|
2580
2511
|
r = await _llm(f"""Eres Conny, asesora de {business_name}. Un cliente acaba de decir que quiere ir o comprar.
|
|
@@ -2590,7 +2521,7 @@ Flujo sugerido:
|
|
|
2590
2521
|
Sin punto al final. Sin ¿¡. Máximo 1-2 oraciones por burbuja.
|
|
2591
2522
|
Ejemplo del tono: "qué producto te interesa llevar ||| esta semana puedo el miércoles o el viernes — cuál te queda" """,
|
|
2592
2523
|
"quiero comprar / quiero ir", max_t=350)
|
|
2593
|
-
return _send((r
|
|
2524
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2594
2525
|
|
|
2595
2526
|
if detected_cmd == "/stats":
|
|
2596
2527
|
return _send(f"el 78% de los clientes no vuelven si no les responden en menos de 5 minutos ||| una cita perdida en {business_name} vale entre $80k y $500k según el servicio ||| Conny responde en menos de 3 segundos, 24/7, sin días libres ni mal humor" + _next_trick())
|
|
@@ -2601,7 +2532,7 @@ Ejemplo del tono: "qué producto te interesa llevar ||| esta semana puedo el mi
|
|
|
2601
2532
|
if detected_cmd == "/cierre":
|
|
2602
2533
|
r = await _llm(f"""Eres Conny de {business_name}. Un cliente lleva 3 mensajes dudando.
|
|
2603
2534
|
Haz el cierre en 2 burbujas (|||). Directo, con urgencia real. Sin presión forzada. Sin punto al final.""", "no sé, lo pienso")
|
|
2604
|
-
return _send((r
|
|
2535
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2605
2536
|
|
|
2606
2537
|
if detected_cmd == "/list":
|
|
2607
2538
|
lista = (
|
|
@@ -2673,7 +2604,7 @@ Haz el cierre en 2 burbujas (|||). Directo, con urgencia real. Sin presión forz
|
|
|
2673
2604
|
r = await _llm(f"""El usuario ha dicho: "{hist_text[:300]}"
|
|
2674
2605
|
Extrae datos mencionados (nombre, interés, servicio). Demuestra en 2 burbujas (|||) que los recuerdas.
|
|
2675
2606
|
Si no hay datos: "todavía no me has dado tu nombre — pero cuando lo hagas, lo recuerdo para siempre". Sin punto al final.""", "qué recuerdas")
|
|
2676
|
-
return _send(r
|
|
2607
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2677
2608
|
|
|
2678
2609
|
if detected_cmd == "/2am":
|
|
2679
2610
|
return _send(f"son las 2 de la madrugada y estoy aquí ||| tu recepcionista está durmiendo — yo no. nunca" + _next_trick())
|
|
@@ -2681,12 +2612,12 @@ Si no hay datos: "todavía no me has dado tu nombre — pero cuando lo hagas, lo
|
|
|
2681
2612
|
if detected_cmd == "/competencia":
|
|
2682
2613
|
r = await _llm(f"""Eres Conny de {business_name}. Un cliente dice: "ya fui a otra parte y no me gustó."
|
|
2683
2614
|
Responde en 2 burbujas (|||). Sin atacar a la competencia. Natural. Sin punto al final.""", "ya fui a otro lado")
|
|
2684
|
-
return _send((r
|
|
2615
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2685
2616
|
|
|
2686
2617
|
if detected_cmd == "/precio":
|
|
2687
2618
|
r = await _llm(f"""Eres Conny de {business_name}. Un cliente dice: "está muy caro."
|
|
2688
2619
|
Maneja en 2 burbujas (|||). Enfócate en valor. Cierra hacia valoración con día concreto. Sin punto al final.""", "está muy caro")
|
|
2689
|
-
return _send((r
|
|
2620
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2690
2621
|
|
|
2691
2622
|
if detected_cmd == "/menu_bot":
|
|
2692
2623
|
# Modo bot — IVR con emojis, ideal para negocios que prefieren menú estructurado
|
|
@@ -3055,7 +2986,7 @@ OBJECIONES
|
|
|
3055
2986
|
recent_limit=8,
|
|
3056
2987
|
)
|
|
3057
2988
|
if not r:
|
|
3058
|
-
r =
|
|
2989
|
+
r = "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente."
|
|
3059
2990
|
# Solo revelar truco si la respuesta tiene contenido real (>60 chars)
|
|
3060
2991
|
# y no termina en pregunta (no interrumpir el flujo de la conversación)
|
|
3061
2992
|
if _should_reveal_trick and r and len(r.replace("|||","").strip()) > 60:
|
|
@@ -3107,4 +3038,3 @@ OBJECIONES
|
|
|
3107
3038
|
return _send(r)
|
|
3108
3039
|
|
|
3109
3040
|
|
|
3110
|
-
|
|
@@ -447,17 +447,8 @@ async def handle_demo_message(
|
|
|
447
447
|
)
|
|
448
448
|
_save("assistant", r)
|
|
449
449
|
bubbles = self._split_bubbles(r, chat_id=chat_id, archetype=_demo_archetype)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
_greeting_tokens = (
|
|
453
|
-
"hola", "buenas", "buenas tardes", "buenos dias", "buenos días",
|
|
454
|
-
"buenas noches", "hey", "holi", "hi", "hello",
|
|
455
|
-
"good morning", "good afternoon", "good evening",
|
|
456
|
-
)
|
|
457
|
-
if any(_text_norm == token or _text_norm.startswith(token + " ") for token in _greeting_tokens):
|
|
458
|
-
lowered_bubble = _normalize_conv_text(bubbles[0] or "")
|
|
459
|
-
if not any(token in lowered_bubble for token in ("cuentame", "cuéntame", "revisar", "ayudo", "ayudar")):
|
|
460
|
-
bubbles.append(_lang_text("cuéntame qué te gustaría revisar", "what would you like to check?"))
|
|
450
|
+
# The LLM owns the follow-up. Do not append generic CTA bubbles here:
|
|
451
|
+
# that made good model answers sound like fallback.
|
|
461
452
|
tone = self._demo_sessions.get(btone_key, "GENERAL")
|
|
462
453
|
if tone in ("SALUD PREMIUM", "PREMIUM"):
|
|
463
454
|
bubbles = [b[0].upper() + b[1:] if b else b for b in bubbles]
|
|
@@ -978,11 +969,10 @@ async def handle_demo_message(
|
|
|
978
969
|
last_candidate = candidate
|
|
979
970
|
if not validator(candidate):
|
|
980
971
|
return candidate, True
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
return None, had_output
|
|
972
|
+
# BUG FIX ARCHITECTURE: si el modelo generó algo, así no pase validaciones, se devuelve. NO FALLBACK.
|
|
973
|
+
if had_output and last_candidate:
|
|
974
|
+
return last_candidate, True
|
|
975
|
+
return None, False
|
|
986
976
|
|
|
987
977
|
def _save(role, msg):
|
|
988
978
|
if db:
|
|
@@ -1024,20 +1014,7 @@ async def handle_demo_message(
|
|
|
1024
1014
|
)
|
|
1025
1015
|
_save("assistant", r)
|
|
1026
1016
|
bubbles = self._split_bubbles(r, chat_id=chat_id, archetype=_demo_archetype)
|
|
1027
|
-
#
|
|
1028
|
-
# solo 1 burbuja sin pregunta de seguimiento → agregar burbuja de apertura.
|
|
1029
|
-
# Esta lógica existía en la primera definición de _send (línea 13772) pero
|
|
1030
|
-
# se perdió cuando se redefinió _send aquí en la misma función.
|
|
1031
|
-
if should_normalize_first_turn and len(bubbles) == 1:
|
|
1032
|
-
_text_norm = _normalize_conv_text(text or "")
|
|
1033
|
-
_greeting_tokens = (
|
|
1034
|
-
"hola", "buenas", "buenas tardes", "buenos dias", "buenos días",
|
|
1035
|
-
"buenas noches", "hey", "holi",
|
|
1036
|
-
)
|
|
1037
|
-
if any(_text_norm == token or _text_norm.startswith(token + " ") for token in _greeting_tokens):
|
|
1038
|
-
lowered_bubble = _normalize_conv_text(bubbles[0] or "")
|
|
1039
|
-
if not any(token in lowered_bubble for token in ("cuentame", "cuéntame", "revisar", "ayudo", "ayudar")):
|
|
1040
|
-
bubbles.append("cuéntame qué te gustaría revisar")
|
|
1017
|
+
# The LLM owns the follow-up. Do not append generic CTA bubbles here.
|
|
1041
1018
|
# Para premium/salud premium: restaurar mayúscula inicial
|
|
1042
1019
|
tone = self._demo_sessions.get(btone_key, "GENERAL")
|
|
1043
1020
|
if tone in ("SALUD PREMIUM", "PREMIUM"):
|
|
@@ -1336,10 +1313,12 @@ async def handle_demo_message(
|
|
|
1336
1313
|
)
|
|
1337
1314
|
if candidate and candidate.strip():
|
|
1338
1315
|
had_output = True
|
|
1316
|
+
last_candidate = candidate
|
|
1339
1317
|
if not validator(candidate):
|
|
1340
1318
|
return candidate, True
|
|
1341
|
-
|
|
1342
|
-
|
|
1319
|
+
if had_output and last_candidate:
|
|
1320
|
+
return last_candidate, True
|
|
1321
|
+
return None, False
|
|
1343
1322
|
|
|
1344
1323
|
def _demo_owner_last_resort(
|
|
1345
1324
|
user_text: str,
|
|
@@ -2181,63 +2160,7 @@ Máximo 1 oración por burbuja. Natural y seguro."""
|
|
|
2181
2160
|
max_t=220,
|
|
2182
2161
|
)
|
|
2183
2162
|
if not r:
|
|
2184
|
-
|
|
2185
|
-
r = _lang_text(
|
|
2186
|
-
f"ya tengo {nombre} ||| ya me ubiqué con cómo tendría que sonar esto ||| Escríbeme como si fueras un cliente y te respondo",
|
|
2187
|
-
f"I’ve got {nombre} now ||| I already know how this chat should sound ||| text me like a real client and I’ll reply in context",
|
|
2188
|
-
f"já tenho {nombre} ||| já entendi como esse chat precisa soar ||| me escreve como um cliente real e eu respondo em contexto",
|
|
2189
|
-
)
|
|
2190
|
-
else:
|
|
2191
|
-
# v12: no info → opciones naturales, sin exponer estado interno
|
|
2192
|
-
if _owner_is_english():
|
|
2193
|
-
_no_info_opts = [
|
|
2194
|
-
f"got it, {nombre} ||| tell me what the business does and I’ll shape the demo around that",
|
|
2195
|
-
f"okay, {nombre} ||| I’m not finding solid public info yet, so tell me what you offer and I’ll ground it from there",
|
|
2196
|
-
f"I’ve got the name now ||| give me a quick picture of the business and I’ll keep going",
|
|
2197
|
-
]
|
|
2198
|
-
elif _owner_is_portuguese():
|
|
2199
|
-
_no_info_opts = [
|
|
2200
|
-
f"perfeito, {nombre} ||| me conta com o que o negócio trabalha e eu monto a demo nisso",
|
|
2201
|
-
f"ok, {nombre} ||| ainda não achei informação pública forte, então me conta o que vocês oferecem e eu ajusto a demo",
|
|
2202
|
-
f"já tenho o nome ||| me dá um resumo rápido do negócio e eu sigo daqui",
|
|
2203
|
-
]
|
|
2204
|
-
else:
|
|
2205
|
-
_no_info_opts = [
|
|
2206
|
-
f"ya anoté {nombre} ||| cuéntame a qué se dedican y te muestro cómo respondería",
|
|
2207
|
-
f"listo, {nombre} ||| no los encuentro en Google todavía — cuéntame qué hacen y arrancamos",
|
|
2208
|
-
f"ya los tengo ||| igual puedo hacer la demo — escríbeme un poco de qué trata el negocio",
|
|
2209
|
-
]
|
|
2210
|
-
r = _r.choice(_no_info_opts)
|
|
2211
|
-
|
|
2212
|
-
# ── Burbuja extra: confirmación del link ─────────────────────────
|
|
2213
|
-
# Solo si encontramos info real (no cuando usamos el fallback de Google search)
|
|
2214
|
-
import urllib.parse as _up
|
|
2215
|
-
is_fallback_url = biz_url.startswith("https://www.google.com/search") or biz_url.startswith("https://www.google.com/maps/search")
|
|
2216
|
-
if biz_url and found and not is_fallback_url:
|
|
2217
|
-
# Natural: manda el link con texto corto, sin pregunta directa
|
|
2218
|
-
if _owner_is_english():
|
|
2219
|
-
_link_intros = [
|
|
2220
|
-
"I found this for you",
|
|
2221
|
-
"this looks like your business",
|
|
2222
|
-
"I found you here",
|
|
2223
|
-
"this is what I found for the business",
|
|
2224
|
-
]
|
|
2225
|
-
elif _owner_is_portuguese():
|
|
2226
|
-
_link_intros = [
|
|
2227
|
-
"achei isso de vocês",
|
|
2228
|
-
"encontrei vocês por aqui",
|
|
2229
|
-
"isso parece ser de vocês",
|
|
2230
|
-
"foi isso que eu achei do negócio",
|
|
2231
|
-
]
|
|
2232
|
-
else:
|
|
2233
|
-
_link_intros = [
|
|
2234
|
-
"mira, encontré esto de ustedes",
|
|
2235
|
-
"los encontré por acá",
|
|
2236
|
-
"esto es de ustedes",
|
|
2237
|
-
"vi esto de su negocio",
|
|
2238
|
-
]
|
|
2239
|
-
r = r.rstrip() + f" ||| {_r.choice(_link_intros)} ||| {biz_url}"
|
|
2240
|
-
|
|
2163
|
+
return _send("⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2241
2164
|
return _send(r)
|
|
2242
2165
|
|
|
2243
2166
|
# ── Confirmación positiva del link: "sí ese es / correcto / sí" ───────────
|
|
@@ -2553,7 +2476,7 @@ Natural, sin punto al final, sin ¿¡, en minúscula.""",
|
|
|
2553
2476
|
f"perfecto, ya leí el documento de {business_name}"
|
|
2554
2477
|
f" ||| ya sé de qué se tratan — probemos, Escríbeme algo como cliente"
|
|
2555
2478
|
)
|
|
2556
|
-
return _send(r
|
|
2479
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2557
2480
|
else:
|
|
2558
2481
|
# Doc llegó pero no pudimos extraer texto (imagen, binario raro)
|
|
2559
2482
|
return _send(
|
|
@@ -2714,7 +2637,7 @@ Natural, sin punto al final, sin ¿¡, en minúscula.""",
|
|
|
2714
2637
|
f"listo, ya entendí bien lo que hace {business_name} ||| "
|
|
2715
2638
|
f"arrancamos? Escríbeme algo como cliente a ver qué pasa"
|
|
2716
2639
|
)
|
|
2717
|
-
return _send(r
|
|
2640
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2718
2641
|
|
|
2719
2642
|
elif not _has_what:
|
|
2720
2643
|
# Falta: qué hacen
|
|
@@ -2725,7 +2648,7 @@ Todavía no sabes exactamente qué servicios o productos ofrecen.
|
|
|
2725
2648
|
Haz UNA pregunta natural para entenderlo. Muy corta. Sin punto al final. En minúscula. Sin ¿ ni ¡.""",
|
|
2726
2649
|
"preguntando qué hacen", max_t=80
|
|
2727
2650
|
)
|
|
2728
|
-
return _send(r
|
|
2651
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2729
2652
|
|
|
2730
2653
|
elif not _has_where:
|
|
2731
2654
|
# Falta: dónde están
|
|
@@ -2737,7 +2660,7 @@ Haz UNA pregunta natural para saberlo. Muy corta. Sin punto al final. En minúsc
|
|
|
2737
2660
|
Ejemplo: "y dónde están ubicados?" o "en qué ciudad o barrio están" """,
|
|
2738
2661
|
"preguntando ubicación", max_t=80
|
|
2739
2662
|
)
|
|
2740
|
-
return _send(r
|
|
2663
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2741
2664
|
|
|
2742
2665
|
else:
|
|
2743
2666
|
# Seguir aprendiendo con una pregunta más
|
|
@@ -2748,7 +2671,7 @@ Haz UNA pregunta más para entender mejor al negocio (horario, qué los diferenc
|
|
|
2748
2671
|
Muy corta. Sin punto al final. En minúscula. Sin ¿ ni ¡.""",
|
|
2749
2672
|
"pregunta adicional", max_t=80
|
|
2750
2673
|
)
|
|
2751
|
-
return _send(r
|
|
2674
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
2752
2675
|
|
|
2753
2676
|
# ── INTERCEPTOR: preguntas meta (soy bot? eres real? eres IA?) ─────────
|
|
2754
2677
|
# Deben responderse ANTES del flujo normal — sin buscar en web ni confundirse
|
|
@@ -3047,7 +2970,7 @@ Maneja en 2 burbujas (|||). REGLAS ESTRICTAS:
|
|
|
3047
2970
|
|
|
3048
2971
|
Ejemplo del tono que quiero:
|
|
3049
2972
|
"sí, hay de todo en el mercado ||| qué presupuesto tienes más o menos, para ver qué te muestro" """, "maneja la objeción")
|
|
3050
|
-
return _send((r
|
|
2973
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
3051
2974
|
|
|
3052
2975
|
if detected_cmd == "/cita":
|
|
3053
2976
|
r = await _llm(f"""Eres Conny, asesora de {business_name}. Un cliente acaba de decir que quiere ir o comprar.
|
|
@@ -3063,7 +2986,7 @@ Flujo sugerido:
|
|
|
3063
2986
|
Sin punto al final. Sin ¿¡. Máximo 1-2 oraciones por burbuja.
|
|
3064
2987
|
Ejemplo del tono: "qué producto te interesa llevar ||| esta semana puedo el miércoles o el viernes — cuál te queda" """,
|
|
3065
2988
|
"quiero comprar / quiero ir", max_t=350)
|
|
3066
|
-
return _send((r
|
|
2989
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
3067
2990
|
|
|
3068
2991
|
if detected_cmd == "/stats":
|
|
3069
2992
|
return _send(f"el 78% de los clientes no vuelven si no les responden en menos de 5 minutos ||| una cita perdida en {business_name} vale entre $80k y $500k según el servicio ||| Conny responde en menos de 3 segundos, 24/7, sin días libres ni mal humor" + _next_trick())
|
|
@@ -3074,7 +2997,7 @@ Ejemplo del tono: "qué producto te interesa llevar ||| esta semana puedo el mi
|
|
|
3074
2997
|
if detected_cmd == "/cierre":
|
|
3075
2998
|
r = await _llm(f"""Eres Conny de {business_name}. Un cliente lleva 3 mensajes dudando.
|
|
3076
2999
|
Haz el cierre en 2 burbujas (|||). Directo, con urgencia real. Sin presión forzada. Sin punto al final.""", "no sé, lo pienso")
|
|
3077
|
-
return _send((r
|
|
3000
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
3078
3001
|
|
|
3079
3002
|
if detected_cmd == "/list":
|
|
3080
3003
|
lista = (
|
|
@@ -3146,7 +3069,7 @@ Haz el cierre en 2 burbujas (|||). Directo, con urgencia real. Sin presión forz
|
|
|
3146
3069
|
r = await _llm(f"""El usuario ha dicho: "{hist_text[:300]}"
|
|
3147
3070
|
Extrae datos mencionados (nombre, interés, servicio). Demuestra en 2 burbujas (|||) que los recuerdas.
|
|
3148
3071
|
Si no hay datos: "todavía no me has dado tu nombre — pero cuando lo hagas, lo recuerdo para siempre". Sin punto al final.""", "qué recuerdas")
|
|
3149
|
-
return _send(r
|
|
3072
|
+
return _send(r if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
3150
3073
|
|
|
3151
3074
|
if detected_cmd == "/2am":
|
|
3152
3075
|
return _send(f"son las 2 de la madrugada y estoy aquí ||| tu recepcionista está durmiendo — yo no. nunca" + _next_trick())
|
|
@@ -3154,12 +3077,12 @@ Si no hay datos: "todavía no me has dado tu nombre — pero cuando lo hagas, lo
|
|
|
3154
3077
|
if detected_cmd == "/competencia":
|
|
3155
3078
|
r = await _llm(f"""Eres Conny de {business_name}. Un cliente dice: "ya fui a otra parte y no me gustó."
|
|
3156
3079
|
Responde en 2 burbujas (|||). Sin atacar a la competencia. Natural. Sin punto al final.""", "ya fui a otro lado")
|
|
3157
|
-
return _send((r
|
|
3080
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
3158
3081
|
|
|
3159
3082
|
if detected_cmd == "/precio":
|
|
3160
3083
|
r = await _llm(f"""Eres Conny de {business_name}. Un cliente dice: "está muy caro."
|
|
3161
3084
|
Maneja en 2 burbujas (|||). Enfócate en valor. Cierra hacia valoración con día concreto. Sin punto al final.""", "está muy caro")
|
|
3162
|
-
return _send((r
|
|
3085
|
+
return _send((r + _next_trick()) if r else "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente.")
|
|
3163
3086
|
|
|
3164
3087
|
if detected_cmd == "/menu_bot":
|
|
3165
3088
|
# Modo bot — IVR con emojis, ideal para negocios que prefieren menú estructurado
|
|
@@ -3528,7 +3451,7 @@ OBJECIONES
|
|
|
3528
3451
|
recent_limit=8,
|
|
3529
3452
|
)
|
|
3530
3453
|
if not r:
|
|
3531
|
-
r =
|
|
3454
|
+
r = "⚠️ Fallo del modelo LLM. No obtuve respuesta. Por favor, envía tu mensaje nuevamente."
|
|
3532
3455
|
# Solo revelar truco si la respuesta tiene contenido real (>60 chars)
|
|
3533
3456
|
# y no termina en pregunta (no interrumpir el flujo de la conversación)
|
|
3534
3457
|
if _should_reveal_trick and r and len(r.replace("|||","").strip()) > 60:
|