director-ai / client /src /components /AITerminal.tsx
algorembrant's picture
Upload 79 files
11f4e50 verified
import { useState, useRef, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { io, Socket } from 'socket.io-client';
import { motion } from 'framer-motion';
import { RootState } from '../store';
interface LogEntry {
type: 'info' | 'success' | 'warning' | 'error' | 'system';
message: string;
timestamp: string;
}
let socket: Socket | null = null;
export default function AITerminal() {
const { token } = useSelector((state: RootState) => state.auth);
const [logs, setLogs] = useState<LogEntry[]>([
{
type: 'system',
message: 'Workspace Activity Monitor',
timestamp: new Date().toLocaleTimeString(),
},
{
type: 'info',
message: 'Waiting for Google Antigravity CLI to connect and perform actions...',
timestamp: new Date().toLocaleTimeString(),
},
]);
const bottomRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!token) return;
// Connect as a Browser viewport
socket = io('http://localhost:5000/browser', {
auth: { token },
});
socket.on('connect', () => {
addLog('system', 'Browser viewport connected to Workspace Hub');
});
socket.on('disconnect', () => {
addLog('error', 'Disconnected from Workspace Hub');
});
socket.on('agent:activity_log', (data: { message: string, type: 'info' | 'success' | 'warning' | 'error', timestamp: string }) => {
setLogs(prev => [...prev, {
type: data.type,
message: data.message,
timestamp: new Date(data.timestamp).toLocaleTimeString(),
}]);
});
return () => {
socket?.disconnect();
socket = null;
};
}, [token]);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [logs]);
function addLog(type: LogEntry['type'], message: string) {
setLogs((prev) => [...prev, {
type,
message,
timestamp: new Date().toLocaleTimeString(),
}]);
}
function getTextColor(type: LogEntry['type']) {
switch (type) {
case 'info': return 'text-light-400';
case 'success': return 'text-green-400';
case 'warning': return 'text-gold-400';
case 'error': return 'text-red-400';
case 'system': return 'text-blue-400';
default: return 'text-light-400';
}
}
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="terminal-bg overflow-hidden border border-dark-400/30 rounded-xl"
>
<div className="flex items-center justify-between px-4 py-2 bg-dark-800 border-b border-dark-400/30">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-blue-500 animate-pulse" />
<span className="text-xs text-light-500 font-mono tracking-wider">Agent Activity Log</span>
</div>
</div>
<div className="h-48 overflow-y-auto px-4 py-3 space-y-1">
{logs.map((log, i) => (
<div key={i} className={`font-mono text-xs leading-relaxed ${getTextColor(log.type)}`}>
<span className="text-dark-400 mr-2 select-none">[{log.timestamp}]</span>
<span className="break-words">{log.message}</span>
</div>
))}
<div ref={bottomRef} />
</div>
</motion.div>
);
}