God Mode: Sync All Admin Pages & Components. Fix Navigation. Fix Schemas.
This commit is contained in:
0
src/pages/admin/analytics/errors.astro
Normal file
0
src/pages/admin/analytics/errors.astro
Normal file
0
src/pages/admin/analytics/index.astro
Normal file
0
src/pages/admin/analytics/index.astro
Normal file
14
src/pages/admin/analytics/metrics.astro
Normal file
14
src/pages/admin/analytics/metrics.astro
Normal 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>
|
||||
0
src/pages/admin/analytics/performance.astro
Normal file
0
src/pages/admin/analytics/performance.astro
Normal file
0
src/pages/admin/assembler/bulk-generate.astro
Normal file
0
src/pages/admin/assembler/bulk-generate.astro
Normal file
17
src/pages/admin/assembler/composer.astro
Normal file
17
src/pages/admin/assembler/composer.astro
Normal 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>
|
||||
0
src/pages/admin/assembler/index.astro
Normal file
0
src/pages/admin/assembler/index.astro
Normal file
0
src/pages/admin/assembler/quality-check.astro
Normal file
0
src/pages/admin/assembler/quality-check.astro
Normal file
15
src/pages/admin/assembler/workflow.astro
Normal file
15
src/pages/admin/assembler/workflow.astro
Normal 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>
|
||||
14
src/pages/admin/automations/workflow.astro
Normal file
14
src/pages/admin/automations/workflow.astro
Normal 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>
|
||||
17
src/pages/admin/blocks/editor.astro
Normal file
17
src/pages/admin/blocks/editor.astro
Normal 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>
|
||||
0
src/pages/admin/campaigns/index.astro
Normal file
0
src/pages/admin/campaigns/index.astro
Normal file
20
src/pages/admin/collections/avatar-variants.astro
Normal file
20
src/pages/admin/collections/avatar-variants.astro
Normal 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>
|
||||
151
src/pages/admin/collections/campaign-masters.astro
Normal file
151
src/pages/admin/collections/campaign-masters.astro
Normal 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>
|
||||
20
src/pages/admin/collections/cartesian-patterns.astro
Normal file
20
src/pages/admin/collections/cartesian-patterns.astro
Normal 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>{city}</code> or <code>{service}</code> to generate millions of unique combinations.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CartesianManager client:only="react" />
|
||||
</div>
|
||||
</Layout>
|
||||
20
src/pages/admin/collections/content-fragments.astro
Normal file
20
src/pages/admin/collections/content-fragments.astro
Normal 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>
|
||||
195
src/pages/admin/collections/generation-jobs.astro
Normal file
195
src/pages/admin/collections/generation-jobs.astro
Normal 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>
|
||||
21
src/pages/admin/collections/geo-intelligence.astro
Normal file
21
src/pages/admin/collections/geo-intelligence.astro
Normal 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>
|
||||
21
src/pages/admin/collections/headline-inventory.astro
Normal file
21
src/pages/admin/collections/headline-inventory.astro
Normal 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>
|
||||
21
src/pages/admin/collections/offer-blocks.astro
Normal file
21
src/pages/admin/collections/offer-blocks.astro
Normal 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>
|
||||
21
src/pages/admin/collections/page-blocks.astro
Normal file
21
src/pages/admin/collections/page-blocks.astro
Normal 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>
|
||||
20
src/pages/admin/collections/spintax-dictionaries.astro
Normal file
20
src/pages/admin/collections/spintax-dictionaries.astro
Normal 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>{key|word|variant}</code> syntax.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SpintaxManager client:only="react" />
|
||||
</div>
|
||||
</Layout>
|
||||
17
src/pages/admin/content/avatars.astro
Normal file
17
src/pages/admin/content/avatars.astro
Normal 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>
|
||||
0
src/pages/admin/content/pages.astro
Normal file
0
src/pages/admin/content/pages.astro
Normal file
0
src/pages/admin/content/posts.astro
Normal file
0
src/pages/admin/content/posts.astro
Normal file
14
src/pages/admin/content/work_log.astro
Normal file
14
src/pages/admin/content/work_log.astro
Normal 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>
|
||||
9
src/pages/admin/factory.astro
Normal file
9
src/pages/admin/factory.astro
Normal 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>
|
||||
222
src/pages/admin/factory/[id].astro
Normal file
222
src/pages/admin/factory/[id].astro
Normal 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>
|
||||
63
src/pages/admin/factory/index.astro
Normal file
63
src/pages/admin/factory/index.astro
Normal 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>
|
||||
19
src/pages/admin/factory/jobs.astro
Normal file
19
src/pages/admin/factory/jobs.astro
Normal 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>
|
||||
33
src/pages/admin/factory/kanban.astro
Normal file
33
src/pages/admin/factory/kanban.astro
Normal 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>
|
||||
15
src/pages/admin/index.astro
Normal file
15
src/pages/admin/index.astro
Normal 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>
|
||||
0
src/pages/admin/intelligence/avatar-metrics.astro
Normal file
0
src/pages/admin/intelligence/avatar-metrics.astro
Normal file
107
src/pages/admin/intelligence/avatars.astro
Normal file
107
src/pages/admin/intelligence/avatars.astro
Normal 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>
|
||||
21
src/pages/admin/intelligence/geo-targeting.astro
Normal file
21
src/pages/admin/intelligence/geo-targeting.astro
Normal 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>
|
||||
80
src/pages/admin/intelligence/index.astro
Normal file
80
src/pages/admin/intelligence/index.astro
Normal 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>
|
||||
0
src/pages/admin/intelligence/patterns.astro
Normal file
0
src/pages/admin/intelligence/patterns.astro
Normal file
19
src/pages/admin/intelligence/reports.astro
Normal file
19
src/pages/admin/intelligence/reports.astro
Normal 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>
|
||||
0
src/pages/admin/leads/[id].astro
Normal file
0
src/pages/admin/leads/[id].astro
Normal file
19
src/pages/admin/leads/index.astro
Normal file
19
src/pages/admin/leads/index.astro
Normal 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>
|
||||
8
src/pages/admin/locations.astro
Normal file
8
src/pages/admin/locations.astro
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import AdminLayout from '../../layouts/AdminLayout.astro';
|
||||
import LocationBrowser from '../../components/admin/LocationBrowser';
|
||||
---
|
||||
|
||||
<AdminLayout title="Locations">
|
||||
<LocationBrowser client:load />
|
||||
</AdminLayout>
|
||||
8
src/pages/admin/media/templates.astro
Normal file
8
src/pages/admin/media/templates.astro
Normal 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>
|
||||
20
src/pages/admin/pages/[id].astro
Normal file
20
src/pages/admin/pages/[id].astro
Normal 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>
|
||||
21
src/pages/admin/pages/index.astro
Normal file
21
src/pages/admin/pages/index.astro
Normal 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>
|
||||
19
src/pages/admin/scheduler/index.astro
Normal file
19
src/pages/admin/scheduler/index.astro
Normal 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>
|
||||
20
src/pages/admin/seo/articles/[id].astro
Normal file
20
src/pages/admin/seo/articles/[id].astro
Normal 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>
|
||||
22
src/pages/admin/seo/articles/index.astro
Normal file
22
src/pages/admin/seo/articles/index.astro
Normal 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>
|
||||
8
src/pages/admin/seo/campaigns.astro
Normal file
8
src/pages/admin/seo/campaigns.astro
Normal 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>
|
||||
14
src/pages/admin/seo/fragments.astro
Normal file
14
src/pages/admin/seo/fragments.astro
Normal 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>
|
||||
14
src/pages/admin/seo/headlines.astro
Normal file
14
src/pages/admin/seo/headlines.astro
Normal 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>
|
||||
9
src/pages/admin/settings.astro
Normal file
9
src/pages/admin/settings.astro
Normal 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>
|
||||
0
src/pages/admin/system/work-log.astro
Normal file
0
src/pages/admin/system/work-log.astro
Normal file
0
src/pages/admin/testing/content-quality.astro
Normal file
0
src/pages/admin/testing/content-quality.astro
Normal file
0
src/pages/admin/testing/index.astro
Normal file
0
src/pages/admin/testing/index.astro
Normal file
0
src/pages/admin/testing/link-checker.astro
Normal file
0
src/pages/admin/testing/link-checker.astro
Normal file
0
src/pages/admin/testing/seo-validation.astro
Normal file
0
src/pages/admin/testing/seo-validation.astro
Normal file
15
src/pages/admin/testing/suite.astro
Normal file
15
src/pages/admin/testing/suite.astro
Normal 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>
|
||||
Reference in New Issue
Block a user