God Mode: Sync All Admin Pages & Components. Fix Navigation. Fix Schemas.

This commit is contained in:
cawcenter
2025-12-14 19:28:20 -05:00
parent 189abfb384
commit e97bdee388
112 changed files with 5035 additions and 0 deletions

View File

View File

View File

@@ -0,0 +1,14 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import { MetricsDashboard } from '@/components/analytics/MetricsDashboard';
---
<AdminLayout title="Advanced Analytics">
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-white mb-2">Command Center Analytics</h1>
<p className="text-slate-400">Real-time deep dive into platform performance metrics.</p>
</div>
<MetricsDashboard client:only="react" />
</div>
</AdminLayout>

View File

@@ -0,0 +1,17 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import TemplateComposer from '@/components/assembler/TemplateComposer';
---
<AdminLayout title="Template Composer">
<div className="h-full flex flex-col space-y-4">
<div>
<h1 className="text-2xl font-bold text-white mb-1">Assembler Station</h1>
<p className="text-slate-400 text-sm">Design intelligent content templates with Spintax and Variables.</p>
</div>
<div className="flex-1 min-h-0">
<TemplateComposer client:only="react" />
</div>
</div>
</AdminLayout>

View File

View File

@@ -0,0 +1,15 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import BulkGenerator from '@/components/assembler/BulkGenerator';
---
<AdminLayout title="Content Assembly Workflow">
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-white mb-2">Bulk Assembly</h1>
<p className="text-slate-400">Generate hundreds of articles using your templates and data sources.</p>
</div>
<BulkGenerator client:only="react" />
</div>
</AdminLayout>

View File

@@ -0,0 +1,14 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import AutomationBuilder from '@/components/automations/AutomationBuilder';
---
<AdminLayout title="Visual Automation Builder">
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-white mb-2">Workflow Automations</h1>
<p className="text-slate-400">Visually design complex content pipelines.</p>
</div>
<AutomationBuilder client:only="react" />
</div>
</AdminLayout>

View File

@@ -0,0 +1,17 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import VisualBlockEditor from '@/components/blocks/VisualBlockEditor';
---
<AdminLayout title="Visual Block Editor">
<div className="h-full flex flex-col space-y-4">
<div>
<h1 className="text-2xl font-bold text-white mb-1">Visual Page Builder</h1>
<p className="text-slate-400 text-sm">Drag and drop blocks to design article templates and landing pages.</p>
</div>
<div className="flex-1 min-h-0">
<VisualBlockEditor client:only="react" />
</div>
</div>
</AdminLayout>

View File

View File

@@ -0,0 +1,20 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import AvatarVariantsManager from '@/components/admin/intelligence/AvatarVariantsManager';
---
<Layout title="Avatar Variants | Spark Intelligence">
<div class="p-8 space-y-8">
<div class="flex justify-between items-start">
<div>
<h1 class="text-3xl font-bold text-white tracking-tight">🧬 Variant Laboratory</h1>
<p class="text-zinc-400 mt-2 max-w-2xl">
Fine-tune specific persona variations. Create "Aggressive" sales clones or "Empathetic" support agents
derived from your base Avatars.
</p>
</div>
</div>
<AvatarVariantsManager client:load />
</div>
</Layout>

View File

