Step 2 Gemini Chat Api
📘 Tutorial: Build a Gemini Chat App Using Your Own API Key
🔍 Overview — What We Will Build & How
✅ What we will build
A single-page Gemini Chat web app that:
-
Lets users generate and use their own Gemini API key
-
Stores the API key safely in the browser
-
Supports model selection (flash / flash-lite / pro)
-
Shows a “Thinking…” message while Gemini responds
-
Renders Markdown responses (code blocks, lists, etc.)
-
Allows users to copy Gemini responses with one click
🧩 How we will build it
-
One file:
index.html -
Technologies used:
- HTML (structure)
- Bootstrap (UI)
- JavaScript +
fetch(Gemini API) localStorage(save API key)marked.js(Markdown rendering)
We’ll do this in 3 easy steps.
🧩 STEP 1 — UI + API Key Guidance & Storage
What happens in this step
-
Show a guided API key setup screen
-
Explain how to generate a Gemini API key
-
Save the key in
localStorage -
Automatically skip setup if key already exists
-
Prepare the full chat UI (hidden initially)
How to know you’re on the right track
✅ You see the API key card
✅ Clicking Continue opens the chat
✅ Refreshing the page skips setup
✅ Step 1 Code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gemini Chat</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background:#f5f7fb; }
.center { min-height:100vh; display:flex; align-items:center; justify-content:center; }
.chat { height:90vh; }
.messages { flex:1; overflow:auto; background:#fff; padding:1rem; border-radius:.75rem; }
.msg { max-width:75%; padding:.5rem .8rem; border-radius:1rem; margin-bottom:.5rem; }
.user { margin-left:auto; background:#e7f0ff; }
.bot { background:#f1f1f1; }
textarea { resize:none; max-height:7.5rem; }
</style>
</head>
<body>
<!-- API KEY SETUP -->
<div id="setup" class="center">
<div class="card shadow-sm" style="max-width:420px;width:100%">
<div class="card-body text-center">
<h5 class="mb-3">Get free Gemini API Key</h5>
<p class="small text-muted mb-3">
1. Open <a href="https://aistudio.google.com/app/apikey" target="_blank">Google AI Studio</a><br>
2. Click <strong>Create API key</strong><br>
3. Copy & paste it below
</p>
<input id="apiKey" class="form-control mb-3 text-center" placeholder="Paste API Key">
<button class="btn btn-primary w-100" onclick="start()">Continue</button>
</div>
</div>
</div>
<!-- CHAT UI -->
<div id="chat" class="container d-none chat">
<div class="d-flex flex-column h-100 py-3">
<h5 class="text-center mb-2">💬 Gemini Chat</h5>
<div id="messages" class="messages mb-2"></div>
<textarea id="prompt" class="form-control mb-2" placeholder="Type message..."></textarea>
<button class="btn btn-primary" onclick="send()">Send</button>
</div>
</div>
<script>
const messages = document.getElementById("messages");
// Auto open chat if API key already saved
if (localStorage.GEMINI_KEY) showChat();
function start() {
const key = apiKey.value.trim();
if (!key) return alert("API key required");
localStorage.GEMINI_KEY = key;
showChat();
}
function showChat() {
setup.classList.add("d-none");
chat.classList.remove("d-none");
}
function send() {
alert("Next step: call Gemini API");
}
</script>
</body>
</html>
🧩 STEP 2 — Call Gemini API + Show “Thinking…” + Model Selection
What happens in this step
-
User message appears instantly
-
A “Thinking…” message is shown
-
Selected Gemini model is used
-
Gemini response replaces “Thinking…”
How to know you’re on the right track
✅ “Thinking…” appears while waiting
✅ Response comes from selected model
✅ Step 2 Code (replace only <script>)
<script>
const messages = document.getElementById("messages");
if (localStorage.GEMINI_KEY) showChat();
function start() {
const key = apiKey.value.trim();
if (!key) return alert("API key required");
localStorage.GEMINI_KEY = key;
showChat();
}
function showChat() {
setup.classList.add("d-none");
chat.classList.remove("d-none");
}
function add(text, cls) {
const d = document.createElement("div");
d.className = `msg ${cls}`;
d.textContent = text;
messages.appendChild(d);
messages.scrollTop = messages.scrollHeight;
return d;
}
async function send() {
const text = prompt.value.trim();
if (!text) return;
prompt.value = "";
add(text, "user");
const thinking = add("Thinking...", "bot");
const model = document.querySelector('input[name="model"]:checked')?.value || "gemini-2.5-flash";
try {
const res = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${localStorage.GEMINI_KEY}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [{ parts: [{ text }] }]
})
}
);
const data = await res.json();
thinking.remove();
add(data?.candidates?.[0]?.content?.parts?.[0]?.text || "No response", "bot");
} catch {
thinking.remove();
add("Error occurred", "bot");
}
}
</script>
🧩 STEP 3 — Markdown Rendering + Copy Response (Final)
What happens in this step
-
Gemini responses render as Markdown
-
Code blocks, lists, links are styled
-
Each bot message has a Copy button
How to know you’re on the right track
✅ Code blocks look formatted
✅ Clicking Copy copies the response
✅ FINAL STEP — Full Final Code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gemini Chat</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- marked (markdown renderer) -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
body { background:#f5f7fb; }
.center { min-height:100vh; display:flex; align-items:center; justify-content:center; }
.chat { height:90vh; }
.messages { flex:1; overflow:auto; background:#fff; padding:1rem; border-radius:.75rem; }
.msg { max-width:75%; padding:.5rem .8rem; border-radius:1rem; margin-bottom:.5rem; }
.user { margin-left:auto; background:#e7f0ff; }
.bot { background:#f1f1f1; }
.thinking { font-style:italic; opacity:.75; }
textarea { resize:none; max-height:7.5rem; }
/* markdown friendly */
.msg pre { margin:.5rem 0 0; padding:.75rem; border-radius:.5rem; background:#111; color:#eee; overflow:auto; }
.msg code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
.msg p { margin:0 0 .5rem; }
.msg p:last-child { margin-bottom:0; }
.msg ul, .msg ol { margin:.4rem 0 .4rem 1.25rem; }
.msg blockquote { margin:.5rem 0; padding-left:.75rem; border-left:3px solid #ccc; color:#555; }
.msg a { word-break: break-word; }
</style>
</head>
<body>
<!-- API KEY -->
<div id="setup" class="center">
<div class="card shadow-sm" style="max-width:420px;width:100%">
<div class="card-body text-center">
<h5 class="mb-3">Get free Gemini API Key</h5>
<p class="small text-muted mb-3">
1. Open <a href="https://aistudio.google.com/app/apikey" target="_blank">Google AI Studio</a><br>
2. Click <strong>Create API key</strong><br>
3. Copy & paste it below
</p>
<input id="apiKey" class="form-control mb-3 text-center" placeholder="Paste API Key">
<button class="btn btn-primary w-100" onclick="start()">Continue</button>
</div>
</div>
</div>
<!-- CHAT -->
<div id="chat" class="container d-none chat">
<div class="d-flex flex-column h-100 py-3">
<h5 class="text-center mb-2">💬 Gemini Chat</h5>
<div id="messages" class="messages shadow-sm mb-2"></div>
<textarea id="prompt" rows="1" class="form-control mb-2"
placeholder="Type message..."
oninput="this.style.height='auto';this.style.height=Math.min(this.scrollHeight,120)+'px'"></textarea>
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" id="m1" name="model" value="gemini-2.5-flash" checked>
<label class="form-check-label" for="m1">gemini-2.5-flash</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="m2" name="model" value="gemini-2.5-flash-lite">
<label class="form-check-label" for="m2">gemini-2.5-flash-lite</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="m3" name="model" value="gemini-2.5-pro">
<label class="form-check-label" for="m3">gemini-2.5-pro</label>
</div>
</div>
<button class="btn btn-primary" onclick="send()">Send</button>
</div>
</div>
</div>
<script>
const messages = document.getElementById("messages");
if (localStorage.GEMINI_KEY) showChat();
function start() {
const key = document.getElementById("apiKey").value.trim();
if (!key) return alert("API key required");
localStorage.GEMINI_KEY = key;
showChat();
}
function showChat() {
document.getElementById("setup").classList.add("d-none");
document.getElementById("chat").classList.remove("d-none");
}
function add(text, cls, asMarkdown = false) {
const wrap = document.createElement("div");
wrap.className = `msg ${cls}`;
const content = document.createElement("div");
if (asMarkdown) content.innerHTML = marked.parse(text);
else content.textContent = text;
wrap.appendChild(content);
if (cls.includes("bot")) {
const btn = document.createElement("button");
btn.className = "btn btn-sm btn-outline-secondary mt-2";
btn.textContent = "Copy";
btn.onclick = async () => {
try {
await navigator.clipboard.writeText(text);
btn.textContent = "Copied!";
setTimeout(() => (btn.textContent = "Copy"), 800);
} catch {
btn.textContent = "Copy failed";
setTimeout(() => (btn.textContent = "Copy"), 800);
}
};
wrap.appendChild(btn);
}
messages.appendChild(wrap);
messages.scrollTop = messages.scrollHeight;
return wrap;
}
async function send() {
const input = document.getElementById("prompt");
const text = input.value.trim();
if (!text) return;
input.value = "";
add(text, "user");
const thinkingEl = add("Thinking...", "bot thinking");
const model = document.querySelector('input[name="model"]:checked').value;
try {
const r = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${localStorage.GEMINI_KEY}`,
{
method:"POST",
headers:{ "Content-Type":"application/json" },
body:JSON.stringify({ contents:[{ parts:[{ text }] }] })
}
);
const j = await r.json();
thinkingEl.remove();
const reply = j?.candidates?.[0]?.content?.parts?.[0]?.text || "No response";
add(reply, "bot", true);
} catch (e) {
thinkingEl.remove();
add("Error occurred", "bot");
}
}
</script>
</body>
</html>
🔜 Suggested Next To-Dos (Enhancements)
Multiple Google accounts & API key management
-
Create a small PHP script that stores multiple Gemini API keys in a JSON file (one key per Google account or project).
-
Load this JSON on the frontend and show a dropdown to select an account/key before chatting.
-
Use cases:
- Separate keys for personal / work / demo projects
- Switch keys when one hits rate limits
- Team usage where each member uses their own key
-
Example flow:
keys.json→{ "account_name": "API_KEY" }- PHP endpoint → returns available accounts
- UI dropdown → selects which key to use for the request
Basic backend hardening
-
Move API keys out of the browser into PHP (proxy requests to Gemini).
-
Log usage per account (date, model, token count).
-
Add simple limits per key (daily / hourly).
-
This prepares the app for real production use.
💡 Practical Use Cases With This Simple Code
Portfolio / Website Builder
-
Generate hero text, about sections, services descriptions
-
Create SEO-friendly content per section
-
Rewrite text in different tones (professional, friendly, minimal)
Reports & Documentation
-
Generate project reports, summaries, and explanations
-
Convert raw notes into structured Markdown reports
-
Create release notes or changelogs
Learning & Knowledge Assistant
-
Explain topics step by step (Java, SQL, Spring Boot, etc.)
-
Generate examples and simple explanations
-
Act as a personal study helper
App & Admin Panel Integration
-
Show AI-powered suggestions in forms
-
Help users write descriptions, comments, or messages
-
Provide inline explanations or help tips
Support & Help Systems
-
Answer FAQs using prompt-based guidance
-
Generate troubleshooting steps
-
Explain errors or logs in simple language