@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,316 @@
|
|
|
1
|
+
"""Audio transcription handler with multi-provider fallback (Gemini, Groq, OpenRouter)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
from typing import Optional, Tuple
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
# TODO: replace with `from ..core.config import Config` once core.config exists
|
|
13
|
+
from conny_config import Config
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger("conny.audio")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AudioHandler:
|
|
19
|
+
"""
|
|
20
|
+
Audio transcription handler with multiple providers:
|
|
21
|
+
- Gemini 2.0 Flash (primary, best context comprehension)
|
|
22
|
+
- Groq Whisper (fallback 1)
|
|
23
|
+
- OpenRouter Whisper (fallback 2)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self._audio_cache: dict = {}
|
|
28
|
+
|
|
29
|
+
def _audio_suffix(self, mime: str) -> str:
|
|
30
|
+
"""Map mime type to file extension."""
|
|
31
|
+
mapping = {
|
|
32
|
+
"audio/ogg": ".ogg",
|
|
33
|
+
"audio/oga": ".ogg",
|
|
34
|
+
"audio/opus": ".ogg",
|
|
35
|
+
"audio/mp3": ".mp3",
|
|
36
|
+
"audio/mpeg": ".mp3",
|
|
37
|
+
"audio/wav": ".wav",
|
|
38
|
+
"audio/x-wav": ".wav",
|
|
39
|
+
"audio/mp4": ".m4a",
|
|
40
|
+
"audio/x-m4a": ".m4a",
|
|
41
|
+
"audio/webm": ".webm",
|
|
42
|
+
}
|
|
43
|
+
return mapping.get((mime or "").lower(), ".ogg")
|
|
44
|
+
|
|
45
|
+
async def transcribe_audio(
|
|
46
|
+
self,
|
|
47
|
+
file_id: str,
|
|
48
|
+
platform: str = "telegram",
|
|
49
|
+
wa_media_id: str = None,
|
|
50
|
+
) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Transcribe audio via Gemini 2.0 Flash (native) with Whisper fallback.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
file_id: Audio file ID
|
|
56
|
+
platform: Source platform (telegram, whatsapp, whatsapp_cloud)
|
|
57
|
+
wa_media_id: WhatsApp Cloud media ID (if applicable)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Transcribed text or error message
|
|
61
|
+
"""
|
|
62
|
+
audio_bytes, mime_type = None, "audio/ogg"
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
66
|
+
if platform == "telegram":
|
|
67
|
+
r = await client.get(
|
|
68
|
+
f"https://api.telegram.org/bot{Config.TELEGRAM_TOKEN}/getFile",
|
|
69
|
+
params={"file_id": file_id},
|
|
70
|
+
)
|
|
71
|
+
fp = r.json()["result"]["file_path"]
|
|
72
|
+
ext = fp.rsplit(".", 1)[-1].lower() if "." in fp else "ogg"
|
|
73
|
+
mime_type = {
|
|
74
|
+
"ogg": "audio/ogg", "mp3": "audio/mp3", "wav": "audio/wav",
|
|
75
|
+
"m4a": "audio/mp4", "oga": "audio/ogg", "opus": "audio/ogg",
|
|
76
|
+
}.get(ext, "audio/ogg")
|
|
77
|
+
ar = await client.get(
|
|
78
|
+
f"https://api.telegram.org/file/bot{Config.TELEGRAM_TOKEN}/{fp}"
|
|
79
|
+
)
|
|
80
|
+
audio_bytes = ar.content
|
|
81
|
+
|
|
82
|
+
elif platform == "whatsapp_cloud" and wa_media_id:
|
|
83
|
+
mr = await client.get(
|
|
84
|
+
f"https://graph.facebook.com/v20.0/{wa_media_id}",
|
|
85
|
+
headers={"Authorization": f"Bearer {Config.WA_ACCESS_TOKEN}"},
|
|
86
|
+
)
|
|
87
|
+
url = mr.json().get("url", "")
|
|
88
|
+
if url:
|
|
89
|
+
dl = await client.get(
|
|
90
|
+
url,
|
|
91
|
+
headers={"Authorization": f"Bearer {Config.WA_ACCESS_TOKEN}"},
|
|
92
|
+
)
|
|
93
|
+
audio_bytes, mime_type = (
|
|
94
|
+
dl.content,
|
|
95
|
+
mr.json().get("mime_type", "audio/ogg"),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# WhatsApp Bridge (Baileys) — audio base64 inline
|
|
99
|
+
if platform == "whatsapp" and file_id.startswith("wa_b64:"):
|
|
100
|
+
try:
|
|
101
|
+
_, mime_part, b64_data = file_id.split(":", 2)
|
|
102
|
+
mime_type = mime_part or "audio/ogg"
|
|
103
|
+
audio_bytes = base64.b64decode(b64_data)
|
|
104
|
+
except Exception:
|
|
105
|
+
return "[no pude escuchar, puedes escribirlo?]"
|
|
106
|
+
|
|
107
|
+
if not audio_bytes:
|
|
108
|
+
return "[no pude escuchar, puedes escribirlo?]"
|
|
109
|
+
|
|
110
|
+
# Try transcription with Gemini 2.0 Flash
|
|
111
|
+
result = await self._transcribe_gemini(audio_bytes, mime_type)
|
|
112
|
+
if result:
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
# Fallback 1: Groq Whisper
|
|
116
|
+
result = await self._transcribe_groq_whisper(audio_bytes, mime_type)
|
|
117
|
+
if result:
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
# Fallback 2: OpenRouter Whisper
|
|
121
|
+
result = await self._transcribe_openrouter_whisper(audio_bytes, mime_type)
|
|
122
|
+
if result:
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
return "[no se pudo transcribir el audio]"
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
log.error(f"[audio] Error: {e}", exc_info=True)
|
|
129
|
+
return "[no pude escuchar, puedes escribirlo?]"
|
|
130
|
+
|
|
131
|
+
async def _transcribe_gemini(self, audio_bytes: bytes, mime_type: str) -> Optional[str]:
|
|
132
|
+
"""Transcribe using Gemini 2.0 Flash."""
|
|
133
|
+
effective_mime = "audio/ogg" if mime_type in ("audio/oga", "audio/opus") else mime_type
|
|
134
|
+
|
|
135
|
+
gemini_keys = Config.GEMINI_API_KEYS or [
|
|
136
|
+
k for k in [
|
|
137
|
+
Config.GEMINI_API_KEY,
|
|
138
|
+
Config.GEMINI_API_KEY_2,
|
|
139
|
+
Config.GEMINI_API_KEY_3,
|
|
140
|
+
Config.GEMINI_API_KEY_4,
|
|
141
|
+
Config.GEMINI_API_KEY_5,
|
|
142
|
+
Config.GEMINI_API_KEY_6,
|
|
143
|
+
] if k
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
for gkey in gemini_keys:
|
|
147
|
+
try:
|
|
148
|
+
b64 = base64.b64encode(audio_bytes).decode()
|
|
149
|
+
payload = {
|
|
150
|
+
"contents": [{
|
|
151
|
+
"parts": [
|
|
152
|
+
{"inline_data": {"mime_type": effective_mime, "data": b64}},
|
|
153
|
+
{
|
|
154
|
+
"text": "Transcribe este mensaje de voz en español exactamente "
|
|
155
|
+
"como se dice. Devuelve SOLO el texto transcrito, sin "
|
|
156
|
+
"comillas ni comentarios. Mantén el tono coloquial tal como se habla."
|
|
157
|
+
},
|
|
158
|
+
]
|
|
159
|
+
}],
|
|
160
|
+
"generationConfig": {"temperature": 0.0, "maxOutputTokens": 500},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async with httpx.AsyncClient(timeout=25.0) as client:
|
|
164
|
+
resp = await client.post(
|
|
165
|
+
f"https://generativelanguage.googleapis.com/v1beta/models/"
|
|
166
|
+
f"gemini-2.5-flash:generateContent?key={gkey}",
|
|
167
|
+
json=payload,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if resp.status_code == 200:
|
|
171
|
+
parts = resp.json().get("candidates", [{}])[0].get("content", {}).get("parts", [{}])
|
|
172
|
+
t = parts[0].get("text", "").strip() if parts else ""
|
|
173
|
+
if t and len(t) > 2:
|
|
174
|
+
log.info(f"[audio] Gemini OK: {t[:80]}")
|
|
175
|
+
return t
|
|
176
|
+
elif resp.status_code in (408, 429, 500, 502, 503, 504):
|
|
177
|
+
continue # rotate key
|
|
178
|
+
else:
|
|
179
|
+
log.warning(f"[audio] Gemini {resp.status_code}: {resp.text[:120]}")
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
except Exception as eg:
|
|
183
|
+
log.warning(f"[audio] Gemini error: {eg}")
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
async def _transcribe_groq_whisper(
|
|
189
|
+
self,
|
|
190
|
+
audio_bytes: bytes,
|
|
191
|
+
mime_type: str,
|
|
192
|
+
) -> Optional[str]:
|
|
193
|
+
"""Transcribe using Groq Whisper (fallback 1)."""
|
|
194
|
+
if not Config.GROQ_API_KEY:
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
tmp_path = None
|
|
198
|
+
try:
|
|
199
|
+
suffix = self._audio_suffix(mime_type)
|
|
200
|
+
filename = f"audio{suffix}"
|
|
201
|
+
|
|
202
|
+
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
|
|
203
|
+
tmp.write(audio_bytes)
|
|
204
|
+
tmp_path = tmp.name
|
|
205
|
+
|
|
206
|
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
207
|
+
with open(tmp_path, "rb") as f:
|
|
208
|
+
resp = await client.post(
|
|
209
|
+
"https://api.groq.com/openai/v1/audio/transcriptions",
|
|
210
|
+
headers={"Authorization": f"Bearer {Config.GROQ_API_KEY}"},
|
|
211
|
+
files={"file": (filename, f, mime_type)},
|
|
212
|
+
data={
|
|
213
|
+
"model": "whisper-large-v3-turbo",
|
|
214
|
+
"language": "es",
|
|
215
|
+
"response_format": "json",
|
|
216
|
+
"temperature": "0",
|
|
217
|
+
"prompt": "Transcribe este audio en español tal como se dice, sin comentarios adicionales.",
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if tmp_path and os.path.exists(tmp_path):
|
|
222
|
+
os.unlink(tmp_path)
|
|
223
|
+
|
|
224
|
+
if resp.status_code == 200:
|
|
225
|
+
payload = resp.json()
|
|
226
|
+
t = (payload.get("text") or "").strip()
|
|
227
|
+
if t:
|
|
228
|
+
log.info(f"[audio] Groq Whisper OK: {t[:80]}")
|
|
229
|
+
return t
|
|
230
|
+
else:
|
|
231
|
+
log.warning(f"[audio] Groq Whisper {resp.status_code}: {resp.text[:160]}")
|
|
232
|
+
|
|
233
|
+
except Exception as eg:
|
|
234
|
+
log.warning(f"[audio] Groq Whisper error: {eg}")
|
|
235
|
+
if tmp_path and os.path.exists(tmp_path):
|
|
236
|
+
try:
|
|
237
|
+
os.unlink(tmp_path)
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
async def _transcribe_openrouter_whisper(
|
|
244
|
+
self,
|
|
245
|
+
audio_bytes: bytes,
|
|
246
|
+
mime_type: str,
|
|
247
|
+
) -> Optional[str]:
|
|
248
|
+
"""Transcribe using OpenRouter Whisper (fallback 2)."""
|
|
249
|
+
if not Config.OPENROUTER_API_KEY:
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
tmp_path = None
|
|
253
|
+
try:
|
|
254
|
+
suffix = self._audio_suffix(mime_type)
|
|
255
|
+
filename = f"audio{suffix}"
|
|
256
|
+
|
|
257
|
+
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
|
|
258
|
+
tmp.write(audio_bytes)
|
|
259
|
+
tmp_path = tmp.name
|
|
260
|
+
|
|
261
|
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
262
|
+
with open(tmp_path, "rb") as f:
|
|
263
|
+
resp = await client.post(
|
|
264
|
+
"https://openrouter.ai/api/v1/audio/transcriptions",
|
|
265
|
+
headers={"Authorization": f"Bearer {Config.OPENROUTER_API_KEY}"},
|
|
266
|
+
files={"file": (filename, f, mime_type)},
|
|
267
|
+
data={
|
|
268
|
+
"model": getattr(Config, "WHISPER_MODEL", "openai/whisper-large-v3"),
|
|
269
|
+
"language": "es",
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if tmp_path and os.path.exists(tmp_path):
|
|
274
|
+
os.unlink(tmp_path)
|
|
275
|
+
|
|
276
|
+
if resp.status_code == 200:
|
|
277
|
+
t = resp.json().get("text", "").strip()
|
|
278
|
+
if t:
|
|
279
|
+
log.info(f"[audio] Whisper OK: {t[:80]}")
|
|
280
|
+
return t
|
|
281
|
+
else:
|
|
282
|
+
log.warning(f"[audio] OpenRouter Whisper {resp.status_code}: {resp.text[:160]}")
|
|
283
|
+
|
|
284
|
+
except Exception as ew:
|
|
285
|
+
log.warning(f"[audio] Whisper error: {ew}")
|
|
286
|
+
if tmp_path and os.path.exists(tmp_path):
|
|
287
|
+
try:
|
|
288
|
+
os.unlink(tmp_path)
|
|
289
|
+
except Exception:
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
def clear_cache(self) -> None:
|
|
295
|
+
"""Clear the audio cache."""
|
|
296
|
+
self._audio_cache.clear()
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
async def transcribe_audio(
|
|
300
|
+
file_id: str,
|
|
301
|
+
platform: str = "telegram",
|
|
302
|
+
wa_media_id: str = None,
|
|
303
|
+
) -> str:
|
|
304
|
+
"""
|
|
305
|
+
Convenience function for audio transcription.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
file_id: Audio file ID
|
|
309
|
+
platform: Source platform
|
|
310
|
+
wa_media_id: WhatsApp Cloud media ID
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Transcribed text or error message
|
|
314
|
+
"""
|
|
315
|
+
handler = AudioHandler()
|
|
316
|
+
return await handler.transcribe_audio(file_id, platform, wa_media_id)
|