@@ -0,0 +1,151 @@
---
/**
* Campaign Masters Management
* Full CRUD for campaign_masters collection
*/
import AdminLayout from '@/layouts/AdminLayout.astro';
import { getDirectusClient } from '@/lib/directus/client';
import { readItems } from '@directus/sdk';
const client = getDirectusClient();
let campaigns = [];
let error = null;
let stats = {
total: 0,
active: 0,
draft: 0,
completed: 0,
};
try {
campaigns = await client.request(readItems('campaign_masters', {
fields: ['*'],
sort: ['-date_created'],
}));
stats.total = campaigns.length;
stats.active = campaigns.filter((c: any) => c.status === 'active').length;
stats.draft = campaigns.filter((c: any) => c.status === 'draft').length;
stats.completed = campaigns.filter((c: any) => c.status === 'completed').length;
} catch (e) {
console.error('Error fetching campaigns:', e);
error = e instanceof Error ? e.message : 'Unknown error';
}
---
<AdminLayout title="Campaign Masters">
<div class="space-y-6">
<!-- Header -->
<div class="flex justify-between items-center">
<div>
<h1 class="spark-heading text-3xl">📢 Campaign Masters</h1>
<p class="text-silver mt-1">Manage marketing campaigns and content strategies</p>
</div>
<div class="flex gap-3">
<button class="spark-btn-secondary text-sm" onclick="window.dispatchEvent(new CustomEvent('import-modal'))">
📥 Import
</button>
<button class="spark-btn-secondary text-sm" onclick="window.dispatchEvent(new CustomEvent('export-data', {detail: {collection: 'campaign_masters'}}))">
📤 Export
</button>
<a href="/admin/collections/campaign-masters/new" class="spark-btn-primary text-sm">
✨ New Campaign
</a>
</div>
</div>
{error && (
<div class="spark-card p-4 border-red-500 text-red-400">
<strong>Error:</strong> {error}
</div>
)}
<!-- Stats -->
<div class="grid grid-cols-4 gap-4">
<div class="spark-card p-6">
<div class="spark-label mb-2">Total Campaigns</div>
<div class="spark-data text-3xl">{stats.total}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Active</div>
<div class="spark-data text-3xl text-green-400">{stats.active}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Draft</div>
<div class="spark-data text-3xl text-yellow-400">{stats.draft}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Completed</div>
<div class="spark-data text-3xl text-blue-400">{stats.completed}</div>
</div>
</div>
<!-- Campaigns Grid -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
{campaigns.map((campaign: any) => (
<div class="spark-card spark-card-hover p-6">
<div class="flex items-start justify-between mb-4">
<div>
<h3 class="text-white font-semibold text-lg">{campaign.campaign_name || 'Unnamed Campaign'}</h3>
<p class="text-silver/70 text-sm mt-1">
{campaign.description?.substring(0, 100) || 'No description'}
</p>
</div>
<span class={`px-3 py-1 rounded text-xs font-medium ${
campaign.status === 'active' ? 'bg-green-500/20 text-green-400 border border-green-500/30' :
campaign.status === 'draft' ? 'bg-yellow-500/20 text-yellow-400 border border-yellow-500/30' :
campaign.status === 'completed' ? 'bg-blue-500/20 text-blue-400 border border-blue-500/30' :
'bg-graphite border border-edge-subtle text-silver'
}`}>
{campaign.status || 'draft'}
</span>
</div>
<div class="space-y-2 text-sm mb-4">
{campaign.target_count && (
<div>
<span class="spark-label">Targets:</span>
<span class="text-silver ml-2">{campaign.target_count} items</span>
</div>
)}
{campaign.articles_generated && (
<div>
<span class="spark-label">Generated:</span>
<span class="text-gold ml-2">{campaign.articles_generated} articles</span>
</div>
)}
</div>
<div class="flex gap-2">
<a href={`/admin/collections/campaign-masters/${campaign.id}`} class="spark-btn-ghost text-xs px-3 py-1 flex-1 text-center">
Edit
</a>
<a href={`/admin/seo/articles?campaign=${campaign.id}`} class="spark-btn-secondary text-xs px-3 py-1 flex-1 text-center">
View Articles
</a>
</div>
</div>
))}
{campaigns.length === 0 && !error && (
<div class="col-span-full spark-card p-12 text-center">
<p class="text-silver/50">No campaigns found. Create your first campaign!</p>
</div>
)}
</div>
</div>
</AdminLayout>
<script>
window.addEventListener('export-data', async (e: any) => {
const { collection } = e.detail;
const response = await fetch(`/api/collections/${collection}/export`);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${collection}-${new Date().toISOString().split('T')[0]}.json`;
a.click();
});
</script>

View File

@@ -0,0 +1,20 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import CartesianManager from '@/components/admin/intelligence/CartesianManager';
---
<Layout title="Cartesian Patterns | Spark Intelligence">
<div class="p-8 space-y-6">
<div class="flex justify-between items-start">
<div>
<h1 class="text-3xl font-bold text-white tracking-tight">📐 Cartesian Patterns</h1>
<p class="text-zinc-400 mt-2 max-w-2xl">
Create logic-based sentence formulas. Combine text, spintax, and variables like
<code>&#123;city&#125;</code> or <code>&#123;service&#125;</code> to generate millions of unique combinations.
</p>
</div>
</div>
<CartesianManager client:only="react" />
</div>
</Layout>

View File

@@ -0,0 +1,20 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import GenericCollectionManager from '@/components/admin/collections/GenericCollectionManager';
---
<Layout title="Content Fragments | Spark Intelligence">
<div class="p-8">
<GenericCollectionManager
client:only="react"
collection="content_fragments"
title="Content Fragments"
displayField="key"
fields={[
{ key: 'key', label: 'Fragment Key', type: 'text' },
{ key: 'content', label: 'Content', type: 'textarea' },
{ key: 'tags', label: 'Tags (JSON)', type: 'json' }
]}
/>
</div>
</Layout>

View File

@@ -0,0 +1,195 @@
---
/**
* Generation Jobs Management
* Queue monitoring and job management for content_fragments collection
*/
import AdminLayout from '@/layouts/AdminLayout.astro';
import { getDirectusClient } from '@/lib/directus/client';
import { readItems } from '@directus/sdk';
const client = getDirectusClient();
let jobs = [];
let error = null;
let stats = {
total: 0,
pending: 0,
processing: 0,
completed: 0,
failed: 0,
};
try {
jobs = await client.request(readItems('generation_jobs', {
fields: ['*'],
sort: ['-date_created'],
limit: 100,
}));
stats.total = jobs.length;
stats.pending = jobs.filter((j: any) => j.status === 'pending').length;
stats.processing = jobs.filter((j: any) => j.status === 'processing').length;
stats.completed = jobs.filter((j: any) => j.status === 'completed').length;
stats.failed = jobs.filter((j: any) => j.status === 'failed').length;
} catch (e) {
console.error('Error fetching jobs:', e);
error = e instanceof Error ? e.message : 'Unknown error';
}
---
<AdminLayout title="Generation Jobs">
<div class="space-y-6">
<!-- Header -->
<div class="flex justify-between items-center">
<div>
<h1 class="spark-heading text-3xl">⚙️ Generation Jobs</h1>
<p class="text-silver mt-1">Content generation queue monitoring</p>
</div>
<div class="flex gap-3">
<button class="spark-btn-secondary text-sm" onclick="location.reload()">
🔄 Refresh
</button>
<button class="spark-btn-secondary text-sm" onclick="window.dispatchEvent(new CustomEvent('export-data', {detail: {collection: 'generation_jobs'}}))">
📤 Export
</button>
<button class="spark-btn-primary text-sm" onclick="window.dispatchEvent(new CustomEvent('clear-completed'))">
🧹 Clear Completed
</button>
</div>
</div>
{error && (
<div class="spark-card p-4 border-red-500 text-red-400">
<strong>Error:</strong> {error}
</div>
)}
<!-- Stats -->
<div class="grid grid-cols-5 gap-4">
<div class="spark-card p-6">
<div class="spark-label mb-2">Total Jobs</div>
<div class="spark-data text-3xl">{stats.total}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Pending</div>
<div class="spark-data text-3xl text-yellow-400">{stats.pending}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Processing</div>
<div class="spark-data text-3xl text-blue-400 animate-pulse">{stats.processing}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Completed</div>
<div class="spark-data text-3xl text-green-400">{stats.completed}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Failed</div>
<div class="spark-data text-3xl text-red-400">{stats.failed}</div>
</div>
</div>
<!-- Jobs Table -->
<div class="spark-card overflow-hidden">
<div class="p-6 border-b border-edge-subtle">
<h2 class="text-white font-semibold">Recent Jobs</h2>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-graphite">
<tr>
<th class="text-left px-6 py-3 spark-label">Job ID</th>
<th class="text-left px-6 py-3 spark-label">Type</th>
<th class="text-left px-6 py-3 spark-label">Status</th>
<th class="text-left px-6 py-3 spark-label">Progress</th>
<th class="text-left px-6 py-3 spark-label">Created</th>
<th class="text-right px-6 py-3 spark-label">Actions</th>
</tr>
</thead>
<tbody>
{jobs.map((job: any, index: number) => (
<tr class={index % 2 === 0 ? 'bg-black/20' : ''}>
<td class="px-6 py-4">
<code class="text-xs text-silver/70">{job.id.slice(0, 8)}...</code>
</td>
<td class="px-6 py-4 text-white">{job.job_type || 'Article'}</td>
<td class="px-6 py-4">
<span class={`px-2 py-1 rounded text-xs ${
job.status === 'completed' ? 'bg-green-500/20 text-green-400' :
job.status === 'processing' ? 'bg-blue-500/20 text-blue-400 animate-pulse' :
job.status === 'pending' ? 'bg-yellow-500/20 text-yellow-400' :
job.status === 'failed' ? 'bg-red-500/20 text-red-400' :
'bg-graphite text-silver'
}`}>
{job.status || 'pending'}
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-2">
<div class="flex-1 bg-graphite rounded-full h-2 overflow-hidden">
<div
class={`h-full ${
job.status === 'completed' ? 'bg-green-500' :
job.status === 'processing' ? 'bg-blue-500' :
job.status === 'failed' ? 'bg-red-500' :
'bg-yellow-500'
}`}
style={`width: ${job.progress || 0}%`}
></div>
</div>
<span class="text-xs text-silver w-12 text-right">{job.progress || 0}%</span>
</div>
</td>
<td class="px-6 py-4 text-silver text-sm">
{job.date_created ? new Date(job.date_created).toLocaleString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}) : 'Unknown'}
</td>
<td class="px-6 py-4 text-right">
{job.status === 'failed' && (
<button class="spark-btn-ghost text-xs px-3 py-1" onclick={`alert('Error: ${job.error_message || 'Unknown error'}')`}>
View Error
</button>
)}
{job.status === 'completed' && job.output_id && (
<a href={`/admin/seo/articles/${job.output_id}`} class="spark-btn-ghost text-xs px-3 py-1">
View Output
</a>
)}
</td>
</tr>
))}
</tbody>
</table>
{jobs.length === 0 && !error && (
<div class="p-12 text-center">
<p class="text-silver/50">No generation jobs found. Queue is empty!</p>
</div>
)}
</div>
</div>
</div>
</AdminLayout>
<script>
window.addEventListener('export-data', async (e: any) => {
const { collection } = e.detail;
const response = await fetch(`/api/collections/${collection}/export`);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${collection}-${new Date().toISOString().split('T')[0]}.json`;
a.click();
});
window.addEventListener('clear-completed', async () => {
if (!confirm('Delete all completed jobs?')) return;
// TODO: API endpoint to delete completed jobs
alert('Feature coming soon!');
});
</script>

