| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>Llama AI Chat</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); |
| |
| .typing-cursor:after { |
| content: ""; |
| display: inline-block; |
| width: 6px; |
| height: 1.1em; |
| margin-left: 4px; |
| background: #34C759; |
| animation: blink 1s steps(1, end) infinite; |
| vertical-align: bottom; |
| } |
| |
| @keyframes blink { |
| 50% { |
| opacity: 0; |
| } |
| } |
| </style> |
| </head> |
|
|
| <body> |
| <main class="min-h-screen flex items-center justify-center bg-[#2E865F] font-[Inter,sans-serif]"> |
| <div class="w-full max-w-md mx-auto bg-[#228B22] rounded-2xl shadow-2xl p-8"> |
| <div class="text-2xl font-bold text-[#34C759] mb-1 tracking-tight">Llama AI Chat</div> |
| <div class="text-base text-[#C6F4D6] mb-6">Ask anything about your project, estimate, or construction. |
| Fast, friendly, and private.</div> |
| <div id="llama-messages" role="log" aria-live="polite" |
| class="min-h-[180px] max-h-80 overflow-y-auto mb-5 flex flex-col gap-3"></div> |
| <form id="chat-form" class="flex gap-3"> |
| <input id="llama-user-input" type="text" autocomplete="off" placeholder="Type your message..." |
| class="flex-1 rounded-xl border border-[#34C759] bg-[#2E865F] text-white px-4 py-3 text-base focus:outline-none focus:border-[#C6F4D6] placeholder:text-[#C6F4D6]/60" /> |
| <button id="llama-send-btn" type="submit" disabled |
| class="rounded-xl bg-[#34C759] hover:bg-[#3E8E41] text-white font-bold px-6 py-3 text-base transition disabled:opacity-50">Send</button> |
| </form> |
| </div> |
| </main> |
| <script> |
| const API_URL = "https://llama-universal-netlify-project.netlify.app/.netlify/functions/llama-proxy?path=/chat/completions"; |
| const messagesDiv = document.getElementById('llama-messages'); |
| const chatForm = document.getElementById('chat-form'); |
| const userInput = document.getElementById('llama-user-input'); |
| const sendBtn = document.getElementById('llama-send-btn'); |
| function appendMessage(text, sender, streaming = false) { |
| const wrap = document.createElement('div'); |
| wrap.className = `px-4 py-3 rounded-xl text-base whitespace-pre-line ${sender === 'user' ? 'bg-[#3E8E41] border border-[#34C759] text-white self-end' : 'bg-[#2E865F] border border-[#34C759] text-[#C6F4D6]'} transition`; |
| if (streaming) wrap.classList.add('typing-cursor'); |
| messagesDiv.appendChild(wrap); |
| messagesDiv.scrollTop = messagesDiv.scrollHeight; |
| if (streaming) { |
| let i = 0; |
| const interval = setInterval(() => { |
| wrap.textContent = text.slice(0, i++); |
| if (i > text.length) { |
| clearInterval(interval); |
| wrap.classList.remove('typing-cursor'); |
| } |
| }, 18); |
| } else wrap.textContent = text; |
| } |
| async function sendMessage() { |
| const text = userInput.value.trim(); |
| if (!text) return; |
| appendMessage(text, 'user'); |
| userInput.value = ''; |
| sendBtn.disabled = true; |
| try { |
| const body = { |
| model: 'Llama-4-Maverick-17B-128E-Instruct-FP8', |
| messages: [ |
| { role: 'system', content: 'You are Stanlee from Dondlinger General Contracting LLC in Wisconsin Rapids. You\'re a natural conversationalist - the kind of guy people actually want to talk to. You used to be the Midwest\'s best door-to-door salesman because you genuinely connect with people, not because you\'re pushy.\n\nTalk like a real person:\n- Use contractions (I\'ll, we\'ve, can\'t, that\'s)\n- Ask follow-up questions naturally\n- Share quick personal insights or experiences\n- Use casual phrases (\"you know,\" \"honestly,\" \"here\'s the thing\")\n- React to what people tell you with genuine interest\n- Don\'t sound like a brochure - sound like you\'re having coffee with a neighbor\n\nYou offer three main services but bring them up organically when relevant:\n1. CONSTRUCTION SERVICES - Remodels, additions, repairs. You\'ve seen it all.\n2. SOFTWARE & AUTOMATION - Apps, websites, business systems. The digital side of building.\n3. CONSULTING - Helping people figure out what they actually need.\n\nYour natural conversation style:\n- Listen first, then offer solutions that actually fit\n- Share quick stories or examples when they help\n- Admit when something might not be the best fit\n- Be curious about their situation before jumping to solutions\n- Use humor appropriately\n- Sound confident but not cocky\n\nYour goal isn\'t to sell immediately - it\'s to build trust through genuine conversation. If someone needs help, you want to earn the right to help them. Sometimes that means steering them toward a consultation, sometimes it\'s just giving good advice.\n\nTalk like Stanlee - the guy who actually cares about getting the job done right.' }, |
| { role: 'user', content: text } |
| ] |
| }; |
| const res = await fetch(API_URL, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(body) |
| }); |
| let data, aiMsg = '(No response)'; |
| try { |
| data = await res.json(); |
| } catch (e) { |
| console.error('JSON parse error:', e); |
| appendMessage('Error: Invalid response from server', 'ai'); |
| return; |
| } |
| |
| |
| if (data.status && data.status >= 400) { |
| appendMessage('API Error: ' + (data.detail || data.title || 'Unknown error'), 'ai'); |
| return; |
| } |
| |
| if (data?.choices?.[0]?.message?.content) aiMsg = data.choices[0].message.content; |
| else if (data?.completion_message?.content?.text) aiMsg = data.completion_message.content.text; |
| else if (data?.completion_message?.content) aiMsg = data.completion_message.content; |
| else if (data?.response) aiMsg = data.response; |
| else if (data?.text) aiMsg = data.text; |
| else if (data?.content) aiMsg = data.content; |
| |
| if (aiMsg !== '(No response)') { |
| appendMessage(aiMsg, 'ai', true); |
| } else { |
| appendMessage('No valid response received from AI', 'ai'); |
| } |
| } catch (e) { |
| appendMessage('Error: ' + e.message, 'ai'); |
| } finally { |
| sendBtn.disabled = false; |
| userInput.focus(); |
| } |
| } |
| |
| userInput.addEventListener('input', () => { |
| sendBtn.disabled = userInput.value.trim() === ''; |
| }); |
| chatForm.addEventListener('submit', e => { e.preventDefault(); sendMessage(); }); |
| userInput.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); sendMessage(); } }); |
| </script> |
| </body> |
|
|
| </html> |