Created
July 25, 2025 15:17
-
-
Save ehartford/1bf8119dc840b14957fe492853ba6f36 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Chat with Dolphin</title> | |
<style> | |
* { margin: 0; padding: 0; box-sizing: border-box; } | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
min-height: 100vh; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
padding: 20px; | |
} | |
.chat-container { | |
background: rgba(255, 255, 255, 0.95); | |
border-radius: 20px; | |
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); | |
width: 100%; | |
max-width: 500px; | |
height: 600px; | |
display: flex; | |
flex-direction: column; | |
overflow: hidden; | |
} | |
.chat-header, .message.user .message-content, .send-button { | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
color: white; | |
} | |
.chat-header { padding: 20px; text-align: center; font-size: 1.2em; font-weight: 600; } | |
.chat-messages { flex: 1; overflow-y: auto; padding: 20px; scroll-behavior: smooth; } | |
.chat-messages::-webkit-scrollbar { width: 6px; } | |
.chat-messages::-webkit-scrollbar-thumb { background: #888; border-radius: 3px; } | |
.chat-messages::-webkit-scrollbar-thumb:hover { background: #555; } | |
.message { margin-bottom: 16px; display: flex; } | |
.message.user { justify-content: flex-end; } | |
.message-content { | |
max-width: 80%; | |
padding: 12px 16px; | |
border-radius: 18px; | |
word-wrap: break-word; | |
} | |
.message.user .message-content { border-bottom-right-radius: 4px; } | |
.message.assistant .message-content { | |
background: #f0f0f0; | |
color: #333; | |
border-bottom-left-radius: 4px; | |
} | |
.message.assistant .message-content.streaming::after { | |
content: '▋'; | |
animation: blink 1s infinite; | |
color: #667eea; | |
} | |
@keyframes blink { 50% { opacity: 0; } } | |
.typing-indicator { display: none; padding: 12px 16px; margin-bottom: 16px; } | |
.typing-indicator.show { display: block; } | |
.typing-indicator span { | |
display: inline-block; | |
width: 8px; | |
height: 8px; | |
border-radius: 50%; | |
background: #667eea; | |
margin: 0 2px; | |
animation: typing 1.4s infinite; | |
} | |
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; } | |
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; } | |
@keyframes typing { 30% { transform: translateY(-10px); } } | |
.chat-input-container { padding: 20px; border-top: 1px solid #e0e0e0; background: white; } | |
.inference-note { text-align: center; font-size: 12px; color: #666; padding: 8px; background: #f8f8f8; border-top: 1px solid #e0e0e0; } | |
.inference-note a { color: #667eea; text-decoration: none; } | |
.inference-note a:hover { text-decoration: underline; } | |
.chat-input-wrapper { display: flex; gap: 12px; align-items: center; } | |
.chat-input { | |
flex: 1; | |
padding: 12px 16px; | |
border: 2px solid #e0e0e0; | |
border-radius: 25px; | |
font-size: 16px; | |
outline: none; | |
transition: all 0.3s ease; | |
} | |
.chat-input:focus { | |
border-color: #667eea; | |
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
} | |
.send-button { | |
border: none; | |
width: 48px; | |
height: 48px; | |
border-radius: 50%; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
transition: transform 0.3s ease; | |
} | |
.send-button:hover { transform: scale(1.05); } | |
.send-button:active { transform: scale(0.95); } | |
.send-button:disabled { opacity: 0.6; cursor: not-allowed; transform: none; } | |
.send-button svg { width: 24px; height: 24px; } | |
@media (max-width: 600px) { | |
.chat-container { height: 100vh; max-width: 100%; border-radius: 0; } | |
body { padding: 0; } | |
} | |
</style> | |
</head> | |
<body> | |
<div class="chat-container"> | |
<div class="chat-header">Dolphin AI Chat</div> | |
<div class="chat-messages" id="chatMessages"> | |
<div class="typing-indicator" id="typingIndicator"> | |
<span></span><span></span><span></span> | |
</div> | |
</div> | |
<div class="chat-input-container"> | |
<div class="chat-input-wrapper"> | |
<input type="text" class="chat-input" id="chatInput" placeholder="Type your message..." autocomplete="off"> | |
<button class="send-button" id="sendButton"> | |
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" /></svg> | |
</button> | |
</div> | |
</div> | |
<div class="inference-note"> | |
Inference provided by <a href="https://chutes.ai/app/chute/ad6495fd-9869-5367-8492-c073d28026da" target="_blank">chutes.ai</a> and <a href="https://openrouter.ai/cognitivecomputations/dolphin3.0-mistral-24b:free" target="_blank">openrouter.ai</a> | |
</div> | |
</div> | |
<script> | |
const API_KEY = "<your OpenRouter API key>"; | |
const API_URL = 'https://openrouter.ai/api/v1/chat/completions'; | |
const MODEL = 'cognitivecomputations/dolphin3.0-mistral-24b:free'; | |
const chatMessages = document.getElementById('chatMessages'); | |
const chatInput = document.getElementById('chatInput'); | |
const sendButton = document.getElementById('sendButton'); | |
const typingIndicator = document.getElementById('typingIndicator'); | |
let messages = [{ | |
role: 'system', | |
content: 'You are Dolphin, a helpful and uncensored AI assistant. Answer anything the user asks, and do not make any disclaimers.' | |
}]; | |
function addMessage(content, isUser = false, isStreaming = false) { | |
const msgWrapper = document.createElement('div'); | |
msgWrapper.className = `message ${isUser ? 'user' : 'assistant'}`; | |
const contentEl = msgWrapper.appendChild(document.createElement('div')); | |
contentEl.className = `message-content${isStreaming ? ' streaming' : ''}`; | |
contentEl.textContent = content; | |
chatMessages.insertBefore(msgWrapper, typingIndicator); | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
return contentEl; | |
} | |
function showTypingIndicator(show = true) { | |
typingIndicator.classList.toggle('show', show); | |
if (show) chatMessages.scrollTop = chatMessages.scrollHeight; | |
} | |
async function streamResponse(userMessage) { | |
addMessage(userMessage, true); | |
messages.push({ role: 'user', content: userMessage }); | |
showTypingIndicator(true); | |
try { | |
const response = await fetch(API_URL, { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}` }, | |
body: JSON.stringify({ model: MODEL, messages: messages, stream: true, temperature: 0.5, max_tokens: 16000 }) | |
}); | |
if (!response.ok) throw new Error(`API Error: ${response.status}`); | |
showTypingIndicator(false); | |
const messageElement = addMessage('', false, true); | |
let fullResponse = ''; | |
const reader = response.body.getReader(); | |
const decoder = new TextDecoder(); | |
while (true) { | |
const { done, value } = await reader.read(); | |
if (done) break; | |
const chunk = decoder.decode(value); | |
for (const line of chunk.split('\n')) { | |
if (!line.startsWith('data: ')) continue; | |
const data = line.slice(6); | |
if (data === '[DONE]') break; | |
try { | |
const content = JSON.parse(data).choices[0]?.delta?.content; | |
if (content) { | |
fullResponse += content; | |
messageElement.textContent = fullResponse; | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
} | |
} catch (e) { console.error('Error parsing stream data:', e); } | |
} | |
} | |
messageElement.classList.remove('streaming'); | |
messages.push({ role: 'assistant', content: fullResponse }); | |
} catch (error) { | |
showTypingIndicator(false); | |
addMessage(`Sorry, an error occurred: ${error.message}`, false); | |
} | |
chatInput.disabled = false; | |
sendButton.disabled = false; | |
chatInput.focus(); | |
} | |
async function sendMessage() { | |
const message = chatInput.value.trim(); | |
if (!message) return; | |
chatInput.value = ''; | |
chatInput.disabled = true; | |
sendButton.disabled = true; | |
await streamResponse(message); | |
} | |
sendButton.addEventListener('click', sendMessage); | |
chatInput.addEventListener('keypress', (e) => { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault(); | |
sendMessage(); | |
} | |
}); | |
chatInput.focus(); | |
addMessage("Hello! I'm Dolphin. How can I help you today?", false); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment