@@ -87,7 +90,6 @@
stopBtn: document.getElementById('stopBtn'),
clearBtn: document.getElementById('clearBtn'),
newChatBtn: document.getElementById('newChatBtn'),
- modelName: document.getElementById('modelName'),
modelBadge: document.getElementById('modelBadge'),
apiBadge: document.getElementById('apiBadge'),
statusText: document.getElementById('statusText'),
@@ -98,7 +100,14 @@
get apiKey() { return localStorage.getItem('openrouter_api_key') || DEFAULT_API_KEY || ''; },
set apiKey(v) { localStorage.setItem('openrouter_api_key', v || ''); updateStatus(); },
get model() { return localStorage.getItem('openrouter_model') || DEFAULT_MODEL; },
- set model(v) { localStorage.setItem('openrouter_model', v || DEFAULT_MODEL); el.modelName.textContent = store.model; },
+ set model(v) { localStorage.setItem('openrouter_model', v || DEFAULT_MODEL); },
+ };
+
+ // === Helpers ===
+ const getModelShort = () => {
+ const m = store.model || '';
+ const name = m.includes('/') ? m.split('/').pop() : m;
+ return name;
};
// === Runtime state ===
@@ -108,14 +117,29 @@
function addMessage(role, content) {
const row = document.createElement('div');
row.className = 'flex gap-3';
+
const avatar = document.createElement('div');
avatar.className = 'shrink-0 h-8 w-8 rounded-full flex items-center justify-center ' + (role === 'user' ? 'bg-gray-900 text-white' : 'bg-gray-200 text-gray-900');
avatar.textContent = role === 'user' ? '🧑' : '🤖';
+
+ const right = document.createElement('div');
+ right.className = 'flex flex-col';
+
+ if (role !== 'user') {
+ const name = document.createElement('div');
+ name.className = 'text-xs font-medium text-gray-500 mb-0.5';
+ name.textContent = getModelShort();
+ right.appendChild(name);
+ }
+
const bubble = document.createElement('div');
bubble.className = 'rounded-2xl px-4 py-3 text-[15px] leading-relaxed whitespace-pre-wrap ' + (role === 'user' ? 'bg-gray-50 border border-gray-200' : 'bg-gray-100');
bubble.textContent = content;
+
+ right.appendChild(bubble);
row.appendChild(avatar);
- row.appendChild(bubble);
+ row.appendChild(right);
+
el.messages.appendChild(row);
state.messages.push({ role, content });
queueMicrotask(() => el.chat.scrollTo({ top: el.chat.scrollHeight, behavior: 'smooth' }));
@@ -125,14 +149,27 @@
function addAssistantBubbleStreaming() {
const row = document.createElement('div');
row.className = 'flex gap-3';
+
const avatar = document.createElement('div');
avatar.className = 'shrink-0 h-8 w-8 rounded-full flex items-center justify-center bg-gray-200 text-gray-900';
avatar.textContent = '🤖';
+
+ const right = document.createElement('div');
+ right.className = 'flex flex-col';
+
+ const name = document.createElement('div');
+ name.className = 'text-xs font-medium text-gray-500 mb-0.5';
+ name.textContent = getModelShort();
+
const bubble = document.createElement('div');
bubble.className = 'rounded-2xl px-4 py-3 text-[15px] leading-relaxed bg-gray-100 text-gray-800 whitespace-pre-wrap';
bubble.textContent = '';
+
+ right.appendChild(name);
+ right.appendChild(bubble);
row.appendChild(avatar);
- row.appendChild(bubble);
+ row.appendChild(right);
+
el.messages.appendChild(row);
queueMicrotask(() => el.chat.scrollTo({ top: el.chat.scrollHeight }));
return bubble;
@@ -144,7 +181,7 @@
if (intro) {
const introRow = document.createElement('div');
introRow.className = 'flex gap-3';
- introRow.innerHTML = `
🤖
New chat
You're in a fresh conversation. Ask away.
`;
+ introRow.innerHTML = `
🤖
New chat
You're in a fresh conversation. Ask away.
`;
el.messages.appendChild(introRow);
}
}
@@ -162,7 +199,6 @@
const apiKey = store.apiKey;
const model = store.model;
if (!apiKey) {
- // Local demo fallback
const text = localDemoReply(state.messages[state.messages.length - 1]?.content || '');
onDelta(text, true);
return { ok: true, text };
@@ -188,7 +224,6 @@
throw new Error(errText || ('HTTP ' + res.status));
}
- // Stream via SSE-style chunks
const reader = res.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
@@ -226,7 +261,6 @@
} catch (e) {
console.error(e);
const msg = String(e?.message || e);
- // Minimal triage
let hint = 'Request failed.';
if (/401|unauthorized/i.test(msg)) hint = 'Unauthorized (check API key).';
else if (/429|rate/i.test(msg)) hint = 'Rate limited (slow down or upgrade).';
@@ -242,7 +276,7 @@
function localDemoReply(prompt) {
const tips = [
'Tip: click the key badge to set your OpenRouter API key.',
- 'Model is clickable at the top—tap the model badge to change it.',
+ 'Click the robot to change the model.',
'New chats are stateless here—no history is kept.'
];
const tip = tips[Math.floor(Math.random() * tips.length)];
@@ -266,20 +300,13 @@
const assistantBubble = addAssistantBubbleStreaming();
- const res = await askOpenRouterStreaming((delta, done) => {
+ await askOpenRouterStreaming((delta, done) => {
assistantBubble.textContent += delta;
if (done) {
el.sendBtn.disabled = false;
el.stopBtn.classList.add('hidden');
state.busy = false;
- // ensure assistant message stored
- const lastIdx = state.messages.length - 1;
- // replace temp assistant if last is not assistant yet
- if (state.messages[lastIdx]?.role !== 'assistant') {
- state.messages.push({ role: 'assistant', content: assistantBubble.textContent });
- } else {
- state.messages[lastIdx].content = assistantBubble.textContent;
- }
+ state.messages.push({ role: 'assistant', content: assistantBubble.textContent });
queueMicrotask(() => el.chat.scrollTo({ top: el.chat.scrollHeight, behavior: 'smooth' }));
}
});
@@ -302,9 +329,9 @@
// Clear / New
el.clearBtn.addEventListener('click', () => clearChat(false));
- document.getElementById('newChatBtn')?.addEventListener('click', () => clearChat(true));
+ el.newChatBtn.addEventListener('click', () => clearChat(true));
- // Model badge click
+ // Model badge click (robot)
el.modelBadge.addEventListener('click', () => {
const input = prompt('Enter model name (e.g., openai/gpt-4o):', store.model);
if (input === null) return;
@@ -325,14 +352,13 @@
const url = new URL(location.href);
const key = url.searchParams.get('key');
const model = url.searchParams.get('model');
- if (key) store.apiKey = key; // transient: saved to localStorage by design
+ if (key) store.apiKey = key;
if (model) store.model = model;
}
function init() {
initFromQuery();
updateStatus();
- el.modelName.textContent = store.model;
}
init();