@innvisor/conny-ai 9.7.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/.env.example +68 -0
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +369 -0
- package/brand-assets/A_dark_luxury_web_background_202605210700.jpeg +0 -0
- package/brand-assets/Conny.web.logo.png +0 -0
- package/brand-assets/Logo_Conny_Petalo_Claro.png +0 -0
- package/brand-assets/cl-nica-de-las-am-ricas/manifest.json +22 -0
- package/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt +11 -0
- package/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt +11 -0
- package/brand-assets/cl-nica-las-am-ricas/manifest.json +22 -0
- package/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt +11 -0
- package/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt +11 -0
- package/brand-assets/conny-demo/manifest.json +22 -0
- package/brand-assets/conny-demo/processed/business-identity.txt +7 -0
- package/brand-assets/conny-demo/raw/business-identity.txt +7 -0
- package/brand-assets/conny-logo.png +0 -0
- package/brand-assets/web.background.png +0 -0
- package/brand_assets.py +323 -0
- package/conny +28 -0
- package/conny-chat.py +579 -0
- package/conny-omni.py +3843 -0
- package/conny.py +113 -0
- package/conny_agents/__init__.py +1 -0
- package/conny_agents/agenda.py +1 -0
- package/conny_agents/captacion.py +1 -0
- package/conny_agents/conocimiento.py +1 -0
- package/conny_agents/escalacion.py +1 -0
- package/conny_agents/objeciones.py +1 -0
- package/conny_agents/seguimiento.py +1 -0
- package/conny_app.py +287 -0
- package/conny_audio.py +350 -0
- package/conny_audio_learn.py +84 -0
- package/conny_brain_v10.py +804 -0
- package/conny_bridge.py +656 -0
- package/conny_calendar.py +169 -0
- package/conny_cli.py +11784 -0
- package/conny_cli_bb.py +437 -0
- package/conny_commands.py +243 -0
- package/conny_config.py +215 -0
- package/conny_core/__init__.py +3 -0
- package/conny_core/conversation_engine.py +446 -0
- package/conny_core/first_turn_ops.py +287 -0
- package/conny_core/persona_registry.py +157 -0
- package/conny_core/prompt_ops.py +561 -0
- package/conny_cron.py +72 -0
- package/conny_demo_v2.py +209 -0
- package/conny_demo_voice.py +134 -0
- package/conny_design.py +43 -0
- package/conny_doctor.py +319 -0
- package/conny_domino.py +696 -0
- package/conny_generator.py +447 -0
- package/conny_google_auth.py +159 -0
- package/conny_i18n.py +619 -0
- package/conny_init.py +509 -0
- package/conny_integrations/__init__.py +4 -0
- package/conny_integrations/llm.py +1 -0
- package/conny_integrations/vault.py +77 -0
- package/conny_integrations/whatsapp.py +1 -0
- package/conny_intelligence.py +65 -0
- package/conny_learning.py +154 -0
- package/conny_memory.py +243 -0
- package/conny_memory_engine.py +292 -0
- package/conny_nova_proxy.py +170 -0
- package/conny_nuke_robot_phrases.py +493 -0
- package/conny_pairing.py +253 -0
- package/conny_patch.py +291 -0
- package/conny_persona_cli.py +150 -0
- package/conny_router.py +308 -0
- package/conny_runtime_ops.py +271 -0
- package/conny_session.py +516 -0
- package/conny_skills/__init__.py +1 -0
- package/conny_skills/demo_mode.py +35 -0
- package/conny_skills/text_processing.py +1 -0
- package/conny_skills/tone_detection.py +1 -0
- package/conny_smart_features.py +333 -0
- package/conny_studio.py +161 -0
- package/conny_sync_fix.py +306 -0
- package/conny_tui.py +512 -0
- package/conny_tui_select.py +202 -0
- package/conny_ultra_config.py +411 -0
- package/conny_uncertainty.py +174 -0
- package/conny_utils.py +87 -0
- package/conny_voice.py +156 -0
- package/conny_voice_engine.py +124 -0
- package/conny_web_search.py +66 -0
- package/conny_weekly_report.py +85 -0
- package/conny_worm.py +88 -0
- package/core/__init__.py +25 -0
- package/ecosystem.config.js +24 -0
- package/fix_init.py +27 -0
- package/install.sh +78 -0
- package/knowledge_base.py +330 -0
- package/nova/rules/default.yaml +37 -0
- package/nova_bridge.py +509 -0
- package/npm/conny.js +471 -0
- package/package.json +102 -0
- package/personas/conny/base/default.yaml +35 -0
- package/personas/conny/base/estetica_whatsapp.yaml +36 -0
- package/requirements.txt +14 -0
- package/run.sh +47 -0
- package/search.py +465 -0
- package/smart_handoff.py +1150 -0
- package/src/__init__.py +0 -0
- package/src/conny/__init__.py +0 -0
- package/src/conny/admin/__init__.py +0 -0
- package/src/conny/admin/api.py +234 -0
- package/src/conny/admin/dashboard.py +772 -0
- package/src/conny/api/__init__.py +0 -0
- package/src/conny/api/routes.py +8851 -0
- package/src/conny/brain/__init__.py +15 -0
- package/src/conny/brain/engine.py +804 -0
- package/src/conny/brain/learning.py +154 -0
- package/src/conny/brain/memory.py +324 -0
- package/src/conny/brain/smart_features.py +333 -0
- package/src/conny/brain/uncertainty.py +167 -0
- package/src/conny/channels/__init__.py +0 -0
- package/src/conny/channels/audio.py +316 -0
- package/src/conny/channels/cli.py +11795 -0
- package/src/conny/channels/logo_art.py +11 -0
- package/src/conny/channels/voice.py +156 -0
- package/src/conny/core/__init__.py +0 -0
- package/src/conny/core/config.py +215 -0
- package/src/conny/core/cron.py +72 -0
- package/src/conny/core/messenger.py +563 -0
- package/src/conny/core/router.py +297 -0
- package/src/conny/core/session.py +312 -0
- package/src/conny/demo/__init__.py +0 -0
- package/src/conny/demo/handler.py +3110 -0
- package/src/conny/integrations/__init__.py +19 -0
- package/src/conny/integrations/calendar.py +169 -0
- package/src/conny/integrations/knowledge.py +312 -0
- package/src/conny/integrations/search.py +66 -0
- package/src/conny/personas/__init__.py +0 -0
- package/src/conny/personas/generator.py +447 -0
- package/src/conny/production/__init__.py +0 -0
- package/src/conny/production/domino.py +696 -0
- package/src/conny/production/guard.py +550 -0
- package/src/conny/production/handoff.py +1150 -0
- package/src/conny/production/monitor.py +353 -0
- package/src/conny/utils/__init__.py +2 -0
- package/src/conny/utils/helpers.py +75 -0
- package/src/conny/utils/i18n.py +619 -0
- package/src/core/admin_engines.py +772 -0
- package/src/core/globals.py +11845 -0
- package/src/core/orchestrator.py +273 -0
- package/src/core/production_monitor.py +353 -0
- package/src/core/runtime.py +5487 -0
- package/src/domain/onboarding_flow.py +230 -0
- package/src/domain/prompts/__init__.py +1 -0
- package/src/domain/prompts/prospect_pitch.py +282 -0
- package/src/domain/send_guard.py +636 -0
- package/src/domain/swarm/queen.py +96 -0
- package/src/infrastructure/llm_providers/engine.py +487 -0
- package/src/interfaces/mcp_server.py +73 -0
- package/src/interfaces/nova_bridge.py +58 -0
- package/src/interfaces/web/admin_api.py +1379 -0
- package/src/interfaces/web/app.py +9408 -0
- package/src/interfaces/web/demo_handler.py +3450 -0
- package/src/interfaces/web/static/generate_avatars.py +46 -0
- package/v7/__init__.py +46 -0
- package/v7/agents/__init__.py +46 -0
- package/v7/agents/agenda.py +77 -0
- package/v7/agents/base.py +216 -0
- package/v7/agents/captacion.py +60 -0
- package/v7/agents/conocimiento.py +69 -0
- package/v7/agents/escalacion.py +83 -0
- package/v7/agents/objeciones.py +109 -0
- package/v7/agents/seguimiento.py +71 -0
- package/v7/memory/__init__.py +46 -0
- package/v7/memory/patient_profile.py +200 -0
- package/v7/orchestrator.py +275 -0
- package/v7/postprocess.py +127 -0
- package/v7/router.py +239 -0
- package/verify_conversation_impl.py +48 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""conny_calendar.py — Google Calendar: check availability + book appointments."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import json, logging, os
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, List, Optional
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger("conny.calendar")
|
|
10
|
+
|
|
11
|
+
VAULT_DIR = Path("integrations/vault")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConnyCalendar:
|
|
15
|
+
"""Google Calendar integration for booking appointments."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, instance_id: str = "default"):
|
|
18
|
+
self.instance_id = instance_id
|
|
19
|
+
self._tokens_file = VAULT_DIR / instance_id / "google_tokens.json"
|
|
20
|
+
self._creds_file = VAULT_DIR / instance_id / "google_credentials.json"
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def is_connected(self) -> bool:
|
|
24
|
+
return self._tokens_file.exists()
|
|
25
|
+
|
|
26
|
+
async def _get_access_token(self) -> Optional[str]:
|
|
27
|
+
"""Get valid access token (refresh if needed)."""
|
|
28
|
+
if not self._tokens_file.exists():
|
|
29
|
+
return None
|
|
30
|
+
tokens = json.loads(self._tokens_file.read_text())
|
|
31
|
+
refresh_token = tokens.get("refresh_token")
|
|
32
|
+
if not refresh_token:
|
|
33
|
+
return tokens.get("access_token")
|
|
34
|
+
|
|
35
|
+
# Load client creds
|
|
36
|
+
if not self._creds_file.exists():
|
|
37
|
+
return tokens.get("access_token")
|
|
38
|
+
creds_data = json.loads(self._creds_file.read_text())
|
|
39
|
+
inner = creds_data.get("installed") or creds_data.get("web") or creds_data
|
|
40
|
+
client_id = inner.get("client_id", "")
|
|
41
|
+
client_secret = inner.get("client_secret", "")
|
|
42
|
+
|
|
43
|
+
if not client_id:
|
|
44
|
+
return tokens.get("access_token")
|
|
45
|
+
|
|
46
|
+
# Refresh
|
|
47
|
+
try:
|
|
48
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
49
|
+
r = await client.post("https://oauth2.googleapis.com/token", data={
|
|
50
|
+
"client_id": client_id,
|
|
51
|
+
"client_secret": client_secret,
|
|
52
|
+
"refresh_token": refresh_token,
|
|
53
|
+
"grant_type": "refresh_token",
|
|
54
|
+
})
|
|
55
|
+
if r.status_code == 200:
|
|
56
|
+
new = r.json()
|
|
57
|
+
tokens["access_token"] = new["access_token"]
|
|
58
|
+
self._tokens_file.write_text(json.dumps(tokens, indent=2))
|
|
59
|
+
return new["access_token"]
|
|
60
|
+
except Exception as e:
|
|
61
|
+
log.warning(f"[calendar] refresh failed: {e}")
|
|
62
|
+
|
|
63
|
+
return tokens.get("access_token")
|
|
64
|
+
|
|
65
|
+
async def get_availability(self, date: str = None, days_ahead: int = 3) -> List[Dict]:
|
|
66
|
+
"""Get available slots for the next N days."""
|
|
67
|
+
token = await self._get_access_token()
|
|
68
|
+
if not token:
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
if not date:
|
|
72
|
+
start = datetime.now()
|
|
73
|
+
else:
|
|
74
|
+
try:
|
|
75
|
+
start = datetime.strptime(date, "%Y-%m-%d")
|
|
76
|
+
except:
|
|
77
|
+
start = datetime.now()
|
|
78
|
+
|
|
79
|
+
end = start + timedelta(days=days_ahead)
|
|
80
|
+
time_min = start.isoformat() + "Z"
|
|
81
|
+
time_max = end.isoformat() + "Z"
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
85
|
+
r = await client.get(
|
|
86
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
|
|
87
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
88
|
+
params={"timeMin": time_min, "timeMax": time_max,
|
|
89
|
+
"singleEvents": "true", "orderBy": "startTime"},
|
|
90
|
+
)
|
|
91
|
+
if r.status_code == 200:
|
|
92
|
+
events = r.json().get("items", [])
|
|
93
|
+
return [{"summary": e.get("summary", "Ocupado"),
|
|
94
|
+
"start": e.get("start", {}).get("dateTime", ""),
|
|
95
|
+
"end": e.get("end", {}).get("dateTime", "")}
|
|
96
|
+
for e in events]
|
|
97
|
+
else:
|
|
98
|
+
log.warning(f"[calendar] events fetch failed: {r.status_code}")
|
|
99
|
+
return []
|
|
100
|
+
except Exception as e:
|
|
101
|
+
log.error(f"[calendar] error: {e}")
|
|
102
|
+
return []
|
|
103
|
+
|
|
104
|
+
async def create_appointment(self, patient_name: str, phone: str,
|
|
105
|
+
service: str, date_time: str,
|
|
106
|
+
duration_minutes: int = 30) -> Optional[str]:
|
|
107
|
+
"""Create a calendar event for an appointment."""
|
|
108
|
+
token = await self._get_access_token()
|
|
109
|
+
if not token:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
start_dt = datetime.fromisoformat(date_time)
|
|
114
|
+
except:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
end_dt = start_dt + timedelta(minutes=duration_minutes)
|
|
118
|
+
|
|
119
|
+
event = {
|
|
120
|
+
"summary": f"Cita: {patient_name} — {service}",
|
|
121
|
+
"description": f"Paciente: {patient_name}\nTeléfono: {phone}\nServicio: {service}\nAgendado por: Conny AI",
|
|
122
|
+
"start": {"dateTime": start_dt.isoformat(), "timeZone": "America/Bogota"},
|
|
123
|
+
"end": {"dateTime": end_dt.isoformat(), "timeZone": "America/Bogota"},
|
|
124
|
+
"reminders": {
|
|
125
|
+
"useDefault": False,
|
|
126
|
+
"overrides": [
|
|
127
|
+
{"method": "popup", "minutes": 60},
|
|
128
|
+
{"method": "popup", "minutes": 1440},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
135
|
+
r = await client.post(
|
|
136
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
|
|
137
|
+
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
|
138
|
+
json=event,
|
|
139
|
+
)
|
|
140
|
+
if r.status_code in (200, 201):
|
|
141
|
+
created = r.json()
|
|
142
|
+
log.info(f"[calendar] appointment created: {created.get('id')}")
|
|
143
|
+
return created.get("id")
|
|
144
|
+
else:
|
|
145
|
+
log.error(f"[calendar] create failed: {r.status_code} {r.text[:200]}")
|
|
146
|
+
return None
|
|
147
|
+
except Exception as e:
|
|
148
|
+
log.error(f"[calendar] create error: {e}")
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
async def get_availability_summary(self, days_ahead: int = 3) -> str:
|
|
152
|
+
"""Get human-readable availability summary for Conny to use."""
|
|
153
|
+
events = await self.get_availability(days_ahead=days_ahead)
|
|
154
|
+
if not events:
|
|
155
|
+
return "sin citas programadas los próximos días — disponibilidad abierta"
|
|
156
|
+
|
|
157
|
+
busy_times = []
|
|
158
|
+
for e in events[:10]:
|
|
159
|
+
start = e.get("start", "")
|
|
160
|
+
if start:
|
|
161
|
+
try:
|
|
162
|
+
dt = datetime.fromisoformat(start.replace("Z", ""))
|
|
163
|
+
busy_times.append(f"{dt.strftime('%A %d')} a las {dt.strftime('%I:%M%p')}: {e['summary']}")
|
|
164
|
+
except:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
if busy_times:
|
|
168
|
+
return "Citas existentes:\n" + "\n".join(busy_times)
|
|
169
|
+
return "calendario disponible"
|