Recovered state: integrated TrendSurferAgent, restored frontend/backend files, and cleaned up recovery scripts
This commit is contained in:
55
frontend/src/api/agentsTeam.ts
Normal file
55
frontend/src/api/agentsTeam.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { apiClient, aiApiClient } from "./client";
|
||||
|
||||
export type AgentTeamCatalogEntry = {
|
||||
agent_key: string;
|
||||
agent_type?: string;
|
||||
role?: string;
|
||||
responsibilities: string[];
|
||||
tools: string[];
|
||||
defaults?: {
|
||||
display_name_template?: string;
|
||||
enabled?: boolean;
|
||||
schedule?: any;
|
||||
};
|
||||
profile?: {
|
||||
display_name?: string | null;
|
||||
enabled?: boolean;
|
||||
schedule?: any;
|
||||
notification_prefs?: any;
|
||||
tone?: any;
|
||||
system_prompt?: string | null;
|
||||
task_prompt_template?: string | null;
|
||||
reporting_prefs?: any;
|
||||
updated_at?: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
export async function getAgentTeam(): Promise<AgentTeamCatalogEntry[]> {
|
||||
const res = await apiClient.get("/api/agents/team");
|
||||
return res.data?.data?.agents || [];
|
||||
}
|
||||
|
||||
export async function saveAgentProfile(agentKey: string, payload: Record<string, any>) {
|
||||
const res = await apiClient.post(`/api/agents/team/${encodeURIComponent(agentKey)}`, payload);
|
||||
return res.data?.data?.profile;
|
||||
}
|
||||
|
||||
export async function aiOptimizeAgentProfile(
|
||||
agentKey: string,
|
||||
scope: "agent" | "system_prompt" | "task_prompt_template",
|
||||
contextCard: Record<string, any>
|
||||
) {
|
||||
const res = await aiApiClient.post(`/api/agents/team/${encodeURIComponent(agentKey)}/ai-optimize`, {
|
||||
scope,
|
||||
context_card: contextCard,
|
||||
});
|
||||
return res.data?.data?.suggestion;
|
||||
}
|
||||
|
||||
export async function previewAgentProfile(agentKey: string, contextCard: Record<string, any>) {
|
||||
const res = await aiApiClient.post(`/api/agents/team/${encodeURIComponent(agentKey)}/preview`, {
|
||||
context_card: contextCard,
|
||||
});
|
||||
return res.data?.data?.preview;
|
||||
}
|
||||
|
||||
149
frontend/src/api/brandAssets.ts
Normal file
149
frontend/src/api/brandAssets.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { apiClient, aiApiClient } from './client';
|
||||
|
||||
export interface AssetResponse {
|
||||
success: boolean;
|
||||
image_url?: string;
|
||||
image_base64?: string;
|
||||
optimized_prompt?: string;
|
||||
asset_id?: number;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface VoiceCloneResponse {
|
||||
success: boolean;
|
||||
custom_voice_id?: string;
|
||||
preview_audio_url?: string;
|
||||
asset_id?: number;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const generateBrandAvatar = async (
|
||||
prompt: string,
|
||||
stylePreset?: string,
|
||||
aspectRatio: string = "1:1"
|
||||
): Promise<AssetResponse> => {
|
||||
try {
|
||||
const response = await apiClient.post('/onboarding/assets/generate-avatar', {
|
||||
prompt,
|
||||
style_preset: stylePreset,
|
||||
aspect_ratio: aspectRatio,
|
||||
user_id: "current_user" // Backend extracts actual user
|
||||
});
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
console.error('Avatar generation error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.detail || 'Failed to generate avatar'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const optimizeAvatarPrompt = async (prompt: string): Promise<AssetResponse> => {
|
||||
try {
|
||||
const response = await apiClient.post('/onboarding/assets/enhance-prompt', {
|
||||
prompt,
|
||||
user_id: "current_user"
|
||||
});
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
console.error('Prompt enhancement error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.detail || 'Failed to enhance prompt'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const createAvatarVariation = async (
|
||||
prompt: string,
|
||||
file: File
|
||||
): Promise<AssetResponse> => {
|
||||
// TODO: Implement backend endpoint for variation
|
||||
// For now, return a mock error or handle as new generation
|
||||
console.warn("createAvatarVariation not fully implemented in backend");
|
||||
return {
|
||||
success: false,
|
||||
error: "Feature not available yet"
|
||||
};
|
||||
};
|
||||
|
||||
export const enhanceBrandAvatar = async (
|
||||
file: File
|
||||
): Promise<AssetResponse> => {
|
||||
// TODO: Implement backend endpoint for enhancement (upscaling)
|
||||
console.warn("enhanceBrandAvatar not fully implemented in backend");
|
||||
return {
|
||||
success: false,
|
||||
error: "Feature not available yet"
|
||||
};
|
||||
};
|
||||
|
||||
export const setBrandAvatar = async (
|
||||
data: {
|
||||
image_base64: string;
|
||||
domain_name?: string;
|
||||
title: string;
|
||||
}
|
||||
): Promise<AssetResponse> => {
|
||||
// TODO: Implement backend endpoint to set as active avatar
|
||||
// For now, simulate success
|
||||
return {
|
||||
success: true,
|
||||
message: "Avatar set as active"
|
||||
};
|
||||
};
|
||||
|
||||
export interface VoiceCloneParams {
|
||||
audioFile: File;
|
||||
engine: 'minimax' | 'qwen3';
|
||||
customVoiceId?: string;
|
||||
model?: string;
|
||||
text?: string;
|
||||
referenceText?: string;
|
||||
language?: string;
|
||||
needNoiseReduction?: boolean;
|
||||
needVolumeNormalization?: boolean;
|
||||
accuracy?: number;
|
||||
languageBoost?: string;
|
||||
}
|
||||
|
||||
export const createVoiceClone = async (
|
||||
params: VoiceCloneParams
|
||||
): Promise<VoiceCloneResponse> => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', params.audioFile);
|
||||
formData.append('engine', params.engine);
|
||||
formData.append('user_id', "current_user");
|
||||
|
||||
if (params.customVoiceId) formData.append('custom_voice_id', params.customVoiceId);
|
||||
if (params.model) formData.append('model', params.model);
|
||||
if (params.text) formData.append('text', params.text);
|
||||
if (params.referenceText) formData.append('reference_text', params.referenceText);
|
||||
if (params.language) formData.append('language', params.language);
|
||||
if (params.needNoiseReduction !== undefined) formData.append('need_noise_reduction', String(params.needNoiseReduction));
|
||||
if (params.needVolumeNormalization !== undefined) formData.append('need_volume_normalization', String(params.needVolumeNormalization));
|
||||
if (params.accuracy !== undefined) formData.append('accuracy', String(params.accuracy));
|
||||
if (params.languageBoost) formData.append('language_boost', params.languageBoost);
|
||||
|
||||
// Legacy field support (voiceName was used before)
|
||||
// We might want to remove this if backend doesn't need it
|
||||
formData.append('voice_name', 'My Voice Clone');
|
||||
|
||||
const response = await apiClient.post('/onboarding/assets/create-voice-clone', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
console.error('Voice cloning error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.detail || 'Failed to create voice clone'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -121,16 +121,23 @@ apiClient.interceptors.request.use(
|
||||
try {
|
||||
const token = await authTokenGetter();
|
||||
if (token) {
|
||||
config.headers = config.headers || {};
|
||||
(config.headers as any)['Authorization'] = `Bearer ${token}`;
|
||||
config.headers = config.headers || {};
|
||||
(config.headers as any)['Authorization'] = `Bearer ${token}`;
|
||||
console.log(`[apiClient] ✅ Added auth token to request: ${config.url}`);
|
||||
} else {
|
||||
// Token getter returned null - reject request to prevent 401 errors
|
||||
// ProtectedRoute should ensure user is authenticated before components render
|
||||
console.error(`[apiClient] ❌ authTokenGetter returned null for ${config.url} - rejecting request`);
|
||||
console.error(`[apiClient] User ID from localStorage: ${localStorage.getItem('user_id') || 'none'}`);
|
||||
console.error(`[apiClient] This usually means user is not signed in or token expired. ProtectedRoute should prevent this.`);
|
||||
return Promise.reject(new Error('Authentication token not available. Please sign in to continue.'));
|
||||
// Token getter returned null - reject request to prevent 401 errors
|
||||
// ProtectedRoute should ensure user is authenticated before components render
|
||||
console.error(`[apiClient] ❌ authTokenGetter returned null for ${config.url} - rejecting request`);
|
||||
console.error(`[apiClient] User ID from localStorage: ${localStorage.getItem('user_id') || 'none'}`);
|
||||
|
||||
// Redirect if on protected route to force re-auth
|
||||
const isRootRoute = window.location.pathname === '/';
|
||||
if (!isRootRoute) {
|
||||
console.warn('[apiClient] Redirecting to login due to missing auth token');
|
||||
try { window.location.assign('/'); } catch {}
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Authentication token not available. Please sign in to continue.'));
|
||||
}
|
||||
} catch (tokenError) {
|
||||
console.error(`[apiClient] ❌ Error getting auth token for ${config.url}:`, tokenError);
|
||||
@@ -208,13 +215,12 @@ apiClient.interceptors.response.use(
|
||||
}
|
||||
|
||||
// If retry failed, token is expired - sign out user and redirect to sign in
|
||||
const isOnboardingRoute = window.location.pathname.includes('/onboarding');
|
||||
const isRootRoute = window.location.pathname === '/';
|
||||
const isContentPlanningRoute = window.location.pathname.includes('/content-planning');
|
||||
|
||||
// Don't redirect from root route or content-planning during app initialization
|
||||
// ProtectedRoute should handle authentication state
|
||||
if (!isRootRoute && !isOnboardingRoute && !isContentPlanningRoute) {
|
||||
if (!isRootRoute && !isContentPlanningRoute) {
|
||||
// Token expired - sign out user and redirect to landing/sign-in
|
||||
console.warn('401 Unauthorized - token expired, signing out user');
|
||||
|
||||
@@ -248,13 +254,12 @@ apiClient.interceptors.response.use(
|
||||
|
||||
// Handle 401 errors that weren't retried (e.g., no authTokenGetter, already retried, etc.)
|
||||
if (error?.response?.status === 401 && (originalRequest._retry || !authTokenGetter)) {
|
||||
const isOnboardingRoute = window.location.pathname.includes('/onboarding');
|
||||
const isRootRoute = window.location.pathname === '/';
|
||||
const isContentPlanningRoute = window.location.pathname.includes('/content-planning');
|
||||
|
||||
// Don't redirect for content-planning during initial load - let ProtectedRoute handle it
|
||||
// This prevents redirect loops when requests are made before auth is fully ready
|
||||
if (!isRootRoute && !isOnboardingRoute && !isContentPlanningRoute) {
|
||||
if (!isRootRoute && !isContentPlanningRoute) {
|
||||
// Token expired - sign out user and redirect
|
||||
console.warn('401 Unauthorized - token expired (not retried), signing out user');
|
||||
localStorage.removeItem('user_id');
|
||||
@@ -343,11 +348,10 @@ aiApiClient.interceptors.response.use(
|
||||
console.error('Token refresh failed:', retryError);
|
||||
}
|
||||
|
||||
const isOnboardingRoute = window.location.pathname.includes('/onboarding');
|
||||
const isRootRoute = window.location.pathname === '/';
|
||||
|
||||
// Don't redirect from root route during app initialization
|
||||
if (!isRootRoute && !isOnboardingRoute) {
|
||||
if (!isRootRoute) {
|
||||
// Token expired - sign out user and redirect
|
||||
console.warn('401 Unauthorized - token expired, signing out user');
|
||||
localStorage.removeItem('user_id');
|
||||
@@ -388,12 +392,35 @@ longRunningApiClient.interceptors.request.use(
|
||||
async (config) => {
|
||||
console.log(`Making long-running ${config.method?.toUpperCase()} request to ${config.url}`);
|
||||
try {
|
||||
const token = authTokenGetter ? await authTokenGetter() : null;
|
||||
if (token) {
|
||||
config.headers = config.headers || {};
|
||||
(config.headers as any)['Authorization'] = `Bearer ${token}`;
|
||||
if (!authTokenGetter) {
|
||||
console.warn(`[longRunningApiClient] ⚠️ authTokenGetter not set for ${config.url} - request may fail authentication`);
|
||||
} else {
|
||||
try {
|
||||
const token = await authTokenGetter();
|
||||
if (token) {
|
||||
config.headers = config.headers || {};
|
||||
(config.headers as any)['Authorization'] = `Bearer ${token}`;
|
||||
} else {
|
||||
console.warn(`[longRunningApiClient] ⚠️ authTokenGetter returned null for ${config.url} - user may not be signed in`);
|
||||
|
||||
// Redirect if on protected route to force re-auth
|
||||
const isRootRoute = window.location.pathname === '/';
|
||||
if (!isRootRoute) {
|
||||
console.warn('[longRunningApiClient] Redirecting to login due to missing auth token');
|
||||
try { window.location.assign('/'); } catch {}
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Authentication token not available. Please sign in or reload the page.'));
|
||||
}
|
||||
} catch (tokenError) {
|
||||
console.error(`[longRunningApiClient] ❌ Error getting auth token for ${config.url}:`, tokenError);
|
||||
return Promise.reject(new Error('Failed to get authentication token.'));
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
console.error(`[longRunningApiClient] ❌ Unexpected error in request interceptor for ${config.url}:`, e);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
@@ -406,13 +433,29 @@ longRunningApiClient.interceptors.response.use(
|
||||
return response;
|
||||
},
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
// If 401 and we haven't retried yet, try to refresh token and retry
|
||||
if (error?.response?.status === 401 && !originalRequest._retry && authTokenGetter) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
try {
|
||||
const newToken = await authTokenGetter();
|
||||
if (newToken) {
|
||||
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
|
||||
return longRunningApiClient(originalRequest);
|
||||
}
|
||||
} catch (retryError) {
|
||||
console.error('Token refresh failed:', retryError);
|
||||
}
|
||||
}
|
||||
|
||||
if (error?.response?.status === 401) {
|
||||
// Only redirect on 401 if we're not in onboarding flow or root route
|
||||
const isOnboardingRoute = window.location.pathname.includes('/onboarding');
|
||||
// Redirect on 401 unless we're on root route (app initialization)
|
||||
// We allow redirect on onboarding to handle expired sessions
|
||||
const isRootRoute = window.location.pathname === '/';
|
||||
|
||||
// Don't redirect from root route during app initialization
|
||||
if (!isRootRoute && !isOnboardingRoute) {
|
||||
if (!isRootRoute) {
|
||||
try { window.location.assign('/'); } catch {}
|
||||
} else {
|
||||
console.warn('401 Unauthorized during initialization - token may need refresh (not redirecting)');
|
||||
@@ -431,7 +474,7 @@ longRunningApiClient.interceptors.response.use(
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Long-running API Error:', error.response?.status, error.response?.data);
|
||||
console.error('Long-running API Error:', error.message || error, error.response?.status, error.response?.data);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
@@ -441,12 +484,35 @@ pollingApiClient.interceptors.request.use(
|
||||
async (config) => {
|
||||
console.log(`Making polling ${config.method?.toUpperCase()} request to ${config.url}`);
|
||||
try {
|
||||
const token = authTokenGetter ? await authTokenGetter() : null;
|
||||
if (token) {
|
||||
config.headers = config.headers || {};
|
||||
(config.headers as any)['Authorization'] = `Bearer ${token}`;
|
||||
if (!authTokenGetter) {
|
||||
console.warn(`[pollingApiClient] ⚠️ authTokenGetter not set for ${config.url} - request may fail authentication`);
|
||||
} else {
|
||||
try {
|
||||
const token = await authTokenGetter();
|
||||
if (token) {
|
||||
config.headers = config.headers || {};
|
||||
(config.headers as any)['Authorization'] = `Bearer ${token}`;
|
||||
} else {
|
||||
console.warn(`[pollingApiClient] ⚠️ authTokenGetter returned null for ${config.url} - user may not be signed in`);
|
||||
|
||||
// Redirect if on protected route to force re-auth
|
||||
const isRootRoute = window.location.pathname === '/';
|
||||
if (!isRootRoute) {
|
||||
console.warn('[pollingApiClient] Redirecting to login due to missing auth token');
|
||||
try { window.location.assign('/'); } catch {}
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Authentication token not available. Please sign in or reload the page.'));
|
||||
}
|
||||
} catch (tokenError) {
|
||||
console.error(`[pollingApiClient] ❌ Error getting auth token for ${config.url}:`, tokenError);
|
||||
return Promise.reject(new Error('Failed to get authentication token.'));
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
console.error(`[pollingApiClient] ❌ Unexpected error in request interceptor for ${config.url}:`, e);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
@@ -459,13 +525,29 @@ pollingApiClient.interceptors.response.use(
|
||||
return response;
|
||||
},
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
// If 401 and we haven't retried yet, try to refresh token and retry
|
||||
if (error?.response?.status === 401 && !originalRequest._retry && authTokenGetter) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
try {
|
||||
const newToken = await authTokenGetter();
|
||||
if (newToken) {
|
||||
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
|
||||
return pollingApiClient(originalRequest);
|
||||
}
|
||||
} catch (retryError) {
|
||||
console.error('Token refresh failed:', retryError);
|
||||
}
|
||||
}
|
||||
|
||||
if (error?.response?.status === 401) {
|
||||
// Only redirect on 401 if we're not in onboarding flow or root route
|
||||
const isOnboardingRoute = window.location.pathname.includes('/onboarding');
|
||||
// Redirect on 401 unless we're on root route (app initialization)
|
||||
// We allow redirect on onboarding to handle expired sessions
|
||||
const isRootRoute = window.location.pathname === '/';
|
||||
|
||||
// Don't redirect from root route during app initialization
|
||||
if (!isRootRoute && !isOnboardingRoute) {
|
||||
if (!isRootRoute) {
|
||||
try { window.location.assign('/'); } catch {}
|
||||
} else {
|
||||
console.warn('401 Unauthorized during initialization - token may need refresh (not redirecting)');
|
||||
@@ -494,7 +576,7 @@ pollingApiClient.interceptors.response.use(
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Polling API Error:', error.response?.status, error.response?.data);
|
||||
console.error('Polling API Error:', error.message || error, error.response?.status, error.response?.data);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
@@ -11,6 +11,8 @@ export interface PersonaGenerationRequest {
|
||||
competitorResearch?: any;
|
||||
sitemapAnalysis?: any;
|
||||
businessData?: any;
|
||||
researchPreferences?: any;
|
||||
deepCompetitorAnalysis?: any;
|
||||
};
|
||||
selected_platforms: string[];
|
||||
user_preferences?: any;
|
||||
@@ -124,15 +126,26 @@ export const getPersonaGenerationOptions = async (): Promise<PersonaOptions> =>
|
||||
* Utility function to prepare onboarding data for persona generation.
|
||||
*/
|
||||
export const prepareOnboardingData = (stepData: any) => {
|
||||
return {
|
||||
websiteAnalysis: stepData?.analysis || null,
|
||||
competitorResearch: {
|
||||
const websiteAnalysis =
|
||||
stepData?.websiteAnalysis ??
|
||||
stepData?.analysis ??
|
||||
null;
|
||||
|
||||
const competitorResearch =
|
||||
stepData?.competitorResearch ??
|
||||
{
|
||||
competitors: stepData?.competitors || [],
|
||||
researchSummary: stepData?.researchSummary || null,
|
||||
socialMediaAccounts: stepData?.socialMediaAccounts || {}
|
||||
},
|
||||
sitemapAnalysis: stepData?.sitemapAnalysis || null,
|
||||
businessData: stepData?.businessData || null
|
||||
socialMediaAccounts: stepData?.socialMediaAccounts || {},
|
||||
};
|
||||
|
||||
return {
|
||||
websiteAnalysis,
|
||||
competitorResearch,
|
||||
sitemapAnalysis: stepData?.sitemapAnalysis ?? null,
|
||||
businessData: stepData?.businessData ?? null,
|
||||
researchPreferences: stepData?.researchPreferences ?? null,
|
||||
deepCompetitorAnalysis: stepData?.deepCompetitorAnalysis ?? null,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
104
frontend/src/api/semanticDashboard.ts
Normal file
104
frontend/src/api/semanticDashboard.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { apiClient } from './client';
|
||||
|
||||
// Semantic health metric interface matching backend SemanticHealthMetric
|
||||
export interface SemanticHealthMetric {
|
||||
metric_name: string;
|
||||
value: number;
|
||||
threshold: number;
|
||||
status: 'healthy' | 'warning' | 'critical';
|
||||
timestamp: string;
|
||||
description: string;
|
||||
recommendations: string[];
|
||||
}
|
||||
|
||||
// Competitor semantic snapshot interface
|
||||
export interface CompetitorSemanticSnapshot {
|
||||
competitor_id: string;
|
||||
competitor_name: string;
|
||||
semantic_overlap: number;
|
||||
unique_topics: string[];
|
||||
content_volume: number;
|
||||
authority_score: number;
|
||||
last_updated: string;
|
||||
trending_topics: string[];
|
||||
}
|
||||
|
||||
// Content semantic insight interface
|
||||
export interface ContentSemanticInsight {
|
||||
insight_id: string;
|
||||
insight_type: 'gap' | 'opportunity' | 'trend' | 'threat';
|
||||
title: string;
|
||||
description: string;
|
||||
confidence_score: number;
|
||||
impact_score: number;
|
||||
related_topics: string[];
|
||||
suggested_actions: string[];
|
||||
created_at: string;
|
||||
expires_at: string;
|
||||
}
|
||||
|
||||
// Semantic dashboard data interface
|
||||
export interface SemanticDashboardData {
|
||||
health_metrics: SemanticHealthMetric[];
|
||||
competitor_snapshots: CompetitorSemanticSnapshot[];
|
||||
content_insights: ContentSemanticInsight[];
|
||||
monitoring_status: 'active' | 'inactive';
|
||||
last_updated: string;
|
||||
}
|
||||
|
||||
// Semantic Dashboard API functions
|
||||
export const semanticDashboardAPI = {
|
||||
// Get semantic health metrics
|
||||
async getSemanticHealth(): Promise<SemanticHealthMetric> {
|
||||
try {
|
||||
const response = await apiClient.get('/api/seo-dashboard/semantic-health');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching semantic health:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get complete semantic dashboard data
|
||||
async getSemanticDashboardData(): Promise<SemanticDashboardData> {
|
||||
try {
|
||||
const response = await apiClient.get('/api/semantic-dashboard/data');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching semantic dashboard data:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get competitor semantic snapshots
|
||||
async getCompetitorSnapshots(): Promise<CompetitorSemanticSnapshot[]> {
|
||||
try {
|
||||
const response = await apiClient.get('/api/semantic-dashboard/competitors');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching competitor snapshots:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get content insights
|
||||
async getContentInsights(): Promise<ContentSemanticInsight[]> {
|
||||
try {
|
||||
const response = await apiClient.get('/api/semantic-dashboard/insights');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching content insights:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Refresh semantic analysis
|
||||
async refreshSemanticAnalysis(): Promise<void> {
|
||||
try {
|
||||
await apiClient.post('/api/semantic-dashboard/refresh');
|
||||
} catch (error) {
|
||||
console.error('Error refreshing semantic analysis:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -52,11 +52,27 @@ export interface SEODashboardData {
|
||||
position: number;
|
||||
};
|
||||
timeseries?: any[];
|
||||
advertools_insights?: any;
|
||||
competitor_insights?: {
|
||||
competitor_keywords: any[];
|
||||
content_gaps: any[];
|
||||
opportunity_score: number;
|
||||
};
|
||||
technical_seo_audit?: {
|
||||
status: string;
|
||||
task_status?: string | null;
|
||||
next_execution?: string | null;
|
||||
pages_audited: number;
|
||||
avg_score: number;
|
||||
fix_scheduled_pages: number;
|
||||
worst_pages: Array<{
|
||||
page_url: string;
|
||||
overall_score: number;
|
||||
status: string;
|
||||
issues_count?: number;
|
||||
}>;
|
||||
error?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// SEO Dashboard API functions
|
||||
@@ -126,4 +142,4 @@ export const seoDashboardAPI = {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user