View File

@@ -0,0 +1,21 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import GeoIntelligenceManager from '@/components/admin/intelligence/GeoIntelligenceManager';
import { CoreProvider } from '@/components/providers/CoreProviders';
---
<Layout title="Geo Intelligence | Spark Platform">
<div class="p-8 space-y-6">
<div class="flex justify-between items-start">
<div>
<h1 class="text-3xl font-bold text-white tracking-tight">🌍 Geo Intelligence</h1>
<p class="text-zinc-400 mt-2">
Visualize your market dominance. Manage region clusters and target specific cities for localized content campaigns.
</p>
</div>
</div>
<CoreProvider client:load>
<GeoIntelligenceManager client:only="react" />
</CoreProvider>
</div>
</Layout>

View File

@@ -0,0 +1,21 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import GenericCollectionManager from '@/components/admin/collections/GenericCollectionManager';
---
<Layout title="Headlines | Spark Intelligence">
<div class="p-8">
<GenericCollectionManager
client:only="react"
collection="headline_inventory"
title="Headline Inventory"
displayField="text"
fields={[
{ key: 'text', label: 'Headline Text', type: 'text' },
{ key: 'type', label: 'Type (H1/H2)', type: 'text' },
{ key: 'category', label: 'Category', type: 'text' },
{ key: 'spintax_root', label: 'Spintax Root', type: 'text' }
]}
/>
</div>
</Layout>

View File

@@ -0,0 +1,21 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import GenericCollectionManager from '@/components/admin/collections/GenericCollectionManager';
---
<Layout title="Offer Blocks | Spark Intelligence">
<div class="p-8">
<GenericCollectionManager
client:only="react"
collection="offer_blocks"
title="Offer Blocks"
displayField="title"
fields={[
{ key: 'title', label: 'Offer Title', type: 'text' },
{ key: 'hook', label: 'Hook / Generator', type: 'textarea' },
{ key: 'pains', label: 'Pains (JSON)', type: 'json' },
{ key: 'solutions', label: 'Solutions (JSON)', type: 'json' }
]}
/>
</div>
</Layout>

