- Added 'provider_configs' and 'model_configs' tables to database. - Refactored ProviderManager to support thread-safe dynamic updates and database overrides. - Implemented 'Models' tab in dashboard to manage model visibility, mapping, and pricing. - Added provider configuration modal to 'Providers' tab. - Integrated database overrides into chat completion logic (enabled state, mapping, and cost).
200 lines
8.2 KiB
JavaScript
200 lines
8.2 KiB
JavaScript
// Providers Page Module
|
|
|
|
class ProvidersPage {
|
|
constructor() {
|
|
this.providers = [];
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
await this.loadProviders();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
async loadProviders() {
|
|
try {
|
|
const data = await window.api.get('/providers');
|
|
this.providers = data;
|
|
this.renderProviders();
|
|
this.renderStats();
|
|
} catch (error) {
|
|
console.error('Error loading providers:', error);
|
|
window.authManager.showToast('Failed to load providers', 'error');
|
|
}
|
|
}
|
|
|
|
renderProviders() {
|
|
const container = document.getElementById('providers-list');
|
|
if (!container) return;
|
|
|
|
if (this.providers.length === 0) {
|
|
container.innerHTML = '<div class="empty-state">No providers configured</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = `
|
|
<div class="provider-cards-grid">
|
|
${this.providers.map(provider => this.renderProviderCard(provider)).join('')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
renderProviderCard(provider) {
|
|
const statusClass = provider.status === 'online' ? 'success' : 'warning';
|
|
const modelCount = provider.models ? provider.models.length : 0;
|
|
|
|
return `
|
|
<div class="provider-card ${provider.status}">
|
|
<div class="provider-card-header">
|
|
<div class="provider-info">
|
|
<h4 class="provider-name">${provider.name}</h4>
|
|
<span class="provider-id">${provider.id}</span>
|
|
</div>
|
|
<span class="status-badge ${statusClass}">
|
|
<i class="fas fa-circle"></i>
|
|
${provider.status}
|
|
</span>
|
|
</div>
|
|
<div class="provider-card-body">
|
|
<div class="provider-meta">
|
|
<div class="meta-item">
|
|
<i class="fas fa-microchip"></i>
|
|
<span>${modelCount} Models Available</span>
|
|
</div>
|
|
<div class="meta-item">
|
|
<i class="fas fa-clock"></i>
|
|
<span>Last used: ${provider.last_used ? window.api.formatTimeAgo(provider.last_used) : 'Never'}</span>
|
|
</div>
|
|
</div>
|
|
<div class="model-tags">
|
|
${(provider.models || []).slice(0, 5).map(m => `<span class="model-tag">${m}</span>`).join('')}
|
|
${modelCount > 5 ? `<span class="model-tag more">+${modelCount - 5} more</span>` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="provider-card-footer">
|
|
<button class="btn btn-secondary btn-sm" onclick="window.providersPage.testProvider('${provider.id}')">
|
|
<i class="fas fa-vial"></i> Test
|
|
</button>
|
|
<button class="btn btn-primary btn-sm" onclick="window.providersPage.configureProvider('${provider.id}')">
|
|
<i class="fas fa-cog"></i> Config
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
renderStats() {
|
|
const container = document.getElementById('provider-stats');
|
|
if (!container) return;
|
|
|
|
const onlineCount = this.providers.filter(p => p.status === 'online').length;
|
|
const totalModels = this.providers.reduce((sum, p) => sum + (p.models ? p.models.length : 0), 0);
|
|
|
|
container.innerHTML = `
|
|
<div class="stat-card">
|
|
<div class="stat-content">
|
|
<div class="stat-value">${this.providers.length}</div>
|
|
<div class="stat-label">Total Providers</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-content">
|
|
<div class="stat-value">${onlineCount}</div>
|
|
<div class="stat-label">Online Status</div>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-content">
|
|
<div class="stat-value">${totalModels}</div>
|
|
<div class="stat-label">Total Models</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
async testProvider(id) {
|
|
window.authManager.showToast(`Testing connection to ${id}...`, 'info');
|
|
try {
|
|
await window.api.post(`/providers/${id}/test`, {});
|
|
window.authManager.showToast(`${id} connection successful!`, 'success');
|
|
this.loadProviders();
|
|
} catch (error) {
|
|
window.authManager.showToast(`${id} test failed: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
configureProvider(id) {
|
|
const provider = this.providers.find(p => p.id === id);
|
|
if (!provider) return;
|
|
|
|
const modal = document.createElement('div');
|
|
modal.className = 'modal active';
|
|
modal.innerHTML = `
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Configure ${provider.name}</h3>
|
|
<button class="modal-close" onclick="this.closest('.modal').remove()">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-control">
|
|
<label class="checkbox-label" style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
|
<input type="checkbox" id="provider-enabled" ${provider.enabled ? 'checked' : ''} style="width: auto;">
|
|
<span>Enable Provider</span>
|
|
</label>
|
|
</div>
|
|
<div class="form-control">
|
|
<label for="provider-base-url">Base URL</label>
|
|
<input type="text" id="provider-base-url" value="${provider.base_url || ''}" placeholder="Default: ${provider.id === 'ollama' ? 'http://localhost:11434/v1' : 'Standard API URL'}">
|
|
</div>
|
|
<div class="form-control">
|
|
<label for="provider-api-key">API Key (Optional / Overwrite)</label>
|
|
<input type="password" id="provider-api-key" placeholder="••••••••••••••••">
|
|
<small>Leave blank to keep existing key from .env or config.toml</small>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="this.closest('.modal').remove()">Cancel</button>
|
|
<button class="btn btn-primary" id="save-provider-config">Save Configuration</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
modal.querySelector('#save-provider-config').onclick = async () => {
|
|
const enabled = modal.querySelector('#provider-enabled').checked;
|
|
const baseUrl = modal.querySelector('#provider-base-url').value;
|
|
const apiKey = modal.querySelector('#provider-api-key').value;
|
|
|
|
try {
|
|
await window.api.put(`/providers/${id}`, {
|
|
enabled,
|
|
base_url: baseUrl || null,
|
|
api_key: apiKey || null
|
|
});
|
|
|
|
window.authManager.showToast(`${provider.name} configuration saved`, 'success');
|
|
modal.remove();
|
|
this.loadProviders();
|
|
} catch (error) {
|
|
window.authManager.showToast(error.message, 'error');
|
|
}
|
|
};
|
|
}
|
|
|
|
setupEventListeners() {
|
|
const testAllBtn = document.getElementById('test-all-providers');
|
|
if (testAllBtn) {
|
|
testAllBtn.onclick = () => {
|
|
this.providers.forEach(p => this.testProvider(p.id));
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
window.initProviders = async () => {
|
|
window.providersPage = new ProvidersPage();
|
|
};
|