Spaces:
Running
Running
| import { useState, useRef, useEffect, useCallback } from "react"; | |
| import { Send, RotateCcw, Zap } from "lucide-react"; | |
| import { useLLM } from "./hooks/useLLM"; | |
| import { LoadingScreen } from "./components/LoadingScreen"; | |
| import { ChatMessage } from "./components/ChatMessage"; | |
| interface Message { | |
| role: "user" | "assistant"; | |
| content: string; | |
| } | |
| const SYSTEM_MESSAGE = { | |
| role: "system", | |
| content: | |
| "You are Maincoder, an expert code generation assistant. Write clean, well-structured code. When responding with code, use markdown code blocks with the appropriate language identifier.", | |
| }; | |
| const EXAMPLES = [ | |
| { | |
| icon: "🐍", | |
| text: "Binary search in Python", | |
| message: "Write a binary search function in Python", | |
| }, | |
| { | |
| icon: "⚡", | |
| text: "Fibonacci with memoization", | |
| message: "Write a fibonacci function with memoization in JavaScript", | |
| }, | |
| ]; | |
| const App = () => { | |
| const [messages, setMessages] = useState<Message[]>([]); | |
| const [input, setInput] = useState(""); | |
| const [isGenerating, setIsGenerating] = useState(false); | |
| const chatRef = useRef<HTMLDivElement>(null); | |
| const inputRef = useRef<HTMLInputElement>(null); | |
| const { isLoading, isReady, error, progress, loadModel, generateResponse, clearHistory } = | |
| useLLM(); | |
| useEffect(() => { | |
| if (chatRef.current) { | |
| chatRef.current.scrollTop = chatRef.current.scrollHeight; | |
| } | |
| }, [messages]); | |
| const clearChat = useCallback(() => { | |
| setMessages([]); | |
| clearHistory(); | |
| }, [clearHistory]); | |
| const sendMessage = useCallback( | |
| async (text: string) => { | |
| if (!text.trim() || !isReady || isGenerating) return; | |
| const userMessage: Message = { role: "user", content: text }; | |
| const currentMessages = [...messages, userMessage]; | |
| setMessages(currentMessages); | |
| setInput(""); | |
| setIsGenerating(true); | |
| try { | |
| const messagesForModel = [ | |
| SYSTEM_MESSAGE, | |
| ...currentMessages.map((m) => ({ role: m.role, content: m.content })), | |
| ]; | |
| setMessages([...currentMessages, { role: "assistant", content: "" }]); | |
| let accumulated = ""; | |
| const response = await generateResponse( | |
| messagesForModel, | |
| (token: string) => { | |
| accumulated += token; | |
| setMessages((prev) => { | |
| const updated = [...prev]; | |
| updated[updated.length - 1] = { | |
| role: "assistant", | |
| content: accumulated, | |
| }; | |
| return updated; | |
| }); | |
| }, | |
| ); | |
| setMessages([ | |
| ...currentMessages, | |
| { role: "assistant", content: response }, | |
| ]); | |
| } catch (err) { | |
| const errorMsg = | |
| err instanceof Error ? err.message : "Generation failed"; | |
| setMessages([ | |
| ...currentMessages, | |
| { role: "assistant", content: `Error: ${errorMsg}` }, | |
| ]); | |
| } finally { | |
| setIsGenerating(false); | |
| setTimeout(() => inputRef.current?.focus(), 0); | |
| } | |
| }, | |
| [messages, isReady, isGenerating, generateResponse], | |
| ); | |
| if (!isReady) { | |
| return ( | |
| <LoadingScreen | |
| isLoading={isLoading} | |
| progress={progress} | |
| error={error} | |
| onLoad={loadModel} | |
| /> | |
| ); | |
| } | |
| return ( | |
| <div className="font-sans min-h-screen bg-gradient-to-br from-[#0a0f1a] via-[#0d1520] to-[#060a12] text-gray-100"> | |
| <div className="flex h-screen w-full py-6 px-4 md:py-10 md:px-8"> | |
| <div className="flex-1 flex flex-col p-6 bg-white/5 backdrop-blur-lg border border-white/10 rounded-3xl shadow-[0_35px_65px_rgba(0,0,0,0.5)] min-h-0"> | |
| {/* Header */} | |
| <div className="flex items-center justify-between mb-6"> | |
| <div className="space-y-1"> | |
| <div className="flex items-center gap-2"> | |
| <Zap size={16} className="text-emerald-400" /> | |
| <span className="text-xs font-semibold uppercase tracking-[0.35em] text-emerald-400"> | |
| WebGPU | |
| </span> | |
| </div> | |
| <h1 className="text-2xl md:text-3xl font-bold text-white"> | |
| Maincoder <span className="text-emerald-400">1B</span> | |
| </h1> | |
| </div> | |
| <button | |
| disabled={isGenerating} | |
| onClick={clearChat} | |
| className={`h-10 flex items-center px-4 rounded-full font-semibold text-sm transition-all border ${ | |
| isGenerating | |
| ? "border-white/10 bg-white/5 text-gray-500 cursor-not-allowed" | |
| : "border-white/15 bg-white/8 text-gray-300 hover:border-emerald-500/40 hover:bg-emerald-500/10" | |
| }`} | |
| > | |
| <RotateCcw size={14} className="mr-2" /> New Chat | |
| </button> | |
| </div> | |
| {/* Chat Area */} | |
| <div | |
| ref={chatRef} | |
| className="flex-grow bg-[#080d16]/80 border border-white/10 rounded-2xl p-6 overflow-y-auto mb-6 space-y-5 shadow-inner min-h-0" | |
| > | |
| {messages.length === 0 ? ( | |
| <div className="flex flex-col items-center justify-center h-full space-y-6"> | |
| <div className="text-center mb-4"> | |
| <h2 className="text-2xl font-semibold text-white mb-1"> | |
| What would you like to code? | |
| </h2> | |
| <p className="text-sm text-gray-400"> | |
| Try an example or type your own prompt | |
| </p> | |
| </div> | |
| <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 max-w-2xl w-full px-4"> | |
| {EXAMPLES.map((example, i) => ( | |
| <button | |
| key={i} | |
| onClick={() => sendMessage(example.message)} | |
| className="group relative overflow-hidden rounded-2xl border border-white/10 bg-white/5 text-left transition-all hover:-translate-y-0.5 hover:shadow-[0_20px_50px_rgba(16,185,129,0.15)]" | |
| > | |
| <span className="pointer-events-none absolute inset-0 bg-gradient-to-r from-emerald-500/15 via-transparent to-transparent opacity-0 transition-opacity group-hover:opacity-100" /> | |
| <div className="relative flex items-center gap-3 px-4 py-3.5"> | |
| <span className="flex size-10 flex-shrink-0 items-center justify-center rounded-full border border-emerald-500/20 bg-emerald-500/10 text-lg"> | |
| {example.icon} | |
| </span> | |
| <span className="text-sm font-medium text-gray-200 group-hover:text-white transition-colors"> | |
| {example.text} | |
| </span> | |
| </div> | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| ) : ( | |
| messages.map((msg, i) => ( | |
| <ChatMessage key={i} role={msg.role} content={msg.content} /> | |
| )) | |
| )} | |
| </div> | |
| {/* Input */} | |
| <div className="flex items-center gap-3"> | |
| <div className="flex flex-1 items-center bg-white/5 border border-white/10 rounded-2xl overflow-hidden shadow-[0_15px_45px_rgba(0,0,0,0.35)]"> | |
| <input | |
| ref={inputRef} | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={(e) => | |
| e.key === "Enter" && !isGenerating && sendMessage(input) | |
| } | |
| disabled={isGenerating} | |
| className="flex-grow bg-transparent px-5 py-3.5 text-base text-white placeholder:text-gray-500 focus:outline-none disabled:opacity-40" | |
| placeholder="Ask Maincoder to write some code..." | |
| /> | |
| <button | |
| onClick={() => sendMessage(input)} | |
| disabled={isGenerating || !input.trim()} | |
| className="h-full px-5 py-3.5 bg-emerald-600 hover:bg-emerald-500 disabled:bg-emerald-600/30 disabled:cursor-not-allowed text-white font-semibold transition-all" | |
| > | |
| <Send size={20} /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default App; | |