diff --git a/frontend/src/components/common/ValidationCard.tsx b/frontend/src/components/common/ValidationCard.tsx index 4320ada8..e03072e5 100644 --- a/frontend/src/components/common/ValidationCard.tsx +++ b/frontend/src/components/common/ValidationCard.tsx @@ -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) { diff --git a/frontend/src/hooks/usePersistentToggle.ts b/frontend/src/hooks/usePersistentToggle.ts index 7807fc3c..b9c2ce42 100644 --- a/frontend/src/hooks/usePersistentToggle.ts +++ b/frontend/src/hooks/usePersistentToggle.ts @@ -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)); diff --git a/frontend/src/pages/Settings/Status.tsx b/frontend/src/pages/Settings/Status.tsx index 50d02fff..65783365 100644 --- a/frontend/src/pages/Settings/Status.tsx +++ b/frontend/src/pages/Settings/Status.tsx @@ -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'); diff --git a/frontend/src/pages/Thinker/Prompts.tsx b/frontend/src/pages/Thinker/Prompts.tsx index f3ee9f71..5cded78b 100644 --- a/frontend/src/pages/Thinker/Prompts.tsx +++ b/frontend/src/pages/Thinker/Prompts.tsx @@ -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}`); diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 277b863e..dbb1a98b 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1061,11 +1061,28 @@ export async function autoGenerateImages(taskIds: number[]): Promise<{ success: } } -export async function generateImagePrompts(contentIds: number[]): Promise { - 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 { - 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 { @@ -1451,8 +1485,10 @@ export interface ModuleSetting { } export async function fetchModuleSettings(moduleName: string): Promise { + // 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; is_active?: boolean }): Promise { @@ -1571,12 +1607,11 @@ export interface UsageLimitsResponse { export async function fetchUsageLimits(): Promise { 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