File size: 9,316 Bytes
3a1960e
 
 
 
 
 
 
 
a7e7318
 
 
284c59f
3a1960e
 
 
 
 
 
 
3672af3
 
 
 
 
 
 
 
 
 
3a1960e
 
3672af3
3a1960e
3672af3
3a1960e
 
3672af3
3a1960e
3672af3
 
 
3a1960e
 
 
 
 
 
3672af3
 
 
 
 
 
 
 
 
 
3a1960e
 
3672af3
 
 
 
 
ab34076
 
3a1960e
 
 
 
 
 
a424734
3a1960e
 
 
 
 
 
 
 
 
 
 
 
 
ab34076
 
 
a7e7318
 
e59019b
ab34076
 
 
 
 
 
 
 
3a1960e
 
ab34076
 
3672af3
ab34076
3a1960e
 
 
 
 
 
 
 
 
 
3672af3
 
3a1960e
 
 
 
 
 
 
 
 
 
 
ab34076
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
953a37d
3a1960e
 
 
 
ab34076
3a1960e
 
 
 
 
 
 
 
 
 
ab34076
 
3a1960e
 
ab34076
 
 
 
 
 
 
 
3a1960e
 
ab34076
 
 
 
3a1960e
 
 
 
 
 
 
ab34076
 
 
3a1960e
 
ab34076
3a1960e
 
 
 
 
 
 
 
ab34076
 
 
 
 
 
 
 
 
 
 
 
3a1960e
 
 
 
 
 
 
 
ab34076
3a1960e
ab34076
3a1960e
 
 
 
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>University Assistant</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet"
        href="{{ url_for('static', filename='style.css') }}">
    <!-- <link rel="stylesheet" href="../static/style.css"> -->

    <script>
        tailwind.config = {
            darkMode: 'class',
        };
    </script>
</head>

