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>