Spaces:
Running
Running
File size: 8,335 Bytes
7ac2545 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | 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;
|