View File

@@ -0,0 +1,21 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import GenericCollectionManager from '@/components/admin/collections/GenericCollectionManager';
---
<Layout title="Page Blocks | Spark Intelligence">
<div class="p-8">
<GenericCollectionManager
client:only="react"
collection="page_blocks"
title="Page Layout Blocks"
displayField="name"
fields={[
{ key: 'name', label: 'Block Name', type: 'text' },
{ key: 'category', label: 'Category', type: 'text' },
{ key: 'html_content', label: 'HTML Structure', type: 'textarea' },
{ key: 'css_content', label: 'CSS / Tailwind', type: 'textarea' }
]}
/>
</div>
</Layout>

View File

@@ -0,0 +1,20 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import SpintaxManager from '@/components/admin/intelligence/SpintaxManager';
---
<Layout title="Spintax Dictionaries | Spark Intelligence">
<div class="p-8 space-y-6">
<div class="flex justify-between items-start">
<div>
<h1 class="text-3xl font-bold text-white tracking-tight">📚 Spintax Library</h1>
<p class="text-zinc-400 mt-2 max-w-2xl">
Manage your vocabulary variations. Use these sets to generate diverse, non-repetitive content
using <code>&#123;key|word|variant&#125;</code> syntax.
</p>
</div>
</div>
<SpintaxManager client:only="react" />
</div>
</Layout>

View File

@@ -0,0 +1,17 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import AvatarIntelligenceManager from '@/components/admin/intelligence/AvatarIntelligenceManager';
import { CoreProvider } from '@/components/providers/CoreProviders';
---
<Layout title="Avatar Intelligence">
<div class="p-8">
<div class="mb-6">
<h1 class="text-3xl font-bold text-white mb-2">🎭 Avatar Intelligence</h1>
<p class="text-gray-400">Manage your base avatars, variants, and target personas. Each avatar represents a unique customer profile.</p>
</div>
<CoreProvider client:load>
<AvatarIntelligenceManager client:load />
</CoreProvider>
</div>
</Layout>

View File

View File

View File

@@ -0,0 +1,14 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import LogViewer from '@/components/admin/content/LogViewer';
---
<Layout title="System Logs">
<div class="p-8">
<div class="mb-6">
<h1 class="text-3xl font-bold text-white mb-2">System Work Log</h1>
<p class="text-gray-400">Real-time backend execution and activity logs.</p>
</div>
<LogViewer client:load />
</div>
</Layout>

View File

@@ -0,0 +1,9 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import ContentFactoryDashboard from '@/components/admin/content/ContentFactoryDashboard';
---
<Layout title="Factory Command Center">
<div class="p-8">
<ContentFactoryDashboard client:load />
</div>
</Layout>

View File

