HackingFactory-v2 / client /src /pages /ChatInterface.tsx
FECUOY's picture
Initial commit: HackingFactory v2 Enhanced with Self-Refining AI features
4c41b3d
import React, { useState, useRef, useEffect } from "react";
import { trpc } from "@/lib/trpc";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Copy, Download, Send, Bot, User, Loader2 } from "lucide-react";
import { toast } from "sonner";
interface Message {
id: string;
role: "user" | "assistant";
content: string;
timestamp: string;
mode?: string;
}
export default function ChatInterface() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [mode, setMode] = useState("auto");
const [contentType, setContentType] = useState("code");
const [isLoading, setIsLoading] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const sendMutation = trpc.chat.sendMessage.useMutation({
onSuccess: (data) => {
setMessages((prev) => [
...prev,
{
id: Date.now().toString(),
role: "assistant",
content: data.content,
timestamp: new Date().toLocaleTimeString(),
mode: data.mode,
},
]);
setIsLoading(false);
},
onError: (error) => {
toast.error("Failed to send message: " + error.message);
setIsLoading(false);
},
});
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSend = async () => {
if (!input.trim()) return;
const userMessage: Message = {
id: Date.now().toString(),
role: "user",
content: input,
timestamp: new Date().toLocaleTimeString(),
};
setMessages((prev) => [...prev, userMessage]);
setInput("");
setIsLoading(true);
await sendMutation.mutateAsync({
message: input,
mode: mode as any,
contentType: contentType as any,
});
};
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
toast.success("Copied to clipboard!");
};
return (
<div className="min-h-screen bg-black text-white">
{/* Header */}
<header className="border-b border-cyan-500/20 bg-black/50 backdrop-blur-sm sticky top-0 z-40">
<div className="container mx-auto px-4 py-4">
<h1 className="text-2xl font-bold bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
AI Code Generator & Evaluator
</h1>
</div>
</header>
<main className="container mx-auto px-4 py-6">
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Chat Area */}
<div className="lg:col-span-3 space-y-4">
{/* Messages */}
<Card className="bg-gray-900/50 border-cyan-500/20 flex flex-col h-96">
<CardContent className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 ? (
<div className="h-full flex items-center justify-center text-gray-500">
<div className="text-center">
<Bot size={48} className="mx-auto mb-4 opacity-50" />
<p>Start a conversation with the AI</p>
</div>
</div>
) : (
messages.map((msg) => (
<div
key={msg.id}
className={`flex gap-3 ${msg.role === "user" ? "justify-end" : "justify-start"}`}
>
{msg.role === "assistant" && (
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-cyan-400 to-blue-500 flex items-center justify-center flex-shrink-0">
<Bot size={16} className="text-black" />
</div>
)}
<div
className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${
msg.role === "user"
? "bg-green-500/20 border border-green-500/50 text-green-100"
: "bg-cyan-500/20 border border-cyan-500/50 text-cyan-100"
}`}
>
<p className="text-sm break-words">{msg.content}</p>
{msg.role === "assistant" && (
<div className="flex gap-2 mt-2">
<Button
size="sm"
variant="ghost"
className="h-6 px-2 text-xs"
onClick={() => copyToClipboard(msg.content)}
>
<Copy size={12} />
</Button>
<Button size="sm" variant="ghost" className="h-6 px-2 text-xs">
<Download size={12} />
</Button>
</div>
)}
</div>
{msg.role === "user" && (
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-green-400 to-emerald-500 flex items-center justify-center flex-shrink-0">
<User size={16} className="text-black" />
</div>
)}
</div>
))
)}
{isLoading && (
<div className="flex gap-3">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-cyan-400 to-blue-500 flex items-center justify-center">
<Loader2 size={16} className="text-black animate-spin" />
</div>
<div className="bg-cyan-500/20 border border-cyan-500/50 px-4 py-2 rounded-lg">
<p className="text-sm text-cyan-100">Processing...</p>
</div>
</div>
)}
<div ref={messagesEndRef} />
</CardContent>
</Card>
{/* Input Area */}
<Card className="bg-gray-900/50 border-cyan-500/20">
<CardContent className="p-4 space-y-4">
<Textarea
placeholder="Enter your code generation or evaluation request..."
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && e.ctrlKey) {
handleSend();
}
}}
className="bg-black/50 border-green-500/20 text-white placeholder-gray-500 focus:border-green-500/50 min-h-24"
/>
<div className="flex gap-2">
<Button
onClick={handleSend}
disabled={isLoading || !input.trim()}
className="bg-gradient-to-r from-cyan-500 to-blue-500 text-black hover:from-cyan-600 hover:to-blue-600 flex-1"
>
<Send size={18} className="mr-2" />
Send
</Button>
</div>
</CardContent>
</Card>
</div>
{/* Sidebar */}
<div className="space-y-4">
{/* Mode Selection */}
<Card className="bg-gray-900/50 border-green-500/20">
<CardHeader>
<CardTitle className="text-green-400 text-sm">Mode</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<Select value={mode} onValueChange={setMode}>
<SelectTrigger className="bg-black/50 border-green-500/20 text-white">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-900 border-green-500/20">
<SelectItem value="auto">Auto</SelectItem>
<SelectItem value="qwen">Qwen (Generate)</SelectItem>
<SelectItem value="deepseek">DeepSeek (Evaluate)</SelectItem>
<SelectItem value="loop">Loop (Iterative)</SelectItem>
</SelectContent>
</Select>
</CardContent>
</Card>
{/* Content Type */}
<Card className="bg-gray-900/50 border-purple-500/20">
<CardHeader>
<CardTitle className="text-purple-400 text-sm">Content Type</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<Select value={contentType} onValueChange={setContentType}>
<SelectTrigger className="bg-black/50 border-purple-500/20 text-white">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-gray-900 border-purple-500/20">
<SelectItem value="code">Code</SelectItem>
<SelectItem value="exploit">Exploit</SelectItem>
<SelectItem value="payload">Payload</SelectItem>
<SelectItem value="information">Information</SelectItem>
<SelectItem value="strategy">Strategy</SelectItem>
</SelectContent>
</Select>
</CardContent>
</Card>
{/* Stats */}
<Card className="bg-gray-900/50 border-orange-500/20">
<CardHeader>
<CardTitle className="text-orange-400 text-sm">Session Stats</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-400">Messages:</span>
<span className="text-orange-400 font-bold">{messages.length}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Mode:</span>
<Badge className="bg-green-500/20 text-green-400">{mode}</Badge>
</div>
</CardContent>
</Card>
</div>
</div>
</main>
</div>
);
}