Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/lib/pages/settings/usage_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1614,7 +1614,7 @@ class _UsagePageState extends State<UsagePage> with TickerProviderStateMixin {
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
interval: maxY > 1 ? (maxY / 4).roundToDouble() : 0.25,
interval: maxY > 1 ? ((maxY / 4).roundToDouble() > 0 ? (maxY / 4).roundToDouble() : 1.0) : 0.25,
getTitlesWidget: (value, meta) {
if (value == meta.max) return const SizedBox();
return SideTitleWidget(
Expand Down
15 changes: 15 additions & 0 deletions backend/database/redis_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,3 +552,18 @@ def get_migration_status(uid: str) -> dict:
def clear_migration_status(uid: str):
key = f"migration_status:{uid}"
r.delete(key)


# ******************************************************
# ************** CREDIT LIMIT NOTIFICATIONS ************
# ******************************************************


def set_credit_limit_notification_sent(uid: str, ttl: int = 60 * 60 * 24):
"""Cache that credit limit notification was sent to user (24 hours TTL by default)"""
r.set(f'users:{uid}:credit_limit_notification_sent', '1', ex=ttl)


def has_credit_limit_notification_been_sent(uid: str) -> bool:
"""Check if credit limit notification was already sent to user recently"""
return r.exists(f'users:{uid}:credit_limit_notification_sent')
13 changes: 13 additions & 0 deletions backend/routers/transcribe.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

from utils.other import endpoints as auth
from utils.other.storage import get_profile_audio_if_exists
from utils.notifications import send_credit_limit_notification

router = APIRouter()

Expand Down Expand Up @@ -89,6 +90,12 @@ async def _listen(
return

if not has_transcription_credits(uid):
# Send credit limit notification (with Redis caching to prevent spam)
try:
await send_credit_limit_notification(uid)
except Exception as e:
print(f"Error sending credit limit notification: {e}")

await websocket.close(code=4002, reason="Usage limit exceeded")
return

Expand Down Expand Up @@ -147,6 +154,12 @@ async def _record_usage_periodically():
last_usage_record_timestamp = current_time

if not has_transcription_credits(uid):
# Send credit limit notification (with Redis caching to prevent spam)
try:
await send_credit_limit_notification(uid)
except Exception as e:
print(f"Error sending credit limit notification: {e}")

nonlocal websocket_close_code
websocket_close_code = 4002
websocket_active = False
Expand Down
58 changes: 58 additions & 0 deletions backend/utils/llm/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,61 @@ async def generate_notification_message(uid: str, name: str, plan_type: str = "b

# Improved fallback messages with more personality
return ("omi", f"Hey {name}! 👋 Thanks for being part of the Omi family! ✨")


async def generate_credit_limit_notification(uid: str, name: str) -> Tuple[str, str]:
"""
Generate a personalized notification when user hits transcription credit limits.
"""
# Get relevant memories for context
memories = await get_relevant_memories(uid, limit=50)
memory_context = ""
if memories:
memory_summaries = [m.get('content', '') for m in memories] # Use all memories for context
memory_context = f"\nRecent conversations include: {', '.join(memory_summaries[:100])}..."

system_prompt = """You're Omi, and you need to gently let a user know they've hit their transcription limits while encouraging them to upgrade to unlimited.

Your Style:
- Warm and understanding, not pushy
- Show genuine care for their journey with you
- Make the upgrade feel like a natural next step
- Reference their usage to show value
- Keep it conversational and friendly
- No emojis (express yourself in words!)
- Under 150 characters total

Key Points to Include:
- They've been actively using transcription (show appreciation)
- Unlimited plan removes all limits
- Can check usage/plans in app or search 'omi unlimited subs' in marketplace
- Make it feel like you're helping them, not selling to them
"""

user_prompt = f"""Create a credit limit notification for {name} who has reached their transcription limits.

Context:
- User's name: {name}
- They've been actively transcribing conversations
- Need to encourage unlimited plan subscription{memory_context}

The message should:
- Acknowledge their active usage positively
- Suggest checking plans in the app or searching 'omi unlimited subs' in marketplace
- Feel helpful, not sales-y
- Be warm and personal to {name}

Return only the notification body text."""

try:
body = await llm_medium.apredict(system_prompt + "\n" + user_prompt)
return "omi", body.strip()

except Exception as e:
print(f"Error generating credit limit notification: {e}")

# Fallback message
return (
"omi",
f"Hey {name}! You've been actively using transcription - that's awesome! You've hit your limit, but unlimited plans remove all restrictions. Check your usage in the app or search 'omi unlimited subs' in the marketplace!",
)
39 changes: 38 additions & 1 deletion backend/utils/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import math
from firebase_admin import messaging, auth
import database.notifications as notification_db
from .llm.notifications import generate_notification_message
from database.redis_db import set_credit_limit_notification_sent, has_credit_limit_notification_been_sent
from .llm.notifications import generate_notification_message, generate_credit_limit_notification


def send_notification(token: str, title: str, body: str, data: dict = None):
Expand Down Expand Up @@ -49,6 +50,42 @@ async def send_subscription_paid_personalized_notification(user_id: str, data: d
send_notification(token, "omi", body, data)


async def send_credit_limit_notification(user_id: str):
"""Send a personalized credit limit notification if not sent recently"""
# Check if notification was sent recently (within 6 hours)
if has_credit_limit_notification_been_sent(user_id):
print(f"Credit limit notification already sent recently for user {user_id}")
return

# Get user's notification token
token = notification_db.get_token_only(user_id)
if not token:
print(f"No notification token found for user {user_id}")
return

# Get user name from Firebase Auth
try:
user = auth.get_user(user_id)
name = user.display_name
if not name and user.email:
name = user.email.split('@')[0].capitalize()
if not name:
name = "there"
except Exception as e:
print(f"Error getting user info from Firebase Auth: {e}")
name = "there"

# Generate personalized credit limit message
title, body = await generate_credit_limit_notification(user_id, name)

# Send notification
send_notification(token, title, body)

# Cache that notification was sent (6 hours TTL)
set_credit_limit_notification_sent(user_id)
print(f"Credit limit notification sent to user {user_id}")


async def send_bulk_notification(user_tokens: list, title: str, body: str):
try:
batch_size = 500
Expand Down