@@ -0,0 +1,222 @@
/**
* Article Workbench - Article Detail Editor
* 3-panel layout: Metadata | Editor | Tools
*/
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import { getDirectusClient, readItem } from '@/lib/directus/client';
const { id } = Astro.params;
if (!id) {
return Astro.redirect('/admin/factory');
}
const client = getDirectusClient();
let article;
try {
article = await client.request(readItem('generated_articles', id));
} catch (error) {
console.error('Error fetching article:', error);
return Astro.redirect('/admin/factory');
}
---
<AdminLayout title={`Edit: ${article.title}`}>
<div class="h-[calc(100vh-80px)] flex gap-6">
<!-- Left Panel: Metadata -->
<div class="w-80 flex-shrink-0 space-y-4 overflow-y-auto">
<div class="spark-card p-6">
<h3 class="spark-heading text-lg mb-4">Metadata</h3>
<!-- Status -->
<div class="mb-4">
<label class="spark-label block mb-2">Status</label>
<select class="spark-input w-full">
<option value="queued">Queued</option>
<option value="generating">Generating</option>
<option value="review" selected={article.status === 'review'}>Review</option>
<option value="approved">Approved</option>
<option value="published">Published</option>
</select>
</div>
<!-- Location -->
<div class="mb-4">
<label class="spark-label block mb-2">Location</label>
<div class="text-silver text-sm">
{article.geo_city && article.geo_state
? `${article.geo_city}, ${article.geo_state}`
: 'Not set'}
</div>
</div>
<!-- SEO Score -->
<div class="mb-4">
<label class="spark-label block mb-2">SEO Score</label>
<div class="spark-data text-2xl">
{article.seo_score || 0}/100
</div>
<div class="h-2 bg-graphite rounded-full mt-2 overflow-hidden">
<div
class="h-full bg-gold-gradient rounded-full transition-all"
style={`width: ${article.seo_score || 0}%`}
></div>
</div>
</div>
<!-- Meta Description -->
<div class="mb-4">
<label class="spark-label block mb-2">Meta Description</label>
<textarea
class="spark-input w-full text-sm"
rows="3"
maxlength="160"
placeholder="Write a compelling meta description..."
>{article.meta_desc}</textarea>
<div class="text-silver/50 text-xs mt-1">
{article.meta_desc?.length || 0}/160
</div>
</div>
</div>
</div>
<!-- Center Panel: Editor -->
<div class="flex-1 flex flex-col spark-card overflow-hidden">
<!-- Editor Header -->
<div class="border-b border-edge-subtle p-4 flex items-center justify-between">
<div class="flex gap-2">
<button class="spark-btn-ghost text-sm" id="visual-mode">Visual</button>
<button class="spark-btn-ghost text-sm" id="code-mode">Code</button>
</div>
<div class="flex items-center gap-3">
<span class="text-silver/50 text-xs" id="auto-save-status">Saved</span>
<button class="spark-btn-secondary text-sm">
Save Draft
</button>
</div>
</div>
<!-- Editor Area -->
<div class="flex-1 overflow-y-auto p-6 bg-void">
<!-- Title -->
<input
type="text"
value={article.title}
class="w-full bg-transparent border-none text-3xl font-bold text-white mb-6 focus:outline-none placeholder:text-silver/30"
placeholder="Article title..."
/>
<!-- Content -->
<div id="editor-container" class="prose prose-invert max-w-none">
<div set:html={article.content_html || '<p class="text-silver/50">Start writing...</p>'} />
</div>
</div>
</div>
<!-- Right Panel: Tools -->
<div class="w-80 flex-shrink-0 space-y-4 overflow-y-auto">
<!-- SEO Tools -->
<div class="spark-card p-6">
<h3 class="spark-heading text-lg mb-4">SEO Tools</h3>
<div class="space-y-3">
<div class="flex items-center justify-between">
<span class="text-silver text-sm">Readability</span>
<span class="spark-data text-sm">Good</span>
</div>
<div class="flex items-center justify-between">
<span class="text-silver text-sm">Keyword Density</span>
<span class="spark-data text-sm">2.3%</span>
</div>
<div class="flex items-center justify-between">
<span class="text-silver text-sm">Word Count</span>
<span class="spark-data text-sm">1,247</span>
</div>
</div>
</div>
<!-- Spintax Info -->
<div class="spark-card p-6">
<h3 class="spark-heading text-lg mb-4">Spintax</h3>
<div class="space-y-2">
<div class="flex items-center justify-between">
<span class="text-silver text-sm">Variations</span>
<span class="spark-data text-sm">432</span>
</div>
<button class="spark-btn-ghost w-full text-sm">
Preview Variations
</button>
</div>
</div>
<!-- Images -->
<div class="spark-card p-6">
<h3 class="spark-heading text-lg mb-4">Featured Image</h3>
{article.featured_image_url ? (
<img
src={article.featured_image_url}
alt="Featured"
class="w-full rounded-lg border border-edge-normal mb-3"
/>
) : (
<div class="w-full aspect-video bg-graphite rounded-lg border border-edge-subtle flex items-center justify-center mb-3">
<span class="text-silver/50 text-sm">No image</span>
</div>
)}
<button class="spark-btn-ghost w-full text-sm">
Generate Image
</button>
</div>
<!-- Logs -->
<div class="spark-card p-6">
<h3 class="spark-heading text-lg mb-4">Activity Log</h3>
<div class="space-y-2 max-h-40 overflow-y-auto">
<div class="text-xs">
<div class="text-silver/50">2 hours ago</div>
<div class="text-silver">Article generated</div>
</div>
<div class="text-xs">
<div class="text-silver/50">1 hour ago</div>
<div class="text-silver">SEO score calculated</div>
</div>
</div>
</div>
</div>
</div>
</AdminLayout>
<script>
// Auto-save functionality
let saveTimeout: number;
const autoSaveStatus = document.getElementById('auto-save-status');
const editorContainer = document.getElementById('editor-container');
if (editorContainer) {
editorContainer.addEventListener('input', () => {
if (autoSaveStatus) {
autoSaveStatus.textContent = 'Saving...';
}
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
// Save logic here
if (autoSaveStatus) {
autoSaveStatus.textContent = 'Saved';
}
}, 2000);
});
}
</script>

View File

@@ -0,0 +1,63 @@
/**
* Factory Floor - Main Production Page
* Kanban/Grid view switcher for articles
*/
---
import AdminLayout from '@/layouts/AdminLayout.astro';
const currentPath = Astro.url.pathname;
const action = Astro.url.searchParams.get('action');
---
<AdminLayout title="Factory Floor">
<div class="space-y-6">
<!-- Header -->
<div class="flex justify-between items-center">
<div>
<h1 class="text-3xl font-bold text-white">Factory Floor</h1>
<p class="text-slate-400 mt-1">Content production workflow</p>
</div>
<div class="flex gap-3">
<a
href="/admin/factory?action=new"
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors"
>
✨ New Campaign
</a>
</div>
</div>
<!-- View Switcher -->
<div class="flex gap-2 bg-slate-800 p-1 rounded-lg w-fit">
<a
href="/admin/factory?view=kanban"
class:list={[
'px-4 py-2 rounded transition-colors',
!Astro.url.searchParams.get('view') || Astro.url.searchParams.get('view') === 'kanban'
? 'bg-slate-700 text-white'
: 'text-slate-400 hover:text-white'
]}
>
📊 Kanban
</a>
<a
href="/admin/factory?view=grid"
class:list={[
'px-4 py-2 rounded transition-colors',
Astro.url.searchParams.get('view') === 'grid'
? 'bg-slate-700 text-white'
: 'text-slate-400 hover:text-white'
]}
>
📋 Grid
</a>
</div>
<!-- Content Area -->
<div class="bg-slate-800 border border-slate-700 rounded-lg p-6">
<p class="text-slate-400">Factory view components will load here</p>
<p class="text-slate-500 text-sm mt-2">Kanban Board and Bulk Grid components coming next...</p>
</div>
</div>
</AdminLayout>

View File

