(key: RuntimeSecretKey, _featureId: RuntimeFeatureId)
| 376 | } |
| 377 | |
| 378 | function renderSecretInput(key: RuntimeSecretKey, _featureId: RuntimeFeatureId): string { |
| 379 | const state = getSecretState(key); |
| 380 | const pending = settingsManager.hasPending(key); |
| 381 | const { validated, message } = settingsManager.getValidationState(key); |
| 382 | const label = HUMAN_LABELS[key] || key; |
| 383 | const signupUrl = SIGNUP_URLS[key]; |
| 384 | const isPlaintext = PLAINTEXT_KEYS.has(key); |
| 385 | const showGetKey = signupUrl && !state.present && !pending; |
| 386 | |
| 387 | const statusText = pending |
| 388 | ? (validated === false ? 'Invalid' : 'Staged') |
| 389 | : !state.present ? 'Missing' : state.valid ? 'Valid' : 'Looks invalid'; |
| 390 | const statusClass = pending |
| 391 | ? (validated === false ? 'warn' : 'staged') |
| 392 | : state.valid ? 'ok' : 'warn'; |
| 393 | const inputClass = pending ? (validated === false ? 'invalid' : 'valid-staged') : ''; |
| 394 | const hintText = pending && validated === false ? (message || 'Invalid value') : null; |
| 395 | |
| 396 | if (key === 'OLLAMA_MODEL') { |
| 397 | const storedModel = pending |
| 398 | ? settingsManager.getPending(key) || '' |
| 399 | : getRuntimeConfigSnapshot().secrets[key]?.value || ''; |
| 400 | return ` |
| 401 | <div class="settings-secret-row"> |
| 402 | <div class="settings-secret-label">${escapeHtml(label)}</div> |
| 403 | <span class="settings-secret-status ${statusClass}">${escapeHtml(statusText)}</span> |
| 404 | <select data-model-select data-feature="${_featureId}" class="${inputClass}"> |
| 405 | ${storedModel ? `<option value="${escapeHtml(storedModel)}" selected>${escapeHtml(storedModel)}</option>` : '<option value="" selected disabled>Loading models...</option>'} |
| 406 | </select> |
| 407 | <input type="text" data-model-manual data-feature="${_featureId}" class="${inputClass} hidden-input" |
| 408 | placeholder="Or type model name" autocomplete="off" |
| 409 | ${storedModel ? `value="${escapeHtml(storedModel)}"` : ''}> |
| 410 | ${hintText ? `<span class="settings-secret-hint">${escapeHtml(hintText)}</span>` : ''} |
| 411 | </div> |
| 412 | `; |
| 413 | } |
| 414 | |
| 415 | const getKeyHtml = showGetKey |
| 416 | ? `<a href="#" data-signup-url="${signupUrl}" class="settings-secret-link">Get key</a>` |
| 417 | : ''; |
| 418 | |
| 419 | return ` |
| 420 | <div class="settings-secret-row"> |
| 421 | <div class="settings-secret-label">${escapeHtml(label)}</div> |
| 422 | <span class="settings-secret-status ${statusClass}">${escapeHtml(statusText)}</span> |
| 423 | <div class="settings-input-wrapper${showGetKey ? ' has-suffix' : ''}"> |
| 424 | <input type="${isPlaintext ? 'text' : 'password'}" data-secret="${key}" data-feature="${_featureId}" |
| 425 | placeholder="${pending ? 'Staged' : 'Enter value...'}" autocomplete="off" class="${inputClass}" |
| 426 | ${pending ? `value="${isPlaintext ? escapeHtml(settingsManager.getPending(key) || '') : MASKED_SENTINEL}"` : (isPlaintext && state.present ? `value="${escapeHtml(getRuntimeConfigSnapshot().secrets[key]?.value || '')}"` : '')}> |
| 427 | ${getKeyHtml} |
| 428 | </div> |
| 429 | ${hintText ? `<span class="settings-secret-hint">${escapeHtml(hintText)}</span>` : ''} |
| 430 | </div> |
| 431 | `; |
| 432 | } |
| 433 | |
| 434 | function initFeatureSectionListeners(area: HTMLElement): void { |
| 435 | area.querySelectorAll<HTMLElement>('[data-feat-toggle-expand]').forEach(header => { |
no test coverage detected