Implement Stage 3: Enhance content metadata and validation features

- Added entity metadata fields to the Tasks model, including entity_type, taxonomy, and cluster_role.
- Updated CandidateEngine to prioritize content relevance based on cluster mappings.
- Introduced metadata completeness scoring in ContentAnalyzer.
- Enhanced validation services to check for entity type and mapping completeness.
- Updated frontend components to display and validate new metadata fields.
- Implemented API endpoints for content validation and metadata persistence.
- Migrated existing data to populate new metadata fields for Tasks and Content.
This commit is contained in:
IGNY8 VPS (Salman)
2025-11-19 19:21:30 +00:00
parent 38f6026e73
commit bae9ea47d8
33 changed files with 2388 additions and 73 deletions

View File

@@ -1601,14 +1601,29 @@ export interface UsageSummary {
}
export async function fetchCreditBalance(): Promise<CreditBalance> {
const response = await fetchAPI('/v1/billing/credits/balance/balance/');
// fetchAPI automatically extracts data field from unified format
return response || {
credits: 0,
plan_credits_per_month: 0,
credits_used_this_month: 0,
credits_remaining: 0,
};
try {
const response = await fetchAPI('/v1/billing/credits/balance/balance/');
// fetchAPI automatically extracts data field from unified format
if (response && typeof response === 'object' && 'credits' in response) {
return response as CreditBalance;
}
// Return default if response is invalid
return {
credits: 0,
plan_credits_per_month: 0,
credits_used_this_month: 0,
credits_remaining: 0,
};
} catch (error: any) {
console.warn('Failed to fetch credit balance, using defaults:', error.message);
// Return default balance on error so UI can still render
return {
credits: 0,
plan_credits_per_month: 0,
credits_used_this_month: 0,
credits_remaining: 0,
};
}
}
export async function fetchCreditUsage(filters?: {
@@ -1867,6 +1882,28 @@ export interface Content {
updated_at: string;
has_image_prompts?: boolean;
has_generated_images?: boolean;
// Stage 3: Metadata fields
entity_type?: string | null;
cluster_name?: string | null;
cluster_id?: number | null;
taxonomy_name?: string | null;
taxonomy_id?: number | null;
cluster_role?: string | null;
// Additional fields used in Linker/Optimizer
source?: string;
sync_status?: string;
internal_links?: Array<{ anchor_text: string; target_content_id: number }>;
linker_version?: number;
optimization_scores?: {
seo_score: number;
readability_score: number;
engagement_score: number;
overall_score: number;
metadata_completeness_score?: number;
has_cluster_mapping?: boolean;
has_taxonomy_mapping?: boolean;
has_attributes?: boolean;
};
}
export interface ContentResponse {
@@ -1914,6 +1951,47 @@ export async function fetchContentById(id: number): Promise<Content> {
return fetchAPI(`/v1/writer/content/${id}/`);
}
// Stage 3: Content Validation API
export interface ContentValidationResult {
content_id: number;
is_valid: boolean;
ready_to_publish: boolean;
validation_errors: Array<{
field: string;
code: string;
message: string;
}>;
publish_errors: Array<{
field: string;
code: string;
message: string;
}>;
metadata: {
has_entity_type: boolean;
entity_type: string | null;
has_cluster_mapping: boolean;
has_taxonomy_mapping: boolean;
};
}
export async function fetchContentValidation(id: number): Promise<ContentValidationResult> {
return fetchAPI(`/v1/writer/content/${id}/validation/`);
}
export async function validateContent(id: number): Promise<{
content_id: number;
is_valid: boolean;
errors: Array<{
field: string;
code: string;
message: string;
}>;
}> {
return fetchAPI(`/v1/writer/content/${id}/validate/`, {
method: 'POST',
});
}
// Site Builder API
export interface SiteBlueprint {
id: number;
@@ -2015,29 +2093,32 @@ export async function fetchSiteBlueprints(filters?: {
if (filters?.page_size) params.append('page_size', filters.page_size.toString());
const queryString = params.toString();
return fetchAPI(`/v1/site-builder/siteblueprint/${queryString ? `?${queryString}` : ''}`);
const endpoint = queryString
? `/v1/site-builder/blueprints/?${queryString}`
: `/v1/site-builder/blueprints/`;
return fetchAPI(endpoint);
}
export async function fetchSiteBlueprintById(id: number): Promise<SiteBlueprint> {
return fetchAPI(`/v1/site-builder/siteblueprint/${id}/`);
return fetchAPI(`/v1/site-builder/blueprints/${id}/`);
}
export async function createSiteBlueprint(data: Partial<SiteBlueprint>): Promise<SiteBlueprint> {
return fetchAPI('/v1/site-builder/siteblueprint/', {
return fetchAPI('/v1/site-builder/blueprints/', {
method: 'POST',
body: JSON.stringify(data),
});
}
export async function updateSiteBlueprint(id: number, data: Partial<SiteBlueprint>): Promise<SiteBlueprint> {
return fetchAPI(`/v1/site-builder/siteblueprint/${id}/`, {
return fetchAPI(`/v1/site-builder/blueprints/${id}/`, {
method: 'PATCH',
body: JSON.stringify(data),
});
}
export async function fetchWizardContext(blueprintId: number): Promise<WizardContext> {
return fetchAPI(`/v1/site-builder/siteblueprint/${blueprintId}/workflow/context/`);
return fetchAPI(`/v1/site-builder/blueprints/${blueprintId}/workflow/context/`);
}
export async function updateWorkflowStep(
@@ -2046,7 +2127,7 @@ export async function updateWorkflowStep(
status: string,
metadata?: Record<string, any>
): Promise<WorkflowState> {
return fetchAPI(`/v1/site-builder/siteblueprint/${blueprintId}/workflow/step/`, {
return fetchAPI(`/v1/site-builder/blueprints/${blueprintId}/workflow/step/`, {
method: 'POST',
body: JSON.stringify({ step, status, metadata }),
});
@@ -2058,7 +2139,7 @@ export async function attachClustersToBlueprint(
clusterIds: number[],
role: 'hub' | 'supporting' | 'attribute' = 'hub'
): Promise<{ attached_count: number; clusters: Array<{ id: number; name: string; role: string; link_id: number }> }> {
return fetchAPI(`/v1/site-builder/siteblueprint/${blueprintId}/clusters/attach/`, {
return fetchAPI(`/v1/site-builder/blueprints/${blueprintId}/clusters/attach/`, {
method: 'POST',
body: JSON.stringify({ cluster_ids: clusterIds, role }),
});
@@ -2069,7 +2150,7 @@ export async function detachClustersFromBlueprint(
clusterIds?: number[],
role?: 'hub' | 'supporting' | 'attribute'
): Promise<{ detached_count: number }> {
return fetchAPI(`/v1/site-builder/siteblueprint/${blueprintId}/clusters/detach/`, {
return fetchAPI(`/v1/site-builder/blueprints/${blueprintId}/clusters/detach/`, {
method: 'POST',
body: JSON.stringify({ cluster_ids: clusterIds, role }),
});
@@ -2106,14 +2187,14 @@ export interface TaxonomyImportRecord {
}
export async function fetchBlueprintsTaxonomies(blueprintId: number): Promise<{ count: number; taxonomies: Taxonomy[] }> {
return fetchAPI(`/v1/site-builder/siteblueprint/${blueprintId}/taxonomies/`);
return fetchAPI(`/v1/site-builder/blueprints/${blueprintId}/taxonomies/`);
}
export async function createBlueprintTaxonomy(
blueprintId: number,
data: TaxonomyCreateData
): Promise<Taxonomy> {
return fetchAPI(`/v1/site-builder/siteblueprint/${blueprintId}/taxonomies/`, {
return fetchAPI(`/v1/site-builder/blueprints/${blueprintId}/taxonomies/`, {
method: 'POST',
body: JSON.stringify(data),
});
@@ -2124,7 +2205,7 @@ export async function importBlueprintsTaxonomies(
records: TaxonomyImportRecord[],
defaultType: string = 'blog_category'
): Promise<{ imported_count: number; taxonomies: Taxonomy[] }> {
return fetchAPI(`/v1/site-builder/siteblueprint/${blueprintId}/taxonomies/import/`, {
return fetchAPI(`/v1/site-builder/blueprints/${blueprintId}/taxonomies/import/`, {
method: 'POST',
body: JSON.stringify({ records, default_type: defaultType }),
});
@@ -2135,7 +2216,7 @@ export async function updatePageBlueprint(
pageId: number,
data: Partial<PageBlueprint>
): Promise<PageBlueprint> {
return fetchAPI(`/v1/site-builder/pageblueprint/${pageId}/`, {
return fetchAPI(`/v1/site-builder/pages/${pageId}/`, {
method: 'PATCH',
body: JSON.stringify(data),
});
@@ -2144,7 +2225,7 @@ export async function updatePageBlueprint(
export async function regeneratePageBlueprint(
pageId: number
): Promise<{ success: boolean; task_id?: string }> {
return fetchAPI(`/v1/site-builder/pageblueprint/${pageId}/regenerate/`, {
return fetchAPI(`/v1/site-builder/pages/${pageId}/regenerate/`, {
method: 'POST',
});
}
@@ -2153,7 +2234,7 @@ export async function generatePageContent(
pageId: number,
force?: boolean
): Promise<{ success: boolean; task_id?: string }> {
return fetchAPI(`/v1/site-builder/pageblueprint/${pageId}/generate_content/`, {
return fetchAPI(`/v1/site-builder/pages/${pageId}/generate_content/`, {
method: 'POST',
body: JSON.stringify({ force: force || false }),
});