feat: custom endpoint fields in new profile form (fixes #170, closes #214)

* feat: add custom endpoint fields to new profile form

* fix: skip config write tests when PyYAML not installed

The 4 unit tests for _write_endpoint_to_config imported yaml directly
without handling ImportError. Added pytest.importorskip('yaml') at
module level so the entire test class skips cleanly in environments
without PyYAML. Removed redundant per-method yaml imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: wire frontend for custom endpoint fields in new profile form

- Add Base URL and API key inputs to the profile create form (index.html)
- Wire panels.js submitProfileCreate() to send base_url and api_key
- Clear new fields on form toggle/cancel
- Add client-side URL format validation (must start with http:// or https://)
- Add server-side URL format validation in routes.py (400 for invalid scheme)
- Add test_api_route_rejects_invalid_base_url() covering the new validation
- Base URL input has placeholder 'http://localhost:11434' per review suggestion

---------

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nesquena-hermes
2026-04-10 11:43:49 -07:00
committed by GitHub
parent 1e27940535
commit da160d675f
5 changed files with 209 additions and 3 deletions

View File

@@ -841,6 +841,8 @@ function toggleProfileForm() {
if (form.style.display !== 'none') {
$('profileFormName').value = '';
$('profileFormClone').checked = false;
if ($('profileFormBaseUrl')) $('profileFormBaseUrl').value = '';
if ($('profileFormApiKey')) $('profileFormApiKey').value = '';
const errEl = $('profileFormError');
if (errEl) errEl.style.display = 'none';
$('profileFormName').focus();
@@ -854,7 +856,15 @@ async function submitProfileCreate() {
if (!name) { errEl.textContent = 'Name is required'; errEl.style.display = ''; return; }
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(name)) { errEl.textContent = 'Lowercase letters, numbers, hyphens, underscores only'; errEl.style.display = ''; return; }
try {
await api('/api/profile/create', { method: 'POST', body: JSON.stringify({ name, clone_config: cloneConfig }) });
const baseUrl = (($('profileFormBaseUrl') && $('profileFormBaseUrl').value) || '').trim();
const apiKey = (($('profileFormApiKey') && $('profileFormApiKey').value) || '').trim();
if (baseUrl && !/^https?:\/\//.test(baseUrl)) {
errEl.textContent = 'Base URL must start with http:// or https://'; errEl.style.display = ''; return;
}
const payload = { name, clone_config: cloneConfig };
if (baseUrl) payload.base_url = baseUrl;
if (apiKey) payload.api_key = apiKey;
await api('/api/profile/create', { method: 'POST', body: JSON.stringify(payload) });
toggleProfileForm();
await loadProfilesPanel();
showToast('Profile created: ' + name);