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:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user