| 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;
|
|
|
|
|
| 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>
|
| );
|
| }
|
|
|