From 44a613f8ecab89fe7bee1f86066d2bf6aa3ea279 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 29 Sep 2026 12:48:52 +0700 Subject: [PATCH] x --- apps/admin/src/App.tsx | 3 + apps/admin/src/components/Sidebar.tsx | 13 +- .../admin/src/pages/BrandIntelligencePage.tsx | 616 ++++++++++++++++++ apps/admin/src/pages/StoreSettingsPage.tsx | 217 +++++- .../migration.sql | 5 + .../migration.sql | 18 + apps/backend/prisma/schema.prisma | 18 + apps/backend/src/index.ts | 3 +- apps/backend/src/routes/brandKnowledge.ts | 262 ++++++++ apps/backend/src/routes/settings.ts | 15 +- apps/mcp-server/index.ts | 48 ++ packages/types/index.ts | 18 + 12 files changed, 1227 insertions(+), 9 deletions(-) create mode 100644 apps/admin/src/pages/BrandIntelligencePage.tsx create mode 100644 apps/backend/prisma/migrations/20260929123000_add_brand_prompt/migration.sql create mode 100644 apps/backend/prisma/migrations/20260929124500_add_brand_knowledge/migration.sql create mode 100644 apps/backend/src/routes/brandKnowledge.ts diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index 70d9b90..ffc11f9 100644 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -18,6 +18,7 @@ import ProtectedRoute from './components/ProtectedRoute' import OrdersPage from './pages/OrdersPage' import OrderDetailPage from './pages/OrderDetailPage' import StoreSettingsPage from './pages/StoreSettingsPage' +import BrandIntelligencePage from './pages/BrandIntelligencePage' import PaymentSettingsPage from './pages/PaymentSettingsPage' import ThemeSettingsPage from './pages/ThemeSettingsPage' import ContentSettingsPage from './pages/ContentSettingsPage' @@ -113,6 +114,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/apps/admin/src/components/Sidebar.tsx b/apps/admin/src/components/Sidebar.tsx index cf5fe35..c449dc4 100644 --- a/apps/admin/src/components/Sidebar.tsx +++ b/apps/admin/src/components/Sidebar.tsx @@ -37,6 +37,7 @@ import { FolderArchive, Map, Target, + BrainCircuit, } from 'lucide-react' import { useStoreSettings } from '../contexts/StoreSettingsContext' import { clearAdminAuth } from '../utils/auth' @@ -140,6 +141,7 @@ export default function Sidebar({ isCollapsed, onToggle }: SidebarProps) { const CMS_MENU = [ { icon: Layers, label: t('nav.pageBuilder', 'Page Builder (CMS)'), to: '/page-builder' }, + { icon: BrainCircuit, label: t('nav.brandIntelligence', 'Brand AI Intelligence'), to: '/brand-intelligence' }, { icon: Sparkles, label: t('nav.cmsBlockShowcase', 'CMS Block Showcase'), to: '/cms-mockup' }, { icon: ImageIcon, label: t('nav.mediaLibrary', 'Media Library'), to: '/media' }, { icon: BookOpen, label: t('nav.scentGuide', 'Scent Guide'), to: '/articles' }, @@ -169,11 +171,12 @@ export default function Sidebar({ isCollapsed, onToggle }: SidebarProps) { ] const SETTINGS_MENU = [ - { icon: Store, label: t('nav.storeSettings', 'Store Settings'), to: '/settings' }, - { icon: CreditCard, label: t('nav.paymentSettings', 'Payment & Shipping'), to: '/settings/payments' }, - { icon: Palette, label: t('nav.themeSettings', 'Theme & Design'), to: '/settings/theme' }, - { icon: FileText, label: t('nav.contentSettings', 'Content'), to: '/settings/content' }, - { icon: Wrench, label: t('nav.systemMaintenance', 'System'), to: '/settings/system' }, + { icon: Store, label: t('nav.storeSettings', 'Store Settings'), to: '/settings' }, + { icon: BrainCircuit, label: t('nav.brandIntelligence', 'Brand AI Intelligence'), to: '/settings/brand-intelligence' }, + { icon: CreditCard, label: t('nav.paymentSettings', 'Payment & Shipping'), to: '/settings/payments' }, + { icon: Palette, label: t('nav.themeSettings', 'Theme & Design'), to: '/settings/theme' }, + { icon: FileText, label: t('nav.contentSettings', 'Content'), to: '/settings/content' }, + { icon: Wrench, label: t('nav.systemMaintenance', 'System'), to: '/settings/system' }, ] const handleLogout = () => { diff --git a/apps/admin/src/pages/BrandIntelligencePage.tsx b/apps/admin/src/pages/BrandIntelligencePage.tsx new file mode 100644 index 0000000..55f1d75 --- /dev/null +++ b/apps/admin/src/pages/BrandIntelligencePage.tsx @@ -0,0 +1,616 @@ +import { useState, useEffect } from 'react' +import { API_BASE, type BrandKnowledge } from '@homaroma/types' +import { + BrainCircuit, + Save, + Loader2, + Copy, + Check, + Wand2, + Sparkles, + CheckCircle2, + Palette, + Users, + Feather, + PackageCheck, + Send, + Code2, + Download +} from 'lucide-react' +import { getAdminToken } from '../utils/auth' + +type ActiveTab = 'identity' | 'visual' | 'products' | 'marketing' | 'compiled' + +export default function BrandIntelligencePage() { + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [saveSuccess, setSaveSuccess] = useState(false) + const [copiedType, setCopiedType] = useState<'md' | 'json' | null>(null) + const [activeTab, setActiveTab] = useState('identity') + + // Form State + const [brandName, setBrandName] = useState('AKÉRIA') + const [tagline, setTagline] = useState('') + const [story, setStory] = useState('') + const [targetAudience, setTargetAudience] = useState('') + const [toneOfVoice, setToneOfVoice] = useState('') + const [visualArtPrompt, setVisualArtPrompt] = useState('') + const [productUsps, setProductUsps] = useState('') + const [marketingGuidelines, setMarketingGuidelines] = useState('') + const [customRules, setCustomRules] = useState('') + const [masterPromptOverride, setMasterPromptOverride] = useState('') + + useEffect(() => { + fetch(`${API_BASE}/brand-knowledge`) + .then((r) => r.json()) + .then((data: BrandKnowledge) => { + setBrandName(data.brandName || 'AKÉRIA') + setTagline(data.tagline || '') + setStory(data.story || '') + setTargetAudience(data.targetAudience || '') + setToneOfVoice(data.toneOfVoice || '') + setVisualArtPrompt(data.visualArtPrompt || '') + setProductUsps(data.productUsps || '') + setMarketingGuidelines(data.marketingGuidelines || '') + setCustomRules(data.customRules || '') + setMasterPromptOverride(data.masterPromptOverride || '') + }) + .catch(console.error) + .finally(() => setLoading(false)) + }, []) + + // Dynamically compile preview on client + const liveCompiledPrompt = masterPromptOverride.trim() + ? masterPromptOverride.trim() + : [ + `# BRAND KNOWLEDGE DIRECTIVE: ${brandName || 'AKÉRIA'}`, + tagline ? `**Tagline:** ${tagline}` : null, + story ? `## 1. BRAND STORY & CORE PHILOSOPHY\n${story.trim()}` : null, + targetAudience ? `## 2. TARGET AUDIENCE & PERSONA\n${targetAudience.trim()}` : null, + toneOfVoice ? `## 3. TONE OF VOICE & MESSAGING STYLE\n${toneOfVoice.trim()}` : null, + visualArtPrompt ? `## 4. VISUAL ART DIRECTION & PHOTOGRAPHY DIRECTIVE\n${visualArtPrompt.trim()}` : null, + productUsps ? `## 5. PRODUCTS, SIGNATURE NOTES & USPs\n${productUsps.trim()}` : null, + marketingGuidelines ? `## 6. MARKETING CHANNELS & CONTENT GUIDELINES\n${marketingGuidelines.trim()}` : null, + customRules ? `## 7. STRICT BRAND COMPLIANCE & RULES\n${customRules.trim()}` : null, + `---\n*This document is the single source of truth for all LLM, AI Image, and Copywriting tasks representing ${brandName || 'AKÉRIA'}.*`, + ] + .filter(Boolean) + .join('\n\n') + + const handleCopyMarkdown = async () => { + try { + await navigator.clipboard.writeText(liveCompiledPrompt) + setCopiedType('md') + setTimeout(() => setCopiedType(null), 2500) + } catch { + // ignore + } + } + + const handleCopyJson = async () => { + try { + const payload = { + brandName, + tagline, + story, + targetAudience, + toneOfVoice, + visualArtPrompt, + productUsps, + marketingGuidelines, + customRules, + compiledPrompt: liveCompiledPrompt, + } + await navigator.clipboard.writeText(JSON.stringify(payload, null, 2)) + setCopiedType('json') + setTimeout(() => setCopiedType(null), 2500) + } catch { + // ignore + } + } + + const handleDownloadMarkdown = () => { + const blob = new Blob([liveCompiledPrompt], { type: 'text/markdown;charset=utf-8' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `${(brandName || 'brand').toLowerCase().replace(/\s+/g, '_')}_ai_knowledge.md` + a.click() + URL.revokeObjectURL(url) + } + + const handleSave = async (e?: React.FormEvent) => { + if (e) e.preventDefault() + setSaving(true) + setSaveSuccess(false) + + try { + const payload = { + brandName, + tagline: tagline || null, + story: story || null, + targetAudience: targetAudience || null, + toneOfVoice: toneOfVoice || null, + visualArtPrompt: visualArtPrompt || null, + productUsps: productUsps || null, + marketingGuidelines: marketingGuidelines || null, + customRules: customRules || null, + masterPromptOverride: masterPromptOverride || null, + } + + const token = getAdminToken() + const res = await fetch(`${API_BASE}/brand-knowledge`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify(payload), + }) + + const data = await res.json() + if (res.ok && data.success) { + setSaveSuccess(true) + setTimeout(() => setSaveSuccess(false), 3500) + } else { + alert(data.error || 'Failed to save brand knowledge') + } + } catch (err) { + console.error('Save brand knowledge error:', err) + alert('Failed to save brand knowledge') + } finally { + setSaving(false) + } + } + + if (loading) { + return ( +
+ +
+ ) + } + + return ( +
+ {/* Page Header */} +
+
+
+ +
+
+
+

Brand AI Intelligence

+ + LLM Knowledge Base + +
+

+ ศูนย์รวมองค์ความรู้ ตัวตน กลิ่น สินค้า และ Art Direction เพื่อส่งต่อให้ AI & Copywriters ทุกระบบ +

+
+
+ + {/* Global Action Buttons */} +
+ + + + + +
+
+ + {saveSuccess && ( +
+ + บันทึกข้อมูล Brand AI Intelligence เรียบร้อยแล้ว พร้อมส่งต่อไปยัง AI System Context ทันที! +
+ )} + + {/* Modern Tabs Header */} +
+ + + + + + + + + +
+ + {/* TAB CONTENT 1: IDENTITY & VOICE */} + {activeTab === 'identity' && ( +
+
+
+
+

+ Brand Core Identity & Philosophy +

+

+ ระบุชื่อแบรนด์ สโลแกน และเรื่องราวที่มา เพื่อให้ LLM สร้างบทความหรือแคปชันที่สะท้อนคุณค่าของแบรนด์ +

+
+
+ +
+
+ + setBrandName(e.target.value)} + className="admin-input w-full font-bold text-white" + placeholder="AKÉRIA" + /> +
+ +
+ + setTagline(e.target.value)} + className="admin-input w-full text-slate-200" + placeholder="Soft Asian Elegance & Mindful Beauty Sanctuary" + /> +
+
+ +
+
+ + {story.length} chars +
+