AI-Character-Chat / index.html
Lyon28's picture
Upload index.html
bb995b4 verified
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Character AI Chat</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: url('background.png'), linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-size: cover;
background-attachment: fixed;
height: 100vh;
overflow: hidden;
}
.chat-container {
height: 100vh;
display: flex;
flex-direction: column;
max-width: 100%;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
}
/* Header */
.chat-header {
background: #075e54;
color: white;
padding: 10px 16px;
display: flex;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 12px;
background: url('avatar.png'), linear-gradient(45deg, #25d366, #128c7e);
background-size: cover;
background-position: center;
}
.header-info {
flex: 1;
}
.char-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 2px;
}
.status {
font-size: 13px;
opacity: 0.8;
color: #dcf8c6;
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
}
.three-dots {
cursor: pointer;
padding: 8px;
border-radius: 50%;
transition: background 0.2s;
}
.three-dots:hover {
background: rgba(255,255,255,0.1);
}
/* Settings Popup */
.settings-popup {
display: none;
position: absolute;
top: 60px;
right: 16px;
background: white;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
min-width: 280px;
z-index: 1000;
padding: 8px 0;
}
.settings-popup.show {
display: block;
animation: popupIn 0.2s ease-out;
}
@keyframes popupIn {
from { opacity: 0; transform: translateY(-10px) scale(0.95); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.settings-section {
padding: 12px 20px;
border-bottom: 1px solid #eee;
}
.settings-section:last-child {
border-bottom: none;
}
.settings-label {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.model-select, .input-field {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
background: #f8f9fa;
}
.input-field {
margin-top: 4px;
}
/* Chat Body */
.chat-body {
flex: 1;
overflow-y: auto;
padding: 20px 16px;
background: url('background.png'),
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3), transparent),
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3), transparent),
linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-size: cover, 400px 400px, 400px 400px, cover;
background-attachment: fixed;
}
/* Message Bubbles */
.message {
display: flex;
margin-bottom: 12px;
animation: messageSlide 0.3s ease-out;
}
@keyframes messageSlide {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.message.user {
justify-content: flex-end;
}
.message.char {
justify-content: flex-start;
}
.message-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin: 0 8px;
background: url('avatar.png'), linear-gradient(45deg, #25d366, #128c7e);
background-size: cover;
background-position: center;
align-self: flex-end;
}
.message.user .message-avatar {
background: linear-gradient(45deg, #0084ff, #00a0ff);
}
.bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
position: relative;
word-wrap: break-word;
backdrop-filter: blur(10px);
}
.message.user .bubble {
background: linear-gradient(135deg, #0084ff, #00a0ff);
color: white;
border-bottom-right-radius: 6px;
}
.message.char .bubble {
background: rgba(255, 255, 255, 0.95);
color: #333;
border-bottom-left-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.message-text {
font-size: 16px;
line-height: 1.4;
margin-bottom: 6px;
}
.message-meta {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
font-size: 12px;
opacity: 0.7;
}
.timestamp {
color: inherit;
}
.checkmarks {
color: #4fc3f7;
font-weight: bold;
}
.message.char .checkmarks {
display: none;
}
/* Typing Indicator */
.typing-indicator {
display: none;
align-items: center;
margin-bottom: 12px;
}
.typing-indicator.show {
display: flex;
}
.typing-dots {
background: rgba(255, 255, 255, 0.95);
border-radius: 18px;
padding: 12px 16px;
margin-left: 48px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.typing-dots span {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #999;
margin-right: 4px;
animation: typing 2s infinite;
}
.typing-dots span:nth-child(2) { animation-delay: 0.2s; }
.typing-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-10px); opacity: 1; }
}
/* Footer */
.chat-footer {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 12px 16px;
border-top: 1px solid rgba(0,0,0,0.1);
position: sticky;
bottom: 0;
}
.input-container {
display: flex;
align-items: flex-end;
gap: 8px;
}
.emoji-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
padding: 8px;
border-radius: 50%;
transition: background 0.2s;
}
.emoji-btn:hover {
background: rgba(0,0,0,0.05);
}
.message-input {
flex: 1;
min-height: 40px;
max-height: 120px;
padding: 10px 16px;
border: 1px solid #ddd;
border-radius: 20px;
font-size: 16px;
font-family: inherit;
resize: none;
outline: none;
background: white;
transition: border-color 0.2s;
}
.message-input:focus {
border-color: #075e54;
}
.send-btn {
background: #075e54;
color: white;
border: none;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
font-size: 18px;
}
.send-btn:hover {
background: #128c7e;
transform: scale(1.05);
}
.send-btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
/* Emoji Picker */
.emoji-picker {
display: none;
position: absolute;
bottom: 70px;
left: 16px;
background: white;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
padding: 16px;
max-width: 300px;
z-index: 1000;
}
.emoji-picker.show {
display: block;
animation: popupIn 0.2s ease-out;
}
.emoji-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 8px;
max-height: 200px;
overflow-y: auto;
}
.emoji-item {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
padding: 8px;
border-radius: 8px;
transition: background 0.2s;
}
.emoji-item:hover {
background: #f0f0f0;
}
/* Responsive */
@media (max-width: 768px) {
.chat-container {
height: 100vh;
}
.bubble {
max-width: 85%;
}
.settings-popup {
right: 8px;
min-width: 260px;
}
}
/* Scrollbar Styling */
.chat-body::-webkit-scrollbar {
width: 6px;
}
.chat-body::-webkit-scrollbar-track {
background: transparent;
}
.chat-body::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.2);
border-radius: 3px;
}
.chat-body::-webkit-scrollbar-thumb:hover {
background: rgba(0,0,0,0.3);
}
</style>
</head>
<body>
<div class="chat-container">
<!-- Header -->
<div class="chat-header">
<div class="avatar"></div>
<div class="header-info">
<div class="char-name" id="charName">Sayang</div>
<div class="status" id="status">online</div>
</div>
<div class="header-actions">
<div class="three-dots" onclick="toggleSettings()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="5" r="2"/>
<circle cx="12" cy="12" r="2"/>
<circle cx="12" cy="19" r="2"/>
</svg>
</div>
</div>
</div>
<!-- Settings Popup -->
<div class="settings-popup" id="settingsPopup">
<div class="settings-section">
<div class="settings-label">Model AI</div>
<select class="model-select" id="modelSelect">
<option value="distil-gpt-2">DistilGPT-2 ⚡</option>
<option value="gpt-2-tinny">GPT-2 Tinny ⚡</option>
<option value="bert-tinny">BERT Tinny 🎭</option>
<option value="distilbert-base-uncased">DistilBERT 🎭</option>
<option value="albert-base-v2">ALBERT Base 🎭</option>
<option value="electra-small">ELECTRA Small 🎭</option>
<option value="t5-small">T5 Small 🔄</option>
<option value="gpt-2">GPT-2 Standard</option>
<option value="tinny-llama">Tinny Llama</option>
<option value="pythia">Pythia</option>
<option value="gpt-neo">GPT-Neo</option>
</select>
</div>
<div class="settings-section">
<div class="settings-label">Karakter</div>
<input type="text" class="input-field" id="charNameInput" placeholder="Nama karakter" value="Sayang">
<input type="text" class="input-field" id="userNameInput" placeholder="Nama kamu" value="Kamu">
</div>
<div class="settings-section">
<div class="settings-label">Situasi & Lokasi</div>
<input type="text" class="input-field" id="situationInput" placeholder="Situasi" value="Santai">
<input type="text" class="input-field" id="locationInput" placeholder="Lokasi" value="Ruang tamu">
</div>
<div class="settings-section">
<div class="settings-label">Panjang Pesan</div>
<input type="range" class="input-field" id="maxLengthRange" min="50" max="300" value="150">
<div style="font-size: 12px; color: #666; margin-top: 4px;">
<span id="maxLengthValue">150</span> karakter maksimal
</div>
</div>
</div>
<!-- Chat Body -->
<div class="chat-body" id="chatBody">
<!-- Welcome Message -->
<div class="message char">
<div class="message-avatar"></div>
<div class="bubble">
<div class="message-text">Hai! Aku siap ngobrol sama kamu nih. Mau bahas apa hari ini? 😊</div>
<div class="message-meta">
<span class="timestamp" id="welcome-time"></span>
</div>
</div>
</div>
<!-- Typing Indicator -->
<div class="typing-indicator" id="typingIndicator">
<div class="message-avatar"></div>
<div class="typing-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
<!-- Footer -->
<div class="chat-footer">
<!-- Emoji Picker -->
<div class="emoji-picker" id="emojiPicker">
<div class="emoji-grid" id="emojiGrid"></div>
</div>
<div class="input-container">
<button class="emoji-btn" onclick="toggleEmojiPicker()">😊</button>
<textarea class="message-input" id="messageInput" placeholder="Ketik pesan..." rows="1"></textarea>
<button class="send-btn" id="sendBtn" onclick="sendMessage()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M2,21L23,12L2,3V10L17,12L2,14V21Z"/>
</svg>
</button>
</div>
</div>
</div>
<script>
// Configuration
const API_BASE = window.location.origin;
let isTyping = false;
let currentSettings = {
model: 'distil-gpt-2',
charName: 'Sayang',
userName: 'Kamu',
situation: 'Santai',
location: 'Ruang tamu',
maxLength: 150
};
// Emoji list
const emojis = [
'😊', '😃', '😄', '😁', '😆', '😅', '😂', '🤣',
'😉', '😊', '😇', '🥰', '😍', '🤩', '😘', '😗',
'😚', '😙', '😋', '😛', '😜', '🤪', '😝', '🤑',
'🤗', '🤭', '🤫', '🤔', '🤐', '🤨', '😐', '😑',
'😶', '😏', '😒', '🙄', '😬', '🤥', '😔', '😪',
'🤤', '😴', '😷', '🤒', '🤕', '🤢', '🤮', '🤧',
'🥵', '🥶', '🥴', '😵', '🤯', '🤠', '🥳', '😎',
'🤓', '🧐', '😕', '😟', '🙁', '☹️', '😮', '😯',
'😲', '😳', '🥺', '😦', '😧', '😨', '😰', '😥',
'😢', '😭', '😱', '😖', '😣', '😞', '😓', '😩',
'😫', '🥱', '😤', '😡', '😠', '🤬', '😈', '👿',
'💀', '☠️', '💩', '🤡', '👹', '👺', '👻', '👽'
];
// Initialize
document.addEventListener('DOMContentLoaded', function() {
initializeApp();
loadEmojiPicker();
setupEventListeners();
setWelcomeTime();
});
function initializeApp() {
// Load settings from elements
document.getElementById('charName').textContent = currentSettings.charName;
document.getElementById('charNameInput').value = currentSettings.charName;
document.getElementById('userNameInput').value = currentSettings.userName;
document.getElementById('situationInput').value = currentSettings.situation;
document.getElementById('locationInput').value = currentSettings.location;
document.getElementById('maxLengthRange').value = currentSettings.maxLength;
document.getElementById('maxLengthValue').textContent = currentSettings.maxLength;
document.getElementById('modelSelect').value = currentSettings.model;
}
function setupEventListeners() {
// Message input
const messageInput = document.getElementById('messageInput');
messageInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
messageInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
// Settings inputs
document.getElementById('charNameInput').addEventListener('input', updateCharName);
document.getElementById('maxLengthRange').addEventListener('input', updateMaxLength);
// Model select
document.getElementById('modelSelect').addEventListener('change', function() {
currentSettings.model = this.value;
});
// Click outside to close popups
document.addEventListener('click', function(e) {
if (!e.target.closest('.settings-popup') && !e.target.closest('.three-dots')) {
document.getElementById('settingsPopup').classList.remove('show');
}
if (!e.target.closest('.emoji-picker') && !e.target.closest('.emoji-btn')) {
document.getElementById('emojiPicker').classList.remove('show');
}
});
}
function setWelcomeTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('id-ID', {
hour: '2-digit',
minute: '2-digit'
});
document.getElementById('welcome-time').textContent = timeString;
}
function updateCharName() {
const newName = document.getElementById('charNameInput').value || 'Sayang';
currentSettings.charName = newName;
document.getElementById('charName').textContent = newName;
}
function updateMaxLength() {
const value = document.getElementById('maxLengthRange').value;
currentSettings.maxLength = parseInt(value);
document.getElementById('maxLengthValue').textContent = value;
}
function toggleSettings() {
const popup = document.getElementById('settingsPopup');
popup.classList.toggle('show');
}
function toggleEmojiPicker() {
const picker = document.getElementById('emojiPicker');
picker.classList.toggle('show');
}
function loadEmojiPicker() {
const grid = document.getElementById('emojiGrid');
emojis.forEach(emoji => {
const button = document.createElement('button');
button.className = 'emoji-item';
button.textContent = emoji;
button.onclick = () => insertEmoji(emoji);
grid.appendChild(button);
});
}
function insertEmoji(emoji) {
const input = document.getElementById('messageInput');
const start = input.selectionStart;
const end = input.selectionEnd;
const text = input.value;
input.value = text.substring(0, start) + emoji + text.substring(end);
input.selectionStart = input.selectionEnd = start + emoji.length;
input.focus();
document.getElementById('emojiPicker').classList.remove('show');
}
function getCurrentTime() {
const now = new Date();
return now.toLocaleTimeString('id-ID', {
hour: '2-digit',
minute: '2-digit'
});
}
function addMessage(text, isUser = false, showTime = true) {
const chatBody = document.getElementById('chatBody');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'char'}`;
const time = showTime ? getCurrentTime() : '';
const checkmarks = isUser ? '<span class="checkmarks">✓✓</span>' : '';
messageDiv.innerHTML = `
<div class="message-avatar"></div>
<div class="bubble">
<div class="message-text">${text}</div>
<div class="message-meta">
<span class="timestamp">${time}</span>
${checkmarks}
</div>
</div>
`;
chatBody.appendChild(messageDiv);
chatBody.scrollTop = chatBody.scrollHeight;
}
function showTyping() {
if (isTyping) return;
isTyping = true;
document.getElementById('status').textContent = 'mengetik...';
document.getElementById('typingIndicator').classList.add('show');
const chatBody = document.getElementById('chatBody');
chatBody.scrollTop = chatBody.scrollHeight;
}
function hideTyping() {
if (!isTyping) return;
isTyping = false;
document.getElementById('status').textContent = 'online';
document.getElementById('typingIndicator').classList.remove('show');
}
async function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (!message) return;
// Update settings from inputs
currentSettings.charName = document.getElementById('charNameInput').value || 'Sayang';
currentSettings.userName = document.getElementById('userNameInput').value || 'Kamu';
currentSettings.situation = document.getElementById('situationInput').value || 'Santai';
currentSettings.location = document.getElementById('locationInput').value || 'Ruang tamu';
// Add user message
addMessage(message, true);
input.value = '';
input.style.height = 'auto';
// Show typing
showTyping();
// Disable send button
const sendBtn = document.getElementById('sendBtn');
sendBtn.disabled = true;
try {
const response = await fetch(`${API_BASE}/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: message,
model: currentSettings.model,
situation: currentSettings.situation,
location: currentSettings.location,
char_name: currentSettings.charName,
user_name: currentSettings.userName,
max_length: currentSettings.maxLength
})
});
const data = await response.json();
// Simulate typing delay
await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000));
hideTyping();
if (data.status === 'success') {
addMessage(data.response);
} else {
addMessage('Maaf, ada masalah dengan sistem. Coba lagi ya! 😅');
}
} catch (error) {
hideTyping();
console.error('Error:', error);
addMessage('Ups, koneksi bermasalah. Coba lagi nanti ya! 🔄');
}
// Re-enable send button
sendBtn.disabled = false;
}
// Handle online/offline status
window.addEventListener('online', function() {
document.getElementById('status').textContent = 'online';
});
window.addEventListener('offline', function() {
if (!isTyping) {
document.getElementById('status').textContent = 'offline';
}
});
</script>
</body>
</html>