fix(product-marketing): route image generation through unified subscription validation

Backend:
- product_image_service.py: Replaced direct wavespeed_client.generate_image()
  with generate_image() from main_image_generation (unified entry point)
- This ensures subscription pre-flight validation (_validate_image_operation)
  and usage tracking (_track_image_operation_usage) are enforced
- Removed _generate_image_with_retry method and WaveSpeedClient dependency
- Animation/video/avatar services already route through ImageStudioManager - no changes needed

Frontend:
- useProductMarketing.ts: Added formatError() helper for 402/429 detection
  across all 8 API operations
- useCampaignCreator.ts: Added formatError() helper for 402/429 detection
  across all 13 API operations
- All error messages now surface subscription limits with upgrade prompts
This commit is contained in:
ajaysi
2026-05-09 17:18:16 +05:30
parent 93a1985d9f
commit 7385100017
3 changed files with 56 additions and 110 deletions

View File

@@ -1,6 +1,15 @@
import { useState, useCallback } from 'react';
import { aiApiClient } from '../api/client';
const formatError = (err: any, fallback: string): string => {
if (err?.response?.status === 402 || err?.response?.status === 429) {
return 'Subscription limit reached. Upgrade your plan to continue using this feature.';
}
if (err?.response?.data?.detail) return String(err.response.data.detail);
if (err?.message) return String(err.message);
return fallback;
};
export interface CampaignCreateRequest {
campaign_name: string;
goal: string;
@@ -211,7 +220,7 @@ export const useCampaignCreator = () => {
setBlueprint(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to create campaign blueprint';
const errorMessage = formatError(err, 'Failed to create campaign blueprint');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -236,7 +245,7 @@ export const useCampaignCreator = () => {
setProposals(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate asset proposals';
const errorMessage = formatError(err, 'Failed to generate asset proposals');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -258,7 +267,7 @@ export const useCampaignCreator = () => {
setGeneratedAsset(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate asset';
const errorMessage = formatError(err, 'Failed to generate asset');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -276,7 +285,7 @@ export const useCampaignCreator = () => {
setBrandDNA(response.data.brand_dna);
return response.data.brand_dna;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get brand DNA';
const errorMessage = formatError(err, 'Failed to get brand DNA');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -294,7 +303,7 @@ export const useCampaignCreator = () => {
);
return response.data.brand_dna;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get channel brand DNA';
const errorMessage = formatError(err, 'Failed to get channel brand DNA');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -315,7 +324,7 @@ export const useCampaignCreator = () => {
setChannelPack(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get channel pack';
const errorMessage = formatError(err, 'Failed to get channel pack');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -337,7 +346,7 @@ export const useCampaignCreator = () => {
setAuditResult(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to audit asset';
const errorMessage = formatError(err, 'Failed to audit asset');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -356,7 +365,7 @@ export const useCampaignCreator = () => {
setCampaigns(response.data.campaigns);
return response.data.campaigns;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to list campaigns';
const errorMessage = formatError(err, 'Failed to list campaigns');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -370,7 +379,7 @@ export const useCampaignCreator = () => {
const response = await aiApiClient.get<CampaignBlueprint>(`/api/campaign-creator/campaigns/${campaignId}`);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get campaign';
const errorMessage = formatError(err, 'Failed to get campaign');
setError(errorMessage);
throw new Error(errorMessage);
}
@@ -382,7 +391,7 @@ export const useCampaignCreator = () => {
const response = await aiApiClient.get<AssetProposalsResponse>(`/api/campaign-creator/campaigns/${campaignId}/proposals`);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get proposals';
const errorMessage = formatError(err, 'Failed to get proposals');
setError(errorMessage);
throw new Error(errorMessage);
}
@@ -400,7 +409,7 @@ export const useCampaignCreator = () => {
setPreflightResult(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to validate campaign pre-flight';
const errorMessage = formatError(err, 'Failed to validate campaign pre-flight');
setError(errorMessage);
const errorResult: PreflightValidationResult = {
can_proceed: false,
@@ -452,7 +461,7 @@ export const useCampaignCreator = () => {
const response = await aiApiClient.get(`/api/product-marketing/personalization/defaults/${formType}`);
return response.data.defaults;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get personalized defaults';
const errorMessage = formatError(err, 'Failed to get personalized defaults');
setError(errorMessage);
throw new Error(errorMessage);
}
@@ -468,7 +477,7 @@ export const useCampaignCreator = () => {
setRecommendations(response.data.recommendations);
return response.data.recommendations;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get recommendations';
const errorMessage = formatError(err, 'Failed to get recommendations');
setError(errorMessage);
throw new Error(errorMessage);
} finally {

View File

@@ -1,6 +1,15 @@
import { useState, useCallback } from 'react';
import { aiApiClient } from '../api/client';
const formatError = (err: any, fallback: string): string => {
if (err?.response?.status === 402 || err?.response?.status === 429) {
return 'Subscription limit reached. Upgrade your plan to continue using this feature.';
}
if (err?.response?.data?.detail) return String(err.response.data.detail);
if (err?.message) return String(err.message);
return fallback;
};
/**
* useProductMarketing Hook
*
@@ -37,7 +46,7 @@ export const useProductMarketing = () => {
setGeneratedProductImage(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate product image';
const errorMessage = formatError(err, 'Failed to generate product image');
setProductImageError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -70,7 +79,7 @@ export const useProductMarketing = () => {
setGeneratedAnimation(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate product animation';
const errorMessage = formatError(err, 'Failed to generate product animation');
setAnimationError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -102,7 +111,7 @@ export const useProductMarketing = () => {
setGeneratedVideo(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate product video';
const errorMessage = formatError(err, 'Failed to generate product video');
setVideoError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -137,7 +146,7 @@ export const useProductMarketing = () => {
setGeneratedAvatar(response.data);
return response.data;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to generate product avatar';
const errorMessage = formatError(err, 'Failed to generate product avatar');
setAvatarError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -174,7 +183,7 @@ export const useProductMarketing = () => {
setInferredConfig(response.data.configuration);
return response.data.configuration;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to infer requirements';
const errorMessage = formatError(err, 'Failed to infer requirements');
setInferenceError(errorMessage);
throw new Error(errorMessage);
} finally {
@@ -194,8 +203,8 @@ export const useProductMarketing = () => {
setBrandDNA(response.data.brand_dna);
return response.data.brand_dna;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get brand DNA';
setError(errorMessage);
const errorMessage = formatError(err, 'Failed to get brand DNA');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
setIsLoadingBrandDNA(false);
@@ -210,8 +219,8 @@ export const useProductMarketing = () => {
setUserPreferences(response.data.preferences);
return response.data.preferences;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get user preferences';
setError(errorMessage);
const errorMessage = formatError(err, 'Failed to get user preferences');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
setIsLoadingPreferences(false);
@@ -225,7 +234,7 @@ export const useProductMarketing = () => {
const response = await aiApiClient.get(`/api/product-marketing/personalization/defaults/${formType}`);
return response.data.defaults;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get personalized defaults';
const errorMessage = formatError(err, 'Failed to get personalized defaults');
setError(errorMessage);
throw new Error(errorMessage);
}
@@ -241,8 +250,8 @@ export const useProductMarketing = () => {
setRecommendations(response.data.recommendations);
return response.data.recommendations;
} catch (err: any) {
const errorMessage = err.response?.data?.detail || err.message || 'Failed to get recommendations';
setError(errorMessage);
const errorMessage = formatError(err, 'Failed to get recommendations');
setError(errorMessage);
throw new Error(errorMessage);
} finally {
setIsLoadingRecommendations(false);