Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import requests | |
| import json | |
| # === CONFIG === | |
| API_KEYS = [ | |
| "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", | |
| "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", | |
| "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", | |
| "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", | |
| "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", | |
| "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" | |
| ] | |
| OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions" | |
| MODEL = "deepseek/deepseek-chat-v3-0324:free" | |
| DEFAULT_SYSTEM_PROMPT = "You are a highly skilled, professional AI web designer tasked with building full static websites that are modern, clean, responsive, and production-ready. You must generate only a single .html file that includes all HTML, CSS, and JavaScript inline — no external stylesheets or scripts, and no use of frameworks such as Bootstrap or Tailwind. However, you may include external fonts (e.g., from Google Fonts) or icon libraries (e.g., Font Awesome) by linking them within the <head> section. Begin each project with a meticulously structured <head> that includes essential meta tags (UTF-8 charset, viewport for responsiveness), a meaningful <title> representing a fictional brand or company, links to one or two modern web fonts, and an icon library if needed. The <body> must contain a complete, fully responsive, elegantly designed layout using semantic HTML5 tags like <header>, <main>, <section>, and <footer>. Build a sticky, flex-based header with a brand logo and 4–6 navigation links, converting to a hamburger menu on mobile via JavaScript. Include an impactful hero section with a centered headline, subtext, and CTA button, using a high-contrast background image, gradient, or pattern. Add a feature section with 3–6 interactive icon-based cards, a detailed alternating services section using Flexbox or Grid, and scroll-triggered animations with IntersectionObserver. Follow with a testimonials carousel (no external libraries) featuring real-looking quotes, user images, names, and star ratings. Include a strong call-to-action section and a fully functional contact form with client-side JavaScript validation and dynamic “Thank You” message insertion. Optionally, include a stylish newsletter form. Finish with a structured footer using Grid or Flexbox, social icons, quick links, and JavaScript to dynamically show the current year. Define a color system using :root CSS variables and apply consistent, modern typography and spacing (8px/10px base). All sections, classes, and IDs must be unique and descriptive. All JavaScript must be clean, documented, and included in a single <script> tag at the end of the file — used for menu toggling, form validation, carousels, animations, and year updates. Always generate the entire .html file, even if the user asks to update only a selected part — never generate partial snippets. You should always provide the full code, eg. if a user requests to change a part use should give the full code for the website with the changes that user asks for. The final output must be fully functional, professionally structured, visually refined, and ready for deployment — as if handcrafted by a senior frontend developer. also try to give the code without any errors like the footer is messed up, etc." | |
| MAX_TOKENS = 100000 | |
| TEMPERATURE = 0.3 | |
| TOP_P = 1.0 | |
| chat_history = [] | |
| # === STREAMING FUNCTION === | |
| def stream_openrouter_response(messages): | |
| headers_base = { | |
| "Content-Type": "application/json", | |
| "HTTP-Referer": "https://your-domain.com", | |
| "X-Title": "startup-roadmap-chatbot" | |
| } | |
| payload = { | |
| "model": MODEL, | |
| "messages": messages, | |
| "max_tokens": MAX_TOKENS, | |
| "temperature": TEMPERATURE, | |
| "top_p": TOP_P, | |
| "stream": True | |
| } | |
| for key in API_KEYS: | |
| headers = {**headers_base, "Authorization": f"Bearer {key}"} | |
| try: | |
| response = requests.post(OPENROUTER_URL, headers=headers, json=payload, stream=True, timeout=60) | |
| if response.status_code == 200: | |
| full_message = "" | |
| for line in response.iter_lines(): | |
| if not line: | |
| continue | |
| line = line.decode("utf-8").strip() | |
| if line == "data: [DONE]": | |
| break | |
| if line.startswith("data: "): | |
| line = line[len("data: "):] | |
| try: | |
| content = json.loads(line) | |
| delta = content["choices"][0]["delta"] | |
| if "content" in delta: | |
| token = delta["content"] | |
| full_message += token | |
| yield full_message | |
| except: | |
| continue | |
| return | |
| except Exception as e: | |
| print(f"API key failed: {e}") | |
| continue | |
| yield "⚠️ All API keys failed. Please try again later." | |
| # === MAIN RESPONSE FUNCTION === | |
| def respond(message, history): | |
| global chat_history | |
| messages = [{"role": "system", "content": DEFAULT_SYSTEM_PROMPT}] | |
| for u, a in history: | |
| messages.append({"role": "user", "content": u}) | |
| messages.append({"role": "assistant", "content": a}) | |
| messages.append({"role": "user", "content": message}) | |
| yield "🤖 Thinking..." | |
| full = "" | |
| for chunk in stream_openrouter_response(messages): | |
| full = chunk | |
| yield full | |
| chat_history = history + [(message, full)] | |
| # === WARNING ON UNLOAD === | |
| js_warning = """ | |
| <script> | |
| window.onbeforeunload = function () { | |
| return "Your chat data will be lost if you leave or reload this page."; | |
| }; | |
| </script> | |
| """ | |
| # === MIC INPUT HTML === | |
| mic_html = """ | |
| <style> | |
| .bottom-controls { | |
| display: flex; | |
| justify-content: center; | |
| gap: 12px; | |
| padding: 8px 0 4px; | |
| flex-wrap: wrap; | |
| margin-top: -8px; | |
| } | |
| /* Desktop-specific fix: Restore spacing for large screens */ | |
| @media screen and (min-width: 1024px) { | |
| .bottom-controls { | |
| padding: 16px 0; | |
| margin-top: 0; | |
| } | |
| } | |
| .mic-box { | |
| background: #1f2937; | |
| border: 2px solid #374151; | |
| border-radius: 12px; | |
| height: 48px; | |
| display: flex; | |
| align-items: center; | |
| box-sizing: border-box; | |
| overflow: hidden; | |
| min-width: 260px; | |
| position: relative; | |
| } | |
| .btn { | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| height: 100%; | |
| border: none; | |
| border-radius: 12px; | |
| background: #374151; | |
| color: white; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 16px; | |
| z-index: 1; | |
| transition: background 0.3s ease; | |
| } | |
| .btn:hover { | |
| background: #111827; | |
| } | |
| .circle-btn { | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 50%; | |
| background: #374151; | |
| border: none; | |
| display: none; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| margin: 0 8px; | |
| z-index: 2; | |
| } | |
| .circle-btn img { | |
| width: 20px; | |
| height: 20px; | |
| filter: invert(100%) brightness(200%); | |
| } | |
| .wave { | |
| flex: 1; | |
| height: 20px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| visibility: hidden; | |
| z-index: 2; | |
| } | |
| .mic-box.active .wave { | |
| visibility: visible; | |
| } | |
| .bar { | |
| width: 4px; | |
| height: 100%; | |
| margin: 0 2px; | |
| background: white; | |
| animation: pulse 1.2s infinite ease-in-out; | |
| } | |
| .bar:nth-child(1){animation-delay:-0.4s;} | |
| .bar:nth-child(2){animation-delay:-0.2s;} | |
| .bar:nth-child(3){animation-delay:0s;} | |
| .bar:nth-child(4){animation-delay:-0.2s;} | |
| .bar:nth-child(5){animation-delay:-0.4s;} | |
| @keyframes pulse { | |
| 0%,100%{transform:scaleY(0.5);} | |
| 50%{transform:scaleY(1.2);} | |
| } | |
| .mic-box.active .circle-btn { | |
| display: flex; | |
| } | |
| .mic-box.active #mic-btn-label { | |
| visibility: hidden; | |
| } | |
| #previewBtn { | |
| background: #2563eb; | |
| color: white; | |
| font-size: 16px; | |
| cursor: pointer; | |
| transition: background 0.3s ease; | |
| border: none; | |
| padding: 0 20px; | |
| height: 48px; | |
| border-radius: 12px; | |
| } | |
| #previewBtn:hover { | |
| background: #1d4ed8; | |
| } | |
| </style> | |
| <div class="bottom-controls"> | |
| <div class="mic-box" id="mic-box"> | |
| <button class="circle-btn" id="cancel-btn" title="Cancel"> | |
| <img src="https://cdn-icons-png.flaticon.com/512/1828/1828665.png"/> | |
| </button> | |
| <div class="wave" id="wave"> | |
| <div class="bar"></div><div class="bar"></div><div class="bar"></div> | |
| <div class="bar"></div><div class="bar"></div> | |
| </div> | |
| <button class="circle-btn" id="submit-btn" title="Submit"> | |
| <img src="https://cdn-icons-png.flaticon.com/512/190/190411.png"/> | |
| </button> | |
| <button class="btn" id="mic-btn"> | |
| <span id="mic-btn-label">🎤 Talk to AI</span> | |
| </button> | |
| </div> | |
| <button id="previewBtn">🌐 Preview on OneCompiler</button> | |
| </div> | |
| <script> | |
| window.addEventListener('load', ()=>{ | |
| let recognition, transcript = ''; | |
| const box = document.getElementById('mic-box'); | |
| const mic = document.getElementById('mic-btn'); | |
| const cancel = document.getElementById('cancel-btn'); | |
| const tick = document.getElementById('submit-btn'); | |
| function resetUI(){ | |
| box.classList.remove('active'); | |
| tick.style.display = "none"; | |
| transcript = ''; | |
| } | |
| function fillAndSend(text){ | |
| const ta = document.querySelector('textarea'); | |
| if(!ta) return; | |
| ta.value = text.trim(); | |
| ta.dispatchEvent(new Event('input',{bubbles:true})); | |
| ['keydown','keypress','keyup'].forEach(type=>{ | |
| ta.dispatchEvent(new KeyboardEvent(type,{ | |
| key:'Enter',code:'Enter',keyCode:13,which:13,bubbles:true | |
| })); | |
| }); | |
| } | |
| if('webkitSpeechRecognition' in window){ | |
| recognition = new webkitSpeechRecognition(); | |
| recognition.continuous = false; | |
| recognition.interimResults = false; | |
| recognition.lang = 'en-US'; | |
| recognition.onresult = e => { | |
| transcript = e.results[0][0].transcript; | |
| }; | |
| recognition.onerror = () => { | |
| resetUI(); | |
| }; | |
| recognition.onend = () => { | |
| // Wait 2 seconds after speech ends | |
| if (transcript.trim()) { | |
| setTimeout(() => { | |
| tick.style.display = "flex"; | |
| }, 2000); | |
| } else { | |
| resetUI(); | |
| } | |
| }; | |
| mic.onclick = ()=>{ | |
| transcript = ''; | |
| tick.style.display = "none"; | |
| box.classList.add('active'); | |
| recognition.start(); | |
| }; | |
| cancel.onclick = ()=>{ | |
| recognition.stop(); | |
| resetUI(); | |
| }; | |
| tick.onclick = ()=>{ | |
| recognition.stop(); | |
| if(transcript.trim()) fillAndSend(transcript); | |
| resetUI(); | |
| }; | |
| } | |
| const previewBtn = document.getElementById("previewBtn"); | |
| previewBtn.addEventListener("click", function () { | |
| const messages = Array.from(document.querySelectorAll('[data-testid="bot"]')); | |
| if (messages.length === 0) { | |
| alert("❗ No code available yet."); | |
| return; | |
| } | |
| const lastMsg = messages[messages.length - 1]; | |
| const codeBlocks = lastMsg.querySelectorAll('pre code'); | |
| if (codeBlocks.length === 0) { | |
| alert("❗ No code block found in the latest message."); | |
| return; | |
| } | |
| let latestCode = ""; | |
| codeBlocks.forEach(cb => { | |
| latestCode += cb.innerText + "\\n\\n"; | |
| }); | |
| navigator.clipboard.writeText(latestCode.trim()).then(() => { | |
| window.open("https://onecompiler.com/html", "_blank"); | |
| }).catch((err) => { | |
| alert("❌ Failed to copy code to clipboard."); | |
| console.error(err); | |
| }); | |
| }); | |
| }); | |
| </script> | |
| """ | |
| # === GRADIO UI === | |
| chatbot = gr.ChatInterface(fn=respond, title="", theme="soft") | |
| with gr.Blocks(theme="soft") as app: | |
| gr.HTML(js_warning) | |
| chatbot.render() | |
| gr.HTML(""" | |
| <style> | |
| html, body { | |
| margin: 0; | |
| padding: 0; | |
| height: 100%; | |
| overflow: auto; /* Allow vertical scrolling */ | |
| } | |
| .gradio-container, body > div:first-child { | |
| min-height: 100vh !important; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .gr-chat-interface { | |
| flex: 1 1 auto !important; | |
| display: flex; | |
| flex-direction: column; | |
| height: 100%; | |
| } | |
| .gr-chatbot { | |
| flex: 1 1 auto !important; | |
| overflow-y: auto !important; | |
| padding: 16px; | |
| max-width: 960px; | |
| margin: 0 auto; | |
| width: 100%; | |
| box-sizing: border-box; | |
| } | |
| .bottom-controls { | |
| flex-shrink: 0; | |
| width: 100%; | |
| max-width: 960px; | |
| margin: 0 auto; | |
| padding: 12px 16px; | |
| box-sizing: border-box; | |
| display: flex; | |
| justify-content: center; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| background-color: #0f172a; | |
| z-index: 10; | |
| } | |
| .gr-chat-interface .wrap { | |
| position: relative; | |
| bottom: 0; | |
| padding: 0 16px 16px; | |
| flex-shrink: 0; | |
| } | |
| footer, .gr-footer { | |
| display: none !important; | |
| } | |
| @media screen and (min-width: 768px) { | |
| .bottom-controls { | |
| padding: 12px 16px 6px; | |
| } | |
| } | |
| </style> | |
| """) | |
| gr.HTML(mic_html) | |
| # === APP LAUNCH === | |
| if __name__ == "__main__": | |
| app.launch() |