@@ -0,0 +1,19 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import JobsManager from '@/components/admin/jobs/JobsManager';
---
<Layout title="Job Queue | Spark Intelligence">
<div class="p-8 space-y-6">
<div class="flex justify-between items-start">
<div>
<h1 class="text-3xl font-bold text-white tracking-tight">⚙️ Generation Queue</h1>
<p class="text-zinc-400 mt-2 max-w-2xl">
Monitor background processing jobs. Watch content generation progress in real-time.
</p>
</div>
</div>
<JobsManager client:only="react" />
</div>
</Layout>

View File

@@ -0,0 +1,33 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import KanbanBoard from '@/components/admin/factory/KanbanBoard';
import { Button } from '@/components/ui/button';
import { Plus } from 'lucide-react';
---
<Layout title="Content Factory Board | Spark Intelligence">
<div class="h-screen flex flex-col overflow-hidden">
<div className="flex-none p-6 pb-2 flex justify-between items-center border-b border-zinc-800/50 bg-zinc-950/50 backdrop-blur-sm z-10">
<div>
<h1 className="text-2xl font-bold text-white tracking-tight flex items-center gap-2">
🏭 Content Factory
<span className="text-xs font-normal text-zinc-500 bg-zinc-900 border border-zinc-800 px-2 py-0.5 rounded-full">Beta</span>
</h1>
<p className="text-zinc-400 text-sm mt-1">
Drag and drop articles to move them through the production pipeline.
</p>
</div>
<div className="flex gap-2">
<a href="/admin/jumpstart/wizard">
<Button className="bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 border-0">
<Plus className="mr-2 h-4 w-4" /> New Article
</Button>
</a>
</div>
</div>
<div className="flex-1 overflow-hidden p-6 bg-zinc-950">
<KanbanBoard client:only="react" />
</div>
</div>
</Layout>

View File

@@ -0,0 +1,15 @@
---
import AdminLayout from '../../layouts/AdminLayout.astro';
import SystemMonitor from '../../components/admin/dashboard/SystemMonitor';
---
<AdminLayout title="Mission Control">
<div class="p-8">
<div class="mb-8">
<h1 class="text-3xl font-bold text-white mb-2">Command Station</h1>
<p class="text-slate-400">System Monitoring, Sub-Station Status, and Content Integrity.</p>
</div>
<SystemMonitor client:load />
</div>
</AdminLayout>

View File

@@ -0,0 +1,107 @@
/**
* Avatar Intelligence Management
* Full CRUD for avatar_intelligence collection
*/
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import { getDirectusClient } from '@/lib/directus/client';
import { readItems } from '@directus/sdk';
const client = getDirectusClient();
let avatars = [];
let error = null;
try {
avatars = await client.request(readItems('avatar_intelligence', {
fields: ['*'],
sort: ['base_name'],
}));
} catch (e) {
console.error('Error fetching avatars:', e);
error = e instanceof Error ? e.message : 'Unknown error';
}
---
<AdminLayout title="Avatar Intelligence">
<div class="space-y-6">
<!-- Header -->
<div class="flex justify-between items-center">
<div>
<h1 class="spark-heading text-3xl">Avatar Intelligence</h1>
<p class="text-silver mt-1">Manage persona profiles and variants</p>
</div>
<div class="flex gap-3">
<button class="spark-btn-secondary text-sm">
📥 Import CSV
</button>
<button class="spark-btn-secondary text-sm">
📤 Export
</button>
<a href="/admin/intelligence/avatars/new" class="spark-btn-primary text-sm">
✨ New Avatar
</a>
</div>
</div>
{error && (
<div class="spark-card p-4 border-red-500 text-red-400">
Error: {error}
</div>
)}
<!-- Stats -->
<div class="grid grid-cols-4 gap-4">
<div class="spark-card p-6">
<div class="spark-label mb-2">Total Avatars</div>
<div class="spark-data text-3xl">{avatars.length}</div>
</div>
</div>
<!-- Avatars Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{avatars.map((avatar: any) => (
<div class="spark-card spark-card-hover p-6">
<div class="flex items-start justify-between mb-4">
<h3 class="text-white font-semibold text-lg">{avatar.base_name}</h3>
<button class="spark-btn-ghost text-xs px-2 py-1">
Edit
</button>
</div>
<div class="space-y-2 text-sm">
<div>
<span class="spark-label">Wealth Cluster:</span>
<span class="text-silver ml-2">{avatar.wealth_cluster || 'Not set'}</span>
</div>
{avatar.business_niches && (
<div>
<span class="spark-label">Niches:</span>
<div class="flex flex-wrap gap-1 mt-1">
{avatar.business_niches.slice(0, 3).map((niche: string) => (
<span class="px-2 py-0.5 bg-graphite border border-edge-subtle rounded text-xs text-silver">
{niche}
</span>
))}
{avatar.business_niches.length > 3 && (
<span class="px-2 py-0.5 text-xs text-silver/50">
+{avatar.business_niches.length - 3} more
</span>
)}
</div>
</div>
)}
</div>
</div>
))}
{avatars.length === 0 && !error && (
<div class="col-span-full spark-card p-12 text-center">
<p class="text-silver/50">No avatars found. Create your first one!</p>
</div>
)}
</div>
</div>
</AdminLayout>

View File

@@ -0,0 +1,21 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import { GeoMap } from '@/components/intelligence/GeoMap';
import { MetricsDashboard } from '@/components/analytics/MetricsDashboard';
---
<AdminLayout title="Geo Intelligence">
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-white mb-2">Market Dominance Map</h1>
<p className="text-slate-400">Visualize your campaign performance across different territories.</p>
</div>
<GeoMap client:only="react" />
<div className="mt-8">
<h2 className="text-xl font-bold text-white mb-4">Regional Performance</h2>
<MetricsDashboard client:only="react" />
</div>
</div>
</AdminLayout>

View File

