(select: HTMLSelectElement)
| 575 | } |
| 576 | |
| 577 | async function loadOllamaModelsIntoSelect(select: HTMLSelectElement): Promise<void> { |
| 578 | const snapshot = getRuntimeConfigSnapshot(); |
| 579 | const ollamaUrl = settingsManager.getPending('OLLAMA_API_URL') |
| 580 | || snapshot.secrets['OLLAMA_API_URL']?.value |
| 581 | || ''; |
| 582 | if (!ollamaUrl) { |
| 583 | select.innerHTML = '<option value="" disabled selected>Set Ollama URL first</option>'; |
| 584 | return; |
| 585 | } |
| 586 | |
| 587 | const currentModel = settingsManager.getPending('OLLAMA_MODEL') |
| 588 | || snapshot.secrets['OLLAMA_MODEL']?.value |
| 589 | || ''; |
| 590 | |
| 591 | const models = await fetchOllamaModels(ollamaUrl); |
| 592 | |
| 593 | if (models.length === 0) { |
| 594 | const manual = select.parentElement?.querySelector<HTMLInputElement>('input[data-model-manual]'); |
| 595 | if (manual) { |
| 596 | select.style.display = 'none'; |
| 597 | manual.classList.remove('hidden-input'); |
| 598 | if (!manual.dataset.listenerAttached) { |
| 599 | manual.dataset.listenerAttached = '1'; |
| 600 | manual.addEventListener('blur', () => { |
| 601 | const model = manual.value.trim(); |
| 602 | if (model) { |
| 603 | settingsManager.setPending('OLLAMA_MODEL', model); |
| 604 | settingsManager.setValidation('OLLAMA_MODEL', true); |
| 605 | manual.classList.remove('invalid'); |
| 606 | manual.classList.add('valid-staged'); |
| 607 | updateFeatureCardStatus('aiOllama'); |
| 608 | renderSidebar(); |
| 609 | } |
| 610 | }); |
| 611 | } |
| 612 | } |
| 613 | return; |
| 614 | } |
| 615 | |
| 616 | const options = currentModel ? '' : '<option value="" selected disabled>Select a model...</option>'; |
| 617 | select.innerHTML = options + models.map(name => |
| 618 | `<option value="${escapeHtml(name)}" ${name === currentModel ? 'selected' : ''}>${escapeHtml(name)}</option>` |
| 619 | ).join(''); |
| 620 | } |
| 621 | |
| 622 | // ── Debug section ── |
| 623 |
no test coverage detected