colana 1.0.0-beta.39 → 1.0.0-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/app.js +91 -13
- package/public/styles.css +39 -0
- package/server/personal-agent-routes.js +20 -1
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -1512,19 +1512,51 @@ async function sendPersonalChatMessage() {
|
|
|
1512
1512
|
if (textSpan) textSpan.remove();
|
|
1513
1513
|
|
|
1514
1514
|
assistantBubble.className = 'chat-bubble error';
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1515
|
+
|
|
1516
|
+
const isAuthError = err.code === 'GATEWAY_AUTH_FAILED' || err.code === 'GATEWAY_AUTH_MISSING';
|
|
1517
|
+
|
|
1518
|
+
if (isAuthError) {
|
|
1519
|
+
// Auth-specific error: show actionable message with "Add API Key" button
|
|
1520
|
+
const msgSpan = document.createElement('span');
|
|
1521
|
+
msgSpan.textContent = err.message || 'No valid API key found for your model provider.';
|
|
1522
|
+
assistantBubble.appendChild(msgSpan);
|
|
1523
|
+
|
|
1524
|
+
const addKeyBtn = document.createElement('button');
|
|
1525
|
+
addKeyBtn.className = 'chat-error-action-btn';
|
|
1526
|
+
addKeyBtn.textContent = 'Add API Key';
|
|
1527
|
+
addKeyBtn.addEventListener('click', (e) => {
|
|
1528
|
+
e.preventDefault();
|
|
1529
|
+
openSettingsModal();
|
|
1530
|
+
// Scroll to Personal AI Agents section after modal opens
|
|
1531
|
+
setTimeout(() => {
|
|
1532
|
+
const modelSection = document.getElementById('openclaw-model-config');
|
|
1533
|
+
if (modelSection) modelSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1534
|
+
}, 300);
|
|
1535
|
+
});
|
|
1536
|
+
assistantBubble.appendChild(addKeyBtn);
|
|
1537
|
+
|
|
1538
|
+
if (err.fix) {
|
|
1539
|
+
const fixHint = document.createElement('div');
|
|
1540
|
+
fixHint.className = 'chat-error-fix-hint';
|
|
1541
|
+
fixHint.textContent = err.fix;
|
|
1542
|
+
assistantBubble.appendChild(fixHint);
|
|
1543
|
+
}
|
|
1544
|
+
} else {
|
|
1545
|
+
// Generic error: existing behavior
|
|
1546
|
+
const errorText = err.fix ? `${err.message} ${err.fix}` : (err.message || 'Failed to get response');
|
|
1547
|
+
assistantBubble.textContent = errorText;
|
|
1548
|
+
|
|
1549
|
+
const catchSettingsLink = document.createElement('a');
|
|
1550
|
+
catchSettingsLink.href = '#';
|
|
1551
|
+
catchSettingsLink.className = 'chat-error-settings-link';
|
|
1552
|
+
catchSettingsLink.textContent = 'Open Settings';
|
|
1553
|
+
catchSettingsLink.addEventListener('click', (e) => {
|
|
1554
|
+
e.preventDefault();
|
|
1555
|
+
openSettingsModal();
|
|
1556
|
+
});
|
|
1557
|
+
assistantBubble.appendChild(document.createTextNode(' \u2014 '));
|
|
1558
|
+
assistantBubble.appendChild(catchSettingsLink);
|
|
1559
|
+
}
|
|
1528
1560
|
|
|
1529
1561
|
const time = document.createElement('span');
|
|
1530
1562
|
time.className = 'chat-time';
|
|
@@ -7530,6 +7562,52 @@ async function loadOpenClawModelConfig() {
|
|
|
7530
7562
|
setOpenClawModel(selectedModel, applyBtn);
|
|
7531
7563
|
};
|
|
7532
7564
|
|
|
7565
|
+
// "Update API key" link — always visible for available models
|
|
7566
|
+
// Covers the case where OpenClaw CLI reports a model as available
|
|
7567
|
+
// but the gateway auth-profiles.json has a stale or missing key
|
|
7568
|
+
const updateKeyLink = document.createElement('a');
|
|
7569
|
+
updateKeyLink.href = '#';
|
|
7570
|
+
updateKeyLink.className = 'openclaw-update-key-link';
|
|
7571
|
+
updateKeyLink.textContent = 'Update API key for this provider';
|
|
7572
|
+
updateKeyLink.style.display = 'none';
|
|
7573
|
+
updateKeyLink.addEventListener('click', (e) => {
|
|
7574
|
+
e.preventDefault();
|
|
7575
|
+
const selectedModel = select.value;
|
|
7576
|
+
const info = modelAvailability[selectedModel];
|
|
7577
|
+
const authRowEl = document.getElementById('openclaw-auth-inline');
|
|
7578
|
+
const envVar = (info && providerEnvMap) ? providerEnvMap[info.provider] : null;
|
|
7579
|
+
if (envVar) {
|
|
7580
|
+
document.getElementById('openclaw-auth-label').textContent = `${envVar}:`;
|
|
7581
|
+
document.getElementById('openclaw-auth-key-input').value = '';
|
|
7582
|
+
document.getElementById('openclaw-auth-key-input').placeholder = `Enter ${info.provider} API key...`;
|
|
7583
|
+
}
|
|
7584
|
+
authRowEl.classList.add('visible');
|
|
7585
|
+
setTimeout(() => {
|
|
7586
|
+
authRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
7587
|
+
const keyInput = document.getElementById('openclaw-auth-key-input');
|
|
7588
|
+
if (keyInput) keyInput.focus();
|
|
7589
|
+
}, 50);
|
|
7590
|
+
});
|
|
7591
|
+
container.appendChild(updateKeyLink);
|
|
7592
|
+
|
|
7593
|
+
// Show/hide the update key link based on model selection
|
|
7594
|
+
const updateKeyLinkVisibility = () => {
|
|
7595
|
+
const selectedModel = select.value;
|
|
7596
|
+
const info = modelAvailability[selectedModel];
|
|
7597
|
+
// Show link when a model is selected and has a known provider with env var mapping,
|
|
7598
|
+
// but only when the auth row is NOT already mandatorily visible (i.e. model is available)
|
|
7599
|
+
const authRowEl = document.getElementById('openclaw-auth-inline');
|
|
7600
|
+
const authVisible = authRowEl && authRowEl.classList.contains('visible');
|
|
7601
|
+
if (info && info.available && providerEnvMap && providerEnvMap[info.provider]) {
|
|
7602
|
+
updateKeyLink.style.display = authVisible ? 'none' : '';
|
|
7603
|
+
} else {
|
|
7604
|
+
updateKeyLink.style.display = 'none';
|
|
7605
|
+
}
|
|
7606
|
+
};
|
|
7607
|
+
select.addEventListener('change', updateKeyLinkVisibility);
|
|
7608
|
+
// Run once on initial render
|
|
7609
|
+
setTimeout(updateKeyLinkVisibility, 0);
|
|
7610
|
+
|
|
7533
7611
|
// Save & Apply — save key then set model
|
|
7534
7612
|
authSaveBtn.onclick = async () => {
|
|
7535
7613
|
const selectedModel = select.value;
|
package/public/styles.css
CHANGED
|
@@ -7466,6 +7466,30 @@ select.form-input option {
|
|
|
7466
7466
|
color: var(--accent-hover, #7c8aff);
|
|
7467
7467
|
}
|
|
7468
7468
|
|
|
7469
|
+
.chat-error-action-btn {
|
|
7470
|
+
display: inline-block;
|
|
7471
|
+
margin: 8px 0 4px;
|
|
7472
|
+
padding: 5px 14px;
|
|
7473
|
+
background: var(--accent, #6366f1);
|
|
7474
|
+
color: #fff;
|
|
7475
|
+
border: none;
|
|
7476
|
+
border-radius: 6px;
|
|
7477
|
+
font-size: 0.82em;
|
|
7478
|
+
font-weight: 500;
|
|
7479
|
+
cursor: pointer;
|
|
7480
|
+
transition: background 0.15s;
|
|
7481
|
+
}
|
|
7482
|
+
.chat-error-action-btn:hover {
|
|
7483
|
+
background: var(--accent-hover, #7c8aff);
|
|
7484
|
+
}
|
|
7485
|
+
|
|
7486
|
+
.chat-error-fix-hint {
|
|
7487
|
+
margin-top: 4px;
|
|
7488
|
+
font-size: 0.78em;
|
|
7489
|
+
color: var(--text-muted, #94a3b8);
|
|
7490
|
+
font-style: italic;
|
|
7491
|
+
}
|
|
7492
|
+
|
|
7469
7493
|
/* --- Notification Bubbles --- */
|
|
7470
7494
|
.chat-bubble.notification {
|
|
7471
7495
|
align-self: flex-start;
|
|
@@ -7831,6 +7855,21 @@ select.form-input option {
|
|
|
7831
7855
|
font-family: inherit;
|
|
7832
7856
|
}
|
|
7833
7857
|
|
|
7858
|
+
.openclaw-update-key-link {
|
|
7859
|
+
display: block;
|
|
7860
|
+
margin: 6px 0 10px;
|
|
7861
|
+
font-size: 0.75rem;
|
|
7862
|
+
color: var(--accent, #6366f1);
|
|
7863
|
+
cursor: pointer;
|
|
7864
|
+
text-decoration: none;
|
|
7865
|
+
opacity: 0.85;
|
|
7866
|
+
transition: opacity 0.15s;
|
|
7867
|
+
}
|
|
7868
|
+
.openclaw-update-key-link:hover {
|
|
7869
|
+
opacity: 1;
|
|
7870
|
+
text-decoration: underline;
|
|
7871
|
+
}
|
|
7872
|
+
|
|
7834
7873
|
.openclaw-auth-hint {
|
|
7835
7874
|
font-size: 0.72rem;
|
|
7836
7875
|
color: var(--text-muted);
|
|
@@ -130,8 +130,27 @@ export function registerPersonalAgentRoutes(app, { sensitiveLimiter }) {
|
|
|
130
130
|
console.error(`[personal-agent] Gateway ${gatewayRes.status}: ${errText.slice(0, 500)}`);
|
|
131
131
|
}
|
|
132
132
|
const status = gatewayRes.status;
|
|
133
|
+
|
|
134
|
+
// For 401, extract provider from current model for actionable guidance
|
|
135
|
+
if (status === 401) {
|
|
136
|
+
const currentModel = getOpenClawDefaultModel() || '';
|
|
137
|
+
const slash = currentModel.indexOf('/');
|
|
138
|
+
const provider = slash > 0 ? currentModel.substring(0, slash).toLowerCase() : null;
|
|
139
|
+
const providerLabel = provider || null;
|
|
140
|
+
const envVar = provider ? PROVIDER_ENV_MAP[provider] : null;
|
|
141
|
+
return res.status(401).json({
|
|
142
|
+
error: providerLabel
|
|
143
|
+
? `Authentication failed — your ${providerLabel} API key may be missing or invalid.`
|
|
144
|
+
: 'Authentication failed — your API key may be missing or invalid.',
|
|
145
|
+
code: 'GATEWAY_AUTH_FAILED',
|
|
146
|
+
fix: envVar
|
|
147
|
+
? `Add or update your ${envVar} in Settings > Personal AI Agents.`
|
|
148
|
+
: `Add your API key in Settings > Personal AI Agents.`,
|
|
149
|
+
provider: provider || null,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
133
153
|
const friendlyMessages = {
|
|
134
|
-
401: 'Authentication failed — check your API key configuration',
|
|
135
154
|
403: 'Access denied by gateway',
|
|
136
155
|
429: 'Rate limit exceeded — please try again shortly',
|
|
137
156
|
503: 'Personal agent is temporarily unavailable',
|