@@ -0,0 +1,80 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import PatternAnalyzer from '@/components/intelligence/PatternAnalyzer';
import GeoTargeting from '@/components/intelligence/GeoTargeting';
import AvatarMetrics from '@/components/intelligence/AvatarMetrics';
import { Brain, Globe, Users } from 'lucide-react';
// Server-side data fetching (simulated for Phase 4 or connected to local API logic)
// In a real scenario, we might call the DB directly here.
// For now, we'll import the mock data logic or just duplicate the mock data for SSR,
// to ensure the page renders with data immediately.
// We can also fetch from our own API if running, but during build, the API might not be up.
// So we will define the initial data here.
const patterns = [
{ id: '1', name: 'High-Value Listicle Structure', type: 'structure', confidence: 0.92, occurrences: 145, last_detected: '2023-10-25', tags: ['listicle', 'viral', 'b2b'] },
{ id: '2', name: 'Emotional Storytelling Hook', type: 'semantic', confidence: 0.88, occurrences: 89, last_detected: '2023-10-26', tags: ['hook', 'emotional', 'intro'] },
{ id: '3', name: 'Data-Backed CTA', type: 'conversion', confidence: 0.76, occurrences: 230, last_detected: '2023-10-24', tags: ['cta', 'sales', 'closing'] },
{ id: '4', name: 'Contrarian Viewpoint', type: 'semantic', confidence: 0.65, occurrences: 54, last_detected: '2023-10-22', tags: ['opinion', 'debate'] },
{ id: '5', name: 'How-To Guide Format', type: 'structure', confidence: 0.95, occurrences: 310, last_detected: '2023-10-27', tags: ['educational', 'long-form'] }
] as any[];
const geoClusters = [
{ id: '1', name: 'North America Tech Hubs', location: '37.7749, -122.4194', audience_size: 154000, engagement_rate: 0.45, dominant_topic: 'SaaS Marketing' },
{ id: '2', name: 'London Finance Sector', location: '51.5074, -0.1278', audience_size: 89000, engagement_rate: 0.38, dominant_topic: 'FinTech' },
{ id: '3', name: 'Singapore Crypto', location: '1.3521, 103.8198', audience_size: 65000, engagement_rate: 0.52, dominant_topic: 'Web3' },
{ id: '4', name: 'Berlin Startup Scene', location: '52.5200, 13.4050', audience_size: 42000, engagement_rate: 0.41, dominant_topic: 'Growth Hacking' },
{ id: '5', name: 'Sydney E-comm', location: '-33.8688, 151.2093', audience_size: 38000, engagement_rate: 0.35, dominant_topic: 'DTC Brands' }
] as any[];
const avatarMetrics = [
{ id: '1', avatar_id: 'a1', name: 'Marketing Max', articles_generated: 45, avg_engagement: 0.82, top_niche: 'SaaS Growth' },
{ id: '2', avatar_id: 'a2', name: 'Finance Fiona', articles_generated: 32, avg_engagement: 0.75, top_niche: 'Personal Finance' },
{ id: '3', avatar_id: 'a3', name: 'Tech Tyler', articles_generated: 68, avg_engagement: 0.68, top_niche: 'AI Tools' },
{ id: '4', avatar_id: 'a4', name: 'Wellness Wendy', articles_generated: 24, avg_engagement: 0.89, top_niche: 'Holistic Health' },
{ id: '5', avatar_id: 'a5', name: 'Crypto Carl', articles_generated: 15, avg_engagement: 0.45, top_niche: 'DeFi' }
] as any[];
---
<AdminLayout title="Intelligence Station">
<div class="space-y-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-600">
Intelligence Headquarters
</h1>
<p class="text-muted-foreground mt-2 text-lg">
Real-time analysis of content performance, audience demographics, and conversion patterns.
</p>
</div>
<div class="flex gap-4">
<div class="bg-card border border-border/50 rounded-lg p-3 text-center min-w-[120px]">
<div class="text-2xl font-bold text-primary">92%</div>
<div class="text-xs text-muted-foreground uppercase tracking-wider">Predictive Acc.</div>
</div>
<div class="bg-card border border-border/50 rounded-lg p-3 text-center min-w-[120px]">
<div class="text-2xl font-bold text-green-500">1.4M</div>
<div class="text-xs text-muted-foreground uppercase tracking-wider">Data Points</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Patterns Section - Spans 2 columns */}
<div class="lg:col-span-2 space-y-6">
<PatternAnalyzer client:load patterns={patterns} />
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<AvatarMetrics client:load metrics={avatarMetrics} />
</div>
</div>
{/* Geo Section - Spans 1 column */}
<div class="lg:col-span-1 space-y-6 h-full">
<GeoTargeting client:only="react" clusters={geoClusters} />
</div>
</div>
</div>
</AdminLayout>

View File

@@ -0,0 +1,19 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import UnderConstruction from '@/components/ui/UnderConstruction';
---
<AdminLayout title="Content Effectiveness">
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-white mb-2">Reports & Analysis</h1>
<p className="text-slate-400">Deep dive into content performance metrics.</p>
</div>
<UnderConstruction
client:only="react"
title="Intelligence Reports"
description="Advanced analytics including conversion tracking, A/B testing results, and heatmaps are coming in the next update."
eta="Planned for Phase 4.1"
/>
</div>
</AdminLayout>

View File

View File

@@ -0,0 +1,19 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import LeadsManager from '@/components/admin/leads/LeadsManager';
---
<Layout title="Leads Management | Spark Intelligence">
<div class="p-8 space-y-6">
<div class="flex justify-between items-start">
<div>
<h1 class="text-3xl font-bold text-white tracking-tight">👥 Leads & Prospects</h1>
<p class="text-zinc-400 mt-2 max-w-2xl">
Manage incoming leads and track their status from "New" to "Converted".
</p>
</div>
</div>
<LeadsManager client:only="react" />
</div>
</Layout>

