| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Kimi-K2.6 Chat</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
| |
| <script src="https://unpkg.com/@phosphor-icons/web"></script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'); |
| |
| :root { |
| --bg-color: #ffffff; |
| --text-main: #0f0f0f; |
| --text-muted: #828282; |
| --accent: #d97757; |
| --input-bg: #f4f4f4; |
| --border-color: #e5e5e5; |
| --user-bubble: #f4f4f4; |
| } |
| |
| body { |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; |
| background-color: var(--bg-color); |
| color: var(--text-main); |
| margin: 0; |
| overflow: hidden; |
| -webkit-font-smoothing: antialiased; |
| } |
| |
| .chat-layout { |
| display: flex; |
| flex-direction: column; |
| height: 100vh; |
| } |
| |
| .header { |
| padding: 16px 24px; |
| font-weight: 500; |
| font-size: 16px; |
| letter-spacing: -0.01em; |
| color: var(--text-main); |
| display: flex; |
| align-items: center; |
| border-bottom: 1px solid transparent; |
| transition: border-bottom 0.2s; |
| } |
| |
| .messages-container { |
| flex: 1; |
| overflow-y: auto; |
| scroll-behavior: smooth; |
| } |
| |
| .messages { |
| max-width: 800px; |
| margin: 0 auto; |
| padding: 24px 20px 48px; |
| display: flex; |
| flex-direction: column; |
| gap: 32px; |
| } |
| |
| .empty-state { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| height: 60vh; |
| color: var(--text-main); |
| gap: 20px; |
| } |
| |
| .empty-state h1 { |
| font-size: 28px; |
| font-weight: 500; |
| letter-spacing: -0.02em; |
| margin: 0; |
| color: var(--text-main); |
| } |
| |
| .empty-logo { |
| width: 48px; |
| height: 48px; |
| background: #fdf2f0; |
| color: var(--accent); |
| border-radius: 12px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 24px; |
| font-weight: 600; |
| box-shadow: 0 4px 12px rgba(217, 119, 87, 0.1); |
| } |
| |
| .message { |
| display: flex; |
| gap: 16px; |
| max-width: 100%; |
| animation: fadeIn 0.3s ease-in-out; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| |
| .message.user { |
| justify-content: flex-end; |
| margin-left: 48px; |
| } |
| .message.user .content-wrapper { |
| background-color: var(--user-bubble); |
| padding: 12px 18px; |
| border-radius: 18px; |
| border-bottom-right-radius: 4px; |
| max-width: 100%; |
| } |
| |
| |
| .message.assistant { |
| justify-content: flex-start; |
| margin-right: 48px; |
| } |
| |
| .avatar { |
| width: 28px; |
| height: 28px; |
| border-radius: 6px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-weight: 600; |
| flex-shrink: 0; |
| font-size: 14px; |
| margin-top: 4px; |
| } |
| .avatar.assistant { |
| background-color: transparent; |
| color: var(--accent); |
| } |
| |
| .content-wrapper { |
| max-width: 100%; |
| line-height: 1.6; |
| font-size: 16px; |
| } |
| |
| .message.assistant .content-wrapper { |
| padding-top: 5px; |
| } |
| |
| .input-wrapper { |
| padding: 0 20px 24px; |
| background: linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 30%); |
| position: relative; |
| } |
| |
| .input-container-inner { |
| max-width: 800px; |
| margin: 0 auto; |
| position: relative; |
| } |
| |
| |
| .input-box { |
| position: relative; |
| background: var(--input-bg); |
| border: 1px solid transparent; |
| border-radius: 16px; |
| padding: 8px; |
| display: flex; |
| flex-direction: column; |
| transition: background 0.2s, border-color 0.2s, box-shadow 0.2s; |
| } |
| |
| .input-box:focus-within { |
| background: white; |
| border-color: var(--border-color); |
| box-shadow: 0 8px 24px rgba(0,0,0,0.05); |
| } |
| |
| .image-preview-container { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 8px 12px; |
| overflow-x: auto; |
| } |
| .image-preview { |
| position: relative; |
| display: inline-block; |
| border-radius: 12px; |
| border: 1px solid var(--border-color); |
| padding: 4px; |
| background: white; |
| } |
| .image-preview img { |
| height: 56px; |
| border-radius: 8px; |
| object-fit: cover; |
| display: block; |
| } |
| .remove-image { |
| position: absolute; |
| top: -6px; |
| right: -6px; |
| background: var(--text-main); |
| color: white; |
| border-radius: 50%; |
| width: 20px; |
| height: 20px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 14px; |
| cursor: pointer; |
| border: none; |
| transition: transform 0.2s; |
| } |
| .remove-image:hover { transform: scale(1.1); } |
| |
| .input-row { |
| display: flex; |
| align-items: flex-end; |
| min-height: 44px; |
| } |
| |
| .btn-attach { |
| width: 44px; |
| height: 44px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| border: none; |
| background: transparent; |
| color: var(--text-muted); |
| border-radius: 12px; |
| cursor: pointer; |
| font-size: 20px; |
| transition: all 0.2s; |
| } |
| .btn-attach:hover { |
| color: var(--text-main); |
| background: rgba(0,0,0,0.05); |
| } |
| |
| textarea { |
| flex: 1; |
| border: none; |
| outline: none; |
| resize: none; |
| max-height: 200px; |
| min-height: 24px; |
| padding: 10px 12px; |
| font-family: inherit; |
| font-size: 16px; |
| background: transparent; |
| line-height: 1.5; |
| color: var(--text-main); |
| } |
| textarea::placeholder { color: #a1a1aa; } |
| |
| .btn-send { |
| width: 36px; |
| height: 36px; |
| border-radius: 50%; |
| border: none; |
| background: var(--accent); |
| color: white; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: all 0.2s; |
| margin: 4px; |
| font-size: 18px; |
| } |
| .btn-send:hover:not(:disabled) { |
| transform: translateY(-1px); |
| box-shadow: 0 4px 12px rgba(217, 119, 87, 0.3); |
| } |
| .btn-send:disabled { |
| background: #e5e5e5; |
| color: white; |
| cursor: not-allowed; |
| } |
| |
| .footer-note { |
| text-align: center; |
| font-size: 13px; |
| color: var(--text-muted); |
| margin-top: 12px; |
| } |
| |
| |
| .markdown { word-break: break-word; } |
| .markdown p { margin-bottom: 1em; } |
| .markdown p:last-child { margin-bottom: 0; } |
| .markdown a { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; } |
| .markdown pre { |
| background: #f9f9f9; |
| border: 1px solid var(--border-color); |
| color: var(--text-main); |
| padding: 16px; |
| border-radius: 12px; |
| overflow-x: auto; |
| margin-bottom: 1em; |
| font-size: 14px; |
| } |
| .markdown code { |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; |
| font-size: 14px; |
| background: #f4f4f4; |
| padding: 3px 6px; |
| border-radius: 6px; |
| color: #eb5757; |
| } |
| .markdown pre code { background: transparent; padding: 0; color: inherit; } |
| .markdown ul, .markdown ol { margin-bottom: 1em; padding-left: 20px; } |
| |
| .loading-dots { |
| display: inline-flex; |
| gap: 4px; |
| align-items: center; |
| height: 24px; |
| } |
| .dot { |
| width: 6px; |
| height: 6px; |
| background: var(--accent); |
| border-radius: 50%; |
| animation: bounce 1.4s infinite ease-in-out both; |
| } |
| .dot:nth-child(1) { animation-delay: -0.32s; } |
| .dot:nth-child(2) { animation-delay: -0.16s; } |
| |
| @keyframes bounce { |
| 0%, 80%, 100% { transform: scale(0); } |
| 40% { transform: scale(1); } |
| } |
| |
| </style> |
| </head> |
| <body> |
| <div class="chat-layout"> |
| <div class="header"> |
| Kimi-K2.6 |
| </div> |
| |
| <div class="messages-container" id="messages-container"> |
| <div class="messages" id="messages"> |
| <div class="empty-state" id="empty-state"> |
| <div class="empty-logo"> |
| <i class="ph ph-sparkle"></i> |
| </div> |
| <h1>What can I help you with?</h1> |
| </div> |
| </div> |
| </div> |
| |
| <div class="input-wrapper"> |
| <div class="input-container-inner"> |
| <div class="input-box"> |
| <div id="image-preview-area" class="image-preview-container" style="display: none;"> |
| <div class="image-preview"> |
| <img id="preview-img" src="" alt="Preview"> |
| <button class="remove-image" onclick="removeImage()"> |
| <i class="ph-bold ph-x"></i> |
| </button> |
| </div> |
| </div> |
| <div class="input-row"> |
| <input type="file" id="file-upload" accept="image/*" style="display: none" onchange="previewImage(event)"> |
| <button class="btn-attach" onclick="document.getElementById('file-upload').click()" title="Attach file"> |
| <i class="ph ph-paperclip"></i> |
| </button> |
| <textarea id="user-input" rows="1" placeholder="Reply to Kimi..." onkeydown="handleKeydown(event)" oninput="autoResize(this)"></textarea> |
| <button class="btn-send" id="send-btn" onclick="sendMessage()" disabled> |
| <i class="ph-bold ph-arrow-up"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| <div class="footer-note"> |
| Kimi-K2.6 runs on Fireworks AI via Hugging Face. Can make mistakes. |
| </div> |
| </div> |
| </div> |
|
|
| <script type="module"> |
| import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; |
| |
| let client = null; |
| let history = []; |
| let selectedFile = null; |
| |
| |
| marked.setOptions({ |
| breaks: true, |
| gfm: true |
| }); |
| |
| async function initClient() { |
| try { |
| client = await Client.connect(window.location.origin); |
| } catch (e) { |
| console.error("Failed to connect:", e); |
| const initError = document.createElement("div"); |
| initError.style = "text-align: center; color: red; font-size: 14px; position: absolute; top: 16px; left: 0; right: 0;"; |
| initError.innerText = "Error connecting to backend."; |
| document.body.appendChild(initError); |
| } |
| } |
| |
| initClient(); |
| |
| window.autoResize = function(textarea) { |
| textarea.style.height = 'auto'; |
| textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px'; |
| |
| const btn = document.getElementById('send-btn'); |
| if (textarea.value.trim() || selectedFile) { |
| btn.disabled = false; |
| } else { |
| btn.disabled = true; |
| } |
| }; |
| |
| window.handleKeydown = function(e) { |
| if (e.key === 'Enter' && !e.shiftKey) { |
| e.preventDefault(); |
| if (!document.getElementById('send-btn').disabled) { |
| sendMessage(); |
| } |
| } |
| }; |
| |
| window.previewImage = function(e) { |
| const file = e.target.files[0]; |
| if (file) { |
| selectedFile = file; |
| const reader = new FileReader(); |
| reader.onload = function(e) { |
| document.getElementById('preview-img').src = e.target.result; |
| document.getElementById('image-preview-area').style.display = 'flex'; |
| document.getElementById('send-btn').disabled = false; |
| } |
| reader.readAsDataURL(file); |
| } |
| e.target.value = ''; |
| }; |
| |
| window.removeImage = function() { |
| selectedFile = null; |
| document.getElementById('image-preview-area').style.display = 'none'; |
| autoResize(document.getElementById('user-input')); |
| }; |
| |
| function scrollToBottom() { |
| const container = document.getElementById('messages-container'); |
| container.scrollTop = container.scrollHeight; |
| } |
| |
| function addMessage(role, text, imageSrc = null) { |
| const emptyState = document.getElementById('empty-state'); |
| if (emptyState) emptyState.style.display = 'none'; |
| |
| const messagesDiv = document.getElementById('messages'); |
| const msgDiv = document.createElement('div'); |
| msgDiv.className = `message ${role}`; |
| |
| let contentStr = ''; |
| |
| if (imageSrc && role === 'user') { |
| contentStr += `<img src="${imageSrc}" style="max-width: 250px; border-radius: 12px; margin-bottom: 8px; border: 1px solid var(--border-color);"><br>`; |
| } |
| |
| if (role === 'assistant') { |
| contentStr = `<div class="avatar assistant"><i class="ph-fill ph-sparkle" style="font-size: 24px;"></i></div>`; |
| contentStr += `<div class="content-wrapper">`; |
| if (text === 'loading') { |
| contentStr += `<div class="loading-dots"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div>`; |
| } else { |
| contentStr += `<div class="markdown">${marked.parse(text)}</div>`; |
| } |
| contentStr += `</div>`; |
| } else { |
| contentStr += `<div class="content-wrapper">`; |
| if (text) { |
| contentStr += `<div style="white-space: pre-wrap;">${text}</div>`; |
| } |
| contentStr += `</div>`; |
| } |
| |
| msgDiv.innerHTML = contentStr; |
| messagesDiv.appendChild(msgDiv); |
| scrollToBottom(); |
| return msgDiv; |
| } |
| |
| window.sendMessage = async function() { |
| const input = document.getElementById('user-input'); |
| const text = input.value.trim(); |
| |
| if (!text && !selectedFile) return; |
| |
| const sendBtn = document.getElementById('send-btn'); |
| sendBtn.disabled = true; |
| input.disabled = true; |
| |
| |
| let imgSrc = null; |
| if (selectedFile) { |
| imgSrc = document.getElementById('preview-img').src; |
| } |
| addMessage('user', text, imgSrc); |
| |
| const fileToSend = selectedFile; |
| const textToSend = text; |
| |
| |
| input.value = ''; |
| input.style.height = 'auto'; |
| removeImage(); |
| |
| |
| const typingDiv = addMessage('assistant', 'loading'); |
| |
| try { |
| let fileData = null; |
| if (fileToSend) { |
| fileData = handle_file(fileToSend); |
| } |
| |
| if (!client) { |
| await initClient(); |
| } |
| |
| |
| if (!client) throw new Error("Could not connect to backend router."); |
| |
| const result = await client.predict("/chat", { |
| message: textToSend, |
| history: history, |
| image: fileData |
| }); |
| |
| |
| const responseText = result.data[0]; |
| |
| |
| history.push([textToSend, responseText]); |
| |
| |
| typingDiv.querySelector('.content-wrapper').innerHTML = `<div class="markdown">${marked.parse(responseText)}</div>`; |
| } catch (err) { |
| console.error(err); |
| typingDiv.querySelector('.content-wrapper').innerHTML = `<div class="markdown" style="color: #ef4444;">It looks like an error occurred: ${err.message}. Ensure the python backend is running without errors.</div>`; |
| } finally { |
| sendBtn.disabled = false; |
| input.disabled = false; |
| input.focus(); |
| autoResize(input); |
| scrollToBottom(); |
| } |
| }; |
| </script> |
| </body> |
| </html> |
|
|