Spaces:
Sleeping
Sleeping
| 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> | |
| ); | |
| } | |