import React, { useState, useEffect, useRef } from 'react';
import { HashRouter, Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom';
import { User, Agent, UserRole, SubscriptionTier, ChatMessage, AnalyticsData } from './types';
import { Button } from './components/Button';
import { Billing } from './components/Billing';
import { AdminPanel } from './components/AdminPanel';
import {
getStoredUser, storeUser, logoutUser,
getStoredAgents, saveAgent, deleteAgent, mockLogin
} from './services/mockDatabase';
import { createAgentChat, sendMessageStreamToAgent } from './services/geminiService';
import { Chat } from '@google/genai';
// --- Icons ---
const RobotIcon = () => ;
const ChartIcon = () => ;
const CreditCardIcon = () => ;
const ShieldIcon = () => ;
const LogoutIcon = () => ;
const PlusIcon = () => ;
// --- Landing Page Component ---
const LandingPage: React.FC<{ onLogin: () => void }> = ({ onLogin }) => {
return (
);
};
// --- Syntax Highlighting Editor Component (VSCode Themed) ---
const SyntaxEditor: React.FC<{ value: string; onChange: (val: string) => void; placeholder?: string }> = ({ value, onChange, placeholder }) => {
const textareaRef = useRef(null);
const preRef = useRef(null);
const [cursorStats, setCursorStats] = useState({ line: 1, col: 1 });
const [highlightedHtml, setHighlightedHtml] = useState('');
// Explicit line height and font settings for pixel-perfect alignment between pre and textarea
const EDITOR_FONT = {
fontFamily: "'Fira Code', 'Menlo', 'Monaco', 'Courier New', monospace",
lineHeight: '24px',
fontSize: '14px',
};
useEffect(() => {
// Custom highlighting logic to support nested code blocks in markdown
const Prism = (window as any).Prism;
if (!Prism) {
setHighlightedHtml(value.replace(/ {
// Use standard markdown grammar if available
if (!Prism.languages.markdown) return Prism.util.encode(text);
// Tokenize the markdown
const tokens = Prism.tokenize(text, Prism.languages.markdown);
// Process tokens to find code blocks and highlight their internal content
return tokens.map((token: any) => {
// If it's a string, just encode it
if (typeof token === 'string') {
return Prism.util.encode(token);
}
// If it's a code block (Prism markdown 'code' token), identify language and re-highlight
if (token.type === 'code' && Array.isArray(token.content)) {
// Identify language from the 'code-language' token inside
let lang = 'text';
const langToken = token.content.find((t: any) => t.type === 'code-language');
if (langToken && typeof langToken.content === 'string') {
lang = langToken.content.trim();
}
// Re-process the content of the code block
const innerHtml = token.content.map((innerToken: any) => {
if (typeof innerToken === 'string') {
// This is typically the code body or whitespace
// If we found a valid language, highlight this string with that language
if (lang && Prism.languages[lang]) {
return Prism.highlight(innerToken, Prism.languages[lang], lang);
}
return Prism.util.encode(innerToken);
}
// Preserve structure of other tokens (punctuation, etc.)
return Prism.Token.stringify(innerToken, 'markdown');
}).join('');
return `${innerHtml}`;
}
// Default token stringification for non-code-block tokens
return Prism.Token.stringify(token, 'markdown');
}).join('');
};
try {
setHighlightedHtml(highlight(value));
} catch (e) {
console.error("Syntax highlight error", e);
setHighlightedHtml(value.replace(/ {
if (textareaRef.current && preRef.current) {
preRef.current.scrollTop = textareaRef.current.scrollTop;
preRef.current.scrollLeft = textareaRef.current.scrollLeft;
}
};
const updateCursorStats = (e: any) => {
const el = e.target;
const val = el.value.substr(0, el.selectionStart);
const lines = val.split('\n');
setCursorStats({
line: lines.length,
col: lines[lines.length - 1].length + 1
});
};
const handleChange = (e: React.ChangeEvent) => {
onChange(e.target.value);
updateCursorStats(e);
};
return (
(null);
// Refs for stable ID and creation date across re-renders and auto-saves
const idRef = useRef(agent?.id || Math.random().toString(36).substr(2, 9));
const createdAtRef = useRef(agent?.createdAt || new Date().toISOString());
const interactionsRef = useRef(agent?.interactions || 0);
// Random color for new agents, stable for existing
const avatarRef = useRef(agent?.avatar_color || `bg-${['blue','green','purple','pink','indigo'][Math.floor(Math.random()*5)]}-500`);
// Ref to hold current form state for the interval closure
const stateRef = useRef({ name, role, desc, instructions, model });
// Update ref whenever state changes
useEffect(() => {
stateRef.current = { name, role, desc, instructions, model };
}, [name, role, desc, instructions, model]);
// Helper to construct the Agent object from current state
const constructAgent = (): Agent => ({
id: idRef.current,
name: stateRef.current.name,
role: stateRef.current.role,
description: stateRef.current.desc,
systemInstruction: stateRef.current.instructions,
model: stateRef.current.model,
createdAt: createdAtRef.current,
interactions: interactionsRef.current,
avatar_color: avatarRef.current
});
// Auto-save logic: runs every 30 seconds
useEffect(() => {
const timer = setInterval(() => {
// Only auto-save if the agent has a name (minimum requirement)
if (stateRef.current.name.trim()) {
const agentToSave = constructAgent();
onSave(agentToSave, true); // keepOpen = true
setLastAutoSave(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
}
}, 30000);
return () => clearInterval(timer);
}, []);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSave(constructAgent(), false); // keepOpen = false (close editor)
};
return (
);
};
// --- Chat Interface ---
const AgentChat: React.FC<{ agent: Agent }> = ({ agent }) => {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [isTyping, setIsTyping] = useState(false);
const chatSessionRef = useRef(null);
const messagesEndRef = useRef(null);
useEffect(() => {
// Reset chat when agent changes
setMessages([{
id: 'init',
role: 'model',
text: `Hello! I am ${agent.name}, your ${agent.role}. How can I help you today?`,
timestamp: Date.now()
}]);
try {
chatSessionRef.current = createAgentChat(agent);
} catch (e) {
console.error("Failed to init chat", e);
}
}, [agent]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const handleSend = async () => {
if (!input.trim() || !chatSessionRef.current) return;
const userMsg: ChatMessage = { id: Date.now().toString(), role: 'user', text: input, timestamp: Date.now() };
setMessages(prev => [...prev, userMsg]);
setInput('');
setIsTyping(true);
try {
const result = await sendMessageStreamToAgent(chatSessionRef.current, userMsg.text);
let fullText = "";
const botMsgId = (Date.now() + 1).toString();
// Add placeholder message
setMessages(prev => [...prev, { id: botMsgId, role: 'model', text: '', timestamp: Date.now() }]);
for await (const chunk of result) {
// chunk.text is a getter, not a function
const chunkText = chunk.text;
if (chunkText) {
fullText += chunkText;
setMessages(prev => prev.map(m => m.id === botMsgId ? { ...m, text: fullText } : m));
}
}
} catch (err) {
setMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', text: "Error: Could not connect to Gemini API. Check API Key.", timestamp: Date.now() }]);
} finally {
setIsTyping(false);
}
};
return (
(getStoredAgents());
const [editingAgent, setEditingAgent] = useState(null);
const [isEditorOpen, setIsEditorOpen] = useState(false);
const [selectedAgentForChat, setSelectedAgentForChat] = useState(null);
// Stats for Analytics
const stats: AnalyticsData = {
totalTokens: 1450230,
activeAgents: agents.length,
messagesExchanged: agents.reduce((acc, curr) => acc + curr.interactions, 0),
costEstimate: 4.25
};
const handleSaveAgent = (agent: Agent, keepOpen: boolean = false) => {
const updatedAgents = saveAgent(agent);
setAgents(updatedAgents);
if (!keepOpen) {
setIsEditorOpen(false);
setEditingAgent(null);
} else {
// If we are auto-saving a new agent, update the editingAgent to the newly created one
// so subsequent auto-saves update the same record instead of creating duplicates.
setEditingAgent(agent);
}
};
const handleDeleteAgent = (id: string) => {
if(confirm('Are you sure you want to delete this agent?')) {
const updated = deleteAgent(id);
setAgents(updated);
if(selectedAgentForChat?.id === id) setSelectedAgentForChat(null);
}
};
const handleUpgrade = (tier: SubscriptionTier) => {
alert(`Redirecting to Stripe Checkout for ${tier} tier... (Mock)`);
// In real app: window.location.href = stripe_checkout_url
};
return (
(getStoredUser());
const [showAuth, setShowAuth] = useState(false);
const handleLogin = (loggedInUser: User) => {
setUser(loggedInUser);
setShowAuth(false);
};
const handleLogout = () => {
logoutUser();
setUser(null);
};
// Route protection logic (simplified)
if (!user) {
if (showAuth) {
return ;
}
return setShowAuth(true)} />;
}
return (
} />
} />
);
};
export default App;
{/* Navbar */}
{/* Hero */}
Deploy Unlimited
{/* Mock UI Preview */}
{/* Footer */}
);
};
// --- Auth Component (Simulated) ---
const AuthPage: React.FC<{ onSuccess: (user: User) => void }> = ({ onSuccess }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isLogin, setIsLogin] = useState(true);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setTimeout(() => {
// Mock login logic
const user = mockLogin(email);
setIsLoading(false);
onSuccess(user);
}, 1000);
};
return (
Deploy Unlimited
AI Agents Instantly
Create, manage, and scale a workforce of intelligent agents powered by Gemini. Automate support, research, and analysis with a single click.
AI Engine Ready
{isLogin ? 'Welcome Back' : 'Create Account'}
Enter your credentials to access your dashboard.
Or continue with
{isLogin ? "Don't have an account? " : "Already have an account? "}
{/* VSCode-like Tab Bar */}
);
};
// --- Agent Editor Component ---
const AgentEditor: React.FC<{
agent?: Agent | null,
onSave: (agent: Agent, keepOpen?: boolean) => void,
onCancel: () => void
}> = ({ agent, onSave, onCancel }) => {
const [name, setName] = useState(agent?.name || '');
const [role, setRole] = useState(agent?.role || '');
const [desc, setDesc] = useState(agent?.description || '');
const [instructions, setInstructions] = useState(agent?.systemInstruction || '');
const [model, setModel] = useState(agent?.model || 'gemini-2.5-flash');
// State to track last auto-save time
const [lastAutoSave, setLastAutoSave] = useState
system_instruction.md
{/* Highlight Layer */}
' : '') }}
/>
{/* Input Layer */}
{/* VSCode-like Status Bar */}
main
0
0
Ln {cursorStats.line}, Col {cursorStats.col}
UTF-8
Markdown
{agent ? 'Edit Agent' : 'Create New Agent'}
{lastAutoSave && ( Auto-saved at {lastAutoSave} )}
{/* Header */}
{/* Messages */}
);
};
// --- Dashboard Layout & Views ---
const Dashboard: React.FC<{ user: User, onLogout: () => void }> = ({ user, onLogout }) => {
const [activeTab, setActiveTab] = useState<'agents' | 'analytics' | 'billing' | 'admin'>('agents');
const [agents, setAgents] = useState
{agent.name[0]}
{agent.name}
{agent.role}
Powered by {agent.model}
{messages.map((msg) => (
))}
{isTyping && (
)}
{/* Input */}
{msg.text}
setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
disabled={isTyping}
/>
{/* Sidebar */}
{/* Main Content */}
{/* Mobile Header */}
AICore Hub
);
};
// --- Main App Component ---
const App: React.FC = () => {
const [user, setUser] = useState
{/* Agent Chat Modal Overlay */}
{selectedAgentForChat && (
)}
{activeTab === 'agents' && (
{isEditorOpen && (
{ setIsEditorOpen(false); setEditingAgent(null); }}
/>
)}
)}
{activeTab === 'analytics' && (
{/* Mock Chart Area */}
)}
{activeTab === 'billing' && (
)}
{activeTab === 'admin' && user.role === UserRole.ADMIN && (
)}
Your Agents
Manage and deploy your intelligent workforce.
{agents.map(agent => (
))}
{agents.length === 0 && !isEditorOpen && (
)}
{agent.name[0]}
{agent.name}
{agent.role}
{agent.description}
{agent.model.replace('gemini-', '')}
No agents created yet.
Create your first agent to get started.
Usage Analytics
Total Tokens
{stats.totalTokens.toLocaleString()}
Messages
{stats.messagesExchanged.toLocaleString()}
Active Agents
{stats.activeAgents}
Est. Cost
${stats.costEstimate}
Visualization libraries (Recharts/D3) would render detailed usage graphs here.