<body class="bg-zinc-100 text-black dark:bg-slate-950 dark:text-white flex items-center justify-center min-h-screen">
    <div
        class="flex w-full flex-col overflow-hidden  border border-gray-300 dark:border-gray-700 shadow-lg rounded-lg h-screen relative">
        <!-- Theme Toggle Icon -->
        <button id="theme-toggle"
            class="absolute top-2 right-2 z-20 flex h-9 w-9 items-center justify-center rounded-full bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 text-black dark:text-white shadow transition-all"
            title="Toggle Theme">
            πŸŒ“
        </button>

        <div class="flex flex-col flex-grow overflow-hidden">
            <div id="chat-container" class="flex-grow overflow-y-auto p-3 space-y-3 bg-zinc-100 dark:bg-slate-950">
                <!-- Chat messages will appear here -->
            </div>
            <div class="border-t p-2 border-gray-300 dark:border-slate-800">
                <div class="flex gap-2">
                    <input id="chat-input"
                        class="flex-grow rounded-md border border-gray-300 dark:border-slate-800 bg-white dark:bg-slate-800 px-3 py-1 text-sm text-black dark:text-white focus:outline-none"
                        type="text" placeholder="Type your question here..." />
                    <button id="send-button" class="rounded-md bg-blue-600 px-3 py-1 text-white hover:bg-blue-700">
                        ➀
                    </button>
                </div>
            </div>
        </div>
    </div>

    <script>
        const toggleButton = document.getElementById("theme-toggle");
        toggleButton.addEventListener("click", () => {
            document.documentElement.classList.toggle("dark");

            // Optional: save preference
            if (document.documentElement.classList.contains("dark")) {
                localStorage.setItem("theme", "dark");
            } else {
                localStorage.setItem("theme", "light");
            }
        });

        // Optional: load theme from localStorage
        if (localStorage.getItem("theme") === "dark") {
            document.documentElement.classList.add("dark");
        }

        const API_BASE_URL = window.location.origin;

        const chatContainer = document.getElementById("chat-container");
        const chatInput = document.getElementById("chat-input");
        const sendButton = document.getElementById("send-button");

        const initialMessage = {
            id: "1",
            text: "Hello! I'm the JUIT University Assistant. How can I help you today? !(The first message may take 10-15s to load)",
            isUser: false,
        };

        let messages = [initialMessage];
        let isTyping = false;

        function renderMessages() {
            chatContainer.innerHTML = "";
            messages.forEach((msg) => {
                const bubble = document.createElement("div");
                bubble.className = `flex items-start gap-2 ${msg.isUser ? "flex-row-reverse" : "flex-row"}`;
                const avatar = document.createElement("img");
                avatar.className = "h-9 w-9 rounded-full object-cover";

                // Use complete URLs for images
                const baseUrl = window.location.origin;
                avatar.src = msg.isUser ? `${baseUrl}/static/profile.png` : `${baseUrl}/static/JUIT_icon.png`;
                // avatar.src = msg.isUser ? `../static/profile.png` : `../static/JUIT_icon.png`;
                avatar.alt = msg.isUser ? "" : "";

                avatar.onerror = function () {
                    // Fallback if image fails to load
                    this.src = msg.isUser ?
                        "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='12' fill='%230ea5e9'/%3E%3C/svg%3E" :
                        "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='12' fill='%23475569'/%3E%3C/svg%3E";
                };

                const msgDiv = document.createElement("div");
                msgDiv.className = `max-w-[75%] break-words rounded-md px-3 py-1 text-sm ${
                    msg.isUser
                        ? "bg-blue-600 text-white rounded-tr-none"
                        : "bg-gray-200 text-black light:bg-gray-700 light:text-white rounded-tl-none"
                }`;
                msgDiv.innerText = msg.text;
                bubble.appendChild(avatar);
                bubble.appendChild(msgDiv);
                chatContainer.appendChild(bubble);
            });
            if (isTyping) {
                const typing = document.createElement("div");
                typing.className = "flex items-start gap-2";
                typing.innerHTML = `
                    <img src="../static/JUIT_icon.png" alt="Bot" class="h-9 w-9 rounded-full object-cover" />
                    <div class="max-w-[75%] rounded-md rounded-tl-none bg-gray-200 light:bg-gray-700 px-3 py-1 text-sm">
                    <div class="dot-typing text-black light:text-white">
                        <span>.</span><span>.</span><span>.</span>
                    </div>
                    </div>
                `;
                chatContainer.appendChild(typing);
            }

            chatContainer.scrollTop = chatContainer.scrollHeight;
        }

        async function simulateBotResponse(userInput) {
            try {
                const response = await fetch(`${API_BASE_URL}/get`, {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                    },
                    body: JSON.stringify({
                        message: userInput
                    }),
                });

                if (!response.ok) {
                    const errorText = await response.text();
                    throw new Error(`API error (${response.status}): ${errorText}`);
                }

                const data = await response.json();
                if (data.error) throw new Error(data.error);

                return data.response;
            } catch (error) {
                console.error("Error fetching response:", error);
                throw error;
            }
        }

        async function handleSendMessage() {
            const text = chatInput.value.trim();
            if (!text || isTyping) return;

            const userMessage = {
                id: Date.now().toString(),
                text,
                isUser: true,
            };

            messages.push(userMessage);
            chatInput.value = "";
            isTyping = true;

            // Render messages, including typing animation
            renderMessages();

            // Add typing animation (". . .")
            const typingMessage = {
                id: "typing",
                text: ". . .", // Use animated dots here
                isUser: false,
            };
            messages.push(typingMessage);

            try {
                const res = await simulateBotResponse(text);

                // Remove typing animation once response is received
                messages.pop(); // Removes the ". . ." animation message

                const botMessage = {
                    id: (Date.now() + 1).toString(),
                    text: res.response,
                    isUser: false,
                };
                messages.push(botMessage);
            } catch (error) {
                // Remove typing animation if there's an error
                messages.pop(); // Removes the ". . ." animation message

                messages.push({
                    id: Date.now().toString(),
                    text: `Error: ${error.message || "Failed to get response. Please try again."}`,
                    isUser: false,
                });
            }

            isTyping = false;
            renderMessages();
        }

        // Check server health on page load
        async function checkServerHealth() {
            try {
                const response = await fetch(`${API_BASE_URL}/health`);
                if (!response.ok) {
                    console.warn("Server health check failed");
                }
            } catch (error) {
                console.error("Server health check error:", error);
            }
        }

        sendButton.addEventListener("click", handleSendMessage);
        chatInput.addEventListener("keydown", (e) => {
            if (e.key === "Enter" && !e.shiftKey) {
                e.preventDefault();
                handleSendMessage();
            }
        });

        // Initialize
        renderMessages();
        checkServerHealth();
    </script>
</body>

</html>