Skip to content

Instantly share code, notes, and snippets.

@ehartford
Created July 25, 2025 15:17
Show Gist options
  • Save ehartford/1bf8119dc840b14957fe492853ba6f36 to your computer and use it in GitHub Desktop.
Save ehartford/1bf8119dc840b14957fe492853ba6f36 to your computer and use it in GitHub Desktop.
<!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