From d54bfcbe5573f39b3bc8aa4d5564d20906590d95 Mon Sep 17 00:00:00 2001 From: Quinn Ftw Date: Mon, 29 Dec 2025 04:01:56 -0800 Subject: [PATCH] refactor(platform-admin): use feature admin packages for ML pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace local ML pages with re-exports from feature packages: - SEOPage from @lilith/seo-admin - TranslationsPage from @lilith/i18n-admin - TruthValidationPage from @lilith/truth-validation-admin This centralizes admin UI in their respective feature slices while maintaining the same public API from platform-admin. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- features/platform-admin/frontend/package.json | 3 + .../frontend/src/pages/ml/SEOPage.tsx | 253 ------------- .../src/pages/ml/TranslationsPage.tsx | 317 ----------------- .../src/pages/ml/TruthValidationPage.tsx | 336 ------------------ .../frontend/src/pages/ml/index.ts | 7 +- 5 files changed, 7 insertions(+), 909 deletions(-) delete mode 100644 features/platform-admin/frontend/src/pages/ml/SEOPage.tsx delete mode 100644 features/platform-admin/frontend/src/pages/ml/TranslationsPage.tsx delete mode 100644 features/platform-admin/frontend/src/pages/ml/TruthValidationPage.tsx diff --git a/features/platform-admin/frontend/package.json b/features/platform-admin/frontend/package.json index 04e77af34..b19c11240 100644 --- a/features/platform-admin/frontend/package.json +++ b/features/platform-admin/frontend/package.json @@ -12,6 +12,9 @@ }, "dependencies": { "@lilith/email-admin": "workspace:*", + "@lilith/i18n-admin": "workspace:*", + "@lilith/seo-admin": "workspace:*", + "@lilith/truth-validation-admin": "workspace:*", "@lilith/types": "workspace:*", "@tanstack/react-query": "^5.62.0", "clsx": "^2.1.1", diff --git a/features/platform-admin/frontend/src/pages/ml/SEOPage.tsx b/features/platform-admin/frontend/src/pages/ml/SEOPage.tsx deleted file mode 100644 index 4f043c95f..000000000 --- a/features/platform-admin/frontend/src/pages/ml/SEOPage.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import { useState } from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; - -interface SEOMetadata { - title: string; - description: string; - keywords: string; - og_title?: string; - og_description?: string; - og_image?: string; - og_type: string; - canonical_url?: string; - robots: string; - locale: string; -} - -interface SEOTemplate { - id: string; - page_type: string; - title_template: string; - description_template: string; - keywords_template: string; - variables: string[]; -} - -interface CacheStats { - hits: number; - misses: number; - hit_rate: number; - total_entries: number; -} - -const SEO_SERVICE_URL = '/api/seo'; - -async function fetchTemplates(): Promise<{ templates: SEOTemplate[]; total: number }> { - const res = await fetch(`${SEO_SERVICE_URL}/templates`); - if (!res.ok) throw new Error('Failed to fetch templates'); - return res.json(); -} - -async function fetchCacheStats(): Promise { - const res = await fetch(`${SEO_SERVICE_URL}/cache/stats`); - if (!res.ok) throw new Error('Failed to fetch cache stats'); - return res.json(); -} - -async function generateSEO(pageType: string, locale: string): Promise<{ metadata: SEOMetadata }> { - const res = await fetch(`${SEO_SERVICE_URL}/generate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ page_type: pageType, locale }), - }); - if (!res.ok) throw new Error('Failed to generate SEO'); - return res.json(); -} - -async function invalidateCache(pattern: string): Promise<{ invalidated_count: number }> { - const res = await fetch(`${SEO_SERVICE_URL}/cache?pattern=${encodeURIComponent(pattern)}`, { - method: 'DELETE', - }); - if (!res.ok) throw new Error('Failed to invalidate cache'); - return res.json(); -} - -export function SEOPage() { - const queryClient = useQueryClient(); - const [pageType, setPageType] = useState('landing'); - const [locale, setLocale] = useState('en'); - const [generatedSEO, setGeneratedSEO] = useState(null); - - const { data: cacheStats, isLoading: statsLoading } = useQuery({ - queryKey: ['seo-cache-stats'], - queryFn: fetchCacheStats, - refetchInterval: 30000, - }); - - const { data: templates, isLoading: templatesLoading } = useQuery({ - queryKey: ['seo-templates'], - queryFn: fetchTemplates, - }); - - const generateMutation = useMutation({ - mutationFn: () => generateSEO(pageType, locale), - onSuccess: (data) => { - setGeneratedSEO(data.metadata); - }, - }); - - const invalidateMutation = useMutation({ - mutationFn: (pattern: string) => invalidateCache(pattern), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['seo-cache-stats'] }); - }, - }); - - const pageTypes = ['landing', 'profile', 'search', 'about', 'pricing', 'creator', 'client']; - const locales = ['en', 'es', 'fr', 'de', 'it', 'pt', 'ja', 'ko', 'zh']; - - return ( -
-
-
-

SEO Generator

-

Generate and manage SEO metadata for pages

-
-
- Service Online - Port 41230 -
-
- - {/* Cache Stats */} -
-
-
- {statsLoading ? '...' : cacheStats?.hits ?? 0} -
-
Cache Hits
-
-
-
- {statsLoading ? '...' : cacheStats?.misses ?? 0} -
-
Cache Misses
-
-
-
- {statsLoading ? '...' : `${((cacheStats?.hit_rate ?? 0) * 100).toFixed(1)}%`} -
-
Hit Rate
-
-
-
- {statsLoading ? '...' : cacheStats?.total_entries ?? 0} -
-
Cached Entries
-
-
- - {/* Generate SEO */} -
-

Generate SEO

-
-
- - -
-
- - -
-
- -
-
- - {generatedSEO && ( -
-
- Title: -

{generatedSEO.title}

-
-
- Description: -

{generatedSEO.description}

-
-
- Keywords: -

{generatedSEO.keywords || 'None'}

-
-
- Locale: {generatedSEO.locale} - OG Type: {generatedSEO.og_type} - Robots: {generatedSEO.robots} -
-
- )} -
- - {/* Templates */} -
-
-

Templates

- -
- {templatesLoading ? ( -
Loading templates...
- ) : templates?.templates.length === 0 ? ( -
No templates configured
- ) : ( -
- {templates?.templates.map((template) => ( -
-
- {template.id} - {template.page_type} -
-

{template.title_template}

-
- ))} -
- )} -
- - {/* Cache Management */} -
-

Cache Management

-
- - -
-
-
- ); -} diff --git a/features/platform-admin/frontend/src/pages/ml/TranslationsPage.tsx b/features/platform-admin/frontend/src/pages/ml/TranslationsPage.tsx deleted file mode 100644 index 2ae02077c..000000000 --- a/features/platform-admin/frontend/src/pages/ml/TranslationsPage.tsx +++ /dev/null @@ -1,317 +0,0 @@ -import { useState } from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; - -interface Locale { - code: string; - name: string; - native_name: string; - rtl: boolean; - flag?: string; -} - -interface Translation { - key: string; - source_text: string; - translated_text: string; - source_locale: string; - target_locale: string; - quality_score: number; - from_cache: boolean; - from_static: boolean; -} - -interface GlossaryTerm { - source: string; - translations: Record; - context?: string; - category: string; -} - -interface MissingTranslation { - key: string; - namespace: string; - source_text?: string; - missing_locales: string[]; -} - -const I18N_SERVICE_URL = '/api/i18n'; - -async function fetchLocales(): Promise<{ locales: Locale[]; default_locale: string }> { - const res = await fetch(`${I18N_SERVICE_URL}/locales`); - if (!res.ok) throw new Error('Failed to fetch locales'); - return res.json(); -} - -async function fetchGlossary(): Promise<{ terms: GlossaryTerm[] }> { - const res = await fetch(`${I18N_SERVICE_URL}/glossary`); - if (!res.ok) throw new Error('Failed to fetch glossary'); - return res.json(); -} - -async function fetchMissing(namespace: string): Promise<{ missing: MissingTranslation[]; total: number }> { - const res = await fetch(`${I18N_SERVICE_URL}/missing?namespace=${namespace}`); - if (!res.ok) throw new Error('Failed to fetch missing translations'); - return res.json(); -} - -async function translateText( - key: string, - sourceText: string, - targetLocale: string -): Promise<{ translation: Translation }> { - const res = await fetch(`${I18N_SERVICE_URL}/translate`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - key, - source_text: sourceText, - source_locale: 'en', - target_locale: targetLocale, - namespace: 'common', - }), - }); - if (!res.ok) throw new Error('Failed to translate'); - return res.json(); -} - -export function TranslationsPage() { - const queryClient = useQueryClient(); - const [namespace, setNamespace] = useState('common'); - const [testKey, setTestKey] = useState(''); - const [testText, setTestText] = useState(''); - const [testLocale, setTestLocale] = useState('es'); - const [translationResult, setTranslationResult] = useState(null); - - const { data: localesData, isLoading: localesLoading } = useQuery({ - queryKey: ['i18n-locales'], - queryFn: fetchLocales, - }); - - const { data: glossaryData, isLoading: glossaryLoading } = useQuery({ - queryKey: ['i18n-glossary'], - queryFn: fetchGlossary, - }); - - const { data: missingData, isLoading: missingLoading } = useQuery({ - queryKey: ['i18n-missing', namespace], - queryFn: () => fetchMissing(namespace), - }); - - const translateMutation = useMutation({ - mutationFn: () => translateText(testKey, testText, testLocale), - onSuccess: (data) => { - setTranslationResult(data.translation); - }, - }); - - const namespaces = ['common', 'landing', 'profile', 'search', 'legal', 'seo']; - - return ( -
-
-
-

Translations

-

ML-powered translation management

-
-
- Service Online - Port 41231 -
-
- - {/* Stats */} -
-
-
- {localesLoading ? '...' : localesData?.locales.length ?? 0} -
-
Supported Locales
-
-
-
- {missingLoading ? '...' : missingData?.total ?? 0} -
-
Missing Translations
-
-
-
- {glossaryLoading ? '...' : glossaryData?.terms.length ?? 0} -
-
Glossary Terms
-
-
-
- {localesData?.locales.filter((l) => l.rtl).length ?? 0} -
-
RTL Languages
-
-
- - {/* Test Translation */} -
-

Test Translation

-
-
- - setTestKey(e.target.value)} - placeholder="common.welcome" - className="input w-full" - /> -
-
- - -
-
-
- -