Refactor API response handling across multiple components to ensure consistency with the unified format. Update error handling and response validation in ValidationCard, usePersistentToggle, Status, Prompts, and api.ts to improve user feedback and maintain compatibility with the new API standards.
This commit is contained in:
@@ -47,14 +47,16 @@ export default function ValidationCard({
|
||||
|
||||
try {
|
||||
// Get saved settings to get API key and model
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So settingsData IS the data object (config object)
|
||||
const settingsData = await fetchAPI(`/v1/system/settings/integrations/${integrationId}/`);
|
||||
|
||||
let apiKey = '';
|
||||
let model = 'gpt-4.1';
|
||||
|
||||
if (settingsData.success && settingsData.data) {
|
||||
apiKey = settingsData.data.apiKey || '';
|
||||
model = settingsData.data.model || 'gpt-4.1';
|
||||
if (settingsData && typeof settingsData === 'object') {
|
||||
apiKey = settingsData.apiKey || '';
|
||||
model = settingsData.model || 'gpt-4.1';
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
@@ -79,30 +81,42 @@ export default function ValidationCard({
|
||||
};
|
||||
}
|
||||
|
||||
// Test endpoint returns Response({success: True, ...}) directly (not unified format)
|
||||
// So fetchAPI may or may not extract it - handle both cases
|
||||
const data = await fetchAPI(`/v1/system/settings/integrations/${integrationId}/test/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
if (data.success) {
|
||||
setTestResult({
|
||||
success: true,
|
||||
message: data.message || 'API connection successful!',
|
||||
model_used: data.model_used || data.model,
|
||||
response: data.response,
|
||||
tokens_used: data.tokens_used,
|
||||
total_tokens: data.total_tokens,
|
||||
cost: data.cost,
|
||||
full_response: data.full_response || {
|
||||
image_url: data.image_url,
|
||||
provider: data.provider,
|
||||
size: data.size,
|
||||
},
|
||||
});
|
||||
// Check if data has success field (direct Response format) or is extracted data
|
||||
if (data && typeof data === 'object' && ('success' in data ? data.success : true)) {
|
||||
// If data has success field, use it; otherwise assume success (extracted data)
|
||||
const isSuccess = data.success !== false;
|
||||
if (isSuccess) {
|
||||
setTestResult({
|
||||
success: true,
|
||||
message: data.message || 'API connection successful!',
|
||||
model_used: data.model_used || data.model,
|
||||
response: data.response,
|
||||
tokens_used: data.tokens_used,
|
||||
total_tokens: data.total_tokens,
|
||||
cost: data.cost,
|
||||
full_response: data.full_response || {
|
||||
image_url: data.image_url,
|
||||
provider: data.provider,
|
||||
size: data.size,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setTestResult({
|
||||
success: false,
|
||||
message: data.error || data.message || 'API connection failed',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setTestResult({
|
||||
success: false,
|
||||
message: data.error || data.message || 'API connection failed',
|
||||
message: 'Invalid response format',
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -168,23 +168,21 @@ export function usePersistentToggle(
|
||||
const endpoint = saveEndpoint.replace('{id}', resourceId);
|
||||
const payload = buildPayload(data || {}, newEnabled);
|
||||
|
||||
const result = await fetchAPI(endpoint, {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// If no error is thrown, assume success
|
||||
await fetchAPI(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
// Update local state
|
||||
const updatedData = { ...(data || {}), enabled: newEnabled };
|
||||
setData(updatedData);
|
||||
setEnabled(newEnabled);
|
||||
|
||||
// Call success callback - pass both enabled state and full config data
|
||||
if (onToggleSuccess) {
|
||||
onToggleSuccess(newEnabled, updatedData);
|
||||
}
|
||||
} else {
|
||||
throw new Error(result.error || 'Failed to save state');
|
||||
// Update local state
|
||||
const updatedData = { ...(data || {}), enabled: newEnabled };
|
||||
setData(updatedData);
|
||||
setEnabled(newEnabled);
|
||||
|
||||
// Call success callback - pass both enabled state and full config data
|
||||
if (onToggleSuccess) {
|
||||
onToggleSuccess(newEnabled, updatedData);
|
||||
}
|
||||
} catch (err: any) {
|
||||
const error = err instanceof Error ? err : new Error(String(err));
|
||||
|
||||
@@ -63,10 +63,10 @@ export default function Status() {
|
||||
|
||||
const fetchStatus = async () => {
|
||||
try {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So response IS the data object
|
||||
const response = await fetchAPI('/v1/system/status/');
|
||||
// Handle unified API response format: {success: true, data: {...}}
|
||||
const statusData = response?.data || response;
|
||||
setStatus(statusData);
|
||||
setStatus(response);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
|
||||
@@ -75,11 +75,10 @@ export default function Prompts() {
|
||||
try {
|
||||
const promises = PROMPT_TYPES.map(async (type) => {
|
||||
try {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So response IS the data object
|
||||
const response = await fetchAPI(`/v1/system/prompts/by_type/${type.key}/`);
|
||||
// Extract data field from unified API response format
|
||||
// Response format: { success: true, data: {...}, request_id: "..." }
|
||||
const data = response?.data || response;
|
||||
return { key: type.key, data };
|
||||
return { key: type.key, data: response };
|
||||
} catch (error) {
|
||||
console.error(`Error loading prompt ${type.key}:`, error);
|
||||
return { key: type.key, data: null };
|
||||
@@ -119,7 +118,10 @@ export default function Prompts() {
|
||||
|
||||
setSaving({ ...saving, [promptType]: true });
|
||||
try {
|
||||
const response = await fetchAPI('/v1/system/prompts/save/', {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}, message: "..."}
|
||||
// But save endpoint returns message in the response, so we need to check if it's still wrapped
|
||||
// For now, assume success if no error is thrown
|
||||
await fetchAPI('/v1/system/prompts/save/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
prompt_type: promptType,
|
||||
@@ -127,16 +129,8 @@ export default function Prompts() {
|
||||
}),
|
||||
});
|
||||
|
||||
// Extract data field from unified API response format
|
||||
// Response format: { success: true, data: {...}, message: "...", request_id: "..." }
|
||||
const data = response?.data || response;
|
||||
|
||||
if (response.success) {
|
||||
toast.success(response.message || 'Prompt saved successfully');
|
||||
await loadPrompts(); // Reload to get updated data
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to save prompt');
|
||||
}
|
||||
toast.success('Prompt saved successfully');
|
||||
await loadPrompts(); // Reload to get updated data
|
||||
} catch (error: any) {
|
||||
console.error('Error saving prompt:', error);
|
||||
toast.error(`Failed to save prompt: ${error.message}`);
|
||||
@@ -152,23 +146,18 @@ export default function Prompts() {
|
||||
|
||||
setSaving({ ...saving, [promptType]: true });
|
||||
try {
|
||||
const response = await fetchAPI('/v1/system/prompts/reset/', {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}, message: "..."}
|
||||
// But reset endpoint returns message in the response, so we need to check if it's still wrapped
|
||||
// For now, assume success if no error is thrown
|
||||
await fetchAPI('/v1/system/prompts/reset/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
prompt_type: promptType,
|
||||
}),
|
||||
});
|
||||
|
||||
// Extract data field from unified API response format
|
||||
// Response format: { success: true, data: {...}, message: "...", request_id: "..." }
|
||||
const data = response?.data || response;
|
||||
|
||||
if (response.success) {
|
||||
toast.success(response.message || 'Prompt reset to default');
|
||||
await loadPrompts(); // Reload to get default value
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to reset prompt');
|
||||
}
|
||||
toast.success('Prompt reset to default');
|
||||
await loadPrompts(); // Reload to get default value
|
||||
} catch (error: any) {
|
||||
console.error('Error resetting prompt:', error);
|
||||
toast.error(`Failed to reset prompt: ${error.message}`);
|
||||
|
||||
@@ -1061,11 +1061,28 @@ export async function autoGenerateImages(taskIds: number[]): Promise<{ success:
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateImagePrompts(contentIds: number[]): Promise<any> {
|
||||
return fetchAPI('/v1/writer/content/generate_image_prompts/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ids: contentIds }),
|
||||
});
|
||||
export async function generateImagePrompts(contentIds: number[]): Promise<{ success: boolean; task_id?: string; message?: string; error?: string }> {
|
||||
try {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So response is already the data object: {task_id: "...", ...}
|
||||
const response = await fetchAPI('/v1/writer/content/generate_image_prompts/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ids: contentIds }),
|
||||
});
|
||||
|
||||
// Wrap extracted data with success: true for frontend compatibility
|
||||
if (response && typeof response === 'object') {
|
||||
return { success: true, ...response } as any;
|
||||
}
|
||||
|
||||
return { success: true, ...response } as any;
|
||||
} catch (error: any) {
|
||||
// Error responses are thrown by fetchAPI, but wrap them for consistency
|
||||
if (error.response && typeof error.response === 'object') {
|
||||
return { success: false, error: error.message, ...error.response } as any;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// TaskImages API functions
|
||||
@@ -1210,14 +1227,31 @@ export async function bulkUpdateImagesStatus(contentId: number, status: string):
|
||||
});
|
||||
}
|
||||
|
||||
export async function generateImages(imageIds: number[], contentId?: number): Promise<any> {
|
||||
return fetchAPI('/v1/writer/images/generate_images/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
ids: imageIds,
|
||||
content_id: contentId
|
||||
}),
|
||||
});
|
||||
export async function generateImages(imageIds: number[], contentId?: number): Promise<{ success: boolean; task_id?: string; message?: string; error?: string }> {
|
||||
try {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So response is already the data object: {task_id: "...", ...}
|
||||
const response = await fetchAPI('/v1/writer/images/generate_images/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
ids: imageIds,
|
||||
content_id: contentId
|
||||
}),
|
||||
});
|
||||
|
||||
// Wrap extracted data with success: true for frontend compatibility
|
||||
if (response && typeof response === 'object') {
|
||||
return { success: true, ...response } as any;
|
||||
}
|
||||
|
||||
return { success: true, ...response } as any;
|
||||
} catch (error: any) {
|
||||
// Error responses are thrown by fetchAPI, but wrap them for consistency
|
||||
if (error.response && typeof error.response === 'object') {
|
||||
return { success: false, error: error.message, ...error.response } as any;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchImages(filters: ImageFilters = {}): Promise<ImageListResponse> {
|
||||
@@ -1451,8 +1485,10 @@ export interface ModuleSetting {
|
||||
}
|
||||
|
||||
export async function fetchModuleSettings(moduleName: string): Promise<ModuleSetting[]> {
|
||||
// fetchAPI extracts data from unified format {success: true, data: [...]}
|
||||
// So response IS the array, not an object with results
|
||||
const response = await fetchAPI(`/v1/system/settings/modules/module/${moduleName}/`);
|
||||
return response.results || [];
|
||||
return Array.isArray(response) ? response : [];
|
||||
}
|
||||
|
||||
export async function createModuleSetting(data: { module_name: string; key: string; config: Record<string, any>; is_active?: boolean }): Promise<ModuleSetting> {
|
||||
@@ -1571,12 +1607,11 @@ export interface UsageLimitsResponse {
|
||||
export async function fetchUsageLimits(): Promise<UsageLimitsResponse> {
|
||||
console.log('Fetching usage limits from:', '/v1/billing/credits/usage/limits/');
|
||||
try {
|
||||
// fetchAPI extracts data from unified format {success: true, data: { limits: [...] }}
|
||||
// So response IS the data object
|
||||
const response = await fetchAPI('/v1/billing/credits/usage/limits/');
|
||||
console.log('Usage limits API response:', response);
|
||||
// Extract data field from unified API response format
|
||||
// Response format: { success: true, data: { limits: [...] }, request_id: "..." }
|
||||
const limitsData = response?.data || response;
|
||||
return limitsData;
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error fetching usage limits:', error);
|
||||
throw error;
|
||||
@@ -1670,14 +1705,31 @@ export async function fetchSeedKeywords(filters?: {
|
||||
* Add SeedKeywords to workflow (create Keywords records)
|
||||
*/
|
||||
export async function addSeedKeywordsToWorkflow(seedKeywordIds: number[], siteId: number, sectorId: number): Promise<{ success: boolean; created: number; errors?: string[] }> {
|
||||
return fetchAPI('/v1/planner/keywords/bulk_add_from_seed/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
seed_keyword_ids: seedKeywordIds,
|
||||
site_id: siteId,
|
||||
sector_id: sectorId,
|
||||
}),
|
||||
});
|
||||
try {
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So response is already the data object: {created: X, ...}
|
||||
const response = await fetchAPI('/v1/planner/keywords/bulk_add_from_seed/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
seed_keyword_ids: seedKeywordIds,
|
||||
site_id: siteId,
|
||||
sector_id: sectorId,
|
||||
}),
|
||||
});
|
||||
|
||||
// Wrap extracted data with success: true for frontend compatibility
|
||||
if (response && typeof response === 'object') {
|
||||
return { success: true, ...response } as any;
|
||||
}
|
||||
|
||||
return { success: true, ...response } as any;
|
||||
} catch (error: any) {
|
||||
// Error responses are thrown by fetchAPI, but wrap them for consistency
|
||||
if (error.response && typeof error.response === 'object') {
|
||||
return { success: false, error: error.message, ...error.response } as any;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Author Profiles API
|
||||
|
||||
Reference in New Issue
Block a user