Phase 6: Jumpstart Wizard UI & WP Client
This commit is contained in:
@@ -26,8 +26,9 @@ export class CartesianEngine {
|
||||
|
||||
/**
|
||||
* Generate a single article based on specific inputs.
|
||||
* @param overrides Optional overrides for slug, title, etc.
|
||||
*/
|
||||
async generateArticle(context: GenerationContext) {
|
||||
async generateArticle(context: GenerationContext, overrides?: any) {
|
||||
const { avatar, niche, city, site, template } = context;
|
||||
const variant = await this.getAvatarVariant(avatar.id, 'neutral'); // Default to neutral or specific
|
||||
|
||||
@@ -51,19 +52,9 @@ export class CartesianEngine {
|
||||
universal = result[0] || {};
|
||||
} catch (e) { console.error(`Block not found: ${blockId}`); }
|
||||
|
||||
// Fetch Personalized Expansion
|
||||
let personal: any = {};
|
||||
try {
|
||||
// Need a way to match block_id + avatar_id.
|
||||
// Our schema imported flat structure?
|
||||
// Ideally we query the offer_blocks_personalized collection
|
||||
// filtering by block_related_id AND avatar_related_id
|
||||
// For prototype, we might have stored it loosely.
|
||||
// Let's assume we can fetch.
|
||||
} catch (e) { }
|
||||
// Fetch Personalized Expansion (Skipped for MVP)
|
||||
|
||||
// MERGE (Simplified for now - using universal only + placeholder)
|
||||
// Real merge adds personal pains to universal pains
|
||||
// MERGE
|
||||
const mergedBlock = {
|
||||
id: blockId,
|
||||
title: universal.title,
|
||||
@@ -84,12 +75,12 @@ export class CartesianEngine {
|
||||
const html = HTMLRenderer.renderArticle(blocksData);
|
||||
|
||||
// 4. Generate Meta
|
||||
const metaTitle = this.generateMetaTitle(context, variant);
|
||||
const metaTitle = overrides?.title || this.generateMetaTitle(context, variant);
|
||||
|
||||
return {
|
||||
title: metaTitle,
|
||||
html_content: html,
|
||||
slug: this.generateSlug(metaTitle),
|
||||
slug: overrides?.slug || this.generateSlug(metaTitle),
|
||||
meta_desc: "Generated description..." // Implementation TBD
|
||||
};
|
||||
}
|
||||
@@ -129,17 +120,17 @@ export class CartesianEngine {
|
||||
// Handle Spintax Content & Components
|
||||
if (block.spintax) {
|
||||
let content = SpintaxParser.parse(block.spintax);
|
||||
|
||||
|
||||
// Dynamic Component Replacement
|
||||
if (content.includes('{{COMPONENT_AVATAR_GRID}}')) {
|
||||
content = content.replace('{{COMPONENT_AVATAR_GRID}}', this.generateAvatarGrid());
|
||||
content = content.replace('{{COMPONENT_AVATAR_GRID}}', this.generateAvatarGrid());
|
||||
}
|
||||
if (content.includes('{{COMPONENT_OPTIN_FORM}}')) {
|
||||
content = content.replace('{{COMPONENT_OPTIN_FORM}}', this.generateOptinForm());
|
||||
content = content.replace('{{COMPONENT_OPTIN_FORM}}', this.generateOptinForm());
|
||||
}
|
||||
|
||||
content = GrammarEngine.resolve(content, variant);
|
||||
resolvedBlock.content = content;
|
||||
resolvedBlock.content = content;
|
||||
}
|
||||
|
||||
return resolvedBlock;
|
||||
@@ -150,7 +141,7 @@ export class CartesianEngine {
|
||||
"Scaling Founder", "Marketing Director", "Ecom Owner", "SaaS CEO", "Local Biz Owner",
|
||||
"Real Estate Agent", "Coach/Consultant", "Agency Owner", "Startup CTO", "Enterprise VP"
|
||||
];
|
||||
|
||||
|
||||
let html = '<div class="grid grid-cols-2 md:grid-cols-5 gap-4 my-8">';
|
||||
avatars.forEach(a => {
|
||||
html += `
|
||||
|
||||
75
frontend/src/lib/wordpress/WordPressClient.ts
Normal file
75
frontend/src/lib/wordpress/WordPressClient.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
export interface WPPost {
|
||||
id: number;
|
||||
date: string;
|
||||
slug: string;
|
||||
status: string;
|
||||
type: string;
|
||||
link: string;
|
||||
title: { rendered: string };
|
||||
content: { rendered: string };
|
||||
excerpt: { rendered: string };
|
||||
}
|
||||
|
||||
export class WordPressClient {
|
||||
private baseUrl: string;
|
||||
private authHeader: string | null = null;
|
||||
|
||||
constructor(domain: string, appPassword?: string) {
|
||||
// Normalize domain
|
||||
this.baseUrl = domain.replace(/\/$/, '');
|
||||
if (!this.baseUrl.startsWith('http')) {
|
||||
this.baseUrl = `https://${this.baseUrl}`;
|
||||
}
|
||||
|
||||
if (appPassword) {
|
||||
// Assumes username is 'admin' or handled in the pass string if formatted 'user:pass'
|
||||
// Usually Application Passwords are just the pwd, requiring a user.
|
||||
// For now, let's assume the user passes "username:app_password" string or implemented later.
|
||||
// We'll stick to public GET for now which doesn't need auth for reading content usually.
|
||||
// If auth is needed:
|
||||
// this.authHeader = `Basic ${btoa(appPassword)}`;
|
||||
}
|
||||
}
|
||||
|
||||
async testConnection(): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`${this.baseUrl}/wp-json/`);
|
||||
return res.ok;
|
||||
} catch (e) {
|
||||
console.error("WP Connection Failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getPages(limit = 100): Promise<WPPost[]> {
|
||||
const url = `${this.baseUrl}/wp-json/wp/v2/pages?per_page=${limit}`;
|
||||
return this.fetchCollection(url);
|
||||
}
|
||||
|
||||
async getPosts(limit = 100): Promise<WPPost[]> {
|
||||
const url = `${this.baseUrl}/wp-json/wp/v2/posts?per_page=${limit}`;
|
||||
return this.fetchCollection(url);
|
||||
}
|
||||
|
||||
async getCategories(): Promise<any[]> {
|
||||
// Fetch all categories
|
||||
return this.fetchCollection(`${this.baseUrl}/wp-json/wp/v2/categories?per_page=100`);
|
||||
}
|
||||
|
||||
async getTags(): Promise<any[]> {
|
||||
// Fetch all tags
|
||||
return this.fetchCollection(`${this.baseUrl}/wp-json/wp/v2/tags?per_page=100`);
|
||||
}
|
||||
|
||||
private async fetchCollection(url: string): Promise<any[]> {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`WP API Error: ${res.status}`);
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
console.error("Fetch Error", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user