Fix preflight NameError, clean up debug logs, remove redundant voice button, fix Tooltip warning
This commit is contained in:
@@ -19,6 +19,15 @@ if TYPE_CHECKING:
|
||||
from .pricing_service import PricingService
|
||||
|
||||
|
||||
def _should_enforce_limit(limit_value: int, tier: str) -> bool:
|
||||
"""
|
||||
Determine if a limit should be enforced.
|
||||
- Free tier: 0 means DISABLED (not unlimited)
|
||||
- Basic/Pro/Enterprise: 0 means UNLIMITED
|
||||
"""
|
||||
return limit_value > 0
|
||||
|
||||
|
||||
class LimitValidator:
|
||||
"""Validates subscription limits for API usage."""
|
||||
|
||||
@@ -107,20 +116,6 @@ class LimitValidator:
|
||||
}
|
||||
return result
|
||||
|
||||
# Helper: Check if a limit should be enforced based on tier
|
||||
def should_enforce_limit(limit_value: int, tier: str) -> bool:
|
||||
"""
|
||||
Determine if a limit should be enforced.
|
||||
- Free tier: 0 means DISABLED (not unlimited)
|
||||
- Basic/Pro/Enterprise: 0 means UNLIMITED
|
||||
"""
|
||||
if tier == 'free':
|
||||
# Free tier: 0 means disabled
|
||||
return limit_value > 0
|
||||
else:
|
||||
# Basic/Pro/Enterprise: 0 means unlimited
|
||||
return limit_value > 0
|
||||
|
||||
# Get user limits with error handling (STRICT: fail on errors)
|
||||
# CRITICAL: Expire SQLAlchemy objects to ensure we get fresh plan data after renewal
|
||||
try:
|
||||
@@ -263,7 +258,7 @@ class LimitValidator:
|
||||
)
|
||||
|
||||
# Enforce limit based on tier (Free: 0=disabled, others: 0=unlimited)
|
||||
if should_enforce_limit(ai_text_gen_limit, user_tier) and current_total_llm_calls >= ai_text_gen_limit:
|
||||
if _should_enforce_limit(ai_text_gen_limit, user_tier) and current_total_llm_calls >= ai_text_gen_limit:
|
||||
logger.error(f"[Subscription Check] AI text generation call limit exceeded for user {user_id}: {current_total_llm_calls}/{ai_text_gen_limit} (provider: {display_provider_name})")
|
||||
result = (False, f"AI text generation call limit reached. Used {current_total_llm_calls} of {ai_text_gen_limit} total AI text generation calls this billing period.", {
|
||||
'current_calls': current_total_llm_calls,
|
||||
@@ -296,7 +291,7 @@ class LimitValidator:
|
||||
call_limit = limits['limits'].get(f"{provider_name}_calls", 0) or 0
|
||||
|
||||
# Enforce limit based on tier (Free: 0=disabled, others: 0=unlimited)
|
||||
if should_enforce_limit(call_limit, user_tier) and current_calls >= call_limit:
|
||||
if _should_enforce_limit(call_limit, user_tier) and current_calls >= call_limit:
|
||||
logger.error(f"[Subscription Check] Call limit exceeded for user {user_id}, provider {display_provider_name}: {current_calls}/{call_limit}")
|
||||
result = (False, f"API call limit reached for {display_provider_name}. Used {current_calls} of {call_limit} calls this billing period.", {
|
||||
'current_calls': current_calls,
|
||||
@@ -329,7 +324,7 @@ class LimitValidator:
|
||||
token_limit = limits['limits'].get(f"{provider_name}_tokens", 0) or 0
|
||||
|
||||
# Enforce limit based on tier (Free: 0=disabled, others: 0=unlimited)
|
||||
if should_enforce_limit(token_limit, user_tier) and (current_tokens + tokens_requested) > token_limit:
|
||||
if _should_enforce_limit(token_limit, user_tier) and (current_tokens + tokens_requested) > token_limit:
|
||||
result = (False, f"Token limit would be exceeded for {display_provider_name}. Current: {current_tokens}, Requested: {tokens_requested}, Limit: {token_limit}", {
|
||||
'current_tokens': current_tokens,
|
||||
'requested_tokens': tokens_requested,
|
||||
@@ -363,7 +358,7 @@ class LimitValidator:
|
||||
try:
|
||||
cost_limit = limits['limits'].get('monthly_cost', 0) or 0
|
||||
# Enforce limit based on tier (Free: 0=disabled, others: 0=unlimited)
|
||||
if should_enforce_limit(cost_limit, user_tier) and usage.total_cost >= cost_limit:
|
||||
if _should_enforce_limit(cost_limit, user_tier) and usage.total_cost >= cost_limit:
|
||||
result = (False, f"Monthly cost limit reached. Current cost: ${usage.total_cost:.2f}, Limit: ${cost_limit:.2f}", {
|
||||
'current_cost': usage.total_cost,
|
||||
'limit': cost_limit,
|
||||
@@ -583,7 +578,7 @@ class LimitValidator:
|
||||
projected_total_llm_calls = total_llm_calls + 1
|
||||
|
||||
# Enforce limit based on tier (Free: 0=disabled, others: 0=unlimited)
|
||||
if should_enforce_limit(ai_text_gen_limit, tier) and projected_total_llm_calls > ai_text_gen_limit:
|
||||
if _should_enforce_limit(ai_text_gen_limit, tier) and projected_total_llm_calls > ai_text_gen_limit:
|
||||
error_info = {
|
||||
'current_calls': total_llm_calls,
|
||||
'limit': ai_text_gen_limit,
|
||||
@@ -691,7 +686,7 @@ class LimitValidator:
|
||||
token_limit = limits.get(provider_tokens_key, 0) or 0
|
||||
|
||||
# Enforce limit based on tier (Free: 0=disabled, others: 0=unlimited)
|
||||
if should_enforce_limit(token_limit, tier) and tokens_requested > 0:
|
||||
if _should_enforce_limit(token_limit, tier) and tokens_requested > 0:
|
||||
projected_tokens = current_provider_tokens + tokens_requested
|
||||
logger.info(f" └─ Token Check: {current_provider_tokens} (current) + {tokens_requested} (requested) = {projected_tokens} (total) / {token_limit} (limit)")
|
||||
|
||||
@@ -754,7 +749,7 @@ class LimitValidator:
|
||||
projected_images = total_images + 1
|
||||
|
||||
# Enforce limit based on tier (Free: 0=disabled, others: 0=unlimited)
|
||||
if should_enforce_limit(image_limit, tier) and projected_images > image_limit:
|
||||
if _should_enforce_limit(image_limit, tier) and projected_images > image_limit:
|
||||
error_info = {
|
||||
'current_images': total_images,
|
||||
'limit': image_limit,
|
||||
@@ -776,7 +771,7 @@ class LimitValidator:
|
||||
projected_video_calls = total_video_calls + 1
|
||||
|
||||
# Enforce limit based on tier (Free: 0=disabled, others: 0=unlimited)
|
||||
if should_enforce_limit(video_limit, tier) and projected_video_calls > video_limit:
|
||||
if _should_enforce_limit(video_limit, tier) and projected_video_calls > video_limit:
|
||||
error_info = {
|
||||
'current_calls': total_video_calls,
|
||||
'limit': video_limit,
|
||||
@@ -796,7 +791,7 @@ class LimitValidator:
|
||||
projected_image_edit_calls = total_image_edit_calls + 1
|
||||
|
||||
# Enforce limit based on tier (Free: 0=disabled, others: 0=unlimited)
|
||||
if should_enforce_limit(image_edit_limit, tier) and projected_image_edit_calls > image_edit_limit:
|
||||
if _should_enforce_limit(image_edit_limit, tier) and projected_image_edit_calls > image_edit_limit:
|
||||
error_info = {
|
||||
'current_calls': total_image_edit_calls,
|
||||
'limit': image_edit_limit,
|
||||
@@ -833,7 +828,7 @@ class LimitValidator:
|
||||
# Check WaveSpeed combined limit if actual_provider is WaveSpeed
|
||||
if actual_provider_name == 'wavespeed':
|
||||
wavespeed_limit = limits.get('wavespeed_calls', 0) or 0
|
||||
if should_enforce_limit(wavespeed_limit, tier):
|
||||
if _should_enforce_limit(wavespeed_limit, tier):
|
||||
wavespeed_usage = usage.wavespeed_calls or 0
|
||||
projected_wavespeed = wavespeed_usage + 1
|
||||
if projected_wavespeed > wavespeed_limit:
|
||||
|
||||
@@ -45,12 +45,12 @@ class UsageTrackingService:
|
||||
self._enforce_cache: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
def _get_authoritative_billing_period_keys(self, user_id: str, billing_period: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Return authoritative billing period lookup keys anchored to subscription period boundaries."""
|
||||
"""Return authoritative billing period lookup keys. Always uses calendar month for consistency."""
|
||||
subscription = self.db.query(UserSubscription).filter(
|
||||
UserSubscription.user_id == user_id
|
||||
).first()
|
||||
|
||||
# If caller explicitly requested a billing period, keep it authoritative for that read.
|
||||
# If caller explicitly requested a billing period, use it
|
||||
if billing_period:
|
||||
return {
|
||||
"billing_period": billing_period,
|
||||
@@ -59,23 +59,15 @@ class UsageTrackingService:
|
||||
"period_end": subscription.current_period_end if subscription else None,
|
||||
}
|
||||
|
||||
if subscription and subscription.current_period_start and subscription.current_period_end:
|
||||
start_key = subscription.current_period_start.strftime("%Y-%m")
|
||||
end_key = subscription.current_period_end.strftime("%Y-%m")
|
||||
lookup_periods = [start_key] if start_key == end_key else [start_key, end_key]
|
||||
return {
|
||||
"billing_period": start_key,
|
||||
"lookup_periods": lookup_periods,
|
||||
"period_start": subscription.current_period_start,
|
||||
"period_end": subscription.current_period_end,
|
||||
}
|
||||
|
||||
resolved_period = self.pricing_service.get_current_billing_period(user_id) or datetime.now().strftime("%Y-%m")
|
||||
# ALWAYS use current calendar month for billing period to ensure consistency
|
||||
# This prevents data loss when subscription spans month boundaries
|
||||
current_period = datetime.now().strftime("%Y-%m")
|
||||
|
||||
return {
|
||||
"billing_period": resolved_period,
|
||||
"lookup_periods": [resolved_period],
|
||||
"period_start": None,
|
||||
"period_end": None,
|
||||
"billing_period": current_period,
|
||||
"lookup_periods": [current_period],
|
||||
"period_start": subscription.current_period_start if subscription else None,
|
||||
"period_end": subscription.current_period_end if subscription else None,
|
||||
}
|
||||
|
||||
async def track_api_usage(self, user_id: str, provider: APIProvider,
|
||||
@@ -207,11 +199,14 @@ class UsageTrackingService:
|
||||
).first()
|
||||
|
||||
if not summary:
|
||||
logger.info(f"[UsageTracking] Creating new UsageSummary for user={user_id}, period={period_keys['billing_period']}")
|
||||
summary = UsageSummary(
|
||||
user_id=user_id,
|
||||
billing_period=period_keys["billing_period"]
|
||||
)
|
||||
self.db.add(summary)
|
||||
else:
|
||||
logger.debug(f"[UsageTracking] Found existing UsageSummary for user={user_id}, period={summary.billing_period}, calls={summary.total_calls}")
|
||||
|
||||
# Update provider-specific counters
|
||||
provider_name = provider.value
|
||||
@@ -384,12 +379,19 @@ class UsageTrackingService:
|
||||
period_keys = self._get_authoritative_billing_period_keys(user_id, requested_billing_period)
|
||||
billing_period = period_keys["billing_period"]
|
||||
|
||||
logger.debug(f"[get_user_usage_stats] user={user_id}, billing_period={billing_period}, lookup_periods={period_keys['lookup_periods']}")
|
||||
|
||||
# Get usage summary
|
||||
summary = self.db.query(UsageSummary).filter(
|
||||
UsageSummary.user_id == user_id,
|
||||
UsageSummary.billing_period.in_(period_keys["lookup_periods"])
|
||||
).first()
|
||||
|
||||
if summary:
|
||||
logger.debug(f"[get_user_usage_stats] Found summary: period={summary.billing_period}, calls={summary.total_calls}, cost={summary.total_cost}")
|
||||
else:
|
||||
logger.debug(f"[get_user_usage_stats] No summary found for user={user_id}, period={billing_period}")
|
||||
|
||||
# Get user limits
|
||||
limits = self.pricing_service.get_user_limits(user_id)
|
||||
|
||||
|
||||
@@ -482,7 +482,7 @@ class SpeechGenerator:
|
||||
|
||||
audio_url = self._extract_audio_url(outputs)
|
||||
downloaded_audio = self._download_audio(audio_url, timeout)
|
||||
logger.warning(f"[WaveSpeed] qwen3_voice_clone downloaded {len(downloaded_audio)} bytes, first_16hex: {downloaded_audio[:16].hex()}")
|
||||
logger.warning(f"[WaveSpeed] qwen3_voice_clone downloaded {len(downloaded_audio)} bytes")
|
||||
return downloaded_audio
|
||||
|
||||
def cosyvoice_voice_clone(
|
||||
|
||||
Reference in New Issue
Block a user