mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
feat(web): merge Providers into Inference tab with UX improvements
This commit is contained in:
@@ -4705,7 +4705,6 @@ function loadSettingsSubtab(subtab) {
|
||||
else if (subtab === 'extensions') { loadExtensions(); startPairingPoll(); }
|
||||
else if (subtab === 'mcp') loadMcpServers();
|
||||
else if (subtab === 'skills') loadSkills();
|
||||
else if (subtab === 'providers') loadConfig();
|
||||
if (subtab !== 'extensions' && subtab !== 'channels') stopPairingPoll();
|
||||
}
|
||||
|
||||
@@ -4864,9 +4863,11 @@ function loadInferenceSettings() {
|
||||
}
|
||||
container.innerHTML = '';
|
||||
renderStructuredSettingsInto(container, INFERENCE_SETTINGS, settings, activeValues);
|
||||
loadConfig();
|
||||
}).catch(function(err) {
|
||||
container.innerHTML = '<div class="empty-state">' + I18n.t('common.loadFailed') + ': '
|
||||
+ escapeHtml(err.message) + '</div>';
|
||||
loadConfig();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5662,7 +5663,7 @@ function apiFetchVoid(path, options) {
|
||||
// Fields: id, name, adapter, base_url, builtin, default_model, api_key_required, can_list_models
|
||||
// nearai/bedrock use special auth flows — no Configure button (api_key_required=false, can_list_models=false)
|
||||
const BUILTIN_PROVIDERS = [
|
||||
{ id: 'nearai', name: 'NEAR AI', adapter: 'nearai', base_url: 'https://api.near.ai/v1', builtin: true, default_model: '', api_key_required: false, can_list_models: false },
|
||||
{ id: 'nearai', name: 'NEAR AI', adapter: 'nearai', base_url: 'https://private-chat-stg.near.ai/v1', builtin: true, default_model: 'zai-org/GLM-5-FP8', api_key_required: true, can_list_models: true },
|
||||
{ id: 'openai', name: 'OpenAI', adapter: 'open_ai_completions', base_url: 'https://api.openai.com/v1', builtin: true, default_model: 'gpt-5-mini', api_key_required: true, can_list_models: true },
|
||||
{ id: 'anthropic', name: 'Anthropic', adapter: 'anthropic', base_url: 'https://api.anthropic.com', builtin: true, default_model: 'claude-sonnet-4-20250514', api_key_required: true, can_list_models: true },
|
||||
{ id: 'ollama', name: 'Ollama', adapter: 'ollama', base_url: 'http://localhost:11434', builtin: true, default_model: 'llama3', api_key_required: false, can_list_models: true },
|
||||
@@ -5735,6 +5736,11 @@ function loadConfig() {
|
||||
});
|
||||
}
|
||||
|
||||
function scrollToProviders() {
|
||||
const section = document.getElementById('providers-section');
|
||||
if (section) section.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
function renderProviders() {
|
||||
const list = document.getElementById('providers-list');
|
||||
const allProviders = [...BUILTIN_PROVIDERS, ..._customProviders].sort((a, b) => {
|
||||
@@ -5763,8 +5769,8 @@ function renderProviders() {
|
||||
const editBtn = !p.builtin
|
||||
? '<button class="provider-action-btn" data-action="edit-custom-provider" data-id="' + escHtml(p.id) + '">' + I18n.t('common.edit') + '</button>'
|
||||
: '';
|
||||
// Show Configure for built-in providers that support it (not nearai/bedrock)
|
||||
const configureBtn = p.builtin && p.id !== 'nearai' && p.id !== 'bedrock'
|
||||
// Show Configure for built-in providers that support it (not bedrock — uses AWS credential chain)
|
||||
const configureBtn = p.builtin && p.id !== 'bedrock'
|
||||
? '<button class="provider-action-btn" data-action="configure-builtin-provider" data-id="' + escHtml(p.id) + '">' + I18n.t('config.configureProvider') + '</button>'
|
||||
: '';
|
||||
const useBtn = !isActive
|
||||
@@ -5820,7 +5826,7 @@ function setActiveProvider(id) {
|
||||
_activeLlmBackend = id;
|
||||
_selectedModel = defaultModel || '';
|
||||
renderProviders();
|
||||
document.getElementById('providers-list').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
scrollToProviders();
|
||||
document.getElementById('config-restart-notice').style.display = 'flex';
|
||||
showToast(I18n.t('config.providerActivated', { name: id }));
|
||||
})
|
||||
@@ -5998,6 +6004,7 @@ document.getElementById('save-provider-btn').addEventListener('click', () => {
|
||||
if (isActive) _selectedModel = model;
|
||||
renderProviders();
|
||||
resetProviderForm();
|
||||
scrollToProviders();
|
||||
document.getElementById('config-restart-notice').style.display = 'flex';
|
||||
showToast(I18n.t('config.providerConfigured', { name: id }));
|
||||
})
|
||||
@@ -6038,6 +6045,7 @@ document.getElementById('save-provider-btn').addEventListener('click', () => {
|
||||
if (isActive) _selectedModel = model;
|
||||
renderProviders();
|
||||
resetProviderForm();
|
||||
scrollToProviders();
|
||||
document.getElementById('config-restart-notice').style.display = 'flex';
|
||||
showToast(I18n.t('config.providerUpdated', { name }));
|
||||
}).catch((e) => {
|
||||
@@ -6063,6 +6071,7 @@ document.getElementById('save-provider-btn').addEventListener('click', () => {
|
||||
saveCustomProviders().then(() => {
|
||||
renderProviders();
|
||||
resetProviderForm();
|
||||
scrollToProviders();
|
||||
document.getElementById('config-restart-notice').style.display = 'flex';
|
||||
showToast(I18n.t('config.providerAdded', { name }));
|
||||
}).catch((e) => {
|
||||
@@ -6126,7 +6135,7 @@ document.getElementById('fetch-models-btn').addEventListener('click', () => {
|
||||
|
||||
const btn = document.getElementById('fetch-models-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '…';
|
||||
btn.textContent = 'Fetching…';
|
||||
|
||||
apiFetch('/api/llm/list_models', {
|
||||
method: 'POST',
|
||||
@@ -6140,6 +6149,7 @@ document.getElementById('fetch-models-btn').addEventListener('click', () => {
|
||||
.map((m) => `<option value="${escHtml(m)}"${m === currentModel ? ' selected' : ''}>${escHtml(m)}</option>`)
|
||||
.join('');
|
||||
select.style.display = '';
|
||||
btn.style.display = 'none';
|
||||
showToast(I18n.t('config.modelsFetched', { count: data.models.length }));
|
||||
} else {
|
||||
showToast(data.message || I18n.t('config.modelsFetchFailed'), 'error');
|
||||
@@ -6148,7 +6158,7 @@ document.getElementById('fetch-models-btn').addEventListener('click', () => {
|
||||
.catch((e) => showToast(e.message, 'error'))
|
||||
.finally(() => {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '↻';
|
||||
btn.textContent = '↻ Fetch available models';
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -82,10 +82,8 @@
|
||||
</div>
|
||||
<div class="config-form-row">
|
||||
<label data-i18n="config.providerModel">Default Model</label>
|
||||
<div class="provider-model-input-group">
|
||||
<input type="text" id="provider-model" placeholder="gpt-4o">
|
||||
<button id="fetch-models-btn" class="btn-fetch-models" type="button" data-i18n-title="config.fetchModels" title="Fetch models">↻</button>
|
||||
</div>
|
||||
<input type="text" id="provider-model" placeholder="gpt-4o">
|
||||
<button id="fetch-models-btn" class="btn-fetch-models" type="button" data-i18n="config.fetchModels">↻ Fetch available models</button>
|
||||
<select id="provider-model-select" style="display:none"></select>
|
||||
</div>
|
||||
<div id="test-connection-result" class="test-connection-result" style="display:none"></div>
|
||||
@@ -346,7 +344,6 @@
|
||||
<button class="settings-subtab" data-settings-subtab="extensions" data-i18n="tab.extensions">Extensions</button>
|
||||
<button class="settings-subtab" data-settings-subtab="mcp" data-i18n="settings.mcp">MCP</button>
|
||||
<button class="settings-subtab" data-settings-subtab="skills" data-i18n="tab.skills">Skills</button>
|
||||
<button class="settings-subtab" data-settings-subtab="providers" data-i18n="settings.providers">Providers</button>
|
||||
</div>
|
||||
<div class="settings-content">
|
||||
<div class="settings-toolbar">
|
||||
@@ -357,8 +354,23 @@
|
||||
<button id="settings-import-btn" class="settings-toolbar-btn" data-i18n="settings.import">Import</button>
|
||||
</div>
|
||||
<div class="settings-subpanel active" id="settings-inference">
|
||||
<div class="extensions-container" id="settings-inference-content">
|
||||
<div class="empty-state" data-i18n="common.loading">Loading settings...</div>
|
||||
<div class="extensions-container">
|
||||
<div id="settings-inference-content">
|
||||
<div class="empty-state" data-i18n="common.loading">Loading settings...</div>
|
||||
</div>
|
||||
<div class="extensions-section" id="providers-section">
|
||||
<div class="config-section-header">
|
||||
<h3 data-i18n="config.modelProviders">Model Providers</h3>
|
||||
<button id="add-provider-btn" class="btn-add-provider" data-i18n="config.addProvider">+ Add Provider</button>
|
||||
</div>
|
||||
<div class="config-notice" id="config-restart-notice" style="display:none">
|
||||
<span>⚠</span>
|
||||
<span data-i18n="config.restartNotice">Changes take effect after restart.</span>
|
||||
</div>
|
||||
<div id="providers-list" class="providers-list">
|
||||
<div class="empty-state" data-i18n="common.loading">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-subpanel" id="settings-agent">
|
||||
@@ -442,23 +454,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-subpanel" id="settings-providers">
|
||||
<div class="extensions-container">
|
||||
<div class="extensions-section">
|
||||
<div class="config-section-header">
|
||||
<h3 data-i18n="config.modelProviders">Model Providers</h3>
|
||||
<button id="add-provider-btn" class="btn-add-provider" data-i18n="config.addProvider">+ Add Provider</button>
|
||||
</div>
|
||||
<div class="config-notice" id="config-restart-notice" style="display:none">
|
||||
<span>⚠</span>
|
||||
<span data-i18n="config.restartNotice">Changes take effect after restart.</span>
|
||||
</div>
|
||||
<div id="providers-list" class="providers-list">
|
||||
<div class="empty-state" data-i18n="common.loading">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5159,32 +5159,25 @@ body.theme-transition *:not(svg):not(path):not(line):not(circle):not(rect) {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.provider-model-input-group {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.provider-model-input-group input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-fetch-models {
|
||||
padding: 5px 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-top: 6px;
|
||||
padding: 5px 11px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.btn-fetch-models:hover {
|
||||
color: var(--text);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 8%, transparent);
|
||||
}
|
||||
|
||||
.btn-fetch-models:disabled {
|
||||
|
||||
Reference in New Issue
Block a user