diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/client/index.html b/client/index.html index 32aac9f..d7c1a6e 100644 --- a/client/index.html +++ b/client/index.html @@ -1,17 +1,15 @@ - + - ATLAS Social Command Center - + diff --git a/client/src/App.tsx b/client/src/App.tsx index 0828668..1f4b266 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,38 +1,69 @@ import { Toaster } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import NotFound from "@/pages/NotFound"; -import { Route, Switch } from "wouter"; +import { Route, Switch, useLocation } from "wouter"; import ErrorBoundary from "./components/ErrorBoundary"; import { ThemeProvider } from "./contexts/ThemeContext"; -import Home from "./pages/Home"; +import { AppShell } from "./components/layout/AppShell"; +import { Suspense, lazy } from "react"; +// Lazy-load all pages +const WarRoom = lazy(() => import("./pages/WarRoom")); +const Signals = lazy(() => import("./pages/Signals")); +const Drafts = lazy(() => import("./pages/Drafts")); +const Calendar = lazy(() => import("./pages/Calendar")); +const Campaigns = lazy(() => import("./pages/Campaigns")); +const Products = lazy(() => import("./pages/Products")); +const Approvals = lazy(() => import("./pages/Approvals")); +const Analytics = lazy(() => import("./pages/Analytics")); +const Settings = lazy(() => import("./pages/Settings")); -function Router() { +function LoadingFallback() { return ( - - - - {/* Final fallback route */} - - +
+
+
+ + ATLAS LOADING... + +
+
); } -// NOTE: About Theme -// - First choose a default theme according to your design style (dark or light bg), than change color palette in index.css -// to keep consistent foreground/background color across components -// - If you want to make theme switchable, pass `switchable` ThemeProvider and use `useTheme` hook +function AppRoutes() { + const [location] = useLocation(); + + return ( + + }> + + + + + + + + + + + + + + + ); +} function App() { return ( - + - + diff --git a/client/src/components/layout/AppShell.tsx b/client/src/components/layout/AppShell.tsx new file mode 100644 index 0000000..f0e4ae0 --- /dev/null +++ b/client/src/components/layout/AppShell.tsx @@ -0,0 +1,130 @@ +/** + * AppShell — 3-Spalten-Layout + * Bloomberg Terminal × Raumschiff-Brücke + * + * Layout: Sidebar (160px) | Content (flex-grow) | CoPilot (320px) + */ + +import { useState } from 'react'; +import { Sidebar } from './Sidebar'; +import { CoPilotPanel } from './CoPilotPanel'; + +interface AppShellProps { + children: React.ReactNode; + currentPath: string; +} + +export function AppShell({ children, currentPath }: AppShellProps) { + const [copilotOpen, setCopilotOpen] = useState(true); + + return ( +
+ {/* Left Sidebar Navigation */} + + + {/* Main Content Area */} +
+ {/* Top Bar */} +
+
+ + ATLAS OS + + + + Social Command Center + +
+ +
+ {/* System Status */} +
+
+ + Postiz + +
+ +
+ + {/* Live Clock */} + + +
+ + {/* Co-Pilot Toggle */} + +
+
+ + {/* Page Content */} +
+ {children} +
+
+ + {/* Right Co-Pilot Panel */} + {copilotOpen && ( + setCopilotOpen(false)} /> + )} +
+ ); +} + +function LiveClock() { + const [time, setTime] = useState(new Date()); + + // Update every second + useState(() => { + const interval = setInterval(() => setTime(new Date()), 1000); + return () => clearInterval(interval); + }); + + return ( + + {time.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' })} + + ); +} diff --git a/client/src/components/layout/CoPilotPanel.tsx b/client/src/components/layout/CoPilotPanel.tsx new file mode 100644 index 0000000..a362959 --- /dev/null +++ b/client/src/components/layout/CoPilotPanel.tsx @@ -0,0 +1,409 @@ +/** + * CoPilotPanel — AI Co-Pilot Rechts-Panel + * + * Kommando-Eingabe, Vorschläge, LLM-Integration + * Width: 320px, fixed right + */ + +import { useState, useRef, useEffect } from 'react'; +import { X, Send, Sparkles, Zap, FileEdit, Calendar, Package } from 'lucide-react'; + +interface Message { + id: string; + role: 'user' | 'assistant'; + content: string; + timestamp: Date; +} + +const SUGGESTED_COMMANDS = [ + { icon: FileEdit, label: 'Draft für pit_001 erstellen', cmd: 'Erstelle einen LinkedIn-Draft für Shelly Plus Plug S mit Affiliate-UTM-Link' }, + { icon: Zap, label: 'Signal analysieren', cmd: 'Analysiere die aktuellen Signale und schlage 3 Post-Ideen vor' }, + { icon: Calendar, label: 'Wochenplan erstellen', cmd: 'Erstelle einen optimalen Posting-Plan für diese Woche' }, + { icon: Package, label: 'Produkt-Post', cmd: 'Schreibe einen Instagram-Post für das beste Produkt im Katalog' }, +]; + +interface CoPilotPanelProps { + onClose: () => void; +} + +export function CoPilotPanel({ onClose }: CoPilotPanelProps) { + const [messages, setMessages] = useState([ + { + id: '0', + role: 'assistant', + content: 'ATLAS Co-Pilot bereit. Ich helfe dir mit Drafts, Kampagnen, Signalanalyse und Content-Planung. Was soll ich tun?', + timestamp: new Date(), + } + ]); + const [input, setInput] = useState(''); + const [loading, setLoading] = useState(false); + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + const sendMessage = async (text: string) => { + if (!text.trim() || loading) return; + + const userMsg: Message = { + id: Date.now().toString(), + role: 'user', + content: text.trim(), + timestamp: new Date(), + }; + + setMessages(prev => [...prev, userMsg]); + setInput(''); + setLoading(true); + + try { + // Call built-in LLM via Manus proxy + const res = await fetch(import.meta.env.VITE_FRONTEND_FORGE_API_URL || 'https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${import.meta.env.VITE_FRONTEND_FORGE_API_KEY || ''}`, + }, + body: JSON.stringify({ + model: 'gpt-4o-mini', + messages: [ + { + role: 'system', + content: `Du bist ATLAS Co-Pilot, ein AI-Assistent für Social Media Content-Erstellung und -Management. + +Du hilfst Torsten Hiss (hiss@clara360.de) beim: +- Erstellen von Social Media Drafts (LinkedIn, Instagram, TikTok) +- Analysieren von Signalen und Trends +- Planen von Content-Kalendern +- Optimieren von Affiliate-Links (UTM-Tracking) +- Verwalten des Produktkatalogs (catalog.json) + +Kontext: +- Postiz ist die Publishing-Engine (postiz.atlas-os.ai) +- Produkte kommen aus catalog.json (search.atlas-os.ai/catalog.json) +- Aktuelles Hauptprodukt: Shelly Plus Plug S (pit_001) +- Affiliate-Regel: display_status=active nur wenn program_status=approved + +Antworte präzise, direkt und auf Deutsch. Keine Floskeln.`, + }, + ...messages.slice(-10).map(m => ({ role: m.role, content: m.content })), + { role: 'user', content: text.trim() }, + ], + max_tokens: 500, + temperature: 0.7, + }), + }); + + if (res.ok) { + const data = await res.json(); + const reply = data.choices?.[0]?.message?.content || 'Keine Antwort erhalten.'; + + setMessages(prev => [...prev, { + id: Date.now().toString(), + role: 'assistant', + content: reply, + timestamp: new Date(), + }]); + } else { + throw new Error(`HTTP ${res.status}`); + } + } catch { + // Fallback response + setMessages(prev => [...prev, { + id: Date.now().toString(), + role: 'assistant', + content: getFallbackResponse(text), + timestamp: new Date(), + }]); + } finally { + setLoading(false); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(input); + } + }; + + return ( +