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:
@@ -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 }),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user