diff --git a/zportal-chat.html b/zportal-chat.html
index f853de8..14f70ac 100644
--- a/zportal-chat.html
+++ b/zportal-chat.html
@@ -1148,6 +1148,16 @@ function setStreaming(on){
// ── Send Message (SSE Streaming) ──
async function sendMessage(text){
if(!text||!activeProvider||streaming) return;
+ // Check if provider has an API key
+ if(!activeProvider.api_key){
+ hideWelcome();
+ history.push({role:'user',content:text});
+ addMessage(text,'user',null);
+ addMessage('No API key configured for '+activeProvider.name+'. Click the key/edit icon next to the provider in the sidebar to add your API key, or select a provider that has one.','assistant',{error:true});
+ $('message').value='';
+ $('message').style.height='auto';
+ return;
+ }
if(!currentSessionId) currentSessionId='sess-'+Date.now();
history.push({role:'user',content:text});
addMessage(text,'user',null);
@@ -1178,8 +1188,12 @@ async function sendMessage(text){
const chunk=JSON.parse(line.slice(5));
if(chunk.type==='done'){ streamDone=true; break; }
if(chunk.type==='error'){
- if(!bubbleEl) bubbleEl=addMessage(chunk.delta,'assistant',{error:true});
- else bubbleEl.innerHTML+=esc(chunk.delta);
+ let errMsg=chunk.delta||'Unknown error';
+ if(errMsg.includes('HTTP 401')) errMsg='Authentication failed — the API key for this provider is missing or invalid. Open Settings to configure it.';
+ else if(errMsg.includes('HTTP 429')) errMsg='Rate limited — too many requests. Wait a moment and try again.';
+ else if(errMsg.includes('HTTP 403')) errMsg='Access denied — check your API key and permissions.';
+ if(!bubbleEl) bubbleEl=addMessage(errMsg,'assistant',{error:true});
+ else bubbleEl.innerHTML+=esc(errMsg);
continue;
}
if(chunk.type==='delta'){