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 {
|
try {
|
||||||
// Get saved settings to get API key and model
|
// 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}/`);
|
const settingsData = await fetchAPI(`/v1/system/settings/integrations/${integrationId}/`);
|
||||||
|
|
||||||
let apiKey = '';
|
let apiKey = '';
|
||||||
let model = 'gpt-4.1';
|
let model = 'gpt-4.1';
|
||||||
|
|
||||||
if (settingsData.success && settingsData.data) {
|
if (settingsData && typeof settingsData === 'object') {
|
||||||
apiKey = settingsData.data.apiKey || '';
|
apiKey = settingsData.apiKey || '';
|
||||||
model = settingsData.data.model || 'gpt-4.1';
|
model = settingsData.model || 'gpt-4.1';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!apiKey) {
|
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/`, {
|
const data = await fetchAPI(`/v1/system/settings/integrations/${integrationId}/test/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(requestBody),
|
body: JSON.stringify(requestBody),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.success) {
|
// Check if data has success field (direct Response format) or is extracted data
|
||||||
setTestResult({
|
if (data && typeof data === 'object' && ('success' in data ? data.success : true)) {
|
||||||
success: true,
|
// If data has success field, use it; otherwise assume success (extracted data)
|
||||||
message: data.message || 'API connection successful!',
|
const isSuccess = data.success !== false;
|
||||||
model_used: data.model_used || data.model,
|
if (isSuccess) {
|
||||||
response: data.response,
|
setTestResult({
|
||||||
tokens_used: data.tokens_used,
|
success: true,
|
||||||
total_tokens: data.total_tokens,
|
message: data.message || 'API connection successful!',
|
||||||
cost: data.cost,
|
model_used: data.model_used || data.model,
|
||||||
full_response: data.full_response || {
|
response: data.response,
|
||||||
image_url: data.image_url,
|
tokens_used: data.tokens_used,
|
||||||
provider: data.provider,
|
total_tokens: data.total_tokens,
|
||||||
size: data.size,
|
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 {
|
} else {
|
||||||
setTestResult({
|
setTestResult({
|
||||||
success: false,
|
success: false,
|
||||||
message: data.error || data.message || 'API connection failed',
|
message: 'Invalid response format',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -168,23 +168,21 @@ export function usePersistentToggle(
|
|||||||
const endpoint = saveEndpoint.replace('{id}', resourceId);
|
const endpoint = saveEndpoint.replace('{id}', resourceId);
|
||||||
const payload = buildPayload(data || {}, newEnabled);
|
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',
|
method: 'POST',
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.success) {
|
// Update local state
|
||||||
// Update local state
|
const updatedData = { ...(data || {}), enabled: newEnabled };
|
||||||
const updatedData = { ...(data || {}), enabled: newEnabled };
|
setData(updatedData);
|
||||||
setData(updatedData);
|
setEnabled(newEnabled);
|
||||||
setEnabled(newEnabled);
|
|
||||||
|
|
||||||
// Call success callback - pass both enabled state and full config data
|
// Call success callback - pass both enabled state and full config data
|
||||||
if (onToggleSuccess) {
|
if (onToggleSuccess) {
|
||||||
onToggleSuccess(newEnabled, updatedData);
|
onToggleSuccess(newEnabled, updatedData);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(result.error || 'Failed to save state');
|
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const error = err instanceof Error ? err : new Error(String(err));
|
const error = err instanceof Error ? err : new Error(String(err));
|
||||||
|
|||||||
@@ -63,10 +63,10 @@ export default function Status() {
|
|||||||
|
|
||||||
const fetchStatus = async () => {
|
const fetchStatus = async () => {
|
||||||
try {
|
try {
|
||||||
|
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||||
|
// So response IS the data object
|
||||||
const response = await fetchAPI('/v1/system/status/');
|
const response = await fetchAPI('/v1/system/status/');
|
||||||
// Handle unified API response format: {success: true, data: {...}}
|
setStatus(response);
|
||||||
const statusData = response?.data || response;
|
|
||||||
setStatus(statusData);
|
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||||
|
|||||||
@@ -75,11 +75,10 @@ export default function Prompts() {
|
|||||||
try {
|
try {
|
||||||
const promises = PROMPT_TYPES.map(async (type) => {
|
const promises = PROMPT_TYPES.map(async (type) => {
|
||||||
try {
|
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}/`);
|
const response = await fetchAPI(`/v1/system/prompts/by_type/${type.key}/`);
|
||||||
// Extract data field from unified API response format
|
return { key: type.key, data: response };
|
||||||
// Response format: { success: true, data: {...}, request_id: "..." }
|
|
||||||
const data = response?.data || response;
|
|
||||||
return { key: type.key, data };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error loading prompt ${type.key}:`, error);
|
console.error(`Error loading prompt ${type.key}:`, error);
|
||||||
return { key: type.key, data: null };
|
return { key: type.key, data: null };
|
||||||
@@ -119,7 +118,10 @@ export default function Prompts() {
|
|||||||
|
|
||||||
setSaving({ ...saving, [promptType]: true });
|
setSaving({ ...saving, [promptType]: true });
|
||||||
try {
|
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',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
prompt_type: promptType,
|
prompt_type: promptType,
|
||||||
@@ -127,16 +129,8 @@ export default function Prompts() {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extract data field from unified API response format
|
toast.success('Prompt saved successfully');
|
||||||
// Response format: { success: true, data: {...}, message: "...", request_id: "..." }
|
await loadPrompts(); // Reload to get updated data
|
||||||
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');
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error saving prompt:', error);
|
console.error('Error saving prompt:', error);
|
||||||
toast.error(`Failed to save prompt: ${error.message}`);
|
toast.error(`Failed to save prompt: ${error.message}`);
|
||||||
@@ -152,23 +146,18 @@ export default function Prompts() {
|
|||||||
|
|
||||||
setSaving({ ...saving, [promptType]: true });
|
setSaving({ ...saving, [promptType]: true });
|
||||||
try {
|
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',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
prompt_type: promptType,
|
prompt_type: promptType,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extract data field from unified API response format
|
toast.success('Prompt reset to default');
|
||||||
// Response format: { success: true, data: {...}, message: "...", request_id: "..." }
|
await loadPrompts(); // Reload to get default value
|
||||||
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');
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error resetting prompt:', error);
|
console.error('Error resetting prompt:', error);
|
||||||
toast.error(`Failed to reset prompt: ${error.message}`);
|
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> {
|
export async function generateImagePrompts(contentIds: number[]): Promise<{ success: boolean; task_id?: string; message?: string; error?: string }> {
|
||||||
return fetchAPI('/v1/writer/content/generate_image_prompts/', {
|
try {
|
||||||
method: 'POST',
|
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||||
body: JSON.stringify({ ids: contentIds }),
|
// 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
|
// 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> {
|
export async function generateImages(imageIds: number[], contentId?: number): Promise<{ success: boolean; task_id?: string; message?: string; error?: string }> {
|
||||||
return fetchAPI('/v1/writer/images/generate_images/', {
|
try {
|
||||||
method: 'POST',
|
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||||
body: JSON.stringify({
|
// So response is already the data object: {task_id: "...", ...}
|
||||||
ids: imageIds,
|
const response = await fetchAPI('/v1/writer/images/generate_images/', {
|
||||||
content_id: contentId
|
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> {
|
export async function fetchImages(filters: ImageFilters = {}): Promise<ImageListResponse> {
|
||||||
@@ -1451,8 +1485,10 @@ export interface ModuleSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchModuleSettings(moduleName: string): Promise<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}/`);
|
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> {
|
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> {
|
export async function fetchUsageLimits(): Promise<UsageLimitsResponse> {
|
||||||
console.log('Fetching usage limits from:', '/v1/billing/credits/usage/limits/');
|
console.log('Fetching usage limits from:', '/v1/billing/credits/usage/limits/');
|
||||||
try {
|
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/');
|
const response = await fetchAPI('/v1/billing/credits/usage/limits/');
|
||||||
console.log('Usage limits API response:', response);
|
console.log('Usage limits API response:', response);
|
||||||
// Extract data field from unified API response format
|
return response;
|
||||||
// Response format: { success: true, data: { limits: [...] }, request_id: "..." }
|
|
||||||
const limitsData = response?.data || response;
|
|
||||||
return limitsData;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching usage limits:', error);
|
console.error('Error fetching usage limits:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -1670,14 +1705,31 @@ export async function fetchSeedKeywords(filters?: {
|
|||||||
* Add SeedKeywords to workflow (create Keywords records)
|
* Add SeedKeywords to workflow (create Keywords records)
|
||||||
*/
|
*/
|
||||||
export async function addSeedKeywordsToWorkflow(seedKeywordIds: number[], siteId: number, sectorId: number): Promise<{ success: boolean; created: number; errors?: string[] }> {
|
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/', {
|
try {
|
||||||
method: 'POST',
|
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||||
body: JSON.stringify({
|
// So response is already the data object: {created: X, ...}
|
||||||
seed_keyword_ids: seedKeywordIds,
|
const response = await fetchAPI('/v1/planner/keywords/bulk_add_from_seed/', {
|
||||||
site_id: siteId,
|
method: 'POST',
|
||||||
sector_id: sectorId,
|
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
|
// Author Profiles API
|
||||||
|
|||||||
Reference in New Issue
Block a user