<!
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Chatbot - Powered by Gemini</title>
<meta name="description" content="Chat with our AI assistant powered by Gemini.
Get instant answers to your questions in a beautiful, responsive interface.">
<style>
/* CSS from your original request */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--background: 220 25% 97%;
--foreground: 220 20% 10%;
--card: 0 0% 100%;
--card-foreground: 220 20% 10%;
--primary: 210 100% 50%;
--primary-foreground: 0 0% 100%;
--secondary: 220 15% 95%;
--muted: 220 15% 95%;
--muted-foreground: 220 10% 40%;
--border: 220 15% 88%;
--input: 220 15% 88%;
--ring: 210 100% 50%;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(180deg, hsl(var(--background)), hsl(220 25%
94%));
min-height: 100vh;
color: hsl(var(--foreground));
}
.container {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 100%;
}
/* Header */
header {
background: hsl(var(--card));
border-bottom: 1px solid hsl(var(--border));
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.header-content {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.header-icon {
background: linear-gradient(135deg, hsl(210 100% 50%), hsl(190 100%
50%));
padding: 0.5rem;
border-radius: 0.75rem;
box-shadow: 0 0 20px hsla(210 100% 50% / 0.3);
}
.header-icon svg {
width: 24px;
height: 24px;
color: white;
}
h1 {
font-size: 1.5rem;
font-weight: bold;
color: hsl(var(--foreground));
}
.subtitle {
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
}
/* Main Chat Area */
main {
flex: 1;
overflow: hidden;
padding: 1.5rem 1rem;
}
.chat-wrapper {
max-width: 56rem;
margin: 0 auto;
height: 100%;
}
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
background: hsl(var(--card));
border-radius: 1.5rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
border: 1px solid hsl(var(--border));
overflow: hidden;
}
.messages-area {
flex: 1;
overflow-y: auto;
padding: 1.5rem 1rem;
}
.messages-area::-webkit-scrollbar {
width: 8px;
}
.messages-area::-webkit-scrollbar-track {
background: transparent;
}
.messages-area::-webkit-scrollbar-thumb {
background: hsl(var(--border));
border-radius: 4px;
}
/* Welcome Screen */
.welcome {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
animation: fadeIn 0.6s ease-out;
}
.welcome-icon {
background: linear-gradient(135deg, hsl(210 100% 50%), hsl(190 100%
50%));
padding: 1rem;
border-radius: 1rem;
margin-bottom: 1rem;
box-shadow: 0 0 30px hsla(210 100% 50% / 0.3);
}
.welcome-icon svg {
width: 48px;
height: 48px;
color: white;
}
.welcome h2 {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.welcome p {
color: hsl(var(--muted-foreground));
max-width: 28rem;
}
/* Messages */
.message {
display: flex;
gap: 0.75rem;
margin-bottom: 1.5rem;
animation: slideUp 0.3s ease-out;
}
.[Link] {
flex-direction: row-reverse;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.[Link] .message-avatar {
background: linear-gradient(135deg, hsl(210 100% 50%), hsl(190 100%
50%));
box-shadow: 0 0 15px hsla(210 100% 50% / 0.3);
}
.[Link] .message-avatar {
background: hsl(var(--secondary));
}
.message-avatar svg {
width: 20px;
height: 20px;
color: white;
}
.[Link] .message-avatar svg {
color: hsl(var(--primary));
}
.message-content {
flex: 1;
max-width: 70%;
}
.[Link] .message-content {
text-align: right;
}
.message-bubble {
padding: 0.75rem 1rem;
border-radius: 1rem;
line-height: 1.5;
white-space: pre-wrap; /* Ensures line breaks from the API response are
respected */
}
.[Link] .message-bubble {
background: linear-gradient(135deg, hsl(210 100% 50%), hsl(190 100%
50%));
color: white;
border-bottom-right-radius: 0.25rem;
}
.[Link] .message-bubble {
background: hsl(var(--secondary));
color: hsl(var(--foreground));
border-bottom-left-radius: 0.25rem;
}
/* Typing Indicator */
.typing-indicator {
display: flex;
gap: 0.75rem;
margin-bottom: 1.5rem;
animation: slideUp 0.3s ease-out;
}
.typing-bubble {
background: hsl(var(--secondary));
padding: 0.75rem 1rem;
border-radius: 1rem;
border-bottom-left-radius: 0.25rem;
display: flex;
gap: 0.25rem;
}
.typing-dot {
width: 8px;
height: 8px;
background: hsl(var(--muted-foreground));
border-radius: 50%;
animation: typingDot 1.4s infinite;
}
.typing-dot:nth-child(2) {
animation-delay: 0.2s;
}
.typing-dot:nth-child(3) {
animation-delay: 0.4s;
}
/* Input Area */
.input-area {
border-top: 1px solid hsl(var(--border));
background: hsla(var(--background) / 0.5);
backdrop-filter: blur(8px);
padding: 1rem;
}
.input-container {
display: flex;
gap: 0.75rem;
align-items: flex-end;
}
textarea {
flex: 1;
padding: 0.75rem 1rem;
border: 1px solid hsl(var(--border));
border-radius: 1rem;
background: hsl(var(--card));
color: hsl(var(--foreground));
font-family: inherit;
font-size: 0.95rem;
resize: none;
max-height: 120px;
transition: border-color 0.2s;
}
textarea:focus {
outline: none;
border-color: hsl(var(--primary));
}
textarea::placeholder {
color: hsl(var(--muted-foreground));
}
button {
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, hsl(210 100% 50%), hsl(190 100%
50%));
color: white;
border: none;
border-radius: 1rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 0 15px hsla(210 100% 50% / 0.3);
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 0 25px hsla(210 100% 50% / 0.5);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.error-message {
background: hsl(0 70% 95%);
color: hsl(0 70% 40%);
padding: 0.75rem 1rem;
border-radius: 0.5rem;
margin: 1rem 0; /* Adjusted margin for better flow */
border-left: 4px solid hsl(0 70% 50%);
}
/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes typingDot {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.7;
}
30% {
transform: translateY(-8px);
opacity: 1;
}
}
/* Responsive */
@media (max-width: 768px) {
.message-content {
max-width: 85%;
}
h1 {
font-size: 1.25rem;
}
.welcome h2 {
font-size: 1.25rem;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="header-content">
<div class="header-icon">
<svg xmlns="[Link] width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-
linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2
0 0 1 2 2z"></path>
</svg>
</div>
<div>
<h1>AI Chatbot</h1>
<p class="subtitle">Powered by Gemini</p>
</div>
</div>
</header>
<main>
<div class="chat-wrapper">
<div class="chat-container">
<div class="messages-area" id="messagesArea">
<div class="welcome">
<div class="welcome-icon">
<svg xmlns="[Link] width="48"
height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0
1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
</div>
<h2>Welcome to AI Chatbot</h2>
<p>Start a conversation with our AI assistant. Ask
questions, get answers, and explore the possibilities!</p>
</div>
</div>
<div class="input-area">
<div class="input-container">
<textarea
id="messageInput"
placeholder="Type your message here..."
rows="1"
></textarea>
<button id="sendButton">Send</button>
</div>
</div>
</div>
</div>
</main>
</div>
<script>
// 🚨 IMPORTANT: REPLACE THIS KEY WITH YOUR ACTUAL GOOGLE AI API KEY 🚨
const GEMINI_API_KEY = "AIzaSyDJ5hbH0K2sgtIbMmQMwXrWDzkiSzVkXhE";
const MODEL_NAME = "gemini-2.5-flash";
// State
// The messages array will store the conversation history to maintain context
// It must use 'user' and 'model' roles, alternating.
let messages = [];
let isLoading = false;
// DOM Elements
const messagesArea = [Link]('messagesArea');
const messageInput = [Link]('messageInput');
const sendButton = [Link]('sendButton');
// Auto-resize textarea
[Link]('input', function() {
[Link] = 'auto';
[Link] = [Link]([Link], 120) + 'px';
});
// Handle Enter key
[Link]('keydown', function(e) {
if ([Link] === 'Enter' && ![Link]) {
[Link]();
sendMessage();
}
});
// Send button click
[Link]('click', sendMessage);
/**
* Creates the HTML structure for a single message.
* @param {('user'|'assistant')} role
* @param {string} content
* @returns {string} HTML string
*/
function createMessageHTML(role, content) {
const isUser = role === 'user';
// Use innerText to correctly handle HTML escaping, then replace \n for
line breaks.
const contentHTML = [Link](/\n/g, '<br>');
return `
<div class="message ${role}">
<div class="message-avatar">
${isUser ?
'<svg xmlns="[Link] width="20"
height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4
4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>' :
'<svg xmlns="[Link] width="20"
height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"></path><rect
width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path
d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg>'
}
</div>
<div class="message-content">
<div class="message-bubble">${contentHTML}</div>
</div>
</div>
`;
}
// Create typing indicator
function createTypingIndicator() {
return `
<div class="typing-indicator" id="typingIndicator">
<div class="message-avatar">
<svg xmlns="[Link] width="20" height="20"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-
linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"></path><rect width="16"
height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20
14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg>
</div>
<div class="typing-bubble">
<span class="typing-dot"></span>
<span class="typing-dot"></span>
<span class="typing-dot"></span>
</div>
</div>
`;
}
// Show error message in the chat flow
function showError(message) {
const errorHTML = `<div class="error-message">🚨 ${message}</div>`;
[Link]('beforeend', errorHTML);
scrollToBottom();
}
// Scroll to bottom
function scrollToBottom() {
[Link] = [Link];
}
// Update UI state
function updateUI() {
// Remove welcome screen if there are messages
const welcome = [Link]('.welcome');
if (welcome && [Link] > 0) {
[Link]();
}
// Update button state
[Link] = isLoading;
[Link] = isLoading;
}
/**
* Sends the user message to the Gemini API and handles the response.
*/
async function sendMessage() {
const userText = [Link]();
if (!userText || isLoading) return;
// 1. Setup UI and update history with user message
[Link]({ role: 'user', parts: [{ text: userText }] }); // Store in
the Gemini API format
[Link]('beforeend', createMessageHTML('user',
userText));
[Link] = '';
[Link] = 'auto'; // Reset textarea height
isLoading = true;
updateUI();
// 2. Show typing indicator
[Link]('beforeend', createTypingIndicator());
scrollToBottom();
const typingIndicator = [Link]('typingIndicator');
try {
// 3. Prepare API Call
// Note: This uses the standard fetch API to call the REST endpoint.
const url = `[Link]
{MODEL_NAME}:generateContent?key=${GEMINI_API_KEY}`;
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
// The body contains the conversation history
body: [Link]({ contents: messages })
});
// Remove typing indicator immediately after API response is received
if (typingIndicator) [Link]();
// 4. Handle HTTP Errors (e.g., 400, 403 - bad key, invalid request)
if (![Link]) {
const errorData = await [Link]().catch(() => ({}));
const errorMessage = [Link]?.message || `API Error: Status
${[Link]}`;
throw new Error(errorMessage);
}
let data = await [Link]();
let botReply = "No response text found from the AI.";
// 5. Robust Response Parsing for the official Gemini API format
if ([Link] && [Link] > 0) {
const candidate = [Link][0];
if ([Link]?.parts?.[0]?.text) {
botReply = [Link][0].text;
} else if ([Link] && [Link] !==
'STOP') {
// Handle cases where the response was blocked or incomplete
(e.g., SAFETY)
botReply = `[Response Blocked] The content may have violated
safety policies. Reason: ${[Link]}`;
}
}
// 6. FIX: Add assistant message to history using the correct 'model'
role
[Link]({ role: 'model', parts: [{ text: botReply }] });
// 7. Render message to UI using the 'assistant' class
[Link]('beforeend',
createMessageHTML('assistant', botReply));
scrollToBottom();
} catch (error) {
// Remove typing indicator if it failed before removal
if (typingIndicator) [Link]();
[Link]('Gemini API Error:', error);
showError([Link] || 'An unexpected error occurred. Check your
API key and console for details.');
// On failure, remove the last user message to prevent sending a
// potentially problematic context in the next turn
[Link]();
} finally {
isLoading = false;
updateUI();
}
}
// Initial UI update to set button state
updateUI();
</script>
</body>
</html>