View File

@@ -0,0 +1,8 @@
---
import AdminLayout from '../../layouts/AdminLayout.astro';
import LocationBrowser from '../../components/admin/LocationBrowser';
---
<AdminLayout title="Locations">
<LocationBrowser client:load />
</AdminLayout>

View File

@@ -0,0 +1,8 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import ImageTemplateEditor from '../../../components/admin/ImageTemplateEditor';
---
<AdminLayout title="Image Templates">
<ImageTemplateEditor client:load />
</AdminLayout>

View File

@@ -0,0 +1,20 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import PageEditor from '@/components/admin/pages/PageEditor';
const { id } = Astro.params;
---
<Layout title="Edit Page">
<div class="p-6">
<div class="mb-6">
<a href="/admin/pages" class="text-slate-400 hover:text-white flex items-center gap-2 mb-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg>
Back to Pages
</a>
<h1 class="text-3xl font-bold text-slate-100">Edit Page</h1>
</div>
<PageEditor id={id} client:only="react" />
</div>
</Layout>

View File

@@ -0,0 +1,21 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import PageList from '@/components/admin/pages/PageList';
---
<Layout title="Page Management">
<div class="p-6 space-y-6">
<div class="flex justify-between items-center">
<div>
<h1 class="text-3xl font-bold text-slate-100">Pages</h1>
<p class="text-slate-400">Manage static pages across your sites.</p>
</div>
<a href="/admin/pages/new" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /></svg>
New Page
</a>
</div>
<PageList client:load />
</div>
</Layout>

View File

@@ -0,0 +1,19 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import SchedulerManager from '@/components/admin/scheduler/SchedulerManager';
---
<Layout title="Campaign Scheduler | Spark Intelligence">
<div class="p-8 space-y-6">
<div class="flex justify-between items-start">
<div>
<h1 class="text-3xl font-bold text-white tracking-tight">📅 Automation Center</h1>
<p class="text-zinc-400 mt-2 max-w-2xl">
Schedule bulk generation campaigns and automated workflows.
</p>
</div>
</div>
<SchedulerManager client:only="react" />
</div>
</Layout>

View File

@@ -0,0 +1,20 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import ArticleEditor from '@/components/admin/seo/ArticleEditor';
const { id } = Astro.params;
---
<Layout title="Review Generated Article">
<div class="p-6">
<div class="mb-6">
<a href="/admin/seo/articles" class="text-slate-400 hover:text-white flex items-center gap-2 mb-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /></svg>
Back to Articles
</a>
<h1 class="text-3xl font-bold text-slate-100">Review Article</h1>
</div>
<ArticleEditor id={id} client:only="react" />
</div>
</Layout>

View File

@@ -0,0 +1,22 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import ArticleList from '@/components/admin/seo/ArticleList';
import { getDirectusClient, readItems } from '@/lib/directus/client';
const directus = getDirectusClient();
const articles = await directus.request(readItems('generated_articles', {
fields: ['*'],
limit: 50,
sort: ['-date_created']
})).catch(() => []);
---
<Layout title="Generated Articles">
<div class="p-8">
<div class="mb-6">
<h1 class="text-3xl font-bold text-white mb-2">Generated Articles</h1>
<p class="text-gray-400">Review and manage AI-generated SEO content.</p>
</div>
<ArticleList client:load initialArticles={articles} />
</div>
</Layout>

View File

@@ -0,0 +1,8 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import CampaignManager from '../../../components/admin/CampaignManager';
---
<AdminLayout title="SEO Campaigns">
<CampaignManager client:load />
</AdminLayout>

View File

@@ -0,0 +1,14 @@
---
import Layout from '@/layouts/AdminLayout.astro';
---
<Layout title="Content Fragments">
<div class="p-6">
<h1 class="text-3xl font-bold text-slate-100 mb-6">Content Fragments</h1>
<div class="bg-slate-800 rounded-lg border border-slate-700 p-8 text-center">
<h2 class="text-xl font-bold text-white mb-2">Reusable Content Blocks</h2>
<p class="text-slate-400">Manage global text snippets, CTAs, and bios here.</p>
</div>
</div>
</Layout>

View File

@@ -0,0 +1,14 @@
---
import Layout from '@/layouts/AdminLayout.astro';
---
<Layout title="Headlines">
<div class="p-6">
<h1 class="text-3xl font-bold text-slate-100 mb-6">Headlines & Hooks</h1>
<div class="bg-slate-800 rounded-lg border border-slate-700 p-8 text-center">
<h2 class="text-xl font-bold text-white mb-2">Pattern Library</h2>
<p class="text-slate-400">Manage your Cartesian Headline generation patterns here.</p>
</div>
</div>
</Layout>

View File

@@ -0,0 +1,9 @@
---
import Layout from '@/layouts/AdminLayout.astro';
import SettingsManager from '@/components/admin/SettingsManager';
---
<Layout title="System Settings">
<div class="p-8">
<SettingsManager client:load />
</div>
</Layout>

View File

View File

View File

@@ -0,0 +1,15 @@
---
import AdminLayout from '@/layouts/AdminLayout.astro';
import TestRunner from '@/components/testing/TestRunner';
---
<AdminLayout title="Quality Tests">
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-white mb-2">Quality Assurance Suite</h1>
<p className="text-slate-400">Validate content SEO, readability, and structural integrity.</p>
</div>
<TestRunner client:only="react" />
</div>
</AdminLayout>