colana 1.0.0-beta.76 → 1.0.0-beta.78
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/bin/admin.js +14 -2
- package/package.json +1 -1
- package/public/app.js +168 -28
- package/public/index.html +6 -0
- package/public/styles.css +85 -0
package/bin/admin.js
CHANGED
|
@@ -156,12 +156,14 @@ async function createKey(flags) {
|
|
|
156
156
|
console.error(`\n ${icons.fail} ${c.error}ERROR: --name is required.${c.reset}\n`);
|
|
157
157
|
console.error(' Usage: colana admin create-key --name "Sarah" --type staff [--email x] [--expires 2026-12-31]\n');
|
|
158
158
|
process.exit(1);
|
|
159
|
+
return;
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
const validTypes = ['admin', 'staff', 'beta'];
|
|
162
163
|
if (!validTypes.includes(type)) {
|
|
163
164
|
console.error(`\n ${icons.fail} ${c.error}ERROR: Invalid type "${type}". Must be one of: ${validTypes.join(', ')}${c.reset}\n`);
|
|
164
165
|
process.exit(1);
|
|
166
|
+
return;
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
// Prefer server API (avoids sql.js in-memory DB conflict)
|
|
@@ -174,6 +176,7 @@ async function createKey(flags) {
|
|
|
174
176
|
} catch (err) {
|
|
175
177
|
console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset} ${c.muted}(via server)${c.reset}\n`);
|
|
176
178
|
process.exit(1);
|
|
179
|
+
return;
|
|
177
180
|
}
|
|
178
181
|
} else {
|
|
179
182
|
// Fallback: direct DB access (only safe when server is NOT running)
|
|
@@ -184,6 +187,7 @@ async function createKey(flags) {
|
|
|
184
187
|
} catch (err) {
|
|
185
188
|
console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset}\n`);
|
|
186
189
|
process.exit(1);
|
|
190
|
+
return;
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
|
|
@@ -225,6 +229,7 @@ async function listKeysCmd(flags) {
|
|
|
225
229
|
} catch (err) {
|
|
226
230
|
console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset} ${c.muted}(via server)${c.reset}\n`);
|
|
227
231
|
process.exit(1);
|
|
232
|
+
return;
|
|
228
233
|
}
|
|
229
234
|
} else {
|
|
230
235
|
await ensureDb();
|
|
@@ -232,7 +237,7 @@ async function listKeysCmd(flags) {
|
|
|
232
237
|
result = listKeys(filters);
|
|
233
238
|
}
|
|
234
239
|
|
|
235
|
-
if (!result.keys.length) {
|
|
240
|
+
if (!result || !result.keys || !result.keys.length) {
|
|
236
241
|
console.log('\n No keys found.\n');
|
|
237
242
|
return;
|
|
238
243
|
}
|
|
@@ -263,6 +268,7 @@ async function revokeKeyCmd(positional) {
|
|
|
263
268
|
console.error(`\n ${icons.fail} ${c.error}ERROR: Key ID is required.${c.reset}\n`);
|
|
264
269
|
console.error(' Usage: colana admin revoke-key <id>\n');
|
|
265
270
|
process.exit(1);
|
|
271
|
+
return;
|
|
266
272
|
}
|
|
267
273
|
|
|
268
274
|
// Prefer server API
|
|
@@ -275,6 +281,7 @@ async function revokeKeyCmd(positional) {
|
|
|
275
281
|
} catch (err) {
|
|
276
282
|
console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset} ${c.muted}(via server)${c.reset}\n`);
|
|
277
283
|
process.exit(1);
|
|
284
|
+
return;
|
|
278
285
|
}
|
|
279
286
|
} else {
|
|
280
287
|
await ensureDb();
|
|
@@ -285,6 +292,7 @@ async function revokeKeyCmd(positional) {
|
|
|
285
292
|
} catch (err) {
|
|
286
293
|
console.error(`\n ${icons.fail} ${c.error}${err.message}${c.reset}\n`);
|
|
287
294
|
process.exit(1);
|
|
295
|
+
return;
|
|
288
296
|
}
|
|
289
297
|
}
|
|
290
298
|
}
|
|
@@ -381,7 +389,11 @@ async function main() {
|
|
|
381
389
|
console.error(` ${icons.fail} Unknown command: ${c.value}${command}${c.reset}`);
|
|
382
390
|
console.error(` ${c.muted}Run "colana admin help" for usage.${c.reset}`);
|
|
383
391
|
process.exit(1);
|
|
392
|
+
return;
|
|
384
393
|
}
|
|
385
394
|
}
|
|
386
395
|
|
|
387
|
-
main()
|
|
396
|
+
main().catch((err) => {
|
|
397
|
+
console.error(` ${icons.fail} ${c.error}${err.message || err}${c.reset}`);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
});
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -325,8 +325,8 @@ function renderPersonalStrip() {
|
|
|
325
325
|
const preview = document.getElementById('personal-preview');
|
|
326
326
|
const launchBtn = document.getElementById('personal-launch-btn');
|
|
327
327
|
|
|
328
|
-
// Discovery state: not installed
|
|
329
|
-
const needsSetup = !pa.installed
|
|
328
|
+
// Discovery state: not installed AND not configured (truly needs setup)
|
|
329
|
+
const needsSetup = !pa.installed && !pa.configured;
|
|
330
330
|
strip.classList.toggle('needs-setup', needsSetup && pa.status !== 'running' && pa.status !== 'waiting');
|
|
331
331
|
|
|
332
332
|
// Status dot
|
|
@@ -342,7 +342,7 @@ function renderPersonalStrip() {
|
|
|
342
342
|
|
|
343
343
|
// Preview text
|
|
344
344
|
if (preview) {
|
|
345
|
-
if (!pa.installed) {
|
|
345
|
+
if (!pa.installed && !pa.configured) {
|
|
346
346
|
preview.textContent = 'Personal AI — Set up';
|
|
347
347
|
} else if (!pa.chatEnabled) {
|
|
348
348
|
preview.textContent = 'Personal AI — Add API key';
|
|
@@ -660,8 +660,8 @@ async function openPersonalPanel() {
|
|
|
660
660
|
const clearChatBtn = document.getElementById('personal-panel-clear-chat');
|
|
661
661
|
const composer = document.getElementById('personal-panel-composer');
|
|
662
662
|
|
|
663
|
-
// --- Inline Setup Mode: installed but no API key ---
|
|
664
|
-
if (pa.installed && !pa.chatEnabled) {
|
|
663
|
+
// --- Inline Setup Mode: installed/configured but no API key ---
|
|
664
|
+
if ((pa.installed || pa.configured) && !pa.chatEnabled) {
|
|
665
665
|
if (modeToggle) modeToggle.style.display = 'none';
|
|
666
666
|
if (clearChatBtn) clearChatBtn.style.display = 'none';
|
|
667
667
|
if (composer) composer.classList.add('hidden');
|
|
@@ -830,7 +830,7 @@ async function launchPersonalAgent() {
|
|
|
830
830
|
// shows "Add API key" with a "Set up" button that opens the panel with an
|
|
831
831
|
// inline key input card. Don't spawn (would hit preflight GATEWAY_AUTH_MISSING)
|
|
832
832
|
// and don't auto-open anything — let the user initiate via the strip/button.
|
|
833
|
-
if (state.personalAgent.installed && !state.personalAgent.chatEnabled) {
|
|
833
|
+
if ((state.personalAgent.installed || state.personalAgent.configured) && !state.personalAgent.chatEnabled) {
|
|
834
834
|
return;
|
|
835
835
|
}
|
|
836
836
|
// Guard: don't launch if another launch call is already in-flight
|
|
@@ -1287,9 +1287,10 @@ function renderPersonalPanelSetup() {
|
|
|
1287
1287
|
closePersonalPanel();
|
|
1288
1288
|
openSettingsModal();
|
|
1289
1289
|
setTimeout(() => {
|
|
1290
|
-
const section = document.getElementById('settings-personal-
|
|
1290
|
+
const section = document.getElementById('settings-personal-api-keys')
|
|
1291
|
+
|| document.getElementById('settings-personal-agents');
|
|
1291
1292
|
if (section) section.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1292
|
-
},
|
|
1293
|
+
}, 400);
|
|
1293
1294
|
};
|
|
1294
1295
|
settingsLink.appendChild(settingsAnchor);
|
|
1295
1296
|
card.appendChild(settingsLink);
|
|
@@ -1561,20 +1562,17 @@ async function sendPersonalChatMessage() {
|
|
|
1561
1562
|
addKeyBtn.textContent = 'Add API Key';
|
|
1562
1563
|
addKeyBtn.addEventListener('click', async (e) => {
|
|
1563
1564
|
e.preventDefault();
|
|
1564
|
-
resetModelConfigReady();
|
|
1565
1565
|
openSettingsModal();
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
}
|
|
1576
|
-
modelSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1577
|
-
}
|
|
1566
|
+
// Scroll to the dedicated API Keys section
|
|
1567
|
+
setTimeout(() => {
|
|
1568
|
+
const apiKeysSection = document.getElementById('settings-personal-api-keys');
|
|
1569
|
+
if (apiKeysSection) {
|
|
1570
|
+
apiKeysSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1571
|
+
// Focus the first key input for quick entry
|
|
1572
|
+
const firstInput = apiKeysSection.querySelector('.personal-api-key-input');
|
|
1573
|
+
if (firstInput) setTimeout(() => firstInput.focus(), 300);
|
|
1574
|
+
}
|
|
1575
|
+
}, 400);
|
|
1578
1576
|
});
|
|
1579
1577
|
assistantBubble.appendChild(addKeyBtn);
|
|
1580
1578
|
|
|
@@ -7221,6 +7219,9 @@ async function openSettingsModal() {
|
|
|
7221
7219
|
// Integration Hub status (non-blocking)
|
|
7222
7220
|
renderIntegrationsGrid();
|
|
7223
7221
|
|
|
7222
|
+
// Personal Agent API keys (non-blocking)
|
|
7223
|
+
renderPersonalApiKeys();
|
|
7224
|
+
|
|
7224
7225
|
// OpenClaw model config (non-blocking)
|
|
7225
7226
|
loadOpenClawModelConfig();
|
|
7226
7227
|
} catch (error) {
|
|
@@ -7283,6 +7284,145 @@ async function installCliAgent(provider, btn) {
|
|
|
7283
7284
|
}
|
|
7284
7285
|
}
|
|
7285
7286
|
|
|
7287
|
+
// =========================================================================
|
|
7288
|
+
// Personal Agent API Key Management (Settings)
|
|
7289
|
+
// =========================================================================
|
|
7290
|
+
|
|
7291
|
+
/**
|
|
7292
|
+
* Render the API Keys section in Settings > Personal AI Agents.
|
|
7293
|
+
* Always visible — shows provider key status and input for adding/updating keys.
|
|
7294
|
+
* Uses the same /openclaw/models/auth endpoint as the inline panel setup.
|
|
7295
|
+
*/
|
|
7296
|
+
async function renderPersonalApiKeys() {
|
|
7297
|
+
const container = document.getElementById('settings-personal-api-keys');
|
|
7298
|
+
if (!container) return;
|
|
7299
|
+
|
|
7300
|
+
const providers = [
|
|
7301
|
+
{ id: 'anthropic', label: 'Anthropic', envVar: 'ANTHROPIC_API_KEY', placeholder: 'sk-ant-...', defaultModel: 'anthropic/claude-sonnet-4-20250514' },
|
|
7302
|
+
{ id: 'openai', label: 'OpenAI', envVar: 'OPENAI_API_KEY', placeholder: 'sk-...', defaultModel: 'openai/gpt-4o' },
|
|
7303
|
+
{ id: 'google', label: 'Google', envVar: 'GOOGLE_API_KEY', placeholder: 'AIza...', defaultModel: 'google/gemini-2.0-flash' },
|
|
7304
|
+
{ id: 'groq', label: 'Groq', envVar: 'GROQ_API_KEY', placeholder: 'gsk_...', defaultModel: 'groq/llama-3.3-70b-versatile' },
|
|
7305
|
+
{ id: 'xai', label: 'xAI', envVar: 'XAI_API_KEY', placeholder: 'xai-...', defaultModel: 'xai/grok-3-mini' },
|
|
7306
|
+
{ id: 'mistral', label: 'Mistral', envVar: 'MISTRAL_API_KEY', placeholder: 'Paste key...', defaultModel: 'mistral/mistral-large-latest' },
|
|
7307
|
+
];
|
|
7308
|
+
|
|
7309
|
+
// Fetch current auth status to show which providers have keys configured
|
|
7310
|
+
let authProviders = null;
|
|
7311
|
+
try {
|
|
7312
|
+
const data = await api('/openclaw/models/status');
|
|
7313
|
+
authProviders = data.authProviders;
|
|
7314
|
+
} catch { /* best-effort */ }
|
|
7315
|
+
|
|
7316
|
+
container.textContent = '';
|
|
7317
|
+
|
|
7318
|
+
const grid = document.createElement('div');
|
|
7319
|
+
grid.className = 'personal-api-keys-grid';
|
|
7320
|
+
|
|
7321
|
+
for (const p of providers) {
|
|
7322
|
+
// Check if this provider has a key configured (from authProviders or env)
|
|
7323
|
+
const hasKey = authProviders
|
|
7324
|
+
? (authProviders[p.id]?.authenticated === true || authProviders[p.id]?.status === 'authenticated')
|
|
7325
|
+
: false;
|
|
7326
|
+
|
|
7327
|
+
const row = document.createElement('div');
|
|
7328
|
+
row.className = 'personal-api-key-row';
|
|
7329
|
+
|
|
7330
|
+
const infoDiv = document.createElement('div');
|
|
7331
|
+
infoDiv.className = 'personal-api-key-info';
|
|
7332
|
+
|
|
7333
|
+
const nameSpan = document.createElement('span');
|
|
7334
|
+
nameSpan.className = 'personal-api-key-name';
|
|
7335
|
+
nameSpan.textContent = p.label;
|
|
7336
|
+
infoDiv.appendChild(nameSpan);
|
|
7337
|
+
|
|
7338
|
+
const statusSpan = document.createElement('span');
|
|
7339
|
+
statusSpan.className = 'personal-api-key-status';
|
|
7340
|
+
if (hasKey) {
|
|
7341
|
+
statusSpan.classList.add('configured');
|
|
7342
|
+
statusSpan.textContent = 'Configured';
|
|
7343
|
+
} else {
|
|
7344
|
+
statusSpan.textContent = 'Not configured';
|
|
7345
|
+
}
|
|
7346
|
+
infoDiv.appendChild(statusSpan);
|
|
7347
|
+
|
|
7348
|
+
const actionDiv = document.createElement('div');
|
|
7349
|
+
actionDiv.className = 'personal-api-key-action';
|
|
7350
|
+
|
|
7351
|
+
const keyInput = document.createElement('input');
|
|
7352
|
+
keyInput.type = 'password';
|
|
7353
|
+
keyInput.className = 'personal-api-key-input';
|
|
7354
|
+
keyInput.placeholder = hasKey ? 'Enter new key to update...' : p.placeholder;
|
|
7355
|
+
|
|
7356
|
+
const saveBtn = document.createElement('button');
|
|
7357
|
+
saveBtn.type = 'button';
|
|
7358
|
+
saveBtn.className = 'btn btn-sm btn-primary personal-api-key-save';
|
|
7359
|
+
saveBtn.textContent = hasKey ? 'Update' : 'Save';
|
|
7360
|
+
|
|
7361
|
+
saveBtn.onclick = async () => {
|
|
7362
|
+
const keyValue = keyInput.value.trim();
|
|
7363
|
+
if (!keyValue) { showToast('Enter an API key', 'error'); return; }
|
|
7364
|
+
saveBtn.disabled = true;
|
|
7365
|
+
saveBtn.textContent = 'Saving...';
|
|
7366
|
+
|
|
7367
|
+
try {
|
|
7368
|
+
await api('/openclaw/models/auth', {
|
|
7369
|
+
method: 'POST',
|
|
7370
|
+
body: JSON.stringify({ provider: p.id, apiKey: keyValue }),
|
|
7371
|
+
});
|
|
7372
|
+
|
|
7373
|
+
// Auto-set a default model if this is the first key being added
|
|
7374
|
+
if (!hasKey && p.defaultModel) {
|
|
7375
|
+
saveBtn.textContent = 'Setting model...';
|
|
7376
|
+
try {
|
|
7377
|
+
await api('/openclaw/models/set', {
|
|
7378
|
+
method: 'POST',
|
|
7379
|
+
body: JSON.stringify({ model: p.defaultModel }),
|
|
7380
|
+
});
|
|
7381
|
+
} catch {
|
|
7382
|
+
// Model set failed — non-fatal, user can set manually
|
|
7383
|
+
}
|
|
7384
|
+
}
|
|
7385
|
+
|
|
7386
|
+
showToast(`${p.label} API key saved`, 'success');
|
|
7387
|
+
keyInput.value = '';
|
|
7388
|
+
|
|
7389
|
+
// Refresh the section and model config after gateway picks up the key
|
|
7390
|
+
setTimeout(() => {
|
|
7391
|
+
renderPersonalApiKeys();
|
|
7392
|
+
loadOpenClawModelConfig();
|
|
7393
|
+
loadPersonalAgentStatus();
|
|
7394
|
+
}, 2500);
|
|
7395
|
+
} catch (err) {
|
|
7396
|
+
showToast('Failed to save: ' + err.message, 'error');
|
|
7397
|
+
} finally {
|
|
7398
|
+
saveBtn.disabled = false;
|
|
7399
|
+
saveBtn.textContent = hasKey ? 'Update' : 'Save';
|
|
7400
|
+
}
|
|
7401
|
+
};
|
|
7402
|
+
|
|
7403
|
+
// Submit on Enter key
|
|
7404
|
+
keyInput.addEventListener('keydown', (e) => {
|
|
7405
|
+
if (e.key === 'Enter') { e.preventDefault(); saveBtn.click(); }
|
|
7406
|
+
});
|
|
7407
|
+
|
|
7408
|
+
actionDiv.appendChild(keyInput);
|
|
7409
|
+
actionDiv.appendChild(saveBtn);
|
|
7410
|
+
|
|
7411
|
+
row.appendChild(infoDiv);
|
|
7412
|
+
row.appendChild(actionDiv);
|
|
7413
|
+
grid.appendChild(row);
|
|
7414
|
+
}
|
|
7415
|
+
|
|
7416
|
+
container.appendChild(grid);
|
|
7417
|
+
|
|
7418
|
+
// Hint text
|
|
7419
|
+
const hint = document.createElement('p');
|
|
7420
|
+
hint.className = 'settings-hint';
|
|
7421
|
+
hint.style.cssText = 'margin-top: 8px; font-size: 0.78rem;';
|
|
7422
|
+
hint.textContent = 'Keys are encrypted and stored locally. Only one provider key is needed to get started.';
|
|
7423
|
+
container.appendChild(hint);
|
|
7424
|
+
}
|
|
7425
|
+
|
|
7286
7426
|
// =========================================================================
|
|
7287
7427
|
// OpenClaw Model Configuration
|
|
7288
7428
|
// =========================================================================
|
|
@@ -7902,7 +8042,7 @@ async function loadPersonalAgentStatus() {
|
|
|
7902
8042
|
const hint = document.createElement('p');
|
|
7903
8043
|
hint.className = 'settings-hint personal-auth-hint';
|
|
7904
8044
|
hint.style.cssText = 'margin-top: 6px; color: var(--warning, #e8a838); font-size: 0.82rem;';
|
|
7905
|
-
hint.textContent = '
|
|
8045
|
+
hint.textContent = 'Add an API key in the API Keys section below to enable chat.';
|
|
7906
8046
|
container.appendChild(hint);
|
|
7907
8047
|
}
|
|
7908
8048
|
|
|
@@ -10547,10 +10687,10 @@ function initEventListeners() {
|
|
|
10547
10687
|
const pa = state.personalAgent;
|
|
10548
10688
|
if (pa.panelOpen) {
|
|
10549
10689
|
closePersonalPanel();
|
|
10550
|
-
} else if (pa.installed && !pa.chatEnabled) {
|
|
10551
|
-
// Installed but no API key — open panel with inline setup
|
|
10690
|
+
} else if ((pa.installed || pa.configured) && !pa.chatEnabled) {
|
|
10691
|
+
// Installed/configured but no API key — open panel with inline setup
|
|
10552
10692
|
openPersonalPanel();
|
|
10553
|
-
} else if (pa.sessionId) {
|
|
10693
|
+
} else if (pa.sessionId || pa.chatEnabled) {
|
|
10554
10694
|
openPersonalPanel();
|
|
10555
10695
|
}
|
|
10556
10696
|
});
|
|
@@ -10561,8 +10701,8 @@ function initEventListeners() {
|
|
|
10561
10701
|
personalLaunchBtn.addEventListener('click', (e) => {
|
|
10562
10702
|
e.stopPropagation();
|
|
10563
10703
|
const pa = state.personalAgent;
|
|
10564
|
-
if (!pa.installed) {
|
|
10565
|
-
// Not installed — open Settings scrolled to Personal AI Agents section
|
|
10704
|
+
if (!pa.installed && !pa.configured) {
|
|
10705
|
+
// Not installed and not configured — open Settings scrolled to Personal AI Agents section
|
|
10566
10706
|
openSettingsModal();
|
|
10567
10707
|
setTimeout(() => {
|
|
10568
10708
|
const section = document.getElementById('settings-personal-agents');
|
|
@@ -10571,7 +10711,7 @@ function initEventListeners() {
|
|
|
10571
10711
|
return;
|
|
10572
10712
|
}
|
|
10573
10713
|
if (!pa.chatEnabled) {
|
|
10574
|
-
// Installed but no API key — open panel with inline setup
|
|
10714
|
+
// Installed/configured but no API key — open panel with inline setup
|
|
10575
10715
|
openPersonalPanel();
|
|
10576
10716
|
return;
|
|
10577
10717
|
}
|
package/public/index.html
CHANGED
|
@@ -910,6 +910,12 @@
|
|
|
910
910
|
<!-- Populated dynamically by app.js — personal agents only -->
|
|
911
911
|
</div>
|
|
912
912
|
|
|
913
|
+
<div class="settings-section-title">API Keys</div>
|
|
914
|
+
<p class="settings-hint" style="margin-bottom: 10px;">Add or update API keys to connect your Personal Agent to AI providers.</p>
|
|
915
|
+
<div id="settings-personal-api-keys" class="personal-api-keys-section">
|
|
916
|
+
<!-- Populated dynamically by app.js -->
|
|
917
|
+
</div>
|
|
918
|
+
|
|
913
919
|
<div class="settings-section-title">Model Configuration</div>
|
|
914
920
|
<p class="settings-hint" style="margin-bottom: 10px;">Configure the default AI model for your Personal Agent.</p>
|
|
915
921
|
<div id="settings-openclaw-model" class="openclaw-model-config">
|
package/public/styles.css
CHANGED
|
@@ -7751,6 +7751,91 @@ select.form-input option {
|
|
|
7751
7751
|
background: var(--bg-secondary);
|
|
7752
7752
|
}
|
|
7753
7753
|
|
|
7754
|
+
/* --- Settings: Personal Agent API Keys --- */
|
|
7755
|
+
.personal-api-keys-section {
|
|
7756
|
+
margin: 8px 0;
|
|
7757
|
+
}
|
|
7758
|
+
|
|
7759
|
+
.personal-api-keys-grid {
|
|
7760
|
+
display: flex;
|
|
7761
|
+
flex-direction: column;
|
|
7762
|
+
gap: 6px;
|
|
7763
|
+
}
|
|
7764
|
+
|
|
7765
|
+
.personal-api-key-row {
|
|
7766
|
+
display: flex;
|
|
7767
|
+
align-items: center;
|
|
7768
|
+
justify-content: space-between;
|
|
7769
|
+
gap: 12px;
|
|
7770
|
+
padding: 8px 12px;
|
|
7771
|
+
background: var(--bg-secondary, #1a1a2e);
|
|
7772
|
+
border: 1px solid var(--border-color, #2a2a4a);
|
|
7773
|
+
border-radius: 8px;
|
|
7774
|
+
transition: border-color 0.15s ease;
|
|
7775
|
+
}
|
|
7776
|
+
|
|
7777
|
+
.personal-api-key-row:hover {
|
|
7778
|
+
border-color: var(--border-hover, #3a3a5a);
|
|
7779
|
+
}
|
|
7780
|
+
|
|
7781
|
+
.personal-api-key-info {
|
|
7782
|
+
display: flex;
|
|
7783
|
+
flex-direction: column;
|
|
7784
|
+
gap: 2px;
|
|
7785
|
+
min-width: 100px;
|
|
7786
|
+
flex-shrink: 0;
|
|
7787
|
+
}
|
|
7788
|
+
|
|
7789
|
+
.personal-api-key-name {
|
|
7790
|
+
font-size: 0.88rem;
|
|
7791
|
+
font-weight: 600;
|
|
7792
|
+
color: var(--text-primary, #e0e0e0);
|
|
7793
|
+
}
|
|
7794
|
+
|
|
7795
|
+
.personal-api-key-status {
|
|
7796
|
+
font-size: 0.75rem;
|
|
7797
|
+
color: var(--text-muted, #888);
|
|
7798
|
+
}
|
|
7799
|
+
|
|
7800
|
+
.personal-api-key-status.configured {
|
|
7801
|
+
color: var(--success, #4caf50);
|
|
7802
|
+
}
|
|
7803
|
+
|
|
7804
|
+
.personal-api-key-action {
|
|
7805
|
+
display: flex;
|
|
7806
|
+
align-items: center;
|
|
7807
|
+
gap: 6px;
|
|
7808
|
+
flex: 1;
|
|
7809
|
+
min-width: 0;
|
|
7810
|
+
}
|
|
7811
|
+
|
|
7812
|
+
.personal-api-key-input {
|
|
7813
|
+
flex: 1;
|
|
7814
|
+
min-width: 0;
|
|
7815
|
+
padding: 6px 10px;
|
|
7816
|
+
border-radius: 6px;
|
|
7817
|
+
border: 1px solid var(--border-color, #2a2a4a);
|
|
7818
|
+
background: var(--bg-primary, #0d0d1a);
|
|
7819
|
+
color: var(--text-primary, #e0e0e0);
|
|
7820
|
+
font-size: 0.82rem;
|
|
7821
|
+
font-family: inherit;
|
|
7822
|
+
transition: border-color 0.15s ease;
|
|
7823
|
+
}
|
|
7824
|
+
|
|
7825
|
+
.personal-api-key-input:focus {
|
|
7826
|
+
outline: none;
|
|
7827
|
+
border-color: var(--accent, #7c4dff);
|
|
7828
|
+
}
|
|
7829
|
+
|
|
7830
|
+
.personal-api-key-input::placeholder {
|
|
7831
|
+
color: var(--text-muted, #666);
|
|
7832
|
+
}
|
|
7833
|
+
|
|
7834
|
+
.personal-api-key-save {
|
|
7835
|
+
flex-shrink: 0;
|
|
7836
|
+
white-space: nowrap;
|
|
7837
|
+
}
|
|
7838
|
+
|
|
7754
7839
|
/* --- Settings: OpenClaw Model Config --- */
|
|
7755
7840
|
.openclaw-model-config {
|
|
7756
7841
|
margin: 8px 0;
|