Recovered state: integrated TrendSurferAgent, restored frontend/backend files, and cleaned up recovery scripts

This commit is contained in:
ajaysi
2026-02-08 13:56:57 +05:30
parent 1db10ccd0f
commit e404a86502
333 changed files with 42223 additions and 10875 deletions

View 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;
}

View 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'
};
}
};

View File

@@ -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);
}
);

View File

@@ -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,
};
};

View 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;
}
}
};

View File

@@ -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;
}
}
};
};