@luckydraw/cumulus 0.31.65 → 1.0.0
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/CHANGELOG.md +6 -555
- package/LICENSE +150 -0
- package/README.md +161 -17
- package/dist/gateway/adapters/webchat.d.ts +2 -0
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +22 -2
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/config.d.ts +17 -2
- package/dist/gateway/config.d.ts.map +1 -1
- package/dist/gateway/config.js +10 -3
- package/dist/gateway/config.js.map +1 -1
- package/dist/gateway/daemon.d.ts +3 -1
- package/dist/gateway/daemon.d.ts.map +1 -1
- package/dist/gateway/daemon.js +128 -39
- package/dist/gateway/daemon.js.map +1 -1
- package/dist/gateway/namespaces.d.ts +34 -0
- package/dist/gateway/namespaces.d.ts.map +1 -1
- package/dist/gateway/namespaces.js +58 -0
- package/dist/gateway/namespaces.js.map +1 -1
- package/dist/gateway/server.d.ts +8 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +150 -41
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/setup.d.ts +32 -0
- package/dist/gateway/setup.d.ts.map +1 -1
- package/dist/gateway/setup.js +23 -3
- package/dist/gateway/setup.js.map +1 -1
- package/dist/gateway/static/widget.js +897 -611
- package/dist/lib/gateway.d.ts +30 -8
- package/dist/lib/gateway.d.ts.map +1 -1
- package/dist/lib/gateway.js +36 -11
- package/dist/lib/gateway.js.map +1 -1
- package/dist/lib/history.d.ts +22 -0
- package/dist/lib/history.d.ts.map +1 -1
- package/dist/lib/history.js +59 -21
- package/dist/lib/history.js.map +1 -1
- package/dist/lib/huggingface-provider.d.ts.map +1 -1
- package/dist/lib/huggingface-provider.js +11 -3
- package/dist/lib/huggingface-provider.js.map +1 -1
- package/dist/lib/license.d.ts +76 -0
- package/dist/lib/license.d.ts.map +1 -0
- package/dist/lib/license.js +141 -0
- package/dist/lib/license.js.map +1 -0
- package/docs/agentic-harness-primer.md +283 -0
- package/docs/conditional-continuation.md +167 -0
- package/docs/web-app-agent-guide.md +520 -0
- package/examples/web-app-agent/README.md +187 -0
- package/examples/web-app-agent/agent/mcp-shim.js +105 -0
- package/examples/web-app-agent/gateway.config.example.json +52 -0
- package/examples/web-app-agent/package.json +13 -0
- package/examples/web-app-agent/public/agent/bridge-mount.js +75 -0
- package/examples/web-app-agent/public/agent/chat-client.js +104 -0
- package/examples/web-app-agent/public/agent/commands.js +250 -0
- package/examples/web-app-agent/public/agent/device-thread.js +48 -0
- package/examples/web-app-agent/public/agent/panel.css +107 -0
- package/examples/web-app-agent/public/agent/panel.js +369 -0
- package/examples/web-app-agent/public/app.js +250 -0
- package/examples/web-app-agent/public/index.html +111 -0
- package/examples/web-app-agent/server.js +242 -0
- package/package.json +7 -3
|
@@ -177,6 +177,7 @@
|
|
|
177
177
|
[
|
|
178
178
|
{ provider: 'openai', key: 'openaiApiKey' },
|
|
179
179
|
{ provider: 'huggingface', key: 'hfApiKey' },
|
|
180
|
+
{ provider: 'license', key: 'licenseKey' },
|
|
180
181
|
].forEach(function (entry) {
|
|
181
182
|
var credential = credentials[entry.provider] || {};
|
|
182
183
|
var value = String(credential.value || '').trim();
|
|
@@ -186,6 +187,48 @@
|
|
|
186
187
|
return payload;
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
// ─── Default-application rules (task 130, Option C) ─────────────────────────
|
|
191
|
+
// One gateway default across the whole catalog. Picking an Anthropic model as
|
|
192
|
+
// the gateway default forcibly couples the Claude sub-model default to it —
|
|
193
|
+
// when the gateway default IS Anthropic, `model: 'claude'` +
|
|
194
|
+
// `claudeModels[].default` are the same knob and cannot diverge.
|
|
195
|
+
function applyGatewayDefault(catalog, modelId) {
|
|
196
|
+
var chosen = null;
|
|
197
|
+
catalog.forEach(function (model) {
|
|
198
|
+
model.gatewayDefault = model.id === modelId;
|
|
199
|
+
if (model.gatewayDefault) chosen = model;
|
|
200
|
+
});
|
|
201
|
+
if (chosen && chosen.provider === 'anthropic') {
|
|
202
|
+
catalog.forEach(function (model) {
|
|
203
|
+
if (model.provider === 'anthropic') model.providerDefault = model.id === modelId;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return chosen;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function applyClaudeSubDefault(catalog, modelId) {
|
|
210
|
+
catalog.forEach(function (model) {
|
|
211
|
+
if (model.provider === 'anthropic') model.providerDefault = model.id === modelId;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// The Claude sub-model default is a visible control ONLY when it can actually
|
|
216
|
+
// diverge from the gateway default: the gateway default is non-Anthropic
|
|
217
|
+
// (else coupling forces equality) and there are ≥2 Anthropic models (else
|
|
218
|
+
// there is no choice to make).
|
|
219
|
+
function claudeSubDefaultVisible(catalog) {
|
|
220
|
+
var anthropicCount = 0;
|
|
221
|
+
for (var i = 0; i < catalog.length; i++) {
|
|
222
|
+
if (catalog[i].provider === 'anthropic') anthropicCount++;
|
|
223
|
+
}
|
|
224
|
+
if (anthropicCount < 2) return false;
|
|
225
|
+
var gatewayDefault = null;
|
|
226
|
+
for (var j = 0; j < catalog.length; j++) {
|
|
227
|
+
if (catalog[j].gatewayDefault) gatewayDefault = catalog[j];
|
|
228
|
+
}
|
|
229
|
+
return !gatewayDefault || gatewayDefault.provider !== 'anthropic';
|
|
230
|
+
}
|
|
231
|
+
|
|
189
232
|
// ─── Styles ─────────────────────────────────────────────────────────────────
|
|
190
233
|
var STYLES = [
|
|
191
234
|
'.cumulus-widget * { box-sizing: border-box; margin: 0; padding: 0; }',
|
|
@@ -1197,6 +1240,12 @@
|
|
|
1197
1240
|
' text-decoration: underline; text-underline-offset: 2px;',
|
|
1198
1241
|
'}',
|
|
1199
1242
|
'.cumulus-topbar-changelog:hover { color: #ccc; background: #3a3a3a; }',
|
|
1243
|
+
'.cumulus-topbar-settings {',
|
|
1244
|
+
' background: none; border: none; color: #888; cursor: pointer;',
|
|
1245
|
+
' font-size: 15px; padding: 2px 5px; line-height: 1;',
|
|
1246
|
+
' border-radius: 3px; transition: color 0.2s, background 0.2s;',
|
|
1247
|
+
'}',
|
|
1248
|
+
'.cumulus-topbar-settings:hover { color: #ccc; background: #3a3a3a; }',
|
|
1200
1249
|
'.cumulus-changelog-popover {',
|
|
1201
1250
|
' position: absolute; top: 44px; left: 12px; z-index: 1000;',
|
|
1202
1251
|
' width: min(480px, calc(100vw - 24px)); max-height: 70vh; overflow-y: auto;',
|
|
@@ -1315,137 +1364,188 @@
|
|
|
1315
1364
|
'.cumulus-model-popover select { max-width: 152px; margin: 0; }',
|
|
1316
1365
|
|
|
1317
1366
|
/* ── Settings modal (provider-organized model catalog + credentials) ── */
|
|
1367
|
+
/* These three roots mount on document.body, OUTSIDE .cumulus-widget, so they
|
|
1368
|
+
must carry the font stack themselves or the browser falls back to serif. */
|
|
1369
|
+
'.cumulus-settings-backdrop, .cumulus-context-menu, .cumulus-confirm-dialog {',
|
|
1370
|
+
' font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
|
|
1371
|
+
' -webkit-font-smoothing: antialiased;',
|
|
1372
|
+
'}',
|
|
1318
1373
|
'.cumulus-settings-backdrop {',
|
|
1319
|
-
' position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.
|
|
1374
|
+
' position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.6);',
|
|
1375
|
+
' backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);',
|
|
1320
1376
|
' display: flex; align-items: center; justify-content: center; padding: 20px;',
|
|
1377
|
+
' font-size: 13px; line-height: 1.5; color: #e3e5ea;',
|
|
1321
1378
|
'}',
|
|
1322
1379
|
'.cumulus-settings-modal {',
|
|
1323
|
-
' background: #
|
|
1380
|
+
' background: #1f2023; border: 1px solid #313338; border-radius: 14px;',
|
|
1324
1381
|
' width: min(720px, 100%); max-height: 86vh; display: flex; flex-direction: column;',
|
|
1325
|
-
' box-shadow: 0
|
|
1382
|
+
' box-shadow: 0 24px 64px rgba(0,0,0,0.55); overflow: hidden;',
|
|
1326
1383
|
'}',
|
|
1327
1384
|
'.cumulus-settings-header {',
|
|
1328
1385
|
' display: flex; align-items: flex-start; justify-content: space-between;',
|
|
1329
|
-
' padding: 14px
|
|
1386
|
+
' padding: 16px 20px 14px; border-bottom: 1px solid #2a2b30; flex-shrink: 0;',
|
|
1330
1387
|
'}',
|
|
1331
|
-
'.cumulus-settings-title { font-weight: 600; font-size: 15px; color: #
|
|
1332
|
-
'.cumulus-settings-subtitle { font-size: 12px; color: #
|
|
1333
|
-
'.cumulus-settings-body { padding: 6px
|
|
1388
|
+
'.cumulus-settings-title { font-weight: 600; font-size: 15px; letter-spacing: -0.01em; color: #f2f3f5; }',
|
|
1389
|
+
'.cumulus-settings-subtitle { font-size: 12px; color: #8b8f96; margin-top: 3px; }',
|
|
1390
|
+
'.cumulus-settings-body { padding: 6px 20px 14px; overflow-y: auto; flex: 1; }',
|
|
1334
1391
|
'.cumulus-settings-footer {',
|
|
1335
|
-
' display: flex; align-items: center; gap: 12px; padding: 13px
|
|
1336
|
-
' border-top: 1px solid #
|
|
1392
|
+
' display: flex; align-items: center; gap: 12px; padding: 13px 20px;',
|
|
1393
|
+
' border-top: 1px solid #2a2b30; flex-shrink: 0; background: #1b1c1f;',
|
|
1337
1394
|
'}',
|
|
1338
1395
|
'.cumulus-settings-section-label {',
|
|
1339
|
-
' font-size:
|
|
1340
|
-
' color: #
|
|
1396
|
+
' font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;',
|
|
1397
|
+
' color: #6e7278; margin: 18px 0 8px;',
|
|
1341
1398
|
'}',
|
|
1342
|
-
'.cumulus-settings-body > .cumulus-settings-section-label:first-child { margin-top:
|
|
1399
|
+
'.cumulus-settings-body > .cumulus-settings-section-label:first-child { margin-top: 12px; }',
|
|
1400
|
+
'.cumulus-settings-default-card {',
|
|
1401
|
+
' display: flex; align-items: center; gap: 12px; padding: 12px 14px; margin: 4px 0 2px;',
|
|
1402
|
+
' border: 1px solid rgba(79,140,255,0.35); border-radius: 12px; background: rgba(79,140,255,0.07);',
|
|
1403
|
+
'}',
|
|
1404
|
+
'.cumulus-settings-default-card label { color: #c8d4e8; font-size: 12.5px; font-weight: 500; flex-shrink: 0; }',
|
|
1405
|
+
'.cumulus-settings-default-card select { flex: 1; min-width: 0; }',
|
|
1343
1406
|
'.cumulus-settings-model-row {',
|
|
1344
|
-
' display:
|
|
1345
|
-
'
|
|
1346
|
-
'
|
|
1407
|
+
' display: flex; align-items: center; gap: 10px; padding: 9px 12px;',
|
|
1408
|
+
' border: 1px solid #2c2d32; border-radius: 10px; margin-bottom: 6px; background: #1e1f23;',
|
|
1409
|
+
' transition: border-color 0.12s;',
|
|
1347
1410
|
'}',
|
|
1348
|
-
'.cumulus-settings-model-row
|
|
1349
|
-
'.cumulus-settings-radio-label { color: #777; font-size: 9px; text-align: center; line-height: 1.15; }',
|
|
1411
|
+
'.cumulus-settings-model-row:hover { border-color: #3a3c42; }',
|
|
1350
1412
|
'.cumulus-settings-row-input { min-width: 0; width: 100%; }',
|
|
1351
|
-
'.cumulus-settings-model-row.is-default { border-color:
|
|
1352
|
-
'.cumulus-settings-default-toggle { display: flex; align-items: center; cursor: pointer; flex-shrink: 0; }',
|
|
1353
|
-
'.cumulus-settings-default-toggle input { cursor: pointer; accent-color: #3b82f6; margin: 0; }',
|
|
1413
|
+
'.cumulus-settings-model-row.is-default { border-color: rgba(79,140,255,0.45); background: rgba(79,140,255,0.08); }',
|
|
1354
1414
|
'.cumulus-settings-model-name { flex: 1; min-width: 0; }',
|
|
1355
1415
|
'.cumulus-settings-model-label {',
|
|
1356
|
-
' color: #
|
|
1416
|
+
' color: #e7e8ea; font-size: 13px; font-weight: 500;',
|
|
1417
|
+
' overflow: hidden; text-overflow: ellipsis; white-space: nowrap;',
|
|
1357
1418
|
'}',
|
|
1358
1419
|
'.cumulus-settings-model-id {',
|
|
1359
|
-
' color: #
|
|
1420
|
+
' color: #7d808a; font-size: 11px;',
|
|
1421
|
+
' font-family: ui-monospace, "SF Mono", "Cascadia Code", Consolas, monospace;',
|
|
1360
1422
|
' overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 1px;',
|
|
1361
1423
|
'}',
|
|
1424
|
+
'.cumulus-settings-context-chip {',
|
|
1425
|
+
' color: #9a9da5; font-size: 10.5px; font-variant-numeric: tabular-nums;',
|
|
1426
|
+
' border: 1px solid #383a40; border-radius: 999px; background: #1b1c1f;',
|
|
1427
|
+
' padding: 1px 8px; white-space: nowrap; flex-shrink: 0;',
|
|
1428
|
+
'}',
|
|
1362
1429
|
'.cumulus-settings-default-badge {',
|
|
1363
|
-
' display: none; font-size:
|
|
1364
|
-
' background: rgba(
|
|
1365
|
-
' border-radius:
|
|
1430
|
+
' display: none; font-size: 10.5px; font-weight: 600; color: #8ab4f8;',
|
|
1431
|
+
' background: rgba(79,140,255,0.14); border: 1px solid rgba(79,140,255,0.35);',
|
|
1432
|
+
' border-radius: 999px; padding: 1px 9px; flex-shrink: 0;',
|
|
1366
1433
|
'}',
|
|
1367
1434
|
'.cumulus-settings-model-row.is-default .cumulus-settings-default-badge { display: inline-block; }',
|
|
1435
|
+
'.cumulus-settings-row-actions {',
|
|
1436
|
+
' display: flex; align-items: center; gap: 2px; flex-shrink: 0; opacity: 0;',
|
|
1437
|
+
' transition: opacity 0.12s;',
|
|
1438
|
+
'}',
|
|
1439
|
+
'.cumulus-settings-model-row:hover .cumulus-settings-row-actions,',
|
|
1440
|
+
'.cumulus-settings-model-row:focus-within .cumulus-settings-row-actions { opacity: 1; }',
|
|
1441
|
+
'@media (hover: none) { .cumulus-settings-row-actions { opacity: 1; } }',
|
|
1442
|
+
'.cumulus-settings-row-edit {',
|
|
1443
|
+
' background: none; border: none; color: #6e7278; cursor: pointer; font-size: 13px;',
|
|
1444
|
+
' line-height: 1; padding: 2px 4px; flex-shrink: 0; font-family: inherit;',
|
|
1445
|
+
'}',
|
|
1446
|
+
'.cumulus-settings-row-edit:hover { color: #8ab4f8; }',
|
|
1368
1447
|
'.cumulus-settings-model-remove {',
|
|
1369
|
-
' background: none; border: none; color: #
|
|
1370
|
-
' line-height: 1; padding: 0 2px; flex-shrink: 0;',
|
|
1448
|
+
' background: none; border: none; color: #6e7278; cursor: pointer; font-size: 17px;',
|
|
1449
|
+
' line-height: 1; padding: 0 2px; flex-shrink: 0; font-family: inherit;',
|
|
1371
1450
|
'}',
|
|
1372
1451
|
'.cumulus-settings-model-remove:hover { color: #ef4444; }',
|
|
1452
|
+
'.cumulus-settings-model-row.editing { display: block; }',
|
|
1453
|
+
'.cumulus-settings-edit-grid {',
|
|
1454
|
+
' display: grid; grid-template-columns: minmax(140px,1.3fr) minmax(110px,1fr) 105px auto;',
|
|
1455
|
+
' gap: 8px; align-items: end;',
|
|
1456
|
+
'}',
|
|
1457
|
+
'.cumulus-settings-edit-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }',
|
|
1458
|
+
'.cumulus-settings-edit-field > span {',
|
|
1459
|
+
' color: #6e7278; font-size: 10px; font-weight: 600;',
|
|
1460
|
+
' text-transform: uppercase; letter-spacing: 0.05em;',
|
|
1461
|
+
'}',
|
|
1462
|
+
'.cumulus-settings-claude-default-row {',
|
|
1463
|
+
' display: flex; align-items: center; gap: 10px; padding: 4px 12px 10px;',
|
|
1464
|
+
'}',
|
|
1465
|
+
'.cumulus-settings-claude-default-row label { color: #9a9da5; font-size: 11.5px; flex-shrink: 0; }',
|
|
1466
|
+
'.cumulus-settings-claude-default-row select { flex: 1; min-width: 0; }',
|
|
1373
1467
|
'.cumulus-settings-provider-card {',
|
|
1374
|
-
' border: 1px solid #
|
|
1468
|
+
' border: 1px solid #2c2d32; border-radius: 12px; background: #232428; margin: 10px 0;',
|
|
1375
1469
|
' overflow: hidden;',
|
|
1376
1470
|
'}',
|
|
1377
1471
|
'.cumulus-settings-provider-head {',
|
|
1378
1472
|
' display: flex; align-items: center; justify-content: space-between; gap: 14px;',
|
|
1379
|
-
' padding:
|
|
1473
|
+
' padding: 13px 14px; background: #26272c; border-bottom: 1px solid #2c2d32;',
|
|
1380
1474
|
'}',
|
|
1381
|
-
'.cumulus-settings-provider-title { color: #
|
|
1382
|
-
'.cumulus-settings-provider-desc { color: #
|
|
1475
|
+
'.cumulus-settings-provider-title { color: #f2f3f5; font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; }',
|
|
1476
|
+
'.cumulus-settings-provider-desc { color: #7d808a; font-size: 11.5px; margin-top: 2px; }',
|
|
1383
1477
|
'.cumulus-settings-provider-count {',
|
|
1384
|
-
' color: #
|
|
1385
|
-
'
|
|
1478
|
+
' color: #9a9da5; font-size: 10.5px; font-variant-numeric: tabular-nums;',
|
|
1479
|
+
' border: 1px solid #383a40; border-radius: 999px; background: #1b1c1f;',
|
|
1480
|
+
' padding: 2px 9px; white-space: nowrap;',
|
|
1386
1481
|
'}',
|
|
1387
|
-
'.cumulus-settings-provider-models { padding: 10px 10px
|
|
1388
|
-
'.cumulus-settings-provider-models .cumulus-settings-model-row { margin-bottom:
|
|
1389
|
-
'.cumulus-settings-provider-empty { color: #
|
|
1482
|
+
'.cumulus-settings-provider-models { padding: 10px 10px 4px; }',
|
|
1483
|
+
'.cumulus-settings-provider-models .cumulus-settings-model-row { margin-bottom: 6px; }',
|
|
1484
|
+
'.cumulus-settings-provider-empty { color: #6e7278; font-size: 12px; padding: 8px 4px 14px; }',
|
|
1390
1485
|
'.cumulus-settings-provider-credential {',
|
|
1391
1486
|
' display: grid; grid-template-columns: 120px minmax(0,1fr) auto; align-items: center; gap: 10px;',
|
|
1392
|
-
' padding:
|
|
1487
|
+
' padding: 11px 14px; background: #1b1c1f; border-top: 1px solid #2a2b30;',
|
|
1488
|
+
'}',
|
|
1489
|
+
'.cumulus-settings-credential-note {',
|
|
1490
|
+
' color: #7d808a; font-size: 11.5px; padding: 11px 14px; background: #1b1c1f; border-top: 1px solid #2a2b30;',
|
|
1393
1491
|
'}',
|
|
1394
|
-
'.cumulus-settings-credential-note { color: #777; font-size: 11px; padding: 0 10px 12px; }',
|
|
1395
1492
|
'.cumulus-settings-clear-btn {',
|
|
1396
|
-
' background:
|
|
1397
|
-
' padding: 6px
|
|
1493
|
+
' background: transparent; border: 1px solid #3d3f45; color: #9a9da5; cursor: pointer;',
|
|
1494
|
+
' padding: 6px 10px; border-radius: 8px; font-size: 11.5px; font-family: inherit;',
|
|
1495
|
+
' transition: color 0.12s, border-color 0.12s;',
|
|
1398
1496
|
'}',
|
|
1399
|
-
'.cumulus-settings-clear-btn:hover { color: #ef7777; border-color: #
|
|
1400
|
-
'.cumulus-settings-provider-credential label { color: #
|
|
1497
|
+
'.cumulus-settings-clear-btn:hover { color: #ef7777; border-color: #7a4444; }',
|
|
1498
|
+
'.cumulus-settings-provider-credential label { color: #9a9da5; font-size: 11.5px; }',
|
|
1401
1499
|
'.cumulus-settings-add-card {',
|
|
1402
|
-
' border: 1px dashed #
|
|
1500
|
+
' border: 1px dashed #3d3f45; border-radius: 12px; padding: 13px 14px; margin: 12px 0 8px;',
|
|
1403
1501
|
' background: rgba(255,255,255,0.015);',
|
|
1404
1502
|
'}',
|
|
1405
|
-
'.cumulus-settings-add-title { color: #
|
|
1503
|
+
'.cumulus-settings-add-title { color: #d4d6da; font-size: 12px; font-weight: 600; margin-bottom: 9px; }',
|
|
1406
1504
|
'.cumulus-settings-add-row {',
|
|
1407
1505
|
' display: grid; grid-template-columns: 130px minmax(150px,1.4fr) minmax(120px,1fr) 110px auto;',
|
|
1408
|
-
' gap:
|
|
1506
|
+
' gap: 8px; align-items: center;',
|
|
1507
|
+
'}',
|
|
1508
|
+
'.cumulus-settings-select, .cumulus-settings-input {',
|
|
1509
|
+
' background: #17181b; border: 1px solid #33353b; color: #e3e5ea;',
|
|
1510
|
+
' padding: 7px 10px; border-radius: 8px; font-size: 12.5px; font-family: inherit; outline: none;',
|
|
1511
|
+
' transition: border-color 0.12s, box-shadow 0.12s;',
|
|
1409
1512
|
'}',
|
|
1410
1513
|
'.cumulus-settings-select {',
|
|
1411
|
-
'
|
|
1412
|
-
'
|
|
1514
|
+
' -webkit-appearance: none; appearance: none; padding-right: 26px; cursor: pointer;',
|
|
1515
|
+
' background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2710%27 height=%276%27%3E%3Cpath d=%27M1 1l4 4 4-4%27 stroke=%27%238b8f96%27 stroke-width=%271.5%27 fill=%27none%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27/%3E%3C/svg%3E");',
|
|
1516
|
+
' background-repeat: no-repeat; background-position: right 10px center;',
|
|
1413
1517
|
'}',
|
|
1414
|
-
'.cumulus-settings-input {',
|
|
1415
|
-
'
|
|
1416
|
-
' padding: 7px 9px; border-radius: 6px; font-size: 12px; font-family: inherit; outline: none;',
|
|
1518
|
+
'.cumulus-settings-select:focus, .cumulus-settings-input:focus {',
|
|
1519
|
+
' border-color: #4f8cff; box-shadow: 0 0 0 3px rgba(79,140,255,0.18);',
|
|
1417
1520
|
'}',
|
|
1418
|
-
'.cumulus-settings-input
|
|
1419
|
-
'.cumulus-settings-input::placeholder { color: #5a5a5a; }',
|
|
1521
|
+
'.cumulus-settings-input::placeholder { color: #5c5f66; }',
|
|
1420
1522
|
'.cumulus-settings-cred-row { display: flex; align-items: center; gap: 12px; }',
|
|
1421
|
-
'.cumulus-settings-cred-row label { font-size: 12px; color: #
|
|
1523
|
+
'.cumulus-settings-cred-row label { font-size: 12px; color: #b8bac0; flex-shrink: 0; }',
|
|
1422
1524
|
'.cumulus-settings-btn {',
|
|
1423
|
-
' background: #
|
|
1424
|
-
' padding: 8px
|
|
1525
|
+
' background: #3b82f6; border: none; color: #fff; font-weight: 600;',
|
|
1526
|
+
' padding: 8px 20px; border-radius: 8px; font-size: 13px; cursor: pointer; font-family: inherit;',
|
|
1527
|
+
' transition: background 0.12s;',
|
|
1425
1528
|
'}',
|
|
1426
|
-
'.cumulus-settings-btn:hover { background: #
|
|
1427
|
-
'.cumulus-settings-btn:disabled { background: #
|
|
1529
|
+
'.cumulus-settings-btn:hover { background: #2f6fe0; }',
|
|
1530
|
+
'.cumulus-settings-btn:disabled { background: #2c2d32; color: #6e7278; cursor: not-allowed; }',
|
|
1428
1531
|
'.cumulus-settings-add-btn {',
|
|
1429
|
-
' background:
|
|
1430
|
-
' padding: 7px
|
|
1431
|
-
' font-family: inherit; white-space: nowrap;',
|
|
1532
|
+
' background: transparent; border: 1px solid #3d3f45; color: #d4d6da;',
|
|
1533
|
+
' padding: 7px 14px; border-radius: 8px; font-size: 12.5px; cursor: pointer;',
|
|
1534
|
+
' font-family: inherit; white-space: nowrap; transition: background 0.12s, border-color 0.12s;',
|
|
1432
1535
|
'}',
|
|
1433
|
-
'.cumulus-settings-add-btn:hover { background: #
|
|
1434
|
-
'.cumulus-settings-status { font-size: 12px; color: #
|
|
1536
|
+
'.cumulus-settings-add-btn:hover { background: #2a2b30; border-color: #4a4c53; }',
|
|
1537
|
+
'.cumulus-settings-status { font-size: 12px; color: #9a9da5; }',
|
|
1435
1538
|
'.cumulus-settings-status.success { color: #22c55e; }',
|
|
1436
1539
|
'.cumulus-settings-status.error { color: #ef4444; }',
|
|
1437
1540
|
'@media (max-width: 720px) {',
|
|
1438
1541
|
' .cumulus-settings-backdrop { padding: 8px; align-items: stretch; }',
|
|
1439
|
-
' .cumulus-settings-modal { max-height: 100%; border-radius:
|
|
1542
|
+
' .cumulus-settings-modal { max-height: 100%; border-radius: 12px; }',
|
|
1440
1543
|
' .cumulus-settings-add-row { grid-template-columns: 1fr 1fr; }',
|
|
1441
1544
|
' .cumulus-settings-add-row .cumulus-settings-add-btn { grid-column: 1 / -1; }',
|
|
1442
1545
|
' .cumulus-settings-provider-credential { grid-template-columns: 1fr auto; gap: 5px; }',
|
|
1443
1546
|
' .cumulus-settings-provider-credential label { grid-column: 1 / -1; }',
|
|
1444
|
-
' .cumulus-settings-
|
|
1445
|
-
'
|
|
1446
|
-
' }',
|
|
1447
|
-
' .cumulus-settings-model-row .cumulus-settings-row-input { grid-column: 3 / 4; }',
|
|
1448
|
-
' .cumulus-settings-model-row .cumulus-settings-model-remove { grid-column: 4; grid-row: 1 / 3; }',
|
|
1547
|
+
' .cumulus-settings-default-card { flex-direction: column; align-items: stretch; gap: 6px; }',
|
|
1548
|
+
' .cumulus-settings-edit-grid { grid-template-columns: 1fr 1fr; }',
|
|
1449
1549
|
'}',
|
|
1450
1550
|
|
|
1451
1551
|
/* ── Context menu (right-click on thread) ── */
|
|
@@ -4500,6 +4600,7 @@
|
|
|
4500
4600
|
'<button class="cumulus-topbar-check-update" data-testid="webchat-check-update" title="Check for update">↻</button>' +
|
|
4501
4601
|
'<span class="cumulus-topbar-check-result" data-testid="webchat-check-result" style="display:none"></span></span>' +
|
|
4502
4602
|
'<div class="cumulus-topbar-right">' +
|
|
4603
|
+
'<button class="cumulus-topbar-settings" data-testid="webchat-settings-btn" title="Gateway settings — models, providers, API keys">⚙</button>' +
|
|
4503
4604
|
'<span class="cumulus-header-status">' +
|
|
4504
4605
|
'<span class="cumulus-status-dot" data-testid="webchat-status-dot-app"></span>' +
|
|
4505
4606
|
'<span data-testid="webchat-status-text-app">Disconnected</span>' +
|
|
@@ -4689,6 +4790,14 @@
|
|
|
4689
4790
|
});
|
|
4690
4791
|
}
|
|
4691
4792
|
|
|
4793
|
+
// ── Topbar settings gear → gateway settings modal (task 128) ──
|
|
4794
|
+
var topbarSettingsBtn = topbar.querySelector('[data-testid="webchat-settings-btn"]');
|
|
4795
|
+
if (topbarSettingsBtn) {
|
|
4796
|
+
topbarSettingsBtn.addEventListener('click', function () {
|
|
4797
|
+
openSettingsModal();
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4692
4801
|
// ── Click status indicator to force reconnect ──
|
|
4693
4802
|
var appStatusEl = topbar.querySelector('.cumulus-header-status');
|
|
4694
4803
|
if (appStatusEl) {
|
|
@@ -5846,6 +5955,715 @@
|
|
|
5846
5955
|
};
|
|
5847
5956
|
}
|
|
5848
5957
|
|
|
5958
|
+
// ── Settings modal (provider-organized model catalog + credentials) ──
|
|
5959
|
+
// Live-editable via PUT /api/config (task 104) — takes effect on the NEXT turn
|
|
5960
|
+
// with no restart, no JSON edit, no republish. Any valid gateway key.
|
|
5961
|
+
// Gateway-level config, opened from the topbar gear next to the connection
|
|
5962
|
+
// status — not tied to any thread panel (task 128).
|
|
5963
|
+
function openSettingsModal() {
|
|
5964
|
+
var backdrop = document.createElement('div');
|
|
5965
|
+
backdrop.className = 'cumulus-settings-backdrop';
|
|
5966
|
+
backdrop.setAttribute('data-testid', 'webchat-settings-modal');
|
|
5967
|
+
var modal = document.createElement('div');
|
|
5968
|
+
modal.className = 'cumulus-settings-modal';
|
|
5969
|
+
|
|
5970
|
+
var mHeader = document.createElement('div');
|
|
5971
|
+
mHeader.className = 'cumulus-settings-header';
|
|
5972
|
+
var mHeaderText = document.createElement('div');
|
|
5973
|
+
var mTitle = document.createElement('div');
|
|
5974
|
+
mTitle.className = 'cumulus-settings-title';
|
|
5975
|
+
mTitle.textContent = 'Gateway settings';
|
|
5976
|
+
var mSub = document.createElement('div');
|
|
5977
|
+
mSub.className = 'cumulus-settings-subtitle';
|
|
5978
|
+
mSub.textContent =
|
|
5979
|
+
'Manage Anthropic, OpenAI, and HuggingFace models — changes apply to new turns instantly';
|
|
5980
|
+
mHeaderText.appendChild(mTitle);
|
|
5981
|
+
mHeaderText.appendChild(mSub);
|
|
5982
|
+
var mClose = document.createElement('button');
|
|
5983
|
+
mClose.className = 'cumulus-overlay-close';
|
|
5984
|
+
mClose.textContent = '\xD7';
|
|
5985
|
+
mClose.setAttribute('data-testid', 'webchat-settings-close');
|
|
5986
|
+
mClose.addEventListener('click', function () {
|
|
5987
|
+
backdrop.remove();
|
|
5988
|
+
});
|
|
5989
|
+
mHeader.appendChild(mHeaderText);
|
|
5990
|
+
mHeader.appendChild(mClose);
|
|
5991
|
+
modal.appendChild(mHeader);
|
|
5992
|
+
|
|
5993
|
+
var body = document.createElement('div');
|
|
5994
|
+
body.className = 'cumulus-settings-body';
|
|
5995
|
+
|
|
5996
|
+
// Task 130 (Option C): ONE gateway-default control, up top — answers "what
|
|
5997
|
+
// will a new thread use?" before the catalog. The per-row radios are gone.
|
|
5998
|
+
var defaultLabelSection = document.createElement('div');
|
|
5999
|
+
defaultLabelSection.className = 'cumulus-settings-section-label';
|
|
6000
|
+
defaultLabelSection.textContent = 'Default model';
|
|
6001
|
+
body.appendChild(defaultLabelSection);
|
|
6002
|
+
var defaultCard = document.createElement('div');
|
|
6003
|
+
defaultCard.className = 'cumulus-settings-default-card';
|
|
6004
|
+
var defaultCardLabel = document.createElement('label');
|
|
6005
|
+
defaultCardLabel.textContent = 'New threads use';
|
|
6006
|
+
var defaultSelect = document.createElement('select');
|
|
6007
|
+
defaultSelect.className = 'cumulus-settings-select';
|
|
6008
|
+
defaultSelect.setAttribute('data-testid', 'webchat-gw-default-select');
|
|
6009
|
+
defaultSelect.addEventListener('change', function () {
|
|
6010
|
+
applyGatewayDefault(gwState.catalog, defaultSelect.value);
|
|
6011
|
+
renderAll();
|
|
6012
|
+
});
|
|
6013
|
+
defaultCard.appendChild(defaultCardLabel);
|
|
6014
|
+
defaultCard.appendChild(defaultSelect);
|
|
6015
|
+
body.appendChild(defaultCard);
|
|
6016
|
+
|
|
6017
|
+
var modelsLabel = document.createElement('div');
|
|
6018
|
+
modelsLabel.className = 'cumulus-settings-section-label';
|
|
6019
|
+
modelsLabel.textContent = 'Model catalog';
|
|
6020
|
+
body.appendChild(modelsLabel);
|
|
6021
|
+
|
|
6022
|
+
var gwList = document.createElement('div');
|
|
6023
|
+
gwList.setAttribute('data-testid', 'webchat-gateway-models');
|
|
6024
|
+
body.appendChild(gwList);
|
|
6025
|
+
|
|
6026
|
+
var addCard = document.createElement('div');
|
|
6027
|
+
addCard.className = 'cumulus-settings-add-card';
|
|
6028
|
+
var addTitle = document.createElement('div');
|
|
6029
|
+
addTitle.className = 'cumulus-settings-add-title';
|
|
6030
|
+
addTitle.textContent = 'Add a model';
|
|
6031
|
+
addCard.appendChild(addTitle);
|
|
6032
|
+
var addWrap = document.createElement('div');
|
|
6033
|
+
addWrap.className = 'cumulus-settings-add-row';
|
|
6034
|
+
var addProvider = document.createElement('select');
|
|
6035
|
+
addProvider.className = 'cumulus-settings-select';
|
|
6036
|
+
addProvider.setAttribute('data-testid', 'webchat-gw-add-provider');
|
|
6037
|
+
[
|
|
6038
|
+
{ value: 'anthropic', label: 'Anthropic' },
|
|
6039
|
+
{ value: 'openai', label: 'OpenAI' },
|
|
6040
|
+
{ value: 'huggingface', label: 'HuggingFace' },
|
|
6041
|
+
].forEach(function (provider) {
|
|
6042
|
+
var option = document.createElement('option');
|
|
6043
|
+
option.value = provider.value;
|
|
6044
|
+
option.textContent = provider.label;
|
|
6045
|
+
addProvider.appendChild(option);
|
|
6046
|
+
});
|
|
6047
|
+
var addId = document.createElement('input');
|
|
6048
|
+
addId.type = 'text';
|
|
6049
|
+
addId.className = 'cumulus-settings-input';
|
|
6050
|
+
addId.placeholder = 'Model ID / name';
|
|
6051
|
+
addId.setAttribute('data-testid', 'webchat-gw-add-id');
|
|
6052
|
+
var addLabel = document.createElement('input');
|
|
6053
|
+
addLabel.type = 'text';
|
|
6054
|
+
addLabel.className = 'cumulus-settings-input';
|
|
6055
|
+
addLabel.placeholder = 'Display label (optional)';
|
|
6056
|
+
addLabel.setAttribute('data-testid', 'webchat-gw-add-label');
|
|
6057
|
+
var addContext = document.createElement('input');
|
|
6058
|
+
addContext.type = 'number';
|
|
6059
|
+
addContext.min = '1';
|
|
6060
|
+
addContext.step = '1';
|
|
6061
|
+
addContext.className = 'cumulus-settings-input';
|
|
6062
|
+
addContext.placeholder = 'Context tokens';
|
|
6063
|
+
addContext.setAttribute('data-testid', 'webchat-gw-add-context');
|
|
6064
|
+
var addBtn = document.createElement('button');
|
|
6065
|
+
addBtn.type = 'button';
|
|
6066
|
+
addBtn.textContent = '+ Add';
|
|
6067
|
+
addBtn.className = 'cumulus-settings-add-btn';
|
|
6068
|
+
addBtn.setAttribute('data-testid', 'webchat-gw-add-btn');
|
|
6069
|
+
addWrap.appendChild(addProvider);
|
|
6070
|
+
addWrap.appendChild(addId);
|
|
6071
|
+
addWrap.appendChild(addLabel);
|
|
6072
|
+
addWrap.appendChild(addContext);
|
|
6073
|
+
addWrap.appendChild(addBtn);
|
|
6074
|
+
addCard.appendChild(addWrap);
|
|
6075
|
+
body.appendChild(addCard);
|
|
6076
|
+
|
|
6077
|
+
// ── License (task 129) — persist-only: the key lands in gateway.config.json
|
|
6078
|
+
// but enforcement re-verifies at startup, so it takes effect on reload (127).
|
|
6079
|
+
var licenseLabel = document.createElement('div');
|
|
6080
|
+
licenseLabel.className = 'cumulus-settings-section-label';
|
|
6081
|
+
licenseLabel.textContent = 'License';
|
|
6082
|
+
body.appendChild(licenseLabel);
|
|
6083
|
+
var licenseCard = document.createElement('div');
|
|
6084
|
+
licenseCard.className = 'cumulus-settings-add-card';
|
|
6085
|
+
var licenseStatusLine = document.createElement('div');
|
|
6086
|
+
licenseStatusLine.className = 'cumulus-settings-add-title';
|
|
6087
|
+
licenseStatusLine.setAttribute('data-testid', 'webchat-gw-license-status');
|
|
6088
|
+
licenseStatusLine.textContent = 'License status unknown';
|
|
6089
|
+
licenseCard.appendChild(licenseStatusLine);
|
|
6090
|
+
var licensePurpose = document.createElement('div');
|
|
6091
|
+
licensePurpose.className = 'cumulus-settings-subtitle';
|
|
6092
|
+
licensePurpose.setAttribute('data-testid', 'webchat-gw-license-purpose');
|
|
6093
|
+
licensePurpose.textContent =
|
|
6094
|
+
'Required to run web-app agents in production. Unlicensed, each thread ' +
|
|
6095
|
+
'namespace is capped at 5 visitor threads (demo mode); your own threads, ' +
|
|
6096
|
+
'the CLI and the TUI are never limited. Keys are verified offline and ' +
|
|
6097
|
+
'cover one whole-number release. Contact ops@luckydrawdesign.com.';
|
|
6098
|
+
licenseCard.appendChild(licensePurpose);
|
|
6099
|
+
var licenseRow = document.createElement('div');
|
|
6100
|
+
licenseRow.className = 'cumulus-settings-add-row';
|
|
6101
|
+
var licenseInput = document.createElement('input');
|
|
6102
|
+
licenseInput.type = 'text';
|
|
6103
|
+
licenseInput.className = 'cumulus-settings-input';
|
|
6104
|
+
licenseInput.placeholder = 'License key (cumulus-lic-v1.…)';
|
|
6105
|
+
licenseInput.setAttribute('data-testid', 'webchat-gw-license-input');
|
|
6106
|
+
var licenseClearBtn = document.createElement('button');
|
|
6107
|
+
licenseClearBtn.type = 'button';
|
|
6108
|
+
licenseClearBtn.textContent = 'Clear';
|
|
6109
|
+
licenseClearBtn.className = 'cumulus-settings-add-btn';
|
|
6110
|
+
licenseClearBtn.setAttribute('data-testid', 'webchat-gw-license-clear');
|
|
6111
|
+
licenseRow.appendChild(licenseInput);
|
|
6112
|
+
licenseRow.appendChild(licenseClearBtn);
|
|
6113
|
+
licenseCard.appendChild(licenseRow);
|
|
6114
|
+
var licenseNote = document.createElement('div');
|
|
6115
|
+
licenseNote.className = 'cumulus-settings-subtitle';
|
|
6116
|
+
licenseNote.textContent = 'Takes effect on gateway reload';
|
|
6117
|
+
licenseCard.appendChild(licenseNote);
|
|
6118
|
+
body.appendChild(licenseCard);
|
|
6119
|
+
modal.appendChild(body);
|
|
6120
|
+
|
|
6121
|
+
var footer = document.createElement('div');
|
|
6122
|
+
footer.className = 'cumulus-settings-footer';
|
|
6123
|
+
var saveBtn = document.createElement('button');
|
|
6124
|
+
saveBtn.textContent = 'Save';
|
|
6125
|
+
saveBtn.className = 'cumulus-settings-btn';
|
|
6126
|
+
saveBtn.setAttribute('data-testid', 'webchat-gw-save');
|
|
6127
|
+
var gwStatus = document.createElement('span');
|
|
6128
|
+
gwStatus.className = 'cumulus-settings-status';
|
|
6129
|
+
footer.appendChild(saveBtn);
|
|
6130
|
+
footer.appendChild(gwStatus);
|
|
6131
|
+
modal.appendChild(footer);
|
|
6132
|
+
|
|
6133
|
+
backdrop.appendChild(modal);
|
|
6134
|
+
backdrop.addEventListener('click', function (e) {
|
|
6135
|
+
if (e.target === backdrop) backdrop.remove();
|
|
6136
|
+
});
|
|
6137
|
+
document.body.appendChild(backdrop);
|
|
6138
|
+
|
|
6139
|
+
var gwState = { catalog: [], sentinel: null };
|
|
6140
|
+
var credentialInputs = {};
|
|
6141
|
+
var credentialValues = { openai: '', huggingface: '', license: '' };
|
|
6142
|
+
var credentialClears = { openai: false, huggingface: false, license: false };
|
|
6143
|
+
function renderLicenseState(masked, info) {
|
|
6144
|
+
licenseInput.value = credentialValues.license || '';
|
|
6145
|
+
licenseInput.placeholder = credentialClears.license
|
|
6146
|
+
? 'Will be cleared on Save'
|
|
6147
|
+
: masked || 'License key (cumulus-lic-v1.…)';
|
|
6148
|
+
if (!info) {
|
|
6149
|
+
licenseStatusLine.textContent = masked
|
|
6150
|
+
? 'License key configured'
|
|
6151
|
+
: 'No license key — demo mode';
|
|
6152
|
+
return;
|
|
6153
|
+
}
|
|
6154
|
+
var text = info.licensed
|
|
6155
|
+
? 'Licensed to ' + (info.licensee || 'unknown')
|
|
6156
|
+
: 'Demo mode' + (info.reason && info.reason !== 'absent' ? ' (' + info.reason + ')' : '');
|
|
6157
|
+
if (info.pendingReload) text += ' · key saved — takes effect on reload';
|
|
6158
|
+
licenseStatusLine.textContent = text;
|
|
6159
|
+
}
|
|
6160
|
+
licenseInput.addEventListener('input', function () {
|
|
6161
|
+
credentialValues.license = licenseInput.value;
|
|
6162
|
+
credentialClears.license = false;
|
|
6163
|
+
});
|
|
6164
|
+
licenseClearBtn.addEventListener('click', function () {
|
|
6165
|
+
credentialClears.license = true;
|
|
6166
|
+
credentialValues.license = '';
|
|
6167
|
+
renderLicenseState('', gwLicenseInfo);
|
|
6168
|
+
});
|
|
6169
|
+
var gwLicenseMasked = '';
|
|
6170
|
+
var gwLicenseInfo = null;
|
|
6171
|
+
var providerDefs = [
|
|
6172
|
+
{
|
|
6173
|
+
id: 'anthropic',
|
|
6174
|
+
title: 'Anthropic',
|
|
6175
|
+
description: 'Claude CLI models · authentication is managed by the Claude CLI',
|
|
6176
|
+
},
|
|
6177
|
+
{
|
|
6178
|
+
id: 'openai',
|
|
6179
|
+
title: 'OpenAI',
|
|
6180
|
+
description: 'Responses API models with direct tool use',
|
|
6181
|
+
credential: 'openaiApiKey',
|
|
6182
|
+
credentialTestId: 'webchat-gw-openaikey',
|
|
6183
|
+
},
|
|
6184
|
+
{
|
|
6185
|
+
id: 'huggingface',
|
|
6186
|
+
title: 'HuggingFace',
|
|
6187
|
+
description: 'OpenAI-compatible models served through the HuggingFace router',
|
|
6188
|
+
credential: 'hfApiKey',
|
|
6189
|
+
credentialTestId: 'webchat-gw-hfkey',
|
|
6190
|
+
},
|
|
6191
|
+
];
|
|
6192
|
+
|
|
6193
|
+
function setSettingsStatus(message, kind) {
|
|
6194
|
+
gwStatus.textContent = message || '';
|
|
6195
|
+
gwStatus.className = 'cumulus-settings-status' + (kind ? ' ' + kind : '');
|
|
6196
|
+
}
|
|
6197
|
+
|
|
6198
|
+
function formatContextTokens(n) {
|
|
6199
|
+
return n >= 1000 ? Math.round(n / 1000) + 'k' : String(n);
|
|
6200
|
+
}
|
|
6201
|
+
|
|
6202
|
+
function makeEditField(labelText, input) {
|
|
6203
|
+
var field = document.createElement('div');
|
|
6204
|
+
field.className = 'cumulus-settings-edit-field';
|
|
6205
|
+
var caption = document.createElement('span');
|
|
6206
|
+
caption.textContent = labelText;
|
|
6207
|
+
field.appendChild(caption);
|
|
6208
|
+
field.appendChild(input);
|
|
6209
|
+
return field;
|
|
6210
|
+
}
|
|
6211
|
+
|
|
6212
|
+
// Task 130: rows are read-only text at rest (label + mono id + ctx chip +
|
|
6213
|
+
// default badge); ✎ flips one row into labeled inputs. `model.editing` is
|
|
6214
|
+
// UI-only state — serialization rebuilds entries from `model.source` +
|
|
6215
|
+
// explicit fields, so it never reaches the PUT payload.
|
|
6216
|
+
function renderModelRow(model, providerDef, index) {
|
|
6217
|
+
var row = document.createElement('div');
|
|
6218
|
+
row.className =
|
|
6219
|
+
'cumulus-settings-model-row ' +
|
|
6220
|
+
model.provider +
|
|
6221
|
+
(model.gatewayDefault ? ' is-default' : '') +
|
|
6222
|
+
(model.editing ? ' editing' : '');
|
|
6223
|
+
row.setAttribute('data-gw-model', model.id);
|
|
6224
|
+
row.setAttribute('data-testid', 'webchat-gw-row-' + model.provider + '-' + index);
|
|
6225
|
+
|
|
6226
|
+
if (model.editing) {
|
|
6227
|
+
var grid = document.createElement('div');
|
|
6228
|
+
grid.className = 'cumulus-settings-edit-grid';
|
|
6229
|
+
|
|
6230
|
+
var idInput = document.createElement('input');
|
|
6231
|
+
idInput.type = 'text';
|
|
6232
|
+
idInput.className = 'cumulus-settings-input cumulus-settings-row-input';
|
|
6233
|
+
idInput.value = model.id;
|
|
6234
|
+
idInput.setAttribute('aria-label', providerDef.title + ' model ID');
|
|
6235
|
+
idInput.setAttribute('data-testid', 'webchat-gw-id-' + model.provider + '-' + index);
|
|
6236
|
+
idInput.addEventListener('input', function () {
|
|
6237
|
+
model.id = idInput.value;
|
|
6238
|
+
});
|
|
6239
|
+
grid.appendChild(makeEditField('Model ID', idInput));
|
|
6240
|
+
|
|
6241
|
+
var labelInput = document.createElement('input');
|
|
6242
|
+
labelInput.type = 'text';
|
|
6243
|
+
labelInput.className = 'cumulus-settings-input cumulus-settings-row-input';
|
|
6244
|
+
labelInput.value = model.label || model.id;
|
|
6245
|
+
labelInput.setAttribute('aria-label', providerDef.title + ' display label');
|
|
6246
|
+
labelInput.setAttribute(
|
|
6247
|
+
'data-testid',
|
|
6248
|
+
'webchat-gw-label-' + model.provider + '-' + index
|
|
6249
|
+
);
|
|
6250
|
+
labelInput.addEventListener('input', function () {
|
|
6251
|
+
model.label = labelInput.value;
|
|
6252
|
+
});
|
|
6253
|
+
grid.appendChild(makeEditField('Display label', labelInput));
|
|
6254
|
+
|
|
6255
|
+
if (model.provider !== 'anthropic') {
|
|
6256
|
+
var contextInput = document.createElement('input');
|
|
6257
|
+
contextInput.type = 'number';
|
|
6258
|
+
contextInput.min = '1';
|
|
6259
|
+
contextInput.step = '1';
|
|
6260
|
+
contextInput.className = 'cumulus-settings-input cumulus-settings-row-input';
|
|
6261
|
+
contextInput.placeholder = '(default)';
|
|
6262
|
+
contextInput.value = model.contextWindow || '';
|
|
6263
|
+
contextInput.setAttribute('aria-label', providerDef.title + ' context window');
|
|
6264
|
+
contextInput.setAttribute(
|
|
6265
|
+
'data-testid',
|
|
6266
|
+
'webchat-gw-context-' + model.provider + '-' + index
|
|
6267
|
+
);
|
|
6268
|
+
contextInput.addEventListener('input', function () {
|
|
6269
|
+
model.contextWindow = contextInput.value ? Number(contextInput.value) : undefined;
|
|
6270
|
+
});
|
|
6271
|
+
grid.appendChild(makeEditField('Context tokens', contextInput));
|
|
6272
|
+
}
|
|
6273
|
+
|
|
6274
|
+
var doneBtn = document.createElement('button');
|
|
6275
|
+
doneBtn.type = 'button';
|
|
6276
|
+
doneBtn.textContent = 'Done';
|
|
6277
|
+
doneBtn.className = 'cumulus-settings-add-btn';
|
|
6278
|
+
doneBtn.setAttribute('data-testid', 'webchat-gw-done-' + model.provider + '-' + index);
|
|
6279
|
+
doneBtn.addEventListener('click', function () {
|
|
6280
|
+
model.editing = false;
|
|
6281
|
+
renderAll();
|
|
6282
|
+
});
|
|
6283
|
+
grid.appendChild(doneBtn);
|
|
6284
|
+
row.appendChild(grid);
|
|
6285
|
+
return row;
|
|
6286
|
+
}
|
|
6287
|
+
|
|
6288
|
+
var name = document.createElement('div');
|
|
6289
|
+
name.className = 'cumulus-settings-model-name';
|
|
6290
|
+
var labelEl = document.createElement('div');
|
|
6291
|
+
labelEl.className = 'cumulus-settings-model-label';
|
|
6292
|
+
labelEl.textContent = model.label || model.id;
|
|
6293
|
+
var idEl = document.createElement('div');
|
|
6294
|
+
idEl.className = 'cumulus-settings-model-id';
|
|
6295
|
+
idEl.textContent = model.id;
|
|
6296
|
+
name.appendChild(labelEl);
|
|
6297
|
+
name.appendChild(idEl);
|
|
6298
|
+
row.appendChild(name);
|
|
6299
|
+
|
|
6300
|
+
if (model.provider !== 'anthropic' && model.contextWindow) {
|
|
6301
|
+
var chip = document.createElement('span');
|
|
6302
|
+
chip.className = 'cumulus-settings-context-chip';
|
|
6303
|
+
chip.textContent = 'ctx ' + formatContextTokens(model.contextWindow);
|
|
6304
|
+
row.appendChild(chip);
|
|
6305
|
+
}
|
|
6306
|
+
|
|
6307
|
+
var badge = document.createElement('span');
|
|
6308
|
+
badge.className = 'cumulus-settings-default-badge';
|
|
6309
|
+
badge.textContent = 'default';
|
|
6310
|
+
row.appendChild(badge);
|
|
6311
|
+
|
|
6312
|
+
var actions = document.createElement('div');
|
|
6313
|
+
actions.className = 'cumulus-settings-row-actions';
|
|
6314
|
+
|
|
6315
|
+
var edit = document.createElement('button');
|
|
6316
|
+
edit.type = 'button';
|
|
6317
|
+
edit.className = 'cumulus-settings-row-edit';
|
|
6318
|
+
edit.textContent = '✎';
|
|
6319
|
+
edit.title = 'Edit ' + model.id;
|
|
6320
|
+
edit.setAttribute('data-testid', 'webchat-gw-edit-' + model.provider + '-' + index);
|
|
6321
|
+
edit.addEventListener('click', function () {
|
|
6322
|
+
model.editing = true;
|
|
6323
|
+
renderAll();
|
|
6324
|
+
});
|
|
6325
|
+
actions.appendChild(edit);
|
|
6326
|
+
|
|
6327
|
+
var remove = document.createElement('button');
|
|
6328
|
+
remove.type = 'button';
|
|
6329
|
+
remove.className = 'cumulus-settings-model-remove';
|
|
6330
|
+
remove.textContent = '\xD7';
|
|
6331
|
+
remove.title = 'Remove ' + model.id;
|
|
6332
|
+
remove.setAttribute('data-testid', 'webchat-gw-remove-' + model.provider + '-' + index);
|
|
6333
|
+
remove.addEventListener('click', function () {
|
|
6334
|
+
var absoluteIndex = gwState.catalog.indexOf(model);
|
|
6335
|
+
var wasGatewayDefault = model.gatewayDefault;
|
|
6336
|
+
var wasProviderDefault = model.providerDefault;
|
|
6337
|
+
if (absoluteIndex >= 0) gwState.catalog.splice(absoluteIndex, 1);
|
|
6338
|
+
if (wasGatewayDefault && gwState.catalog.length) {
|
|
6339
|
+
applyGatewayDefault(gwState.catalog, gwState.catalog[0].id);
|
|
6340
|
+
}
|
|
6341
|
+
if (
|
|
6342
|
+
wasProviderDefault &&
|
|
6343
|
+
!gwState.catalog.some(function (item) {
|
|
6344
|
+
return item.provider === 'anthropic' && item.providerDefault;
|
|
6345
|
+
})
|
|
6346
|
+
) {
|
|
6347
|
+
var nextClaude = gwState.catalog.find(function (item) {
|
|
6348
|
+
return item.provider === 'anthropic';
|
|
6349
|
+
});
|
|
6350
|
+
if (nextClaude) nextClaude.providerDefault = true;
|
|
6351
|
+
}
|
|
6352
|
+
renderAll();
|
|
6353
|
+
});
|
|
6354
|
+
actions.appendChild(remove);
|
|
6355
|
+
row.appendChild(actions);
|
|
6356
|
+
return row;
|
|
6357
|
+
}
|
|
6358
|
+
|
|
6359
|
+
function renderCredential(card, provider) {
|
|
6360
|
+
if (!provider.credential) {
|
|
6361
|
+
var note = document.createElement('div');
|
|
6362
|
+
note.className = 'cumulus-settings-credential-note';
|
|
6363
|
+
note.textContent = 'Authenticate with `claude login` on the gateway host.';
|
|
6364
|
+
card.appendChild(note);
|
|
6365
|
+
return;
|
|
6366
|
+
}
|
|
6367
|
+
var credentialRow = document.createElement('div');
|
|
6368
|
+
credentialRow.className = 'cumulus-settings-provider-credential';
|
|
6369
|
+
var credentialLabel = document.createElement('label');
|
|
6370
|
+
credentialLabel.textContent = 'API key';
|
|
6371
|
+
var credentialInput = document.createElement('input');
|
|
6372
|
+
credentialInput.type = 'password';
|
|
6373
|
+
credentialInput.autocomplete = 'off';
|
|
6374
|
+
credentialInput.className = 'cumulus-settings-input';
|
|
6375
|
+
credentialInput.placeholder = credentialClears[provider.id]
|
|
6376
|
+
? 'Will be cleared on save'
|
|
6377
|
+
: '(unset)';
|
|
6378
|
+
credentialInput.value = credentialValues[provider.id] || '';
|
|
6379
|
+
credentialInput.setAttribute('data-testid', provider.credentialTestId);
|
|
6380
|
+
credentialInput.addEventListener('input', function () {
|
|
6381
|
+
credentialValues[provider.id] = credentialInput.value;
|
|
6382
|
+
credentialClears[provider.id] = false;
|
|
6383
|
+
credentialInput.placeholder = '(unset)';
|
|
6384
|
+
});
|
|
6385
|
+
credentialInputs[provider.id] = credentialInput;
|
|
6386
|
+
var clear = document.createElement('button');
|
|
6387
|
+
clear.type = 'button';
|
|
6388
|
+
clear.className = 'cumulus-settings-clear-btn';
|
|
6389
|
+
clear.textContent = 'Clear';
|
|
6390
|
+
clear.setAttribute('data-testid', 'webchat-gw-clear-' + provider.id + '-key');
|
|
6391
|
+
clear.addEventListener('click', function () {
|
|
6392
|
+
credentialClears[provider.id] = true;
|
|
6393
|
+
credentialValues[provider.id] = '';
|
|
6394
|
+
credentialInput.value = '';
|
|
6395
|
+
credentialInput.placeholder = 'Will be cleared on save';
|
|
6396
|
+
setSettingsStatus('Credential marked for removal', '');
|
|
6397
|
+
});
|
|
6398
|
+
credentialRow.appendChild(credentialLabel);
|
|
6399
|
+
credentialRow.appendChild(credentialInput);
|
|
6400
|
+
credentialRow.appendChild(clear);
|
|
6401
|
+
card.appendChild(credentialRow);
|
|
6402
|
+
}
|
|
6403
|
+
|
|
6404
|
+
function renderGwModels() {
|
|
6405
|
+
gwList.innerHTML = '';
|
|
6406
|
+
credentialInputs = {};
|
|
6407
|
+
providerDefs.forEach(function (provider) {
|
|
6408
|
+
var providerModels = gwState.catalog.filter(function (model) {
|
|
6409
|
+
return model.provider === provider.id;
|
|
6410
|
+
});
|
|
6411
|
+
var card = document.createElement('section');
|
|
6412
|
+
card.className = 'cumulus-settings-provider-card';
|
|
6413
|
+
card.setAttribute('data-testid', 'webchat-gw-provider-' + provider.id);
|
|
6414
|
+
var head = document.createElement('div');
|
|
6415
|
+
head.className = 'cumulus-settings-provider-head';
|
|
6416
|
+
var heading = document.createElement('div');
|
|
6417
|
+
var title = document.createElement('div');
|
|
6418
|
+
title.className = 'cumulus-settings-provider-title';
|
|
6419
|
+
title.textContent = provider.title;
|
|
6420
|
+
var description = document.createElement('div');
|
|
6421
|
+
description.className = 'cumulus-settings-provider-desc';
|
|
6422
|
+
description.textContent = provider.description;
|
|
6423
|
+
heading.appendChild(title);
|
|
6424
|
+
heading.appendChild(description);
|
|
6425
|
+
var count = document.createElement('span');
|
|
6426
|
+
count.className = 'cumulus-settings-provider-count';
|
|
6427
|
+
count.textContent =
|
|
6428
|
+
providerModels.length + (providerModels.length === 1 ? ' model' : ' models');
|
|
6429
|
+
head.appendChild(heading);
|
|
6430
|
+
head.appendChild(count);
|
|
6431
|
+
card.appendChild(head);
|
|
6432
|
+
var list = document.createElement('div');
|
|
6433
|
+
list.className = 'cumulus-settings-provider-models';
|
|
6434
|
+
if (!providerModels.length) {
|
|
6435
|
+
var empty = document.createElement('div');
|
|
6436
|
+
empty.className = 'cumulus-settings-provider-empty';
|
|
6437
|
+
empty.textContent = 'No ' + provider.title + ' models configured.';
|
|
6438
|
+
list.appendChild(empty);
|
|
6439
|
+
} else {
|
|
6440
|
+
providerModels.forEach(function (model, index) {
|
|
6441
|
+
list.appendChild(renderModelRow(model, provider, index));
|
|
6442
|
+
});
|
|
6443
|
+
}
|
|
6444
|
+
card.appendChild(list);
|
|
6445
|
+
// The Claude sub-model default only surfaces when it can actually
|
|
6446
|
+
// diverge from the gateway default (task 130) — gateway default on a
|
|
6447
|
+
// direct provider, ≥2 Anthropic models. Otherwise coupling makes it
|
|
6448
|
+
// redundant and it stays out of the way.
|
|
6449
|
+
if (provider.id === 'anthropic' && claudeSubDefaultVisible(gwState.catalog)) {
|
|
6450
|
+
var subRow = document.createElement('div');
|
|
6451
|
+
subRow.className = 'cumulus-settings-claude-default-row';
|
|
6452
|
+
var subLabel = document.createElement('label');
|
|
6453
|
+
subLabel.textContent = 'When a thread picks Claude, use';
|
|
6454
|
+
var subSelect = document.createElement('select');
|
|
6455
|
+
subSelect.className = 'cumulus-settings-select';
|
|
6456
|
+
subSelect.setAttribute('data-testid', 'webchat-gw-claude-default-select');
|
|
6457
|
+
var subDefault = '';
|
|
6458
|
+
providerModels.forEach(function (model) {
|
|
6459
|
+
var opt = document.createElement('option');
|
|
6460
|
+
opt.value = model.id;
|
|
6461
|
+
opt.textContent = model.label || model.id;
|
|
6462
|
+
subSelect.appendChild(opt);
|
|
6463
|
+
if (model.providerDefault) subDefault = model.id;
|
|
6464
|
+
});
|
|
6465
|
+
subSelect.value = subDefault || (providerModels[0] && providerModels[0].id) || '';
|
|
6466
|
+
subSelect.addEventListener('change', function () {
|
|
6467
|
+
applyClaudeSubDefault(gwState.catalog, subSelect.value);
|
|
6468
|
+
});
|
|
6469
|
+
subRow.appendChild(subLabel);
|
|
6470
|
+
subRow.appendChild(subSelect);
|
|
6471
|
+
card.appendChild(subRow);
|
|
6472
|
+
}
|
|
6473
|
+
renderCredential(card, provider);
|
|
6474
|
+
gwList.appendChild(card);
|
|
6475
|
+
});
|
|
6476
|
+
}
|
|
6477
|
+
|
|
6478
|
+
function renderDefaultSelect() {
|
|
6479
|
+
defaultSelect.innerHTML = '';
|
|
6480
|
+
if (!gwState.catalog.length) {
|
|
6481
|
+
var none = document.createElement('option');
|
|
6482
|
+
none.value = '';
|
|
6483
|
+
none.textContent = 'No models configured';
|
|
6484
|
+
defaultSelect.appendChild(none);
|
|
6485
|
+
defaultSelect.disabled = true;
|
|
6486
|
+
return;
|
|
6487
|
+
}
|
|
6488
|
+
defaultSelect.disabled = false;
|
|
6489
|
+
providerDefs.forEach(function (provider) {
|
|
6490
|
+
var group = null;
|
|
6491
|
+
gwState.catalog.forEach(function (model) {
|
|
6492
|
+
if (model.provider !== provider.id) return;
|
|
6493
|
+
if (!group) {
|
|
6494
|
+
group = document.createElement('optgroup');
|
|
6495
|
+
group.label = provider.title;
|
|
6496
|
+
defaultSelect.appendChild(group);
|
|
6497
|
+
}
|
|
6498
|
+
var opt = document.createElement('option');
|
|
6499
|
+
opt.value = model.id;
|
|
6500
|
+
opt.textContent = model.label || model.id;
|
|
6501
|
+
group.appendChild(opt);
|
|
6502
|
+
});
|
|
6503
|
+
});
|
|
6504
|
+
var current = gwState.catalog.find(function (model) {
|
|
6505
|
+
return model.gatewayDefault;
|
|
6506
|
+
});
|
|
6507
|
+
defaultSelect.value = current ? current.id : gwState.catalog[0].id;
|
|
6508
|
+
}
|
|
6509
|
+
|
|
6510
|
+
function renderAll() {
|
|
6511
|
+
renderDefaultSelect();
|
|
6512
|
+
renderGwModels();
|
|
6513
|
+
}
|
|
6514
|
+
|
|
6515
|
+
addProvider.addEventListener('change', function () {
|
|
6516
|
+
addContext.disabled = addProvider.value === 'anthropic';
|
|
6517
|
+
if (addContext.disabled) addContext.value = '';
|
|
6518
|
+
});
|
|
6519
|
+
addBtn.addEventListener('click', function () {
|
|
6520
|
+
var id = (addId.value || '').trim();
|
|
6521
|
+
var provider = addProvider.value;
|
|
6522
|
+
var context = addContext.value ? Number(addContext.value) : undefined;
|
|
6523
|
+
if (!id) {
|
|
6524
|
+
setSettingsStatus('Model ID is required', 'error');
|
|
6525
|
+
return;
|
|
6526
|
+
}
|
|
6527
|
+
if (
|
|
6528
|
+
id === 'claude' ||
|
|
6529
|
+
gwState.catalog.some(function (item) {
|
|
6530
|
+
return item.id === id;
|
|
6531
|
+
})
|
|
6532
|
+
) {
|
|
6533
|
+
setSettingsStatus('Duplicate or reserved model ID: ' + id, 'error');
|
|
6534
|
+
return;
|
|
6535
|
+
}
|
|
6536
|
+
if (context !== undefined && (!Number.isInteger(context) || context <= 0)) {
|
|
6537
|
+
setSettingsStatus('Context window must be a positive whole number', 'error');
|
|
6538
|
+
return;
|
|
6539
|
+
}
|
|
6540
|
+
var model = {
|
|
6541
|
+
id: id,
|
|
6542
|
+
label: (addLabel.value || '').trim() || id,
|
|
6543
|
+
provider: provider,
|
|
6544
|
+
contextWindow: provider === 'anthropic' ? undefined : context,
|
|
6545
|
+
gatewayDefault: gwState.catalog.length === 0,
|
|
6546
|
+
providerDefault:
|
|
6547
|
+
provider === 'anthropic' &&
|
|
6548
|
+
!gwState.catalog.some(function (item) {
|
|
6549
|
+
return item.provider === 'anthropic';
|
|
6550
|
+
}),
|
|
6551
|
+
};
|
|
6552
|
+
gwState.catalog.push(model);
|
|
6553
|
+
addId.value = '';
|
|
6554
|
+
addLabel.value = '';
|
|
6555
|
+
addContext.value = '';
|
|
6556
|
+
setSettingsStatus('', '');
|
|
6557
|
+
renderAll();
|
|
6558
|
+
});
|
|
6559
|
+
|
|
6560
|
+
function refreshOpenModelCatalogs() {
|
|
6561
|
+
var refresh = new XMLHttpRequest();
|
|
6562
|
+
refresh.open('GET', '/api/models');
|
|
6563
|
+
refresh.setRequestHeader('X-API-Key', activeApiKey);
|
|
6564
|
+
refresh.onreadystatechange = function () {
|
|
6565
|
+
if (refresh.readyState !== 4 || refresh.status !== 200) return;
|
|
6566
|
+
try {
|
|
6567
|
+
window.dispatchEvent(
|
|
6568
|
+
new CustomEvent('cumulus-model-catalog-updated', {
|
|
6569
|
+
detail: JSON.parse(refresh.responseText),
|
|
6570
|
+
})
|
|
6571
|
+
);
|
|
6572
|
+
} catch (e) {
|
|
6573
|
+
/* keep the saved config even if a panel refresh fails */
|
|
6574
|
+
}
|
|
6575
|
+
};
|
|
6576
|
+
refresh.send();
|
|
6577
|
+
}
|
|
6578
|
+
|
|
6579
|
+
saveBtn.addEventListener('click', function () {
|
|
6580
|
+
var validationError = validateModelCatalog(gwState.catalog);
|
|
6581
|
+
if (validationError) {
|
|
6582
|
+
setSettingsStatus(validationError, 'error');
|
|
6583
|
+
return;
|
|
6584
|
+
}
|
|
6585
|
+
saveBtn.disabled = true;
|
|
6586
|
+
setSettingsStatus('Saving…', '');
|
|
6587
|
+
var payload = addCredentialPatch(modelCatalogToGatewayConfig(gwState), {
|
|
6588
|
+
openai: {
|
|
6589
|
+
value: credentialValues.openai,
|
|
6590
|
+
clear: credentialClears.openai,
|
|
6591
|
+
},
|
|
6592
|
+
huggingface: {
|
|
6593
|
+
value: credentialValues.huggingface,
|
|
6594
|
+
clear: credentialClears.huggingface,
|
|
6595
|
+
},
|
|
6596
|
+
license: {
|
|
6597
|
+
value: credentialValues.license,
|
|
6598
|
+
clear: credentialClears.license,
|
|
6599
|
+
},
|
|
6600
|
+
});
|
|
6601
|
+
var save = new XMLHttpRequest();
|
|
6602
|
+
save.open('PUT', '/api/config');
|
|
6603
|
+
save.setRequestHeader('Content-Type', 'application/json');
|
|
6604
|
+
save.setRequestHeader('X-API-Key', activeApiKey);
|
|
6605
|
+
save.onreadystatechange = function () {
|
|
6606
|
+
if (save.readyState !== 4) return;
|
|
6607
|
+
saveBtn.disabled = false;
|
|
6608
|
+
if (save.status === 200) {
|
|
6609
|
+
try {
|
|
6610
|
+
var response = JSON.parse(save.responseText);
|
|
6611
|
+
gwState = gatewayConfigToModelCatalog(response);
|
|
6612
|
+
credentialClears.openai = false;
|
|
6613
|
+
credentialClears.huggingface = false;
|
|
6614
|
+
credentialClears.license = false;
|
|
6615
|
+
credentialValues.openai = response.openaiApiKey || '';
|
|
6616
|
+
credentialValues.huggingface = response.hfApiKey || '';
|
|
6617
|
+
credentialValues.license = response.licenseKey || '';
|
|
6618
|
+
gwLicenseMasked = response.licenseKey || '';
|
|
6619
|
+
gwLicenseInfo = response.license || null;
|
|
6620
|
+
renderAll();
|
|
6621
|
+
renderLicenseState(gwLicenseMasked, gwLicenseInfo);
|
|
6622
|
+
setSettingsStatus(
|
|
6623
|
+
gwLicenseInfo && gwLicenseInfo.pendingReload
|
|
6624
|
+
? 'Saved — license takes effect on gateway reload'
|
|
6625
|
+
: 'Saved — applies to new turns immediately',
|
|
6626
|
+
'success'
|
|
6627
|
+
);
|
|
6628
|
+
refreshOpenModelCatalogs();
|
|
6629
|
+
} catch (e) {
|
|
6630
|
+
setSettingsStatus('Saved, but the response could not be rendered', 'error');
|
|
6631
|
+
}
|
|
6632
|
+
} else {
|
|
6633
|
+
setSettingsStatus('Error ' + save.status, 'error');
|
|
6634
|
+
}
|
|
6635
|
+
};
|
|
6636
|
+
save.send(JSON.stringify(payload));
|
|
6637
|
+
});
|
|
6638
|
+
|
|
6639
|
+
(function loadGatewayConfig() {
|
|
6640
|
+
var load = new XMLHttpRequest();
|
|
6641
|
+
load.open('GET', '/api/config');
|
|
6642
|
+
load.setRequestHeader('X-API-Key', activeApiKey);
|
|
6643
|
+
load.onreadystatechange = function () {
|
|
6644
|
+
if (load.readyState !== 4) return;
|
|
6645
|
+
if (load.status === 200) {
|
|
6646
|
+
try {
|
|
6647
|
+
var response = JSON.parse(load.responseText);
|
|
6648
|
+
gwState = gatewayConfigToModelCatalog(response);
|
|
6649
|
+
credentialValues.openai = response.openaiApiKey || '';
|
|
6650
|
+
credentialValues.huggingface = response.hfApiKey || '';
|
|
6651
|
+
credentialValues.license = response.licenseKey || '';
|
|
6652
|
+
gwLicenseMasked = response.licenseKey || '';
|
|
6653
|
+
gwLicenseInfo = response.license || null;
|
|
6654
|
+
renderAll();
|
|
6655
|
+
renderLicenseState(gwLicenseMasked, gwLicenseInfo);
|
|
6656
|
+
} catch (e) {
|
|
6657
|
+
setSettingsStatus('Gateway settings response was invalid', 'error');
|
|
6658
|
+
}
|
|
6659
|
+
} else {
|
|
6660
|
+
setSettingsStatus('Load failed (' + load.status + ')', 'error');
|
|
6661
|
+
}
|
|
6662
|
+
};
|
|
6663
|
+
load.send();
|
|
6664
|
+
})();
|
|
6665
|
+
}
|
|
6666
|
+
|
|
5849
6667
|
function buildThreadPanel(threadName) {
|
|
5850
6668
|
var state = getThreadState(threadName);
|
|
5851
6669
|
|
|
@@ -5907,14 +6725,14 @@
|
|
|
5907
6725
|
claudeModelSelect.setAttribute('data-testid', 'webchat-claude-model-select');
|
|
5908
6726
|
claudeModelSelect.setAttribute('data-thread-claude-model', threadName);
|
|
5909
6727
|
claudeModelSelect.setAttribute('title', 'Claude model');
|
|
5910
|
-
//
|
|
5911
|
-
//
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
];
|
|
6728
|
+
// Populated ONLY from /api/models (resp.claudeModels) — task 129 deleted
|
|
6729
|
+
// the hardcoded fallback list. An empty catalog hides the select entirely
|
|
6730
|
+
// (visibility is re-derived in updateEffortVisibility, which also gates
|
|
6731
|
+
// on model === 'claude'); the Settings gear is where models get added.
|
|
6732
|
+
var claudeModelListEmpty = true;
|
|
5916
6733
|
function populateClaudeModels(list) {
|
|
5917
|
-
var models = list && list.length ? list :
|
|
6734
|
+
var models = list && list.length ? list : [];
|
|
6735
|
+
claudeModelListEmpty = models.length === 0;
|
|
5918
6736
|
claudeModelSelect.innerHTML = '';
|
|
5919
6737
|
var def = null;
|
|
5920
6738
|
models.forEach(function (m) {
|
|
@@ -5925,6 +6743,7 @@
|
|
|
5925
6743
|
if (m.default) def = m.id;
|
|
5926
6744
|
});
|
|
5927
6745
|
claudeModelSelect.value = def || (models[0] && models[0].id) || '';
|
|
6746
|
+
claudeModelSelect.style.display = claudeModelListEmpty ? 'none' : '';
|
|
5928
6747
|
}
|
|
5929
6748
|
populateClaudeModels(null);
|
|
5930
6749
|
|
|
@@ -6024,7 +6843,7 @@
|
|
|
6024
6843
|
function updateEffortVisibility() {
|
|
6025
6844
|
var isClaude = modelSelect.value === 'claude';
|
|
6026
6845
|
effortSelect.style.display = isClaude ? '' : 'none';
|
|
6027
|
-
claudeModelSelect.style.display = isClaude ? '' : 'none';
|
|
6846
|
+
claudeModelSelect.style.display = isClaude && !claudeModelListEmpty ? '' : 'none';
|
|
6028
6847
|
// Hide the whole Model/Effort popover rows (label included) for non-Claude providers.
|
|
6029
6848
|
var mr = panel.querySelector('[data-config-row="model"]');
|
|
6030
6849
|
var er = panel.querySelector('[data-config-row="effort"]');
|
|
@@ -6185,546 +7004,13 @@
|
|
|
6185
7004
|
modelPicker.appendChild(configBreadcrumb);
|
|
6186
7005
|
modelPicker.appendChild(modelPopover);
|
|
6187
7006
|
|
|
6188
|
-
// ── Settings modal (provider-organized model catalog + credentials) ──
|
|
6189
|
-
// Live-editable via PUT /api/config (task 104) — takes effect on the NEXT turn
|
|
6190
|
-
// with no restart, no JSON edit, no republish. Any valid gateway key.
|
|
6191
|
-
function openSettingsModal() {
|
|
6192
|
-
var backdrop = document.createElement('div');
|
|
6193
|
-
backdrop.className = 'cumulus-settings-backdrop';
|
|
6194
|
-
backdrop.setAttribute('data-testid', 'webchat-settings-modal');
|
|
6195
|
-
var modal = document.createElement('div');
|
|
6196
|
-
modal.className = 'cumulus-settings-modal';
|
|
6197
|
-
|
|
6198
|
-
var mHeader = document.createElement('div');
|
|
6199
|
-
mHeader.className = 'cumulus-settings-header';
|
|
6200
|
-
var mHeaderText = document.createElement('div');
|
|
6201
|
-
var mTitle = document.createElement('div');
|
|
6202
|
-
mTitle.className = 'cumulus-settings-title';
|
|
6203
|
-
mTitle.textContent = 'Gateway settings';
|
|
6204
|
-
var mSub = document.createElement('div');
|
|
6205
|
-
mSub.className = 'cumulus-settings-subtitle';
|
|
6206
|
-
mSub.textContent =
|
|
6207
|
-
'Manage Anthropic, OpenAI, and HuggingFace models — changes apply to new turns instantly';
|
|
6208
|
-
mHeaderText.appendChild(mTitle);
|
|
6209
|
-
mHeaderText.appendChild(mSub);
|
|
6210
|
-
var mClose = document.createElement('button');
|
|
6211
|
-
mClose.className = 'cumulus-overlay-close';
|
|
6212
|
-
mClose.textContent = '\xD7';
|
|
6213
|
-
mClose.setAttribute('data-testid', 'webchat-settings-close');
|
|
6214
|
-
mClose.addEventListener('click', function () {
|
|
6215
|
-
backdrop.remove();
|
|
6216
|
-
});
|
|
6217
|
-
mHeader.appendChild(mHeaderText);
|
|
6218
|
-
mHeader.appendChild(mClose);
|
|
6219
|
-
modal.appendChild(mHeader);
|
|
6220
|
-
|
|
6221
|
-
var body = document.createElement('div');
|
|
6222
|
-
body.className = 'cumulus-settings-body';
|
|
6223
|
-
|
|
6224
|
-
var modelsLabel = document.createElement('div');
|
|
6225
|
-
modelsLabel.className = 'cumulus-settings-section-label';
|
|
6226
|
-
modelsLabel.textContent = 'Models by provider';
|
|
6227
|
-
body.appendChild(modelsLabel);
|
|
6228
|
-
|
|
6229
|
-
var gwList = document.createElement('div');
|
|
6230
|
-
gwList.setAttribute('data-testid', 'webchat-gateway-models');
|
|
6231
|
-
body.appendChild(gwList);
|
|
6232
|
-
|
|
6233
|
-
var addCard = document.createElement('div');
|
|
6234
|
-
addCard.className = 'cumulus-settings-add-card';
|
|
6235
|
-
var addTitle = document.createElement('div');
|
|
6236
|
-
addTitle.className = 'cumulus-settings-add-title';
|
|
6237
|
-
addTitle.textContent = 'Add a model';
|
|
6238
|
-
addCard.appendChild(addTitle);
|
|
6239
|
-
var addWrap = document.createElement('div');
|
|
6240
|
-
addWrap.className = 'cumulus-settings-add-row';
|
|
6241
|
-
var addProvider = document.createElement('select');
|
|
6242
|
-
addProvider.className = 'cumulus-settings-select';
|
|
6243
|
-
addProvider.setAttribute('data-testid', 'webchat-gw-add-provider');
|
|
6244
|
-
[
|
|
6245
|
-
{ value: 'anthropic', label: 'Anthropic' },
|
|
6246
|
-
{ value: 'openai', label: 'OpenAI' },
|
|
6247
|
-
{ value: 'huggingface', label: 'HuggingFace' },
|
|
6248
|
-
].forEach(function (provider) {
|
|
6249
|
-
var option = document.createElement('option');
|
|
6250
|
-
option.value = provider.value;
|
|
6251
|
-
option.textContent = provider.label;
|
|
6252
|
-
addProvider.appendChild(option);
|
|
6253
|
-
});
|
|
6254
|
-
var addId = document.createElement('input');
|
|
6255
|
-
addId.type = 'text';
|
|
6256
|
-
addId.className = 'cumulus-settings-input';
|
|
6257
|
-
addId.placeholder = 'Model ID / name';
|
|
6258
|
-
addId.setAttribute('data-testid', 'webchat-gw-add-id');
|
|
6259
|
-
var addLabel = document.createElement('input');
|
|
6260
|
-
addLabel.type = 'text';
|
|
6261
|
-
addLabel.className = 'cumulus-settings-input';
|
|
6262
|
-
addLabel.placeholder = 'Display label (optional)';
|
|
6263
|
-
addLabel.setAttribute('data-testid', 'webchat-gw-add-label');
|
|
6264
|
-
var addContext = document.createElement('input');
|
|
6265
|
-
addContext.type = 'number';
|
|
6266
|
-
addContext.min = '1';
|
|
6267
|
-
addContext.step = '1';
|
|
6268
|
-
addContext.className = 'cumulus-settings-input';
|
|
6269
|
-
addContext.placeholder = 'Context tokens';
|
|
6270
|
-
addContext.setAttribute('data-testid', 'webchat-gw-add-context');
|
|
6271
|
-
var addBtn = document.createElement('button');
|
|
6272
|
-
addBtn.type = 'button';
|
|
6273
|
-
addBtn.textContent = '+ Add';
|
|
6274
|
-
addBtn.className = 'cumulus-settings-add-btn';
|
|
6275
|
-
addBtn.setAttribute('data-testid', 'webchat-gw-add-btn');
|
|
6276
|
-
addWrap.appendChild(addProvider);
|
|
6277
|
-
addWrap.appendChild(addId);
|
|
6278
|
-
addWrap.appendChild(addLabel);
|
|
6279
|
-
addWrap.appendChild(addContext);
|
|
6280
|
-
addWrap.appendChild(addBtn);
|
|
6281
|
-
addCard.appendChild(addWrap);
|
|
6282
|
-
body.appendChild(addCard);
|
|
6283
|
-
modal.appendChild(body);
|
|
6284
|
-
|
|
6285
|
-
var footer = document.createElement('div');
|
|
6286
|
-
footer.className = 'cumulus-settings-footer';
|
|
6287
|
-
var saveBtn = document.createElement('button');
|
|
6288
|
-
saveBtn.textContent = 'Save';
|
|
6289
|
-
saveBtn.className = 'cumulus-settings-btn';
|
|
6290
|
-
saveBtn.setAttribute('data-testid', 'webchat-gw-save');
|
|
6291
|
-
var gwStatus = document.createElement('span');
|
|
6292
|
-
gwStatus.className = 'cumulus-settings-status';
|
|
6293
|
-
footer.appendChild(saveBtn);
|
|
6294
|
-
footer.appendChild(gwStatus);
|
|
6295
|
-
modal.appendChild(footer);
|
|
6296
|
-
|
|
6297
|
-
backdrop.appendChild(modal);
|
|
6298
|
-
backdrop.addEventListener('click', function (e) {
|
|
6299
|
-
if (e.target === backdrop) backdrop.remove();
|
|
6300
|
-
});
|
|
6301
|
-
document.body.appendChild(backdrop);
|
|
6302
|
-
|
|
6303
|
-
var gwState = { catalog: [], sentinel: null };
|
|
6304
|
-
var credentialInputs = {};
|
|
6305
|
-
var credentialValues = { openai: '', huggingface: '' };
|
|
6306
|
-
var credentialClears = { openai: false, huggingface: false };
|
|
6307
|
-
var providerDefs = [
|
|
6308
|
-
{
|
|
6309
|
-
id: 'anthropic',
|
|
6310
|
-
title: 'Anthropic',
|
|
6311
|
-
description: 'Claude CLI models · authentication is managed by the Claude CLI',
|
|
6312
|
-
},
|
|
6313
|
-
{
|
|
6314
|
-
id: 'openai',
|
|
6315
|
-
title: 'OpenAI',
|
|
6316
|
-
description: 'Responses API models with direct tool use',
|
|
6317
|
-
credential: 'openaiApiKey',
|
|
6318
|
-
credentialTestId: 'webchat-gw-openaikey',
|
|
6319
|
-
},
|
|
6320
|
-
{
|
|
6321
|
-
id: 'huggingface',
|
|
6322
|
-
title: 'HuggingFace',
|
|
6323
|
-
description: 'OpenAI-compatible models served through the HuggingFace router',
|
|
6324
|
-
credential: 'hfApiKey',
|
|
6325
|
-
credentialTestId: 'webchat-gw-hfkey',
|
|
6326
|
-
},
|
|
6327
|
-
];
|
|
6328
|
-
|
|
6329
|
-
function setSettingsStatus(message, kind) {
|
|
6330
|
-
gwStatus.textContent = message || '';
|
|
6331
|
-
gwStatus.className = 'cumulus-settings-status' + (kind ? ' ' + kind : '');
|
|
6332
|
-
}
|
|
6333
|
-
|
|
6334
|
-
function makeRadioLabel(text, input) {
|
|
6335
|
-
var wrap = document.createElement('label');
|
|
6336
|
-
wrap.className = 'cumulus-settings-radio-label';
|
|
6337
|
-
wrap.appendChild(input);
|
|
6338
|
-
wrap.appendChild(document.createElement('br'));
|
|
6339
|
-
wrap.appendChild(document.createTextNode(text));
|
|
6340
|
-
return wrap;
|
|
6341
|
-
}
|
|
6342
|
-
|
|
6343
|
-
function renderModelRow(model, providerModels, index) {
|
|
6344
|
-
var row = document.createElement('div');
|
|
6345
|
-
row.className =
|
|
6346
|
-
'cumulus-settings-model-row ' +
|
|
6347
|
-
model.provider +
|
|
6348
|
-
(model.gatewayDefault ? ' is-default' : '');
|
|
6349
|
-
row.setAttribute('data-gw-model', model.id);
|
|
6350
|
-
row.setAttribute('data-testid', 'webchat-gw-row-' + model.provider + '-' + index);
|
|
6351
|
-
|
|
6352
|
-
var gatewayRadio = document.createElement('input');
|
|
6353
|
-
gatewayRadio.type = 'radio';
|
|
6354
|
-
gatewayRadio.name = 'gw-default';
|
|
6355
|
-
gatewayRadio.checked = !!model.gatewayDefault;
|
|
6356
|
-
gatewayRadio.setAttribute(
|
|
6357
|
-
'data-testid',
|
|
6358
|
-
'webchat-gw-default-' + model.provider + '-' + index
|
|
6359
|
-
);
|
|
6360
|
-
gatewayRadio.addEventListener('change', function () {
|
|
6361
|
-
gwState.catalog.forEach(function (item) {
|
|
6362
|
-
item.gatewayDefault = false;
|
|
6363
|
-
});
|
|
6364
|
-
model.gatewayDefault = true;
|
|
6365
|
-
if (model.provider === 'anthropic') {
|
|
6366
|
-
gwState.catalog.forEach(function (item) {
|
|
6367
|
-
if (item.provider === 'anthropic') item.providerDefault = false;
|
|
6368
|
-
});
|
|
6369
|
-
model.providerDefault = true;
|
|
6370
|
-
}
|
|
6371
|
-
renderGwModels();
|
|
6372
|
-
});
|
|
6373
|
-
row.appendChild(makeRadioLabel('gateway', gatewayRadio));
|
|
6374
|
-
|
|
6375
|
-
if (model.provider === 'anthropic') {
|
|
6376
|
-
var providerRadio = document.createElement('input');
|
|
6377
|
-
providerRadio.type = 'radio';
|
|
6378
|
-
providerRadio.name = 'gw-anthropic-default';
|
|
6379
|
-
providerRadio.checked = !!model.providerDefault;
|
|
6380
|
-
providerRadio.setAttribute(
|
|
6381
|
-
'data-testid',
|
|
6382
|
-
'webchat-gw-provider-default-anthropic-' + index
|
|
6383
|
-
);
|
|
6384
|
-
providerRadio.addEventListener('change', function () {
|
|
6385
|
-
gwState.catalog.forEach(function (item) {
|
|
6386
|
-
if (item.provider === 'anthropic') item.providerDefault = false;
|
|
6387
|
-
});
|
|
6388
|
-
model.providerDefault = true;
|
|
6389
|
-
if (
|
|
6390
|
-
gwState.catalog.some(function (item) {
|
|
6391
|
-
return item.provider === 'anthropic' && item.gatewayDefault;
|
|
6392
|
-
})
|
|
6393
|
-
) {
|
|
6394
|
-
gwState.catalog.forEach(function (item) {
|
|
6395
|
-
item.gatewayDefault = false;
|
|
6396
|
-
});
|
|
6397
|
-
model.gatewayDefault = true;
|
|
6398
|
-
}
|
|
6399
|
-
renderGwModels();
|
|
6400
|
-
});
|
|
6401
|
-
row.appendChild(makeRadioLabel('Claude', providerRadio));
|
|
6402
|
-
} else {
|
|
6403
|
-
var spacer = document.createElement('span');
|
|
6404
|
-
row.appendChild(spacer);
|
|
6405
|
-
}
|
|
6406
|
-
|
|
6407
|
-
var idInput = document.createElement('input');
|
|
6408
|
-
idInput.type = 'text';
|
|
6409
|
-
idInput.className = 'cumulus-settings-input cumulus-settings-row-input';
|
|
6410
|
-
idInput.value = model.id;
|
|
6411
|
-
idInput.setAttribute('aria-label', providerModels.title + ' model ID');
|
|
6412
|
-
idInput.setAttribute('data-testid', 'webchat-gw-id-' + model.provider + '-' + index);
|
|
6413
|
-
idInput.addEventListener('input', function () {
|
|
6414
|
-
model.id = idInput.value;
|
|
6415
|
-
});
|
|
6416
|
-
row.appendChild(idInput);
|
|
6417
|
-
|
|
6418
|
-
var labelInput = document.createElement('input');
|
|
6419
|
-
labelInput.type = 'text';
|
|
6420
|
-
labelInput.className = 'cumulus-settings-input cumulus-settings-row-input';
|
|
6421
|
-
labelInput.value = model.label || model.id;
|
|
6422
|
-
labelInput.setAttribute('aria-label', providerModels.title + ' display label');
|
|
6423
|
-
labelInput.setAttribute(
|
|
6424
|
-
'data-testid',
|
|
6425
|
-
'webchat-gw-label-' + model.provider + '-' + index
|
|
6426
|
-
);
|
|
6427
|
-
labelInput.addEventListener('input', function () {
|
|
6428
|
-
model.label = labelInput.value;
|
|
6429
|
-
});
|
|
6430
|
-
row.appendChild(labelInput);
|
|
6431
|
-
|
|
6432
|
-
if (model.provider !== 'anthropic') {
|
|
6433
|
-
var contextInput = document.createElement('input');
|
|
6434
|
-
contextInput.type = 'number';
|
|
6435
|
-
contextInput.min = '1';
|
|
6436
|
-
contextInput.step = '1';
|
|
6437
|
-
contextInput.className = 'cumulus-settings-input cumulus-settings-row-input';
|
|
6438
|
-
contextInput.placeholder = 'Context';
|
|
6439
|
-
contextInput.value = model.contextWindow || '';
|
|
6440
|
-
contextInput.setAttribute('aria-label', providerModels.title + ' context window');
|
|
6441
|
-
contextInput.setAttribute(
|
|
6442
|
-
'data-testid',
|
|
6443
|
-
'webchat-gw-context-' + model.provider + '-' + index
|
|
6444
|
-
);
|
|
6445
|
-
contextInput.addEventListener('input', function () {
|
|
6446
|
-
model.contextWindow = contextInput.value ? Number(contextInput.value) : undefined;
|
|
6447
|
-
});
|
|
6448
|
-
row.appendChild(contextInput);
|
|
6449
|
-
}
|
|
6450
|
-
|
|
6451
|
-
var remove = document.createElement('button');
|
|
6452
|
-
remove.type = 'button';
|
|
6453
|
-
remove.className = 'cumulus-settings-model-remove';
|
|
6454
|
-
remove.textContent = '\xD7';
|
|
6455
|
-
remove.title = 'Remove ' + model.id;
|
|
6456
|
-
remove.setAttribute('data-testid', 'webchat-gw-remove-' + model.provider + '-' + index);
|
|
6457
|
-
remove.addEventListener('click', function () {
|
|
6458
|
-
var absoluteIndex = gwState.catalog.indexOf(model);
|
|
6459
|
-
var wasGatewayDefault = model.gatewayDefault;
|
|
6460
|
-
var wasProviderDefault = model.providerDefault;
|
|
6461
|
-
if (absoluteIndex >= 0) gwState.catalog.splice(absoluteIndex, 1);
|
|
6462
|
-
if (wasGatewayDefault && gwState.catalog.length) {
|
|
6463
|
-
var nextGatewayDefault = gwState.catalog[0];
|
|
6464
|
-
nextGatewayDefault.gatewayDefault = true;
|
|
6465
|
-
if (nextGatewayDefault.provider === 'anthropic') {
|
|
6466
|
-
gwState.catalog.forEach(function (item) {
|
|
6467
|
-
if (item.provider === 'anthropic') item.providerDefault = false;
|
|
6468
|
-
});
|
|
6469
|
-
nextGatewayDefault.providerDefault = true;
|
|
6470
|
-
}
|
|
6471
|
-
}
|
|
6472
|
-
if (
|
|
6473
|
-
wasProviderDefault &&
|
|
6474
|
-
!gwState.catalog.some(function (item) {
|
|
6475
|
-
return item.provider === 'anthropic' && item.providerDefault;
|
|
6476
|
-
})
|
|
6477
|
-
) {
|
|
6478
|
-
var nextClaude = gwState.catalog.find(function (item) {
|
|
6479
|
-
return item.provider === 'anthropic';
|
|
6480
|
-
});
|
|
6481
|
-
if (nextClaude) nextClaude.providerDefault = true;
|
|
6482
|
-
}
|
|
6483
|
-
renderGwModels();
|
|
6484
|
-
});
|
|
6485
|
-
row.appendChild(remove);
|
|
6486
|
-
return row;
|
|
6487
|
-
}
|
|
6488
|
-
|
|
6489
|
-
function renderCredential(card, provider) {
|
|
6490
|
-
if (!provider.credential) {
|
|
6491
|
-
var note = document.createElement('div');
|
|
6492
|
-
note.className = 'cumulus-settings-credential-note';
|
|
6493
|
-
note.textContent = 'Authenticate with `claude login` on the gateway host.';
|
|
6494
|
-
card.appendChild(note);
|
|
6495
|
-
return;
|
|
6496
|
-
}
|
|
6497
|
-
var credentialRow = document.createElement('div');
|
|
6498
|
-
credentialRow.className = 'cumulus-settings-provider-credential';
|
|
6499
|
-
var credentialLabel = document.createElement('label');
|
|
6500
|
-
credentialLabel.textContent = 'API key';
|
|
6501
|
-
var credentialInput = document.createElement('input');
|
|
6502
|
-
credentialInput.type = 'password';
|
|
6503
|
-
credentialInput.autocomplete = 'off';
|
|
6504
|
-
credentialInput.className = 'cumulus-settings-input';
|
|
6505
|
-
credentialInput.placeholder = credentialClears[provider.id]
|
|
6506
|
-
? 'Will be cleared on save'
|
|
6507
|
-
: '(unset)';
|
|
6508
|
-
credentialInput.value = credentialValues[provider.id] || '';
|
|
6509
|
-
credentialInput.setAttribute('data-testid', provider.credentialTestId);
|
|
6510
|
-
credentialInput.addEventListener('input', function () {
|
|
6511
|
-
credentialValues[provider.id] = credentialInput.value;
|
|
6512
|
-
credentialClears[provider.id] = false;
|
|
6513
|
-
credentialInput.placeholder = '(unset)';
|
|
6514
|
-
});
|
|
6515
|
-
credentialInputs[provider.id] = credentialInput;
|
|
6516
|
-
var clear = document.createElement('button');
|
|
6517
|
-
clear.type = 'button';
|
|
6518
|
-
clear.className = 'cumulus-settings-clear-btn';
|
|
6519
|
-
clear.textContent = 'Clear';
|
|
6520
|
-
clear.setAttribute('data-testid', 'webchat-gw-clear-' + provider.id + '-key');
|
|
6521
|
-
clear.addEventListener('click', function () {
|
|
6522
|
-
credentialClears[provider.id] = true;
|
|
6523
|
-
credentialValues[provider.id] = '';
|
|
6524
|
-
credentialInput.value = '';
|
|
6525
|
-
credentialInput.placeholder = 'Will be cleared on save';
|
|
6526
|
-
setSettingsStatus('Credential marked for removal', '');
|
|
6527
|
-
});
|
|
6528
|
-
credentialRow.appendChild(credentialLabel);
|
|
6529
|
-
credentialRow.appendChild(credentialInput);
|
|
6530
|
-
credentialRow.appendChild(clear);
|
|
6531
|
-
card.appendChild(credentialRow);
|
|
6532
|
-
}
|
|
6533
|
-
|
|
6534
|
-
function renderGwModels() {
|
|
6535
|
-
gwList.innerHTML = '';
|
|
6536
|
-
credentialInputs = {};
|
|
6537
|
-
providerDefs.forEach(function (provider) {
|
|
6538
|
-
var providerModels = gwState.catalog.filter(function (model) {
|
|
6539
|
-
return model.provider === provider.id;
|
|
6540
|
-
});
|
|
6541
|
-
var card = document.createElement('section');
|
|
6542
|
-
card.className = 'cumulus-settings-provider-card';
|
|
6543
|
-
card.setAttribute('data-testid', 'webchat-gw-provider-' + provider.id);
|
|
6544
|
-
var head = document.createElement('div');
|
|
6545
|
-
head.className = 'cumulus-settings-provider-head';
|
|
6546
|
-
var heading = document.createElement('div');
|
|
6547
|
-
var title = document.createElement('div');
|
|
6548
|
-
title.className = 'cumulus-settings-provider-title';
|
|
6549
|
-
title.textContent = provider.title;
|
|
6550
|
-
var description = document.createElement('div');
|
|
6551
|
-
description.className = 'cumulus-settings-provider-desc';
|
|
6552
|
-
description.textContent = provider.description;
|
|
6553
|
-
heading.appendChild(title);
|
|
6554
|
-
heading.appendChild(description);
|
|
6555
|
-
var count = document.createElement('span');
|
|
6556
|
-
count.className = 'cumulus-settings-provider-count';
|
|
6557
|
-
count.textContent =
|
|
6558
|
-
providerModels.length + (providerModels.length === 1 ? ' model' : ' models');
|
|
6559
|
-
head.appendChild(heading);
|
|
6560
|
-
head.appendChild(count);
|
|
6561
|
-
card.appendChild(head);
|
|
6562
|
-
var list = document.createElement('div');
|
|
6563
|
-
list.className = 'cumulus-settings-provider-models';
|
|
6564
|
-
if (!providerModels.length) {
|
|
6565
|
-
var empty = document.createElement('div');
|
|
6566
|
-
empty.className = 'cumulus-settings-provider-empty';
|
|
6567
|
-
empty.textContent = 'No ' + provider.title + ' models configured.';
|
|
6568
|
-
list.appendChild(empty);
|
|
6569
|
-
} else {
|
|
6570
|
-
providerModels.forEach(function (model, index) {
|
|
6571
|
-
list.appendChild(renderModelRow(model, provider, index));
|
|
6572
|
-
});
|
|
6573
|
-
}
|
|
6574
|
-
card.appendChild(list);
|
|
6575
|
-
renderCredential(card, provider);
|
|
6576
|
-
gwList.appendChild(card);
|
|
6577
|
-
});
|
|
6578
|
-
}
|
|
6579
|
-
|
|
6580
|
-
addProvider.addEventListener('change', function () {
|
|
6581
|
-
addContext.disabled = addProvider.value === 'anthropic';
|
|
6582
|
-
if (addContext.disabled) addContext.value = '';
|
|
6583
|
-
});
|
|
6584
|
-
addBtn.addEventListener('click', function () {
|
|
6585
|
-
var id = (addId.value || '').trim();
|
|
6586
|
-
var provider = addProvider.value;
|
|
6587
|
-
var context = addContext.value ? Number(addContext.value) : undefined;
|
|
6588
|
-
if (!id) {
|
|
6589
|
-
setSettingsStatus('Model ID is required', 'error');
|
|
6590
|
-
return;
|
|
6591
|
-
}
|
|
6592
|
-
if (
|
|
6593
|
-
id === 'claude' ||
|
|
6594
|
-
gwState.catalog.some(function (item) {
|
|
6595
|
-
return item.id === id;
|
|
6596
|
-
})
|
|
6597
|
-
) {
|
|
6598
|
-
setSettingsStatus('Duplicate or reserved model ID: ' + id, 'error');
|
|
6599
|
-
return;
|
|
6600
|
-
}
|
|
6601
|
-
if (context !== undefined && (!Number.isInteger(context) || context <= 0)) {
|
|
6602
|
-
setSettingsStatus('Context window must be a positive whole number', 'error');
|
|
6603
|
-
return;
|
|
6604
|
-
}
|
|
6605
|
-
var model = {
|
|
6606
|
-
id: id,
|
|
6607
|
-
label: (addLabel.value || '').trim() || id,
|
|
6608
|
-
provider: provider,
|
|
6609
|
-
contextWindow: provider === 'anthropic' ? undefined : context,
|
|
6610
|
-
gatewayDefault: gwState.catalog.length === 0,
|
|
6611
|
-
providerDefault:
|
|
6612
|
-
provider === 'anthropic' &&
|
|
6613
|
-
!gwState.catalog.some(function (item) {
|
|
6614
|
-
return item.provider === 'anthropic';
|
|
6615
|
-
}),
|
|
6616
|
-
};
|
|
6617
|
-
gwState.catalog.push(model);
|
|
6618
|
-
addId.value = '';
|
|
6619
|
-
addLabel.value = '';
|
|
6620
|
-
addContext.value = '';
|
|
6621
|
-
setSettingsStatus('', '');
|
|
6622
|
-
renderGwModels();
|
|
6623
|
-
});
|
|
6624
|
-
|
|
6625
|
-
function refreshOpenModelCatalogs() {
|
|
6626
|
-
var refresh = new XMLHttpRequest();
|
|
6627
|
-
refresh.open('GET', '/api/models');
|
|
6628
|
-
refresh.setRequestHeader('X-API-Key', activeApiKey);
|
|
6629
|
-
refresh.onreadystatechange = function () {
|
|
6630
|
-
if (refresh.readyState !== 4 || refresh.status !== 200) return;
|
|
6631
|
-
try {
|
|
6632
|
-
window.dispatchEvent(
|
|
6633
|
-
new CustomEvent('cumulus-model-catalog-updated', {
|
|
6634
|
-
detail: JSON.parse(refresh.responseText),
|
|
6635
|
-
})
|
|
6636
|
-
);
|
|
6637
|
-
} catch (e) {
|
|
6638
|
-
/* keep the saved config even if a panel refresh fails */
|
|
6639
|
-
}
|
|
6640
|
-
};
|
|
6641
|
-
refresh.send();
|
|
6642
|
-
}
|
|
6643
|
-
|
|
6644
|
-
saveBtn.addEventListener('click', function () {
|
|
6645
|
-
var validationError = validateModelCatalog(gwState.catalog);
|
|
6646
|
-
if (validationError) {
|
|
6647
|
-
setSettingsStatus(validationError, 'error');
|
|
6648
|
-
return;
|
|
6649
|
-
}
|
|
6650
|
-
saveBtn.disabled = true;
|
|
6651
|
-
setSettingsStatus('Saving…', '');
|
|
6652
|
-
var payload = addCredentialPatch(modelCatalogToGatewayConfig(gwState), {
|
|
6653
|
-
openai: {
|
|
6654
|
-
value: credentialValues.openai,
|
|
6655
|
-
clear: credentialClears.openai,
|
|
6656
|
-
},
|
|
6657
|
-
huggingface: {
|
|
6658
|
-
value: credentialValues.huggingface,
|
|
6659
|
-
clear: credentialClears.huggingface,
|
|
6660
|
-
},
|
|
6661
|
-
});
|
|
6662
|
-
var save = new XMLHttpRequest();
|
|
6663
|
-
save.open('PUT', '/api/config');
|
|
6664
|
-
save.setRequestHeader('Content-Type', 'application/json');
|
|
6665
|
-
save.setRequestHeader('X-API-Key', activeApiKey);
|
|
6666
|
-
save.onreadystatechange = function () {
|
|
6667
|
-
if (save.readyState !== 4) return;
|
|
6668
|
-
saveBtn.disabled = false;
|
|
6669
|
-
if (save.status === 200) {
|
|
6670
|
-
try {
|
|
6671
|
-
var response = JSON.parse(save.responseText);
|
|
6672
|
-
gwState = gatewayConfigToModelCatalog(response);
|
|
6673
|
-
credentialClears.openai = false;
|
|
6674
|
-
credentialClears.huggingface = false;
|
|
6675
|
-
credentialValues.openai = response.openaiApiKey || '';
|
|
6676
|
-
credentialValues.huggingface = response.hfApiKey || '';
|
|
6677
|
-
renderGwModels();
|
|
6678
|
-
setSettingsStatus('Saved — applies to new turns immediately', 'success');
|
|
6679
|
-
refreshOpenModelCatalogs();
|
|
6680
|
-
} catch (e) {
|
|
6681
|
-
setSettingsStatus('Saved, but the response could not be rendered', 'error');
|
|
6682
|
-
}
|
|
6683
|
-
} else {
|
|
6684
|
-
setSettingsStatus('Error ' + save.status, 'error');
|
|
6685
|
-
}
|
|
6686
|
-
};
|
|
6687
|
-
save.send(JSON.stringify(payload));
|
|
6688
|
-
});
|
|
6689
|
-
|
|
6690
|
-
(function loadGatewayConfig() {
|
|
6691
|
-
var load = new XMLHttpRequest();
|
|
6692
|
-
load.open('GET', '/api/config');
|
|
6693
|
-
load.setRequestHeader('X-API-Key', activeApiKey);
|
|
6694
|
-
load.onreadystatechange = function () {
|
|
6695
|
-
if (load.readyState !== 4) return;
|
|
6696
|
-
if (load.status === 200) {
|
|
6697
|
-
try {
|
|
6698
|
-
var response = JSON.parse(load.responseText);
|
|
6699
|
-
gwState = gatewayConfigToModelCatalog(response);
|
|
6700
|
-
credentialValues.openai = response.openaiApiKey || '';
|
|
6701
|
-
credentialValues.huggingface = response.hfApiKey || '';
|
|
6702
|
-
renderGwModels();
|
|
6703
|
-
} catch (e) {
|
|
6704
|
-
setSettingsStatus('Gateway settings response was invalid', 'error');
|
|
6705
|
-
}
|
|
6706
|
-
} else {
|
|
6707
|
-
setSettingsStatus('Load failed (' + load.status + ')', 'error');
|
|
6708
|
-
}
|
|
6709
|
-
};
|
|
6710
|
-
load.send();
|
|
6711
|
-
})();
|
|
6712
|
-
}
|
|
6713
|
-
|
|
6714
7007
|
panelHeader.appendChild(modelPicker);
|
|
6715
7008
|
|
|
6716
|
-
// Action buttons (
|
|
7009
|
+
// Action buttons (include + revert + select) — gateway settings moved
|
|
7010
|
+
// to the topbar gear (task 128)
|
|
6717
7011
|
var actions = document.createElement('div');
|
|
6718
7012
|
actions.className = 'cumulus-panel-actions';
|
|
6719
7013
|
|
|
6720
|
-
var settingsBtn = document.createElement('button');
|
|
6721
|
-
settingsBtn.className = 'cumulus-panel-action-btn';
|
|
6722
|
-
settingsBtn.setAttribute('data-testid', 'webchat-settings-btn');
|
|
6723
|
-
settingsBtn.setAttribute('title', 'Settings — configure available models');
|
|
6724
|
-
settingsBtn.textContent = '⚙'; // gear
|
|
6725
|
-
settingsBtn.addEventListener('click', openSettingsModal);
|
|
6726
|
-
actions.appendChild(settingsBtn);
|
|
6727
|
-
|
|
6728
7014
|
var includeBtn = document.createElement('button');
|
|
6729
7015
|
includeBtn.className = 'cumulus-panel-action-btn';
|
|
6730
7016
|
includeBtn.setAttribute('data-testid', 'webchat-include-btn');
|