File size: 7,729 Bytes
eb34f53 032114f eb34f53 96843ff 032114f 9859de1 96843ff 28d7e16 96843ff 9859de1 96843ff 0f1dd9b eb34f53 032114f 96843ff 28d7e16 96843ff 28d7e16 96843ff 28d7e16 96843ff f3a2dba 96843ff 128be19 96843ff 28d7e16 96843ff 9859de1 96843ff 9859de1 96843ff 9859de1 96843ff 9859de1 96843ff 9859de1 96843ff 9859de1 96843ff eb34f53 032114f | 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 | <!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;
}
// Check for API errors first
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();
}
}
// Enable/disable send button based on input
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> |