Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI Chat Assistant</title> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
<style> | |
:root { | |
--primary-color: #5438dc; | |
--secondary-color: #6c5ce7; | |
--text-color: #2d3436; | |
--light-color: #f5f6fa; | |
--dark-color: #2d3436; | |
--success-color: #00b894; | |
--warning-color: #fdcb6e; | |
--error-color: #d63031; | |
} | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
} | |
body { | |
background-color: #f9f9f9; | |
color: var(--text-color); | |
line-height: 1.6; | |
display: flex; | |
flex-direction: column; | |
min-height: 100vh; | |
} | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
padding: 20px; | |
display: flex; | |
flex-direction: column; | |
flex-grow: 1; | |
} | |
header { | |
padding: 20px 0; | |
text-align: center; | |
border-bottom: 1px solid #eee; | |
margin-bottom: 20px; | |
} | |
h1 { | |
color: var(--primary-color); | |
font-size: 2.5rem; | |
margin-bottom: 10px; | |
} | |
.subtitle { | |
color: var(--dark-color); | |
opacity: 0.8; | |
font-size: 1.1rem; | |
} | |
.chat-container { | |
border-radius: 12px; | |
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
background-color: white; | |
display: flex; | |
flex-direction: column; | |
flex-grow: 1; | |
overflow: hidden; | |
position: relative; | |
} | |
.chat-header { | |
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
color: white; | |
padding: 15px 20px; | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
} | |
.chat-header i { | |
font-size: 1.5rem; | |
} | |
.chat-header h2 { | |
font-size: 1.3rem; | |
font-weight: 500; | |
} | |
.chat-messages { | |
padding: 20px; | |
overflow-y: auto; | |
flex-grow: 1; | |
max-height: 70vh; | |
display: flex; | |
flex-direction: column; | |
gap: 20px; | |
} | |
.message { | |
max-width: 80%; | |
padding: 12px 16px; | |
border-radius: 12px; | |
line-height: 1.5; | |
position: relative; | |
animation: fadeIn 0.3s ease-out; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; transform: translateY(10px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
.user-message { | |
background-color: var(--primary-color); | |
color: white; | |
align-self: flex-end; | |
border-bottom-right-radius: 4px; | |
} | |
.ai-message { | |
background-color: #f1f3fe; | |
color: var(--text-color); | |
align-self: flex-start; | |
border-bottom-left-radius: 4px; | |
} | |
.message-info { | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
margin-bottom: 5px; | |
font-size: 0.85rem; | |
opacity: 0.7; | |
} | |
.user-message .message-info { | |
color: rgba(255, 255, 255, 0.8); | |
justify-content: flex-end; | |
} | |
.ai-message .message-info { | |
color: rgba(45, 52, 54, 0.7); | |
} | |
.typing-indicator { | |
display: flex; | |
padding: 12px 16px; | |
background-color: #f1f3fe; | |
border-radius: 12px; | |
align-self: flex-start; | |
margin-bottom: 20px; | |
border-bottom-left-radius: 4px; | |
} | |
.typing-dot { | |
width: 8px; | |
height: 8px; | |
background-color: var(--primary-color); | |
border-radius: 50%; | |
margin: 0 2px; | |
animation: typingAnimation 1.4s infinite ease-in-out; | |
} | |
.typing-dot:nth-child(1) { | |
animation-delay: 0s; | |
} | |
.typing-dot:nth-child(2) { | |
animation-delay: 0.2s; | |
} | |
.typing-dot:nth-child(3) { | |
animation-delay: 0.4s; | |
} | |
@keyframes typingAnimation { | |
0%, 60%, 100% { | |
transform: translateY(0); | |
} | |
30% { | |
transform: translateY(-5px); | |
} | |
} | |
.chat-input-container { | |
padding: 15px; | |
background-color: white; | |
border-top: 1px solid #eee; | |
display: flex; | |
gap: 10px; | |
position: relative; | |
} | |
.chat-textarea { | |
flex-grow: 1; | |
border: 1px solid #ddd; | |
border-radius: 24px; | |
padding: 12px 20px; | |
resize: none; | |
font-size: 1rem; | |
outline: none; | |
transition: border 0.3s; | |
max-height: 150px; | |
min-height: 50px; | |
} | |
.chat-textarea:focus { | |
border-color: var(--primary-color); | |
box-shadow: 0 0 0 2px rgba(84, 56, 220, 0.2); | |
} | |
.send-button { | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
border-radius: 50%; | |
width: 50px; | |
height: 50px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
cursor: pointer; | |
transition: background-color 0.3s, transform 0.2s; | |
} | |
.send-button:hover { | |
background-color: var(--secondary-color); | |
} | |
.send-button:active { | |
transform: scale(0.95); | |
} | |
.send-button i { | |
font-size: 1.2rem; | |
} | |
footer { | |
text-align: center; | |
padding: 20px; | |
color: var(--dark-color); | |
opacity: 0.7; | |
font-size: 0.9rem; | |
} | |
.api-key-container { | |
margin-bottom: 20px; | |
padding: 15px; | |
background-color: white; | |
border-radius: 8px; | |
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); | |
display: flex; | |
gap: 10px; | |
} | |
.api-key-input { | |
flex-grow: 1; | |
border: 1px solid #ddd; | |
border-radius: 6px; | |
padding: 10px 15px; | |
font-size: 1rem; | |
outline: none; | |
} | |
.api-key-input:focus { | |
border-color: var(--primary-color); | |
} | |
.save-key-button { | |
background-color: var(--primary-color); | |
color: white; | |
border: none; | |
border-radius: 6px; | |
padding: 10px 20px; | |
cursor: pointer; | |
transition: background-color 0.3s; | |
} | |
.save-key-button:hover { | |
background-color: var(--secondary-color); | |
} | |
.model-selector { | |
margin-bottom: 20px; | |
padding: 10px 15px; | |
border-radius: 6px; | |
background-color: white; | |
border: 1px solid #ddd; | |
outline: none; | |
width: 100%; | |
max-width: 300px; | |
font-size: 1rem; | |
} | |
.model-selector:focus { | |
border-color: var(--primary-color); | |
} | |
.settings-panel { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 15px; | |
margin-bottom: 20px; | |
} | |
.setting-group { | |
background-color: white; | |
padding: 15px; | |
border-radius: 8px; | |
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); | |
flex-grow: 1; | |
min-width: 200px; | |
} | |
.setting-group h3 { | |
margin-bottom: 10px; | |
color: var(--primary-color); | |
font-size: 1rem; | |
} | |
.slider-container { | |
width: 100%; | |
} | |
.slider-container label { | |
display: block; | |
margin-bottom: 5px; | |
font-size: 0.9rem; | |
color: var(--text-color); | |
} | |
.slider-container input[type="range"] { | |
width: 100%; | |
-webkit-appearance: none; | |
height: 6px; | |
border-radius: 3px; | |
background: #ddd; | |
outline: none; | |
} | |
.slider-container input[type="range"]::-webkit-slider-thumb { | |
-webkit-appearance: none; | |
appearance: none; | |
width: 18px; | |
height: 18px; | |
border-radius: 50%; | |
background: var(--primary-color); | |
cursor: pointer; | |
transition: background .15s ease-in-out; | |
} | |
.slider-value { | |
text-align: right; | |
font-size: 0.85rem; | |
color: var(--dark-color); | |
opacity: 0.7; | |
} | |
.clear-chat-button { | |
background-color: #f5f6fa; | |
color: var(--error-color); | |
border: 1px solid var(--error-color); | |
border-radius: 6px; | |
padding: 10px 20px; | |
cursor: pointer; | |
transition: all 0.3s; | |
margin-left: auto; | |
display: block; | |
} | |
.clear-chat-button:hover { | |
background-color: var(--error-color); | |
color: white; | |
} | |
/* Markdown styling */ | |
.message-content { | |
overflow-x: auto; | |
} | |
.message-content h1, | |
.message-content h2, | |
.message-content h3, | |
.message-content h4, | |
.message-content h5, | |
.message-content h6 { | |
margin-top: 1em; | |
margin-bottom: 0.5em; | |
font-weight: bold; | |
} | |
.message-content h1 { | |
font-size: 1.5em; | |
border-bottom: 1px solid rgba(0, 0, 0, 0.1); | |
padding-bottom: 0.3em; | |
} | |
.message-content h2 { | |
font-size: 1.3em; | |
border-bottom: 1px solid rgba(0, 0, 0, 0.1); | |
padding-bottom: 0.2em; | |
} | |
.message-content h3 { | |
font-size: 1.1em; | |
} | |
.message-content p { | |
margin-bottom: 1em; | |
line-height: 1.6; | |
} | |
.message-content ul, | |
.message-content ol { | |
margin-bottom: 1em; | |
padding-left: 2em; | |
} | |
.message-content ul { | |
list-style-type: disc; | |
} | |
.message-content ol { | |
list-style-type: decimal; | |
} | |
.message-content li { | |
margin-bottom: 0.5em; | |
} | |
.message-content blockquote { | |
margin: 1em 0; | |
padding: 0 1em; | |
border-left: 0.25em solid #dfe2e5; | |
color: #6a737d; | |
} | |
.message-content pre { | |
background-color: #f6f8fa; | |
border-radius: 6px; | |
padding: 1em; | |
overflow: auto; | |
margin-bottom: 1em; | |
} | |
.message-content code { | |
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; | |
background-color: rgba(27, 31, 35, 0.05); | |
border-radius: 3px; | |
padding: 0.2em 0.4em; | |
font-size: 0.9em; | |
} | |
.message-content pre code { | |
background-color: transparent; | |
padding: 0; | |
border-radius: 0; | |
} | |
.message-content table { | |
border-collapse: collapse; | |
width: 100%; | |
margin-bottom: 1em; | |
} | |
.message-content th, | |
.message-content td { | |
border: 1px solid #dfe2e5; | |
padding: 6px 13px; | |
} | |
.message-content th { | |
background-color: #f6f8fa; | |
font-weight: bold; | |
text-align: left; | |
} | |
.message-content tr:nth-child(even) { | |
background-color: #f6f8fa; | |
} | |
.message-content a { | |
color: var(--primary-color); | |
text-decoration: none; | |
} | |
.message-content a:hover { | |
text-decoration: underline; | |
} | |
.message-content strong { | |
font-weight: bold; | |
} | |
.message-content em { | |
font-style: italic; | |
} | |
.user-message .message-content a { | |
color: #b7c2ff; | |
} | |
.user-message .message-content a:hover { | |
color: #fff; | |
} | |
@media (max-width: 768px) { | |
.chat-messages { | |
padding: 15px; | |
} | |
.message { | |
max-width: 90%; | |
} | |
.api-key-container { | |
flex-direction: column; | |
} | |
.settings-panel { | |
flex-direction: column; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<header> | |
<h1>AI Chat Assistant</h1> | |
<p class="subtitle">Powered by OpenAI's advanced language models</p> | |
</header> | |
<select class="model-selector" id="modelSelector"> | |
<option value="gpt-4o-mini">GPT-4o-mini</option> | |
<option value="gpt-4o">GPT-4o</option> | |
</select> | |
<div class="settings-panel"> | |
<div class="setting-group"> | |
<h3>Model Settings</h3> | |
<div class="slider-container"> | |
<label for="temperatureSlider">Temperature (Creativity)</label> | |
<input type="range" id="temperatureSlider" min="0" max="1" step="0.1" value="0.7"> | |
<div class="slider-value" id="temperatureValue">0.7</div> | |
</div> | |
<div class="slider-container"> | |
<label for="maxTokensSlider">Max Tokens (Response Length)</label> | |
<input type="range" id="maxTokensSlider" min="50" max="2000" step="50" value="500"> | |
<div class="slider-value" id="maxTokensValue">500</div> | |
</div> | |
</div> | |
</div> | |
<div class="api-key-container"> | |
<input type="password" class="api-key-input" id="apiKeyInput" placeholder="Enter your OpenAI API key"> | |
<button class="save-key-button" id="saveKeyButton"> | |
<i class="fas fa-save"></i> Save | |
</button> | |
</div> | |
<button class="clear-chat-button" id="clearChatButton"> | |
<i class="fas fa-trash-alt"></i> Clear Chat | |
</button> | |
<div class="chat-container"> | |
<div class="chat-header"> | |
<i class="fas fa-robot"></i> | |
<h2>AI Assistant</h2> | |
</div> | |
<div class="chat-messages" id="chatMessages"> | |
<div class="message ai-message"> | |
<div class="message-info"> | |
<span><i class="fas fa-robot"></i> AI Assistant</span> | |
</div> | |
<div class="message-content"> | |
Hello! I'm your AI assistant. How can I help you today? | |
</div> | |
</div> | |
</div> | |
<div class="chat-input-container"> | |
<textarea class="chat-textarea" id="chatInput" placeholder="Type your message here..." rows="1"></textarea> | |
<button class="send-button" id="sendButton"> | |
<i class="fas fa-paper-plane"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
<footer> | |
<p>AI Chat Assistant © 2023 | Uses OpenAI API to generate responses</p> | |
</footer> | |
<script> | |
// Configure marked.js | |
marked.setOptions({ | |
breaks: true, | |
gfm: true, | |
highlight: function(code, lang) { | |
return code; // No syntax highlighting but keeps the code block | |
} | |
}); | |
// DOM Elements | |
const apiKeyInput = document.getElementById('apiKeyInput'); | |
const saveKeyButton = document.getElementById('saveKeyButton'); | |
const chatInput = document.getElementById('chatInput'); | |
const sendButton = document.getElementById('sendButton'); | |
const chatMessages = document.getElementById('chatMessages'); | |
const modelSelector = document.getElementById('modelSelector'); | |
const temperatureSlider = document.getElementById('temperatureSlider'); | |
const temperatureValue = document.getElementById('temperatureValue'); | |
const maxTokensSlider = document.getElementById('maxTokensSlider'); | |
const maxTokensValue = document.getElementById('maxTokensValue'); | |
const clearChatButton = document.getElementById('clearChatButton'); | |
// Chat history | |
let conversationHistory = [ | |
{ | |
role: "assistant", | |
content: "Hello! I'm your AI assistant. How can I help you today?" | |
} | |
]; | |
// Load API key from localStorage | |
if (localStorage.getItem('openai_api_key')) { | |
apiKeyInput.value = localStorage.getItem('openai_api_key'); | |
} | |
// Load settings from localStorage | |
if (localStorage.getItem('model')) { | |
modelSelector.value = localStorage.getItem('model'); | |
} | |
if (localStorage.getItem('temperature')) { | |
temperatureSlider.value = localStorage.getItem('temperature'); | |
temperatureValue.textContent = localStorage.getItem('temperature'); | |
} | |
if (localStorage.getItem('max_tokens')) { | |
maxTokensSlider.value = localStorage.getItem('max_tokens'); | |
maxTokensValue.textContent = localStorage.getItem('max_tokens'); | |
} | |
// Auto-expand textarea as user types | |
chatInput.addEventListener('input', () => { | |
chatInput.style.height = 'auto'; | |
chatInput.style.height = (chatInput.scrollHeight) + 'px'; | |
}); | |
// Save API key | |
saveKeyButton.addEventListener('click', () => { | |
const apiKey = apiKeyInput.value.trim(); | |
if (!apiKey) { | |
alert('Please enter your OpenAI API key'); | |
return; | |
} | |
localStorage.setItem('openai_api_key', apiKey); | |
showToast('API key saved successfully!', 'success'); | |
}); | |
// Update slider values in real time | |
temperatureSlider.addEventListener('input', () => { | |
temperatureValue.textContent = temperatureSlider.value; | |
localStorage.setItem('temperature', temperatureSlider.value); | |
}); | |
maxTokensSlider.addEventListener('input', () => { | |
maxTokensValue.textContent = maxTokensSlider.value; | |
localStorage.setItem('max_tokens', maxTokensSlider.value); | |
}); | |
modelSelector.addEventListener('change', () => { | |
localStorage.setItem('model', modelSelector.value); | |
}); | |
// Clear chat history | |
clearChatButton.addEventListener('click', () => { | |
if (confirm('Are you sure you want to clear the chat history?')) { | |
conversationHistory = [ | |
{ | |
role: "assistant", | |
content: "Hello! I'm your AI assistant. How can I help you today?" | |
} | |
]; | |
chatMessages.innerHTML = ` | |
<div class="message ai-message"> | |
<div class="message-info"> | |
<span><i class="fas fa-robot"></i> AI Assistant</span> | |
</div> | |
<div class="message-content"> | |
Hello! I'm your AI assistant. How can I help you today? | |
</div> | |
</div> | |
`; | |
} | |
}); | |
// Send message function | |
async function sendMessage() { | |
const userMessage = chatInput.value.trim(); | |
if (!userMessage) return; | |
// Check if API key exists | |
if (!localStorage.getItem('openai_api_key')) { | |
showToast('Please enter your OpenAI API key first', 'error'); | |
return; | |
} | |
// Add user message to chat | |
appendMessage('user', userMessage); | |
conversationHistory.push({ role: "user", content: userMessage }); | |
// Clear input | |
chatInput.value = ''; | |
chatInput.style.height = 'auto'; | |
// Show typing indicator | |
const typingIndicator = document.createElement('div'); | |
typingIndicator.className = 'typing-indicator'; | |
typingIndicator.innerHTML = ` | |
<div class="typing-dot"></div> | |
<div class="typing-dot"></div> | |
<div class="typing-dot"></div> | |
`; | |
chatMessages.appendChild(typingIndicator); | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
try { | |
// Call OpenAI API | |
const response = await fetch('https://api.openai.com/v1/chat/completions', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${localStorage.getItem('openai_api_key')}` | |
}, | |
body: JSON.stringify({ | |
model: modelSelector.value, | |
messages: conversationHistory, | |
temperature: parseFloat(temperatureSlider.value), | |
max_tokens: parseInt(maxTokensSlider.value) | |
}) | |
}); | |
// Remove typing indicator | |
chatMessages.removeChild(typingIndicator); | |
if (!response.ok) { | |
const errorData = await response.json(); | |
throw new Error(errorData.error?.message || 'Failed to get response from OpenAI'); | |
} | |
const data = await response.json(); | |
const aiResponse = data.choices[0].message.content; | |
// Add AI response to chat (rendered as Markdown) | |
appendMessage('assistant', aiResponse); | |
conversationHistory.push({ role: "assistant", content: aiResponse }); | |
} catch (error) { | |
chatMessages.removeChild(typingIndicator); | |
console.error('Error:', error); | |
showToast(error.message, 'error'); | |
appendMessage('assistant', `Sorry, I'm having trouble responding. ${error.message}`); | |
} | |
} | |
// Append message to chat with Markdown rendering | |
function appendMessage(role, content) { | |
const messageDiv = document.createElement('div'); | |
messageDiv.className = `message ${role === 'user' ? 'user-message' : 'ai-message'}`; | |
const icon = role === 'user' ? 'fa-user' : 'fa-robot'; | |
const name = role === 'user' ? 'You' : 'AI Assistant'; | |
messageDiv.innerHTML = ` | |
<div class="message-info"> | |
<span><i class="fas ${icon}"></i> ${name}</span> | |
</div> | |
<div class="message-content"> | |
${role === 'assistant' ? marked.parse(content) : escapeHtml(content)} | |
</div> | |
`; | |
chatMessages.appendChild(messageDiv); | |
chatMessages.scrollTop = chatMessages.scrollHeight; | |
} | |
// Simple HTML escape function for user messages | |
function escapeHtml(unsafe) { | |
return unsafe | |
.replace(/&/g, "&") | |
.replace(/</g, "<") | |
.replace(/>/g, ">") | |
.replace(/"/g, """) | |
.replace(/'/g, "'"); | |
} | |
// Show toast notification | |
function showToast(message, type) { | |
const toast = document.createElement('div'); | |
toast.className = `toast ${type}`; | |
toast.textContent = message; | |
document.body.appendChild(toast); | |
setTimeout(() => { | |
toast.classList.add('show'); | |
}, 10); | |
setTimeout(() => { | |
toast.classList.remove('show'); | |
setTimeout(() => { | |
document.body.removeChild(toast); | |
}, 300); | |
}, 3000); | |
} | |
// Event listeners for sending messages | |
sendButton.addEventListener('click', sendMessage); | |
chatInput.addEventListener('keydown', (e) => { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault(); | |
sendMessage(); | |
} | |
}); | |
// Add toast styles | |
const toastStyles = document.createElement('style'); | |
toastStyles.textContent = ` | |
.toast { | |
position: fixed; | |
bottom: 30px; | |
left: 50%; | |
transform: translateX(-50%); | |
padding: 12px 24px; | |
border-radius: 6px; | |
color: white; | |
font-size: 0.9rem; | |
opacity: 0; | |
transition: opacity 0.3s ease; | |
z-index: 1000; | |
} | |
.toast.show { | |
opacity: 1; | |
} | |
.toast.success { | |
background-color: var(--success-color); | |
} | |
.toast.error { | |
background-color: var(--error-color); | |
} | |
.toast.warning { | |
background-color: var(--warning-color); | |
color: var(--dark-color); | |
} | |
`; | |
document.head.appendChild(toastStyles); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> | |
</html> |