| from fastapi import FastAPI |
| from fastapi.responses import HTMLResponse |
| from pydantic import BaseModel |
| from huggingface_hub import hf_hub_download |
| from llama_cpp import Llama |
|
|
| app = FastAPI() |
|
|
| |
| model_path = hf_hub_download( |
| repo_id="Arabic250/gemma-4-gguf-export", |
| filename="gemma-4-e4b.gguf" |
| ) |
|
|
| |
| llm = Llama( |
| model_path=model_path, |
| n_ctx=2048, |
| n_threads=4, |
| n_gpu_layers=0 |
| ) |
|
|
| class ChatRequest(BaseModel): |
| message: str |
|
|
| |
| HTML_CONTENT = """ |
| <!DOCTYPE html> |
| <html lang="ar" dir="rtl"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Gemma 4 Chat</title> |
| <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> |
| <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> |
| <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> |
| <script src="https://cdn.tailwindcss.com"></script> |
| </head> |
| <body class="bg-gray-100 p-4 font-sans"> |
| <div id="root"></div> |
| <script type="text/babel"> |
| function App() { |
| const [messages, setMessages] = React.useState([]); |
| const [input, setInput] = React.useState(""); |
| const [loading, setLoading] = React.useState(false); |
| |
| const sendMessage = async () => { |
| if (!input.trim()) return; |
| const newMessages = [...messages, { role: 'user', text: input }]; |
| setMessages(newMessages); |
| setInput(""); |
| setLoading(true); |
| |
| try { |
| const res = await fetch("/chat", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ message: input }) |
| }); |
| const data = await res.json(); |
| setMessages([...newMessages, { role: 'model', text: data.response }]); |
| } catch (error) { |
| console.error(error); |
| } |
| setLoading(false); |
| }; |
| |
| return ( |
| <div className="max-w-3xl mx-auto bg-white p-6 rounded-lg shadow-lg mt-10 border-t-4 border-blue-500"> |
| <h1 className="text-2xl font-bold mb-6 text-center text-gray-800">محادثة Gemma 4 (React)</h1> |
| <div className="h-[28rem] overflow-y-auto mb-4 p-4 border rounded bg-gray-50 flex flex-col gap-3"> |
| {messages.map((msg, idx) => ( |
| <div key={idx} className={`p-3 rounded-lg w-fit max-w-[80%] ${msg.role === 'user' ? 'bg-blue-100 self-start' : 'bg-green-100 self-end'}`}> |
| <strong className="block mb-1 text-xs text-gray-500">{msg.role === 'user' ? 'أنت' : 'النموذج'}</strong> |
| <span className="text-gray-800">{msg.text}</span> |
| </div> |
| ))} |
| {loading && <div className="text-gray-500 text-sm animate-pulse self-end">النموذج يقوم بالمعالجة الآن...</div>} |
| </div> |
| <div className="flex gap-2"> |
| <input |
| type="text" |
| value={input} |
| onChange={(e) => setInput(e.target.value)} |
| onKeyPress={(e) => e.key === 'Enter' && sendMessage()} |
| className="flex-1 p-3 border rounded focus:outline-none focus:ring-2 focus:ring-blue-400" |
| placeholder="اكتب استفسارك هنا..." |
| /> |
| <button onClick={sendMessage} disabled={loading} className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 disabled:opacity-50 transition-colors font-semibold"> |
| إرسال |
| </button> |
| </div> |
| </div> |
| ); |
| } |
| const root = ReactDOM.createRoot(document.getElementById('root')); |
| root.render(<App />); |
| </script> |
| </body> |
| </html> |
| """ |
|
|
| @app.get("/") |
| def read_root(): |
| return HTMLResponse(content=HTML_CONTENT) |
|
|
| @app.post("/chat") |
| def chat(request: ChatRequest): |
| |
| prompt = f"<start_of_turn>user\n{request.message}<end_of_turn>\n<start_of_turn>model\n" |
| |
| response = llm( |
| prompt, |
| max_tokens=512, |
| stop=["<end_of_turn>"] |
| ) |
| |
| return {"response": response['choices'][0]['text'].strip()} |