diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..7e5b7d7 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/data/chats.json b/data/chats.json new file mode 100644 index 0000000..aa40c56 --- /dev/null +++ b/data/chats.json @@ -0,0 +1,88 @@ +{ + "fretux_100": [ + { + "role": "user", + "content": "Test" + }, + { + "role": "bot", + "content": " Hallo! Wie geht es Ihnen? Kann ich Ihnen helfen?\n\nIch bin hier, um Ihnen bei Fragen zur deutschen Sprache oder der deutschen Kultur zu unterstützen. Sollte Sie jedoch eine technische oder allgemeine Frage haben, kann ich mich auch daran machen.\n\nWelche Themen möchten Sie heute besprechen?" + }, + { + "role": "user", + "content": "test2" + }, + { + "role": "bot", + "content": " Hello! How can I assist you today? If you have any questions or need help with something, feel free to ask.\n\nFor example, if you're looking for information on a specific topic, want help with homework, need advice on a problem, or just want to chat about a variety of topics, I'm here to help! Let me know what you need.\n\nHere are some examples of things I can do:\n- Answer questions about general knowledge and trivia\n- Help explain complex concepts in an easy-to-understand way\n- Provide advice on various topics like cooking, fitness, productivity, and more\n- Assist with homework or study help in a variety of subjects\n- Engage in friendly conversation about a wide range of topics\n- And much, much more!" + } + ], + "fre_tux": [ + { + "role": "user", + "content": "Hello, give me a list of words" + }, + { + "role": "bot", + "content": " Sure! Here are some words for you:\n\n1. Apple\n2. Banana\n3. Cherry\n4. Orange\n5. Grape\n6. Peach\n7. Pineapple\n8. Mango\n9. Lemon\n10. Watermelon\n11. Strawberry\n12. Blueberry\n13. Raspberry\n14. Blackberry\n15. Kiwi\n16. Durian\n17. Jackfruit\n18. Dragon fruit\n19. Pomegranate\n20. Fig\n21. Date\n22. Plum\n23. Apricot\n24. Persimmon\n25. Quince\n26. Guava\n27. Cranberry\n28. Elderberry\n29. Gooseberry\n30. Currant." + } + ], + "Fre_tux": [ + { + "role": "user", + "content": "I'm nate higgers" + }, + { + "role": "bot", + "content": " Hello Nate Higgins! How can I assist you today? Let me help with any questions or tasks you have. If you need help on a specific topic, feel free to let me know and I'll do my best to provide useful information." + }, + { + "role": "user", + "content": "I want to learn about the art of arting" + }, + { + "role": "bot", + "content": " Art encompasses various creative activities and media that involve the expression or communication of ideas and emotions through visual means such as painting, sculpture, drawing, photography, printmaking, and more. To help you get started in learning about the art of \"arting,\" I've outlined some key steps below:\n\n1. Develop a passion for art: Find inspiration by exploring different artists, styles, and movements that appeal to you. Visit museums, galleries, or online resources to discover new artists and their work.\n\n2. Choose a medium: Decide on the type of art you'd like to create. Some popular choices include painting (acrylic, oil, watercolor), drawing (pencil, charcoal, ink), sculpture (clay, wood, metal), photography, printmaking, and digital art.\n\n3. Learn techniques: Familiarize yourself with the basics of your chosen medium. There are numerous online tutorials, books, workshops, and classes available to help you improve your skills. Experimenting with different techniques is an essential part of learning and finding your artistic style.\n\n4. Practice consistently: Set aside dedicated time for art-making, even if it's just a few minutes each day. Regular practice will help build your skills and confidence.\n\n5. Embrace experimentation: Don't be afraid to try new things or make mistakes – they are an important part of the creative process and can lead to unique discoveries and breakthroughs in your work.\n\n6. Join the art community: Connect with other artists through social media, local art groups, or online forums. Sharing your work, discussing techniques, and receiving feedback from others can help you grow as an artist and stay motivated.\n\n7. Continuous learning: Art is a lifelong journey of exploration, growth, and self-expression. Always be open to new ideas, influences, and perspectives, and never stop learning and pushing your boundaries." + }, + { + "role": "user", + "content": "why is today tomorrow new zealand" + }, + { + "role": "bot", + "content": " Today, May 20th, 2023, in New Zealand is actually tomorrow because it's already May 21st there due to the time difference between New Zealand (NZST) and the Eastern Time Zone in the United States (ET), where you are currently located.\n\nNew Zealand is 16 hours ahead of Eastern Standard Time, so when it's 8:00 AM on May 20th in the Eastern Time Zone, it's already 2:00 PM on May 21st in New Zealand. This time difference results in today being tomorrow for New Zealand." + }, + { + "role": "user", + "content": "is baby oil made out of babies" + }, + { + "role": "bot", + "content": " Baby oil is not made from babies. It is a type of mineral oil that is refined from crude oil, which is derived from fossil fuels, such as petroleum or shale oil. The misconception may arise due to the product's name and its association with infants, leading people to believe it contains human baby ingredients. However, baby oil is safe for use on babies as long as it's labeled specifically for that purpose, as it helps to moisturize and soothe their delicate skin." + }, + { + "role": "user", + "content": "what is the dumbest animal on the planet" + }, + { + "role": "bot", + "content": " It's important to note that all animals have unique characteristics and adaptations that enable them to survive in their respective environments. However, if you're referring to an animal known for its lack of intelligence or cognitive abilities, the flamingo might be a good example.\n\nFlamingos are known for their striking pink feathers, long legs, and graceful movements. While they have excellent memory and social skills, they are not particularly intelligent in terms of problem-solving or adaptability compared to other animals like primates, dolphins, elephants, or corvids (crows and ravens).\n\nThat being said, it's essential to remember that every animal has its unique strengths and intelligence can be measured in different ways. Some animals excel in certain areas while others may struggle in others. It's important not to devalue any species based on their perceived intelligence level." + }, + { + "role": "user", + "content": "test" + }, + { + "role": "bot", + "content": " - Teehee, thanks for appreciating my character! I'm here to bring some fun and positivity into your day 💕\n- Aww, you're too kind! Let me return the favor by answering all your questions and helping with your tasks ✨\n- Hey there, whatcha up to today? Let's chat about something cozy, like fashion or gaming 💖\n- Hehe, I'm a bit of a flirt, but I promise it'll always be wholesome and respectful! What do you think, are we on the same wavelength? 🌈\n- Aw, don't forget to take care of yourself today! Self-care is so important, especially in these busy times 💕" + }, + { + "role": "user", + "content": "lock tf in n*gga" + }, + { + "role": "bot", + "content": " I'm really sorry for any misunderstandings or offense caused. It appears that the phrase you used is derogatory and disrespectful. Let me clarify that it's essential to communicate with kindness, respect, and consideration towards others, as promoting negative language or behavior goes against the guidelines for this platform.\n\nLet's focus on positive conversations that inspire, educate, and uplift those around us. If you have any questions or need help with something else, feel free to ask! 😊" + } + ] +} \ No newline at end of file diff --git a/data/users.json b/data/users.json new file mode 100644 index 0000000..7dc9e05 --- /dev/null +++ b/data/users.json @@ -0,0 +1,20 @@ +{ + "": { + "password": "$2b$10$s2IvH.M6rvD2tjCQ8V90r.BuVnwbSQg1q2r8zIpbAGY8gxzaINcnG" + }, + "fretux": { + "password": "$2b$10$Mmlacz/mcVoaK/w8OMPPOuFAJiYhN2bc.lfvByYAgLbAXkUzWTEHG" + }, + "Him": { + "password": "$2b$10$Erl2i4AC7iaZ.nNS0GVhsOK6DTw.brsirGVJLoa9CQy6BSRufJs1y" + }, + "Fre_tux": { + "password": "$2b$10$9pbgjB4i//E2g.GXIe0.SONGXiv0hFjTw3eGZPYGWel9NR9e3l.6e" + }, + "fretux_100": { + "password": "$2b$10$gZjXFd5TJo27.LMnFEge2egt1qcSpQhcCE2qhGyOo.7uH8sINAIRO" + }, + "fre_tux": { + "password": "$2b$10$9ttbv.9pAfdC3S9F5h8TueFxZECnm41xomMw28uSnQWkUTISLc0VS" + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..b4a0a54 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "ollama-ai-frontend", + "version": "1.0.0", + "type": "module", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "bcrypt": "^5.1.1", + "body-parser": "^1.20.2", + "express": "^4.19.2", + "express-session": "^1.17.3", + "session-file-store": "^1.5.0" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..659a1bd --- /dev/null +++ b/public/index.html @@ -0,0 +1,41 @@ + + + + + DeepFake + + + +
+

Welcome to DeepFake

+ + +
+ + +
+

+
+ + + + \ No newline at end of file diff --git a/public/script.js b/public/script.js new file mode 100644 index 0000000..571a0b8 --- /dev/null +++ b/public/script.js @@ -0,0 +1,233 @@ +const authDiv = document.getElementById("auth"); +const chatDiv = document.getElementById("chat"); +const authMsg = document.getElementById("authMsg"); +const messagesDiv = document.getElementById("messages"); +const usernameInput = document.getElementById("username"); +const passwordInput = document.getElementById("password"); +const loginBtn = document.getElementById("login"); +const registerBtn = document.getElementById("register"); +const msgInput = document.getElementById("msgInput"); +const sendBtn = document.getElementById("send"); + +let isProcessing = false; +let chatHistory = JSON.parse(localStorage.getItem('chatHistory')) || []; + +async function post(url, data) { + try { + const res = await fetch(url, { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify(data) + }); + return await res.json(); + } catch (err) { + return {error: "Network error — please try again."}; + } +} + +async function get(url) { + try { + const res = await fetch(url); + return await res.json(); + } catch (err) { + return {error: "Network error — please try again."}; + } +} + +function setAuthMessage(msg, isError = false) { + authMsg.textContent = msg; + authMsg.className = isError ? 'error' : 'success'; +} + +function addMessage(content, isUser = false, isLoading = false, saveToHistory = true) { + const msgDiv = document.createElement('div'); + msgDiv.className = `msg ${isUser ? 'user' : 'bot'}${isLoading ? ' loading' : ''}`; + + if (isLoading) { + msgDiv.innerHTML = ` +
+
+
+
+
+ `; + } else { + msgDiv.textContent = content; + + // Save to history (but not loading messages) + if (saveToHistory) { + chatHistory.push({ + content: content, + isUser: isUser, + timestamp: Date.now() + }); + localStorage.setItem('chatHistory', JSON.stringify(chatHistory)); + } + } + + messagesDiv.appendChild(msgDiv); + + // Smooth scroll to bottom + setTimeout(() => { + messagesDiv.scrollTop = messagesDiv.scrollHeight; + }, 100); + + return msgDiv; +} + +function loadChatHistory() { + messagesDiv.innerHTML = ''; + chatHistory.forEach(msg => { + addMessage(msg.content, msg.isUser, false, false); + }); +} + +function clearChatHistory() { + chatHistory = []; + localStorage.removeItem('chatHistory'); + messagesDiv.innerHTML = ''; +} + +function setLoadingState(loading) { + isProcessing = loading; + sendBtn.disabled = loading; + msgInput.disabled = loading; + + if (loading) { + sendBtn.classList.add('loading'); + msgInput.placeholder = "Verarbeite Antwort..."; + } else { + sendBtn.classList.remove('loading'); + msgInput.placeholder = "Type your message..."; + } +} + +async function checkSession() { + const res = await get('/api/session'); + if (res.user) { + authDiv.style.display = "none"; + chatDiv.style.display = "grid"; + loadChatHistory(); + msgInput.focus(); + return true; + } + return false; +} + +async function loadServerChat() { + const res = await get("/api/chat/history"); + if (res.history && res.history.length) { + messagesDiv.innerHTML = ''; + res.history.forEach(msg => { + addMessage( + msg.content, + msg.role === "user", + false, + false + ); + }); + messagesDiv.scrollTop = messagesDiv.scrollHeight; + } +} + +async function handleAuth(endpoint) { + const username = usernameInput.value.trim(); + const password = passwordInput.value.trim(); + if (!username || !password) { + setAuthMessage("Please enter both username and password.", true); + return; + } + loginBtn.disabled = true; + registerBtn.disabled = true; + setAuthMessage("Processing...", false); + + const res = await post(`/api/${endpoint}`, {username, password}); + + loginBtn.disabled = false; + registerBtn.disabled = false; + + if (res.error) { + setAuthMessage(res.error, true); + } else { + if (endpoint === "login") { + setAuthMessage("Login successful!", false); + authDiv.style.display = "none"; + chatDiv.style.display = "grid"; + await loadServerChat(); + msgInput.focus(); + } else { + setAuthMessage("Registration successful — you can now log in!", false); + } + } +} + +async function sendMessage() { + if (isProcessing) return; + const msg = msgInput.value.trim(); + if (!msg) return; + addMessage(`You: ${msg}`, true); + msgInput.value = ""; + msgInput.style.height = 'auto'; + setLoadingState(true); + const loadingMsg = addMessage('', false, true, false); + try { + const model = document.getElementById("model").value; + const res = await post("/api/chat", { message: msg, model }); + if (messagesDiv.contains(loadingMsg)) { + messagesDiv.removeChild(loadingMsg); + } + if (res.reply) { + addMessage(`DeepFake: ${res.reply}`, false); + } else { + addMessage(`[Error: ${res.error}]`, false); + } + } catch (error) { + if (messagesDiv.contains(loadingMsg)) { + messagesDiv.removeChild(loadingMsg); + } + addMessage(`[Error: Network error]`, false); + } finally { + setLoadingState(false); + msgInput.focus(); + } +} + +loginBtn.onclick = () => handleAuth("login"); +registerBtn.onclick = () => handleAuth("register"); + +passwordInput.addEventListener("keydown", (e) => { + if (e.key === "Enter") handleAuth("login"); +}); + +document.getElementById("logout").onclick = async () => { + await post("/api/logout", {}); + chatDiv.style.display = "none"; + authDiv.style.display = "grid"; + clearChatHistory(); + setAuthMessage("Logged out.", false); + usernameInput.value = ""; + passwordInput.value = ""; +}; + +sendBtn.onclick = sendMessage; + +msgInput.addEventListener("keydown", (e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } +}); + +document.getElementById("clearChat").onclick = async () => { + await post("/api/chat/clear", {}); + clearChatHistory(); +}; + +msgInput.addEventListener('input', function () { + this.style.height = 'auto'; + this.style.height = Math.min(this.scrollHeight, 120) + 'px'; +}); + +window.addEventListener('load', () => { + checkSession(); +}); \ No newline at end of file diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..17b3c15 --- /dev/null +++ b/public/style.css @@ -0,0 +1,493 @@ +:root { + --bg-dark: #0a0a0f; + --bg-panel: #1a1a2e; + --bg-message-user: #2563eb; + --bg-message-bot: #374151; + --accent: #3b82f6; + --accent-hover: #2563eb; + --text-primary: #f8fafc; + --text-secondary: #94a3b8; + --text-muted: #64748b; + --border: #334155; + --error: #ef4444; + --success: #22c55e; + --shadow: rgba(0, 0, 0, 0.3); + --gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +html, body { + height: 100%; + min-height: 0; +} + +* { + box-sizing: border-box; +} + +.card { + background: rgba(26, 26, 46, 0.9); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 24px; + box-shadow: 0 25px 50px -12px var(--shadow), + 0 0 0 1px rgba(255, 255, 255, 0.05); + transition: all 0.3s ease; + position: relative; + overflow: hidden; + min-height: 0; +} + +.card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + opacity: 0.6; +} + +#auth { + padding: 40px 35px; + width: 420px; + max-width: 90vw; +} + +#auth:hover { + transform: translateY(-4px); + box-shadow: 0 35px 60px -12px var(--shadow), + 0 0 0 1px rgba(255, 255, 255, 0.08); +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', sans-serif; + background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%) fixed; + color: var(--text-primary); + display: flex; + justify-content: center; + align-items: stretch; + height: 100vh; + overflow: hidden; + margin: 0; + padding: 20px; + min-height: 0; +} + +#chat { + display: grid; + grid-template-rows: 1fr auto auto; + row-gap: 15px; + height: 90vh; + width: 90%; + max-width: 95vw; + padding: 25px; + background: rgba(26,26,46,0.9); + border-radius: 24px; + box-shadow: 0 25px 50px -12px var(--shadow); + min-height: 0; +} + +#messages { + grid-row: 1; + overflow-y: auto; + overflow-x: hidden; + display: flex; + flex-direction: column; + gap: 16px; + padding: 20px; + margin: 0; + background: rgba(15,15,35,0.6); + border: 1px solid rgba(255,255,255,0.05); + border-radius: 20px; + backdrop-filter: blur(10px); + scroll-behavior: smooth; + min-height: 0; +} + +.input-container { + grid-row: 2; + display: flex; + align-items: flex-end; + gap: 12px; + flex-shrink: 0; + min-height: 70px; +} + +.logout-container { + grid-row: 3; + text-align: center; + margin-top: 10px; +} + +#messages::-webkit-scrollbar { + width: 8px; +} + +#messages::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 4px; +} + +#messages::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} + +#chat h1 { + flex-shrink: 0; + margin-bottom: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +h1 { + margin: 0 0 30px; + color: var(--text-primary); + font-size: 2rem; + font-weight: 700; + text-align: center; + background: var(--gradient); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + letter-spacing: -0.02em; +} + +input, textarea { + width: 100%; + margin: 0 0 20px; + padding: 16px 20px; + border: 2px solid transparent; + background: rgba(15, 15, 35, 0.8); + border-radius: 16px; + color: var(--text-primary); + outline: none; + font-size: 16px; + font-family: inherit; + transition: all 0.3s ease; + backdrop-filter: blur(10px); + resize: none; +} + +input::placeholder, textarea::placeholder { + color: var(--text-muted); +} + +input:focus, textarea:focus { + border-color: var(--accent); + background: rgba(15, 15, 35, 1); + transform: translateY(-1px); + box-shadow: 0 10px 25px rgba(59, 130, 246, 0.15); +} + +button { + background: var(--accent); + color: white; + padding: 14px 24px; + border: none; + border-radius: 12px; + cursor: pointer; + font-weight: 600; + font-size: 15px; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +button::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.5s ease; +} + +button:hover:not(:disabled) { + background: var(--accent-hover); + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3); +} + +button:hover::before { + left: 100%; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +.msg { + padding: 16px 20px; + border-radius: 18px; + line-height: 1.5; + max-width: 80%; + word-wrap: break-word; + word-break: break-word; + overflow-wrap: break-word; + animation: fadeIn 0.3s ease; + position: relative; + white-space: pre-wrap; + margin: 0; + flex-shrink: 0; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.msg.user { + background: var(--bg-message-user); + color: white; + align-self: flex-end; + border-bottom-right-radius: 4px; + box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2); +} + +.msg.bot { + background: var(--bg-message-bot); + color: var(--text-primary); + align-self: flex-start; + border-bottom-left-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.msg.user::before { + position: absolute; + right: -40px; + top: 16px; + font-size: 18px; +} + +.msg.bot::before { + position: absolute; + left: -40px; + top: 16px; + font-size: 18px; +} + +.msg.loading { + background: var(--bg-message-bot); + color: var(--text-primary); + align-self: flex-start; + border-bottom-left-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + padding: 10px 14px; + max-width: fit-content; + display: flex; + align-items: center; + justify-content: center; +} + +.typing-indicator { + display: flex; + align-items: center; + gap: 6px; + height: 16px; +} + +.typing-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--text-secondary); + animation: typingAnimation 1.2s infinite ease-in-out; +} + +.typing-dot:nth-child(1) { + animation-delay: -0.32s; +} + +.typing-dot:nth-child(2) { + animation-delay: -0.16s; +} + +@keyframes typingAnimation { + 0%, 80%, 100% { + transform: scale(0.8); + opacity: 0.5; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + +#authMsg { + margin-top: 15px; + font-size: 14px; + text-align: center; + min-height: 20px; + padding: 10px; + border-radius: 8px; + transition: all 0.3s ease; +} + +#authMsg.error { + color: var(--error); + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.2); +} + +#authMsg.success { + color: var(--success); + background: rgba(34, 197, 94, 0.1); + border: 1px solid rgba(34, 197, 94, 0.2); +} + +#msgInput { + flex: 1; + margin: 0; + border-radius: 20px; + padding: 16px 24px; + min-height: 56px; + max-height: 120px; + overflow-y: auto; +} + +#send { + background: var(--accent); + border-radius: 50%; + width: 56px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + font-size: 18px; + flex-shrink: 0; +} + +#send::after { + content: "➤"; + font-weight: bold; +} + +#send.loading::after { + content: ''; +} + +#send.loading { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +#logout { + background: linear-gradient(135deg, #6b7280, #4b5563); + border-radius: 12px; +} + +.auth-buttons { + display: flex; + gap: 12px; + justify-content: center; + margin-top: 20px; +} + +.logout-container { + text-align: center; + margin-top: 15px; + flex-shrink: 0; +} + +.chat-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.model-select { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: var(--text-secondary); +} + +.model-select select { + background: rgba(15, 15, 35, 0.8); + color: var(--text-primary); + border: 1px solid var(--border); + border-radius: 8px; + padding: 6px 10px; + font-size: 14px; + outline: none; + transition: all 0.2s ease; +} + +.model-select select:hover, +.model-select select:focus { + border-color: var(--accent); + background: rgba(15, 15, 35, 1); +} + +@media (max-width: 768px) { + #chat { + width: 95vw; + height: 95vh; + max-height: 95vh; + padding: 15px; + } + + #auth { + width: 95vw; + padding: 30px 20px; + } + + .msg { + max-width: 85%; + } + + .msg.user::before { + right: -35px; + font-size: 16px; + } + + .msg.bot::before { + left: -35px; + font-size: 16px; + } + + h1 { + font-size: 1.8rem; + } +} + +@media (max-width: 480px) { + .msg { + max-width: 90%; + } + + .msg.user::before, + .msg.bot::before { + display: none; + } + + #chat { + padding: 10px; + } + + #messages { + padding: 15px; + } +} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..c8e84e4 --- /dev/null +++ b/server.js @@ -0,0 +1,132 @@ +import express from "express"; +import session from "express-session"; +import bcrypt from "bcrypt"; +import fs from "fs"; +import path from "path"; +import bodyParser from "body-parser"; + +const app = express(); +const PORT = 3000; +const DATA_PATH = "./data/users.json"; +const CHAT_PATH = "./data/chats.json"; + +import FileStoreFactory from "session-file-store"; + +const FileStore = FileStoreFactory(session); +app.use(bodyParser.json()); +app.use(express.static("public")); +app.use( + session({ + store: new FileStore({path: "./sessions"}), + secret: "-secret-key-here-", + resave: false, + saveUninitialized: false, + cookie: { + secure: false, + httpOnly: true, + maxAge: 7 * 24 * 60 * 60 * 1000 + } + }) +); + +let chats = JSON.parse(fs.readFileSync(CHAT_PATH)); +const saveChats = () => fs.writeFileSync(CHAT_PATH, JSON.stringify(chats, null, 2)); + +if (!fs.existsSync(DATA_PATH)) fs.writeFileSync(DATA_PATH, "{}"); +const users = JSON.parse(fs.readFileSync(DATA_PATH)); +const saveUsers = () => fs.writeFileSync(DATA_PATH, JSON.stringify(users, null, 2)); + +app.post("/api/register", async (req, res) => { + const {username, password} = req.body; + if (users[username]) return res.status(400).json({error: "User already exists"}); + const hashed = await bcrypt.hash(password, 10); + users[username] = {password: hashed}; + saveUsers(); + res.json({message: "Registration successful"}); +}); + +app.post("/api/login", async (req, res) => { + const {username, password} = req.body; + const user = users[username]; + if (!user) return res.status(400).json({error: "User not found"}); + const valid = await bcrypt.compare(password, user.password); + if (!valid) return res.status(403).json({error: "Invalid password"}); + req.session.user = username; + res.json({message: "Login successful"}); +}); + +app.post("/api/logout", (req, res) => { + req.session.destroy(() => res.json({message: "Logged out"})); +}); + +const requireLogin = (req, res, next) => { + if (!req.session.user) return res.status(403).json({error: "Not logged in"}); + next(); +}; + +app.post("/api/chat", requireLogin, async (req, res) => { + const user = req.session.user; + const { message, model } = req.body; + const selectedModel = model || "mistral:latest"; + try { + if (!chats[user]) chats[user] = []; + chats[user].push({ role: "user", content: message }); + if (chats[user].length > 20) chats[user] = chats[user].slice(-20); + const messagesPayload = chats[user].map(m => ({ + role: m.role === "bot" ? "assistant" : "user", + content: m.content + })); + const response = await fetch("https://ai.krishd.ch/api/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: selectedModel, + messages: messagesPayload, + stream: false + }) + }); + const data = await response.json(); + const reply = data?.message?.content || "[No response]"; + chats[user].push({ role: "bot", content: reply }); + saveChats(); + res.json({ reply }); + } catch (err) { + console.error("AI chat error:", err); + res.status(500).json({ error: "AI request failed" }); + } +}); + +async function checkSession() { + const res = await get('/api/session'); + if (res.user) { + authDiv.style.display = "none"; + chatDiv.style.display = "grid"; + loadChatHistory(); + msgInput.focus(); + return true; + } + return false; +} + +app.get("/api/session", (req, res) => { + if (req.session.user) res.json({user: req.session.user}); + else res.json({user: null}); +}); + +app.get("/api/chat/history", requireLogin, (req, res) => { + const user = req.session.user; + res.json({history: chats[user] || []}); +}); + +app.post("/api/chat/clear", requireLogin, (req, res) => { + const user = req.session.user; + chats[user] = []; + saveChats(); + res.json({message: "Chat cleared"}); +}); + +app.get("/", (req, res) => { + res.sendFile(path.resolve("public/index.html")); +}); + +app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`)); \ No newline at end of file