aethex-studio/src/components/ConsolePanel.tsx
Claude 2d7d88fbc6
Add comprehensive interactive terminal and CLI system
New Features:
- Interactive Terminal Component (InteractiveTerminal.tsx)
  - Full command input with auto-completion
  - Command history navigation (up/down arrows)
  - Real-time suggestions
  - Auto-scroll and focus management

- CLI Command System (cli-commands.ts)
  - help: Display available commands
  - clear/cls: Clear terminal
  - run/execute: Execute Lua scripts
  - check/lint: Syntax validation
  - count: Line/word/character counting
  - api: Roblox API documentation lookup
  - template: Template management
  - export: Export scripts to file
  - echo: Print text
  - info: System information

- Enhanced ConsolePanel
  - New "Terminal" tab with interactive CLI
  - Existing log tabs (All, Roblox, Web, Mobile)
  - Props for code context and modification
  - Integrated with file system

- Keyboard Shortcuts
  - Ctrl/Cmd + ` : Toggle terminal (VS Code style)

Technical Details:
- Command execution with context awareness
- Auto-completion for commands
- Command aliases support
- Error handling and user feedback
- History management with localStorage-ready structure
- Integration with existing code editor
2026-01-17 22:37:37 +00:00

215 lines
7.2 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from '@/components/ui/badge';
import { Trash, Terminal, Code } from '@phosphor-icons/react';
import { InteractiveTerminal } from './InteractiveTerminal';
interface ConsoleLog {
id: string;
timestamp: Date;
type: 'log' | 'warn' | 'error' | 'info';
platform: 'roblox' | 'web' | 'mobile' | 'system';
message: string;
}
interface ConsolePanelProps {
collapsed?: boolean;
onToggle?: () => void;
currentCode?: string;
currentFile?: string;
files?: any[];
onCodeChange?: (code: string) => void;
}
export function ConsolePanel({ collapsed, onToggle, currentCode = '', currentFile, files = [], onCodeChange }: ConsolePanelProps) {
const [logs, setLogs] = useState<ConsoleLog[]>([
{
id: '1',
timestamp: new Date(),
type: 'info',
platform: 'system',
message: 'AeThex Studio Console initialized',
},
{
id: '2',
timestamp: new Date(),
type: 'log',
platform: 'roblox',
message: 'Player joined the game!',
},
]);
const autoScrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (autoScrollRef.current) {
autoScrollRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [logs]);
const clearLogs = () => {
setLogs([
{
id: Date.now().toString(),
timestamp: new Date(),
type: 'info',
platform: 'system',
message: 'Console cleared',
},
]);
};
const getLogColor = (type: ConsoleLog['type']) => {
switch (type) {
case 'error':
return 'text-red-400';
case 'warn':
return 'text-yellow-400';
case 'info':
return 'text-blue-400';
default:
return 'text-foreground';
}
};
const getPlatformBadgeColor = (platform: ConsoleLog['platform']) => {
switch (platform) {
case 'roblox':
return 'bg-blue-500/20 text-blue-400 border-blue-500/30';
case 'web':
return 'bg-green-500/20 text-green-400 border-green-500/30';
case 'mobile':
return 'bg-purple-500/20 text-purple-400 border-purple-500/30';
default:
return 'bg-muted/50 text-muted-foreground';
}
};
if (collapsed) {
return (
<div className="h-8 bg-card border-t border-border flex items-center justify-between px-4 cursor-pointer" onClick={onToggle}>
<div className="flex items-center gap-2">
<Terminal size={16} />
<span className="text-sm font-semibold">Console</span>
<Badge variant="secondary" className="text-xs">
{logs.length}
</Badge>
</div>
</div>
);
}
return (
<div className="h-[200px] bg-card border-t border-border flex flex-col">
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
<div className="flex items-center gap-2">
<Terminal size={18} />
<span className="text-sm font-semibold">Console</span>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="sm" onClick={clearLogs}>
<Trash size={16} />
</Button>
</div>
</div>
<Tabs defaultValue="terminal" className="flex-1 flex flex-col">
<TabsList className="mx-4 mt-2 h-8 w-fit">
<TabsTrigger value="terminal" className="text-xs flex items-center gap-1">
<Terminal size={12} />
Terminal
</TabsTrigger>
<TabsTrigger value="all" className="text-xs flex items-center gap-1">
<Code size={12} />
Logs
</TabsTrigger>
<TabsTrigger value="roblox" className="text-xs">Roblox</TabsTrigger>
<TabsTrigger value="web" className="text-xs">Web</TabsTrigger>
<TabsTrigger value="mobile" className="text-xs">Mobile</TabsTrigger>
</TabsList>
<TabsContent value="terminal" className="flex-1 m-0">
<InteractiveTerminal
currentCode={currentCode}
currentFile={currentFile}
files={files}
onCodeChange={onCodeChange}
/>
</TabsContent>
<TabsContent value="all" className="flex-1 m-0">
<ScrollArea className="h-[140px]">
<div className="px-4 py-2 space-y-1 font-mono text-xs">
{logs.map((log) => (
<div key={log.id} className="flex items-start gap-2 py-1">
<span className="text-muted-foreground flex-shrink-0">
{log.timestamp.toLocaleTimeString()}
</span>
<Badge variant="outline" className={`flex-shrink-0 text-xs ${getPlatformBadgeColor(log.platform)}`}>
{log.platform}
</Badge>
<span className={`${getLogColor(log.type)} flex-1`}>
{log.message}
</span>
</div>
))}
<div ref={autoScrollRef} />
</div>
</ScrollArea>
</TabsContent>
<TabsContent value="roblox" className="flex-1 m-0">
<ScrollArea className="h-[140px]">
<div className="px-4 py-2 space-y-1 font-mono text-xs">
{logs.filter((log) => log.platform === 'roblox').map((log) => (
<div key={log.id} className="flex items-start gap-2 py-1">
<span className="text-muted-foreground flex-shrink-0">
{log.timestamp.toLocaleTimeString()}
</span>
<span className={getLogColor(log.type)}>
{log.message}
</span>
</div>
))}
</div>
</ScrollArea>
</TabsContent>
<TabsContent value="web" className="flex-1 m-0">
<ScrollArea className="h-[140px]">
<div className="px-4 py-2 space-y-1 font-mono text-xs">
{logs.filter((log) => log.platform === 'web').map((log) => (
<div key={log.id} className="flex items-start gap-2 py-1">
<span className="text-muted-foreground flex-shrink-0">
{log.timestamp.toLocaleTimeString()}
</span>
<span className={getLogColor(log.type)}>
{log.message}
</span>
</div>
))}
</div>
</ScrollArea>
</TabsContent>
<TabsContent value="mobile" className="flex-1 m-0">
<ScrollArea className="h-[140px]">
<div className="px-4 py-2 space-y-1 font-mono text-xs">
{logs.filter((log) => log.platform === 'mobile').map((log) => (
<div key={log.id} className="flex items-start gap-2 py-1">
<span className="text-muted-foreground flex-shrink-0">
{log.timestamp.toLocaleTimeString()}
</span>
<span className={getLogColor(log.type)}>
{log.message}
</span>
</div>
))}
</div>
</ScrollArea>
</TabsContent>
</Tabs>
</div>
);
}