wappa 0.1.7__py3-none-any.whl → 0.1.9__py3-none-any.whl
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.
Potentially problematic release.
This version of wappa might be problematic. Click here for more details.
- wappa/cli/examples/init/.env.example +33 -0
- wappa/cli/examples/init/app/__init__.py +0 -0
- wappa/cli/examples/init/app/main.py +8 -0
- wappa/cli/examples/init/app/master_event.py +8 -0
- wappa/cli/examples/json_cache_example/.env.example +33 -0
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/main.py +235 -0
- wappa/cli/examples/json_cache_example/app/master_event.py +419 -0
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +275 -0
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +186 -0
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +248 -0
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +190 -0
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +260 -0
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +223 -0
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +176 -0
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +246 -0
- wappa/cli/examples/openai_transcript/.gitignore +63 -4
- wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
- wappa/cli/examples/openai_transcript/app/main.py +8 -0
- wappa/cli/examples/openai_transcript/app/master_event.py +53 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +76 -0
- wappa/cli/examples/redis_cache_example/.env.example +33 -0
- wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
- wappa/cli/examples/redis_cache_example/app/main.py +234 -0
- wappa/cli/examples/redis_cache_example/app/master_event.py +419 -0
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +275 -0
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +186 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +248 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +190 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +260 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +223 -0
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +176 -0
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +246 -0
- wappa/cli/examples/simple_echo_example/.env.example +33 -0
- wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
- wappa/cli/examples/simple_echo_example/app/main.py +183 -0
- wappa/cli/examples/simple_echo_example/app/master_event.py +209 -0
- wappa/cli/examples/wappa_full_example/.env.example +33 -0
- wappa/cli/examples/wappa_full_example/.gitignore +63 -4
- wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +484 -0
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +551 -0
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +492 -0
- wappa/cli/examples/wappa_full_example/app/main.py +257 -0
- wappa/cli/examples/wappa_full_example/app/master_event.py +445 -0
- wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
- wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +425 -0
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +287 -0
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +301 -0
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +483 -0
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +473 -0
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +298 -0
- wappa/cli/main.py +8 -4
- wappa/core/config/settings.py +34 -2
- wappa/persistence/__init__.py +2 -2
- {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/METADATA +1 -1
- {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/RECORD +77 -13
- wappa/cli/examples/init/pyproject.toml +0 -7
- wappa/cli/examples/simple_echo_example/.python-version +0 -1
- wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
- {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/WHEEL +0 -0
- {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.7.dist-info → wappa-0.1.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Message processing utility functions following Single Responsibility Principle.
|
|
3
|
+
|
|
4
|
+
This module provides message-related helper functions used across
|
|
5
|
+
different score modules for consistent message handling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Dict, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
from wappa.webhooks import IncomingMessageWebhook
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def extract_user_data(webhook: IncomingMessageWebhook) -> Dict[str, str]:
|
|
15
|
+
"""
|
|
16
|
+
Extract user data from webhook in a standardized format.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
webhook: Incoming message webhook
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Dictionary with standardized user data
|
|
23
|
+
"""
|
|
24
|
+
return {
|
|
25
|
+
'user_id': webhook.user.user_id,
|
|
26
|
+
'user_name': webhook.user.profile_name or "Unknown User",
|
|
27
|
+
'tenant_id': webhook.tenant.get_tenant_key(),
|
|
28
|
+
'message_id': webhook.message.message_id,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sanitize_message_text(text: str, max_length: int = 500) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Sanitize message text for safe storage and processing.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
text: Raw message text
|
|
38
|
+
max_length: Maximum allowed length
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Sanitized message text
|
|
42
|
+
"""
|
|
43
|
+
if not text:
|
|
44
|
+
return ""
|
|
45
|
+
|
|
46
|
+
# Convert to string and strip whitespace
|
|
47
|
+
sanitized = str(text).strip()
|
|
48
|
+
|
|
49
|
+
# Truncate if too long
|
|
50
|
+
if len(sanitized) > max_length:
|
|
51
|
+
sanitized = sanitized[:max_length-3] + "..."
|
|
52
|
+
|
|
53
|
+
# Replace problematic characters
|
|
54
|
+
sanitized = sanitized.replace('\x00', '').replace('\r\n', '\n')
|
|
55
|
+
|
|
56
|
+
return sanitized
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def format_timestamp(dt: datetime, format_type: str = 'display') -> str:
|
|
60
|
+
"""
|
|
61
|
+
Format timestamps consistently across the application.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
dt: Datetime to format
|
|
65
|
+
format_type: Format type ('display', 'compact', 'iso')
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Formatted timestamp string
|
|
69
|
+
"""
|
|
70
|
+
if format_type == 'display':
|
|
71
|
+
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
72
|
+
elif format_type == 'compact':
|
|
73
|
+
return dt.strftime("%m/%d %H:%M")
|
|
74
|
+
elif format_type == 'iso':
|
|
75
|
+
return dt.isoformat()
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError(f"Unknown format_type: {format_type}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def extract_command_from_message(text: str) -> Tuple[Optional[str], str]:
|
|
81
|
+
"""
|
|
82
|
+
Extract command and remaining text from message.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
text: Message text
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Tuple of (command, remaining_text)
|
|
89
|
+
Command is None if no command found
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
>>> extract_command_from_message("/WAPPA hello")
|
|
93
|
+
("/WAPPA", "hello")
|
|
94
|
+
>>> extract_command_from_message("regular message")
|
|
95
|
+
(None, "regular message")
|
|
96
|
+
"""
|
|
97
|
+
if not text:
|
|
98
|
+
return None, ""
|
|
99
|
+
|
|
100
|
+
text = text.strip()
|
|
101
|
+
|
|
102
|
+
# Check if it starts with a command
|
|
103
|
+
if text.startswith('/'):
|
|
104
|
+
parts = text.split(' ', 1)
|
|
105
|
+
command = parts[0].upper()
|
|
106
|
+
remaining = parts[1] if len(parts) > 1 else ""
|
|
107
|
+
return command, remaining
|
|
108
|
+
|
|
109
|
+
return None, text
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def is_special_command(text: str) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Check if message text contains a special command.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
text: Message text to check
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
True if text contains a recognized command
|
|
121
|
+
"""
|
|
122
|
+
command, _ = extract_command_from_message(text)
|
|
123
|
+
|
|
124
|
+
if not command:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
# List of recognized commands
|
|
128
|
+
special_commands = ['/WAPPA', '/EXIT', '/HISTORY', '/HELP', '/STATUS']
|
|
129
|
+
|
|
130
|
+
return command in special_commands
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_message_type_display_name(message_type: str) -> str:
|
|
134
|
+
"""
|
|
135
|
+
Get human-readable display name for message types.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
message_type: Technical message type
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Human-readable display name
|
|
142
|
+
"""
|
|
143
|
+
type_mapping = {
|
|
144
|
+
'text': 'Text',
|
|
145
|
+
'image': 'Image',
|
|
146
|
+
'audio': 'Audio',
|
|
147
|
+
'video': 'Video',
|
|
148
|
+
'document': 'Document',
|
|
149
|
+
'location': 'Location',
|
|
150
|
+
'contacts': 'Contact',
|
|
151
|
+
'interactive': 'Interactive',
|
|
152
|
+
'button': 'Button Response',
|
|
153
|
+
'list': 'List Response',
|
|
154
|
+
'sticker': 'Sticker',
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return type_mapping.get(message_type.lower(), message_type.title())
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def create_user_greeting(user_name: Optional[str], message_count: int) -> str:
|
|
161
|
+
"""
|
|
162
|
+
Create personalized user greeting message.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
user_name: User's display name (can be None)
|
|
166
|
+
message_count: Number of messages from user
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Personalized greeting text
|
|
170
|
+
"""
|
|
171
|
+
name = user_name or "there"
|
|
172
|
+
|
|
173
|
+
if message_count == 1:
|
|
174
|
+
return f"👋 Hello {name}! Welcome to the Redis Cache Demo!"
|
|
175
|
+
elif message_count < 5:
|
|
176
|
+
return f"👋 Hello {name}! Nice to see you again!"
|
|
177
|
+
else:
|
|
178
|
+
return f"👋 Hello {name}! You're becoming a regular here! ({message_count} messages)"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def format_message_history_display(messages, total_count: int, display_count: int = 20) -> str:
|
|
182
|
+
"""
|
|
183
|
+
Format message history for display to user.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
messages: List of MessageHistory objects
|
|
187
|
+
total_count: Total number of messages in history
|
|
188
|
+
display_count: Number of messages being displayed
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Formatted history text
|
|
192
|
+
"""
|
|
193
|
+
if not messages:
|
|
194
|
+
return "📚 Your message history is empty. Start chatting to build your history!"
|
|
195
|
+
|
|
196
|
+
history_text = f"📚 Your Message History ({total_count} total messages):\n\n"
|
|
197
|
+
|
|
198
|
+
for i, msg_history in enumerate(messages, 1):
|
|
199
|
+
timestamp_str = format_timestamp(msg_history.timestamp, 'compact')
|
|
200
|
+
msg_type = f"[{get_message_type_display_name(msg_history.message_type)}]" if msg_history.message_type != "text" else ""
|
|
201
|
+
|
|
202
|
+
# Truncate long messages for display
|
|
203
|
+
display_message = sanitize_message_text(msg_history.message, 50)
|
|
204
|
+
|
|
205
|
+
history_text += f"{i:2d}. {timestamp_str} {msg_type} {display_message}\n"
|
|
206
|
+
|
|
207
|
+
if total_count > display_count:
|
|
208
|
+
history_text += f"\n... showing last {display_count} of {total_count} messages"
|
|
209
|
+
|
|
210
|
+
return history_text
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def create_cache_info_message(user_profile, cache_stats) -> str:
|
|
214
|
+
"""
|
|
215
|
+
Create informational message about cache status.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
user_profile: User profile data
|
|
219
|
+
cache_stats: Cache statistics data
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Formatted cache information message
|
|
223
|
+
"""
|
|
224
|
+
info_lines = [
|
|
225
|
+
f"👤 Your Profile:",
|
|
226
|
+
f"• Messages sent: {user_profile.message_count}",
|
|
227
|
+
f"• First seen: {format_timestamp(user_profile.first_seen, 'compact')}",
|
|
228
|
+
f"• Last seen: {format_timestamp(user_profile.last_seen, 'compact')}",
|
|
229
|
+
"",
|
|
230
|
+
f"🎯 Special Commands:",
|
|
231
|
+
f"• Send '/WAPPA' to enter special state",
|
|
232
|
+
f"• Send '/EXIT' to leave special state",
|
|
233
|
+
f"• Send '/HISTORY' to see your message history",
|
|
234
|
+
"",
|
|
235
|
+
f"📊 Cache Statistics:",
|
|
236
|
+
f"• Total operations: {cache_stats.total_operations}",
|
|
237
|
+
f"• User cache hit rate: {cache_stats.get_user_hit_rate():.1%}",
|
|
238
|
+
f"• Active states: {cache_stats.state_cache_active}",
|
|
239
|
+
"",
|
|
240
|
+
f"💾 This demo showcases Redis caching:",
|
|
241
|
+
f"• User data cached in user_cache",
|
|
242
|
+
f"• Message history stored in table_cache per user",
|
|
243
|
+
f"• Commands tracked in state_cache"
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
return "\n".join(info_lines)
|
|
@@ -1,10 +1,69 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Wappa
|
|
2
|
+
logs/
|
|
3
|
+
|
|
4
|
+
# Environment variables
|
|
5
|
+
.env
|
|
6
|
+
.env.local
|
|
7
|
+
.env.development
|
|
8
|
+
.env.production
|
|
9
|
+
|
|
10
|
+
# Python
|
|
2
11
|
__pycache__/
|
|
3
|
-
*.py[
|
|
12
|
+
*.py[cod]
|
|
13
|
+
*$py.class
|
|
14
|
+
*.so
|
|
15
|
+
.Python
|
|
4
16
|
build/
|
|
17
|
+
develop-eggs/
|
|
5
18
|
dist/
|
|
19
|
+
downloads/
|
|
20
|
+
eggs/
|
|
21
|
+
.eggs/
|
|
22
|
+
lib/
|
|
23
|
+
lib64/
|
|
24
|
+
parts/
|
|
25
|
+
sdist/
|
|
26
|
+
var/
|
|
6
27
|
wheels/
|
|
7
|
-
|
|
28
|
+
share/python-wheels/
|
|
29
|
+
*.egg-info/
|
|
30
|
+
.installed.cfg
|
|
31
|
+
*.egg
|
|
32
|
+
MANIFEST
|
|
8
33
|
|
|
9
34
|
# Virtual environments
|
|
10
|
-
.venv
|
|
35
|
+
.venv/
|
|
36
|
+
venv/
|
|
37
|
+
ENV/
|
|
38
|
+
env/
|
|
39
|
+
|
|
40
|
+
# IDE
|
|
41
|
+
.vscode/
|
|
42
|
+
.idea/
|
|
43
|
+
*.swp
|
|
44
|
+
*.swo
|
|
45
|
+
|
|
46
|
+
# OS
|
|
47
|
+
.DS_Store
|
|
48
|
+
Thumbs.db
|
|
49
|
+
|
|
50
|
+
# Logs
|
|
51
|
+
app/logs/
|
|
52
|
+
logs/
|
|
53
|
+
*.log
|
|
54
|
+
|
|
55
|
+
# Redis dumps
|
|
56
|
+
dump.rdb
|
|
57
|
+
|
|
58
|
+
# Temporary files
|
|
59
|
+
.tmp/
|
|
60
|
+
temp/
|
|
61
|
+
tmp/
|
|
62
|
+
|
|
63
|
+
# Coverage reports
|
|
64
|
+
htmlcov/
|
|
65
|
+
.tox/
|
|
66
|
+
.coverage
|
|
67
|
+
.coverage.*
|
|
68
|
+
.cache
|
|
69
|
+
.pytest_cache/
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from wappa import WappaEventHandler
|
|
5
|
+
from wappa.webhooks import IncomingMessageWebhook
|
|
6
|
+
from wappa.core.logging import get_logger
|
|
7
|
+
from wappa.core.config import settings
|
|
8
|
+
|
|
9
|
+
from openai import AsyncOpenAI
|
|
10
|
+
|
|
11
|
+
from .openai_utils import AudioProcessingService
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = get_logger("TranscriptEventHandler")
|
|
16
|
+
|
|
17
|
+
class TranscriptEventHandler(WappaEventHandler):
|
|
18
|
+
|
|
19
|
+
async def process_message(self, webhook: IncomingMessageWebhook):
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
message_type = webhook.get_message_type_name()
|
|
23
|
+
|
|
24
|
+
await self.messenger.mark_as_read(webhook.message.message_id, webhook.user.user_id)
|
|
25
|
+
|
|
26
|
+
if message_type == "audio":
|
|
27
|
+
audio_id = webhook.message.audio.id
|
|
28
|
+
|
|
29
|
+
openai_client = AsyncOpenAI(api_key=settings.openai_api_key)
|
|
30
|
+
audio_service = AudioProcessingService(openai_client)
|
|
31
|
+
|
|
32
|
+
# Option 1: Using tempfile context manager (automatic cleanup)
|
|
33
|
+
async with self.messenger.media_handler.download_media_tempfile(audio_id) as audio_download:
|
|
34
|
+
if audio_download.success:
|
|
35
|
+
transcription = await audio_service.transcribe_audio(audio_download.file_path)
|
|
36
|
+
await self.messenger.send_text(f"*Transcript:*\n\n{transcription}", webhook.user.user_id)
|
|
37
|
+
logger.info(f"Transcribed audio from temp file: {audio_download.file_path}")
|
|
38
|
+
else:
|
|
39
|
+
logger.error(f"Failed to download audio: {audio_download.error}")
|
|
40
|
+
await self.messenger.send_text("Sorry, I couldn't download the audio file.", webhook.user.user_id)
|
|
41
|
+
|
|
42
|
+
# Option 2: Memory-only processing (no files created)
|
|
43
|
+
# Uncomment to use bytes-based processing instead:
|
|
44
|
+
# audio_bytes_result = await self.messenger.media_handler.get_media_as_bytes(audio_id)
|
|
45
|
+
# if audio_bytes_result.success:
|
|
46
|
+
# transcription = await audio_service.transcribe_audio(audio_bytes_result.file_data, "audio.ogg")
|
|
47
|
+
# await self.messenger.send_text(f"*Transcript:*\n\n{transcription}", webhook.user.user_id)
|
|
48
|
+
# logger.info(f"Transcribed audio from memory ({audio_bytes_result.file_size} bytes)")
|
|
49
|
+
# else:
|
|
50
|
+
# logger.error(f"Failed to download audio: {audio_bytes_result.error}")
|
|
51
|
+
# await self.messenger.send_text("Sorry, I couldn't download the audio file.", webhook.user.user_id)
|
|
52
|
+
else:
|
|
53
|
+
await self.messenger.send_text("*Hey Wapp@!*\n\nThis app only receives Audio, send a Voice Note for Transcript", webhook.user.user_id)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Removed direct config import
|
|
2
|
+
# from config.env import OPENAI_API_KEY
|
|
3
|
+
from wappa.core.logging import get_logger # Corrected relative import
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Union, BinaryIO
|
|
6
|
+
import io
|
|
7
|
+
|
|
8
|
+
from openai import AsyncOpenAI
|
|
9
|
+
|
|
10
|
+
# Initialize logger with class name
|
|
11
|
+
logger = get_logger("AudioProcessingService")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AudioProcessingService:
|
|
15
|
+
def __init__(self, async_openai_client: AsyncOpenAI):
|
|
16
|
+
# Accept api_key in init
|
|
17
|
+
self.async_openai_client = async_openai_client
|
|
18
|
+
|
|
19
|
+
async def transcribe_audio(self, audio_source: Union[str, Path, bytes, BinaryIO], filename: str = "audio") -> str:
|
|
20
|
+
"""
|
|
21
|
+
Transcribes the audio using OpenAI's speech-to-text API with the gpt-4o-mini-transcribe model.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
audio_source: Can be:
|
|
25
|
+
- str/Path: Path to the audio file to transcribe
|
|
26
|
+
- bytes: Raw audio data
|
|
27
|
+
- BinaryIO: File-like object containing audio data
|
|
28
|
+
filename: Name for the audio (used for OpenAI API, especially for bytes/BinaryIO)
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
The transcribed text.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
# Handle different input types
|
|
35
|
+
if isinstance(audio_source, (str, Path)):
|
|
36
|
+
# File path input (original behavior)
|
|
37
|
+
audio_path = Path(audio_source)
|
|
38
|
+
with audio_path.open("rb") as audio_file:
|
|
39
|
+
transcription = await self.async_openai_client.audio.transcriptions.create(
|
|
40
|
+
model="gpt-4o-mini-transcribe",
|
|
41
|
+
file=audio_file,
|
|
42
|
+
response_format="json",
|
|
43
|
+
)
|
|
44
|
+
logger.debug(f"Transcription successful for file: {audio_path}")
|
|
45
|
+
|
|
46
|
+
elif isinstance(audio_source, bytes):
|
|
47
|
+
# Bytes input - create BytesIO stream
|
|
48
|
+
audio_stream = io.BytesIO(audio_source)
|
|
49
|
+
audio_stream.name = filename # OpenAI API needs a filename attribute
|
|
50
|
+
transcription = await self.async_openai_client.audio.transcriptions.create(
|
|
51
|
+
model="gpt-4o-mini-transcribe",
|
|
52
|
+
file=audio_stream,
|
|
53
|
+
response_format="json",
|
|
54
|
+
)
|
|
55
|
+
logger.debug(f"Transcription successful for bytes data ({len(audio_source)} bytes)")
|
|
56
|
+
|
|
57
|
+
else:
|
|
58
|
+
# File-like object input
|
|
59
|
+
# Ensure it has a name attribute for OpenAI API
|
|
60
|
+
if not hasattr(audio_source, 'name'):
|
|
61
|
+
audio_source.name = filename
|
|
62
|
+
transcription = await self.async_openai_client.audio.transcriptions.create(
|
|
63
|
+
model="gpt-4o-mini-transcribe",
|
|
64
|
+
file=audio_source,
|
|
65
|
+
response_format="json",
|
|
66
|
+
)
|
|
67
|
+
logger.debug(f"Transcription successful for file-like object: {getattr(audio_source, 'name', 'unknown')}")
|
|
68
|
+
|
|
69
|
+
return transcription.text
|
|
70
|
+
|
|
71
|
+
except FileNotFoundError:
|
|
72
|
+
logger.error(f"Audio file not found for transcription: {audio_source}")
|
|
73
|
+
raise
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Error during OpenAI transcription call: {e}", exc_info=True)
|
|
76
|
+
raise
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# ================================================================
|
|
2
|
+
# WAPPA WHATSAPP FRAMEWORK CONFIGURATION
|
|
3
|
+
# ================================================================
|
|
4
|
+
|
|
5
|
+
# General Configuration
|
|
6
|
+
PORT=8000
|
|
7
|
+
TIME_ZONE=America/Bogota
|
|
8
|
+
|
|
9
|
+
# DEBUG or INFO or WARNING or ERROR or CRITICAL
|
|
10
|
+
LOG_LEVEL=DEBUG
|
|
11
|
+
LOG_DIR=./logs
|
|
12
|
+
## Environment DEV or PROD
|
|
13
|
+
ENVIRONMENT=DEV
|
|
14
|
+
|
|
15
|
+
# WhatsApp Graph API
|
|
16
|
+
BASE_URL=https://graph.facebook.com/
|
|
17
|
+
API_VERSION=v23.0
|
|
18
|
+
|
|
19
|
+
# WhatsApp Business API Credentials
|
|
20
|
+
WP_ACCESS_TOKEN=
|
|
21
|
+
WP_PHONE_ID=
|
|
22
|
+
WP_BID=
|
|
23
|
+
|
|
24
|
+
# Webhook Configuration
|
|
25
|
+
WHATSAPP_WEBHOOK_VERIFY_TOKEN=
|
|
26
|
+
|
|
27
|
+
# Redis Configuration (Optional - uncomment to enable Redis persistence)
|
|
28
|
+
REDIS_URL=redis://localhost:6379/
|
|
29
|
+
REDIS_MAX_CONNECTIONS=64
|
|
30
|
+
|
|
31
|
+
# Optional: AI Tools
|
|
32
|
+
# OPENAI_API_KEY=
|
|
33
|
+
|