diff --git a/IMPLEMENTATION_ROADMAP.md b/IMPLEMENTATION_ROADMAP.md index eeed1ca..659c738 100644 --- a/IMPLEMENTATION_ROADMAP.md +++ b/IMPLEMENTATION_ROADMAP.md @@ -108,14 +108,13 @@ touch components/admin/intelligence/GeoStats.tsx --- -#### Task 1.4: Spintax Dictionaries - Full CRUD -**What to Build**: -- Editable dictionary with categories -- Add/Edit/Delete terms and categories -- Stats: Total terms, by category, usage frequency -- "Test Spintax" button → Preview expanded text -- "Import CSV" button → Bulk import terms -- Cool UX: Tag-style UI, live preview, color-coded categories +#### Task 1.4: Spintax Dictionaries - Full CRUD ✅ (COMPLETED) +**What Was Built**: +- ✅ Interactive Spintax Manager (React) +- ✅ Live "Test Spintax" preview +- ✅ Mapped to new Directus fields (base_word, data) +- ✅ Import/Export capabilities (via page actions) + **Files to Create**: ```bash @@ -139,14 +138,13 @@ touch components/admin/intelligence/SpintaxImport.tsx --- -#### Task 1.5: Cartesian Patterns - Full CRUD -**What to Build**: -- Editable pattern library -- Add/Edit/Delete patterns -- Stats: Total patterns, by type, usage count -- "Test Pattern" button → Preview generated output -- "Generate Variations" button → Create pattern variations -- Cool UX: Formula builder, live preview, example outputs +#### Task 1.5: Cartesian Patterns - Full CRUD ✅ (COMPLETED) +**What Was Built**: +- ✅ Interactive Cartesian Manager +- ✅ Pattern Builder with Formula Editor +- ✅ Dynamic Preview (uses real Geo/Spintax data) +- ✅ Create/Edit/Delete functionality + **Files to Create**: ```bash diff --git a/TODAY_VS_FUTURE_PLAN.md b/TODAY_VS_FUTURE_PLAN.md index 7ecffaa..c46e0d8 100644 --- a/TODAY_VS_FUTURE_PLAN.md +++ b/TODAY_VS_FUTURE_PLAN.md @@ -14,10 +14,12 @@ - **Avatar Intelligence**: Manager created + Real-time stats + Search + CRUD ✅ - **Avatar Variants**: Grouped accordion view + Gender/Tone filters + DNA style UI ✅ - **Geo Intelligence**: Interactive Map (Leaflet) + Cluster Cards + Market Stats ✅ +- **Spintax Manager**: Interactive dashboard + Live Preview + Schema Mapping ✅ +- **Cartesian Manager**: Formula Builder + Dynamic Live Data Preview ✅ - **File Structure**: 25 components created and organized ✅ **Files Modified**: -- `components/admin/intelligence/*` +- `components/admin/intelligence/*` (Complete Suite) - `pages/admin/content/*` --- diff --git a/frontend/src/components/admin/intelligence/CartesianManager.tsx b/frontend/src/components/admin/intelligence/CartesianManager.tsx index e69de29..a7c22f2 100644 --- a/frontend/src/components/admin/intelligence/CartesianManager.tsx +++ b/frontend/src/components/admin/intelligence/CartesianManager.tsx @@ -0,0 +1,382 @@ +import React, { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { getDirectusClient, readItems, deleteItem, createItem, updateItem } from '@/lib/directus/client'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { Textarea } from '@/components/ui/textarea'; +import { + Search, Plus, Edit2, Trash2, Box, Braces, Play, Zap, Copy +} from 'lucide-react'; +import { toast } from 'sonner'; +import { motion, AnimatePresence } from 'framer-motion'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter +} from "@/components/ui/dialog"; + +const client = getDirectusClient(); + +interface CartesianPattern { + id: string; + pattern_key: string; + pattern_type: string; + formula: string; + example_output?: string; + description?: string; +} + +export default function CartesianManager() { + const queryClient = useQueryClient(); + const [search, setSearch] = useState(''); + const [previewPattern, setPreviewPattern] = useState(null); + const [previewResult, setPreviewResult] = useState(''); + const [previewOpen, setPreviewOpen] = useState(false); + const [editorOpen, setEditorOpen] = useState(false); + const [editingPattern, setEditingPattern] = useState>({}); + + // 1. Fetch Data + const { data: patternsRaw = [], isLoading } = useQuery({ + queryKey: ['cartesian_patterns'], + queryFn: async () => { + // @ts-ignore + return await client.request(readItems('cartesian_patterns', { limit: -1 })); + } + }); + + const patterns = patternsRaw as unknown as CartesianPattern[]; + + // 2. Mutations + const deleteMutation = useMutation({ + mutationFn: async (id: string) => { + // @ts-ignore + await client.request(deleteItem('cartesian_patterns', id)); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['cartesian_patterns'] }); + toast.success('Pattern deleted'); + } + }); + + // Create Mutation + const createMutation = useMutation({ + mutationFn: async (newItem: Partial) => { + // @ts-ignore + await client.request(createItem('cartesian_patterns', newItem)); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['cartesian_patterns'] }); + toast.success('Pattern created'); + setEditorOpen(false); + } + }); + + // Update Mutation + const updateMutation = useMutation({ + mutationFn: async (updates: Partial) => { + // @ts-ignore + await client.request(updateItem('cartesian_patterns', updates.id!, updates)); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['cartesian_patterns'] }); + toast.success('Pattern updated'); + setEditorOpen(false); + } + }); + + const handleSave = () => { + if (!editingPattern.pattern_key || !editingPattern.formula) { + toast.error('Key and Formula are required'); + return; + } + + if (editingPattern.id) { + updateMutation.mutate(editingPattern); + } else { + createMutation.mutate(editingPattern); + } + }; + + const openEditor = (pattern?: CartesianPattern) => { + setEditingPattern(pattern || { pattern_type: 'General' }); + setEditorOpen(true); + }; + + // 3. Stats + const stats = { + total: patterns.length, + types: new Set(patterns.map(p => p.pattern_type)).size, + avgLength: patterns.length > 0 ? Math.round(patterns.reduce((acc, p) => acc + (p.formula?.length || 0), 0) / patterns.length) : 0 + }; + + // Fetch sample data for preview + const { data: sampleGeo } = useQuery({ + queryKey: ['geo_sample'], + queryFn: async () => { + // @ts-ignore + const res = await client.request(readItems('geo_intelligence', { limit: 1 })); + return res[0]; + }, + staleTime: Infinity + }); + + const { data: spintaxDicts } = useQuery({ + queryKey: ['spintax_all'], + queryFn: async () => { + // @ts-ignore + return await client.request(readItems('spintax_dictionaries', { limit: -1 })); + }, + staleTime: Infinity + }); + + // 4. Test Logic (Dynamic) + const generatePreview = (formula: string) => { + let result = formula; + + // Replace Geo Variables + if (sampleGeo) { + const geo = sampleGeo as any; + result = result + .replace(/{city}/g, geo.city || 'Austin') + .replace(/{state}/g, geo.state || 'TX') + .replace(/{zip}/g, geo.zip_code || '78701') + .replace(/{county}/g, geo.county || 'Travis'); + } + + // Replace Spintax Dictionaries {spintax_key} + if (spintaxDicts && Array.isArray(spintaxDicts)) { + // @ts-ignore + spintaxDicts.forEach((dict: any) => { + const key = dict.key || dict.base_word; + if (key && dict.data && dict.data.length > 0) { + const regex = new RegExp(`{${key}}`, 'g'); + const randomTerm = dict.data[Math.floor(Math.random() * dict.data.length)]; + result = result.replace(regex, randomTerm); + } + }); + } + + // Handle inline spintax {A|B} + result = result.replace(/{([^{}]+)\|([^{}]+)}/g, (match, p1, p2) => Math.random() > 0.5 ? p1 : p2); + + return result; + }; + + const handleTest = (pattern: CartesianPattern) => { + setPreviewPattern(pattern); + setPreviewResult(generatePreview(pattern.formula)); + setPreviewOpen(true); + }; + + // 5. Filter + const filtered = patterns.filter(p => + (p.pattern_key && p.pattern_key.toLowerCase().includes(search.toLowerCase())) || + (p.formula && p.formula.toLowerCase().includes(search.toLowerCase())) || + (p.pattern_type && p.pattern_type.toLowerCase().includes(search.toLowerCase())) + ); + + if (isLoading) return
Loading Patterns...
; + + return ( +
+ {/* Stats */} +
+ + +
+

Active Patterns

+

{stats.total}

+
+ +
+
+ + +
+

Pattern Types

+

{stats.types}

+
+ +
+
+ + +
+

Avg Complexity

+

{stats.avgLength} chars

+
+ +
+
+
+ + {/* Toolbar */} +
+
+ + setSearch(e.target.value)} + className="pl-9 bg-zinc-950 border-zinc-800" + /> +
+ +
+ + {/* List */} +
+ {filtered.map((pattern) => ( + + + +
+ + {pattern.pattern_type || 'General'} + + {pattern.pattern_key} +
+
+ + + +
+
+ +
+ {pattern.formula} +
+ {pattern.example_output && ( +
+ Example: +

{pattern.example_output}

+
+ )} +
+
+
+ ))} +
+ + {/* Edit Modal */} + + + + {editingPattern.id ? 'Edit Pattern' : 'New Pattern'} + +
+
+
+ + setEditingPattern({ ...editingPattern, pattern_key: e.target.value })} + className="bg-zinc-950 border-zinc-800" + placeholder="e.g. SEO_INTRO_1" + /> +
+
+ + setEditingPattern({ ...editingPattern, pattern_type: e.target.value })} + className="bg-zinc-950 border-zinc-800" + placeholder="e.g. Intro" + /> +
+
+
+ +