650 lines
24 KiB
JavaScript
650 lines
24 KiB
JavaScript
// Providers Page Module
|
|
|
|
class ProvidersPage {
|
|
constructor() {
|
|
this.providers = [];
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
// Load data
|
|
await this.loadProviderStats();
|
|
await this.loadProvidersList();
|
|
await this.loadModelsList();
|
|
await this.loadConnectionTests();
|
|
|
|
// Setup event listeners
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
async loadProviderStats() {
|
|
const container = document.getElementById('provider-stats');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = `
|
|
<div class="stat-card">
|
|
<div class="stat-icon primary">
|
|
<i class="fas fa-server"></i>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value">4</div>
|
|
<div class="stat-label">Total Providers</div>
|
|
<div class="stat-change">
|
|
<i class="fas fa-check-circle"></i>
|
|
3 active
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-icon success">
|
|
<i class="fas fa-plug"></i>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value">3</div>
|
|
<div class="stat-label">Connected</div>
|
|
<div class="stat-change positive">
|
|
<i class="fas fa-arrow-up"></i>
|
|
All systems operational
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-icon warning">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value">1</div>
|
|
<div class="stat-label">Issues</div>
|
|
<div class="stat-change">
|
|
<i class="fas fa-info-circle"></i>
|
|
DeepSeek: 85% health
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-icon danger">
|
|
<i class="fas fa-times-circle"></i>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-value">1</div>
|
|
<div class="stat-label">Offline</div>
|
|
<div class="stat-change">
|
|
<i class="fas fa-redo"></i>
|
|
Grok: Connection failed
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
async loadProvidersList() {
|
|
const container = document.getElementById('providers-list');
|
|
if (!container) return;
|
|
|
|
this.providers = [
|
|
{ name: 'OpenAI', enabled: true, status: 'online', apiKey: 'sk-*****123', models: ['gpt-4', 'gpt-3.5-turbo'], lastUsed: '2024-01-15 14:32:15' },
|
|
{ name: 'Gemini', enabled: true, status: 'online', apiKey: 'AIza*****456', models: ['gemini-pro', 'gemini-pro-vision'], lastUsed: '2024-01-15 14:30:45' },
|
|
{ name: 'DeepSeek', enabled: true, status: 'warning', apiKey: 'sk-*****789', models: ['deepseek-chat', 'deepseek-coder'], lastUsed: '2024-01-15 14:28:12' },
|
|
{ name: 'Grok', enabled: false, status: 'offline', apiKey: 'gk-*****012', models: ['grok-beta'], lastUsed: '2024-01-12 10:15:22' }
|
|
];
|
|
|
|
container.innerHTML = this.providers.map(provider => {
|
|
const statusClass = provider.status === 'online' ? 'success' :
|
|
provider.status === 'warning' ? 'warning' : 'danger';
|
|
const statusIcon = provider.status === 'online' ? 'check-circle' :
|
|
provider.status === 'warning' ? 'exclamation-triangle' : 'times-circle';
|
|
|
|
return `
|
|
<div class="provider-card">
|
|
<div class="provider-header">
|
|
<div class="provider-info">
|
|
<h4 class="provider-name">${provider.name}</h4>
|
|
<span class="status-badge ${statusClass}">
|
|
<i class="fas fa-${statusIcon}"></i>
|
|
${provider.status}
|
|
</span>
|
|
</div>
|
|
<div class="provider-actions">
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" ${provider.enabled ? 'checked' : ''} data-provider="${provider.name}">
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
<button class="btn-action" title="Configure" data-action="configure" data-provider="${provider.name}">
|
|
<i class="fas fa-cog"></i>
|
|
</button>
|
|
<button class="btn-action" title="Test Connection" data-action="test" data-provider="${provider.name}">
|
|
<i class="fas fa-play"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="provider-details">
|
|
<div class="detail-item">
|
|
<span class="detail-label">API Key:</span>
|
|
<code class="detail-value">${provider.apiKey}</code>
|
|
<button class="btn-copy" data-text="${provider.apiKey}" title="Copy">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">Models:</span>
|
|
<span class="detail-value">${provider.models.join(', ')}</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">Last Used:</span>
|
|
<span class="detail-value">${provider.lastUsed}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// Add CSS for provider cards
|
|
this.addProviderStyles();
|
|
}
|
|
|
|
addProviderStyles() {
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.provider-card {
|
|
background-color: var(--bg-card);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--border-radius);
|
|
padding: 1rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.provider-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.provider-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.provider-name {
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin: 0;
|
|
}
|
|
|
|
.provider-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.toggle-switch {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 50px;
|
|
height: 24px;
|
|
}
|
|
|
|
.toggle-switch input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.toggle-slider {
|
|
position: absolute;
|
|
cursor: pointer;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: var(--text-light);
|
|
transition: .4s;
|
|
border-radius: 24px;
|
|
}
|
|
|
|
.toggle-slider:before {
|
|
position: absolute;
|
|
content: "";
|
|
height: 16px;
|
|
width: 16px;
|
|
left: 4px;
|
|
bottom: 4px;
|
|
background-color: white;
|
|
transition: .4s;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
input:checked + .toggle-slider {
|
|
background-color: var(--success);
|
|
}
|
|
|
|
input:checked + .toggle-slider:before {
|
|
transform: translateX(26px);
|
|
}
|
|
|
|
.provider-details {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 0.75rem;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.detail-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.detail-label {
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
min-width: 70px;
|
|
}
|
|
|
|
.detail-value {
|
|
color: var(--text-primary);
|
|
flex: 1;
|
|
}
|
|
|
|
.btn-copy {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
font-size: 0.75rem;
|
|
padding: 0.25rem;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
.btn-copy:hover {
|
|
color: var(--primary);
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
async loadModelsList() {
|
|
const container = document.getElementById('models-list');
|
|
if (!container) return;
|
|
|
|
const models = [
|
|
{ provider: 'OpenAI', name: 'gpt-4', enabled: true, context: 8192, maxTokens: 4096 },
|
|
{ provider: 'OpenAI', name: 'gpt-3.5-turbo', enabled: true, context: 16384, maxTokens: 4096 },
|
|
{ provider: 'Gemini', name: 'gemini-pro', enabled: true, context: 32768, maxTokens: 8192 },
|
|
{ provider: 'Gemini', name: 'gemini-pro-vision', enabled: true, context: 32768, maxTokens: 4096 },
|
|
{ provider: 'DeepSeek', name: 'deepseek-chat', enabled: true, context: 16384, maxTokens: 4096 },
|
|
{ provider: 'DeepSeek', name: 'deepseek-coder', enabled: true, context: 16384, maxTokens: 4096 },
|
|
{ provider: 'Grok', name: 'grok-beta', enabled: false, context: 8192, maxTokens: 2048 }
|
|
];
|
|
|
|
container.innerHTML = models.map(model => `
|
|
<div class="model-item">
|
|
<div class="model-header">
|
|
<span class="model-name">${model.name}</span>
|
|
<span class="model-provider">${model.provider}</span>
|
|
</div>
|
|
<div class="model-details">
|
|
<span class="model-detail">
|
|
<i class="fas fa-microchip"></i>
|
|
Context: ${model.context.toLocaleString()} tokens
|
|
</span>
|
|
<span class="model-detail">
|
|
<i class="fas fa-ruler"></i>
|
|
Max: ${model.maxTokens.toLocaleString()} tokens
|
|
</span>
|
|
<span class="model-status ${model.enabled ? 'enabled' : 'disabled'}">
|
|
<i class="fas fa-${model.enabled ? 'check' : 'times'}"></i>
|
|
${model.enabled ? 'Enabled' : 'Disabled'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Add CSS for model items
|
|
this.addModelStyles();
|
|
}
|
|
|
|
addModelStyles() {
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.model-item {
|
|
background-color: var(--bg-secondary);
|
|
border-radius: var(--border-radius-sm);
|
|
padding: 0.75rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.model-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.model-name {
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.model-provider {
|
|
font-size: 0.75rem;
|
|
color: var(--text-secondary);
|
|
background-color: var(--bg-primary);
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.model-details {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.model-detail {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.model-detail i {
|
|
font-size: 0.625rem;
|
|
}
|
|
|
|
.model-status {
|
|
font-size: 0.75rem;
|
|
padding: 0.125rem 0.5rem;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.model-status.enabled {
|
|
background-color: rgba(16, 185, 129, 0.1);
|
|
color: var(--success);
|
|
}
|
|
|
|
.model-status.disabled {
|
|
background-color: rgba(239, 68, 68, 0.1);
|
|
color: var(--danger);
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
async loadConnectionTests() {
|
|
const container = document.getElementById('connection-tests');
|
|
if (!container) return;
|
|
|
|
const tests = [
|
|
{ provider: 'OpenAI', status: 'success', latency: 245, timestamp: '2024-01-15 14:35:00' },
|
|
{ provider: 'Gemini', status: 'success', latency: 189, timestamp: '2024-01-15 14:34:30' },
|
|
{ provider: 'DeepSeek', status: 'warning', latency: 520, timestamp: '2024-01-15 14:34:00' },
|
|
{ provider: 'Grok', status: 'error', latency: null, timestamp: '2024-01-15 14:33:30' }
|
|
];
|
|
|
|
container.innerHTML = tests.map(test => {
|
|
const statusClass = test.status === 'success' ? 'success' :
|
|
test.status === 'warning' ? 'warning' : 'danger';
|
|
const statusIcon = test.status === 'success' ? 'check-circle' :
|
|
test.status === 'warning' ? 'exclamation-triangle' : 'times-circle';
|
|
|
|
return `
|
|
<div class="test-result">
|
|
<div class="test-provider">${test.provider}</div>
|
|
<div class="test-status">
|
|
<span class="status-badge ${statusClass}">
|
|
<i class="fas fa-${statusIcon}"></i>
|
|
${test.status}
|
|
</span>
|
|
</div>
|
|
<div class="test-latency">${test.latency ? `${test.latency}ms` : 'N/A'}</div>
|
|
<div class="test-time">${test.timestamp}</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// Add CSS for test results
|
|
this.addTestStyles();
|
|
}
|
|
|
|
addTestStyles() {
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.test-result {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 1fr 2fr;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
padding: 0.75rem;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.test-result:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.test-provider {
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.test-latency {
|
|
color: var(--text-secondary);
|
|
font-family: monospace;
|
|
}
|
|
|
|
.test-time {
|
|
color: var(--text-light);
|
|
font-size: 0.75rem;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Test all providers button
|
|
const testAllBtn = document.getElementById('test-all-providers');
|
|
if (testAllBtn) {
|
|
testAllBtn.addEventListener('click', () => {
|
|
this.testAllProviders();
|
|
});
|
|
}
|
|
|
|
// Toggle switches
|
|
document.addEventListener('change', (e) => {
|
|
if (e.target.matches('.toggle-switch input')) {
|
|
const provider = e.target.dataset.provider;
|
|
const enabled = e.target.checked;
|
|
this.toggleProvider(provider, enabled);
|
|
}
|
|
});
|
|
|
|
// Action buttons
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.closest('.btn-action')) {
|
|
const button = e.target.closest('.btn-action');
|
|
const action = button.dataset.action;
|
|
const provider = button.dataset.provider;
|
|
|
|
switch (action) {
|
|
case 'configure':
|
|
this.configureProvider(provider);
|
|
break;
|
|
case 'test':
|
|
this.testProvider(provider);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Copy buttons
|
|
if (e.target.closest('.btn-copy')) {
|
|
const button = e.target.closest('.btn-copy');
|
|
const text = button.dataset.text;
|
|
this.copyToClipboard(text);
|
|
|
|
if (window.authManager) {
|
|
window.authManager.showToast('Copied to clipboard', 'success');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
toggleProvider(providerName, enabled) {
|
|
const provider = this.providers.find(p => p.name === providerName);
|
|
if (!provider) return;
|
|
|
|
// In a real app, this would update the provider via API
|
|
provider.enabled = enabled;
|
|
provider.status = enabled ? 'online' : 'offline';
|
|
|
|
if (window.authManager) {
|
|
window.authManager.showToast(
|
|
`${providerName} ${enabled ? 'enabled' : 'disabled'}`,
|
|
enabled ? 'success' : 'warning'
|
|
);
|
|
}
|
|
|
|
// Refresh providers list
|
|
this.loadProvidersList();
|
|
}
|
|
|
|
configureProvider(providerName) {
|
|
const provider = this.providers.find(p => p.name === providerName);
|
|
if (!provider) return;
|
|
|
|
// Show configuration modal
|
|
const modal = document.createElement('div');
|
|
modal.className = 'modal active';
|
|
modal.innerHTML = `
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title">Configure ${providerName}</h3>
|
|
<button class="modal-close">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="configure-provider-form">
|
|
<div class="form-control">
|
|
<label for="api-key">API Key</label>
|
|
<input type="password" id="api-key" value="${provider.apiKey}" placeholder="Enter API key" required>
|
|
</div>
|
|
<div class="form-control">
|
|
<label for="base-url">Base URL (Optional)</label>
|
|
<input type="text" id="base-url" placeholder="https://api.openai.com/v1">
|
|
</div>
|
|
<div class="form-control">
|
|
<label for="timeout">Timeout (seconds)</label>
|
|
<input type="number" id="timeout" value="30" min="1" max="300">
|
|
</div>
|
|
<div class="form-control">
|
|
<label for="retry-count">Retry Count</label>
|
|
<input type="number" id="retry-count" value="3" min="0" max="10">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary close-modal">Cancel</button>
|
|
<button class="btn btn-primary save-config">Save Configuration</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
// Setup event listeners
|
|
const closeBtn = modal.querySelector('.modal-close');
|
|
const closeModalBtn = modal.querySelector('.close-modal');
|
|
const saveBtn = modal.querySelector('.save-config');
|
|
|
|
const closeModal = () => {
|
|
modal.classList.remove('active');
|
|
setTimeout(() => modal.remove(), 300);
|
|
};
|
|
|
|
closeBtn.addEventListener('click', closeModal);
|
|
closeModalBtn.addEventListener('click', closeModal);
|
|
|
|
saveBtn.addEventListener('click', () => {
|
|
// In a real app, this would save provider configuration
|
|
if (window.authManager) {
|
|
window.authManager.showToast(`${providerName} configuration saved`, 'success');
|
|
}
|
|
closeModal();
|
|
});
|
|
|
|
// Close on background click
|
|
modal.addEventListener('click', (e) => {
|
|
if (e.target === modal) {
|
|
closeModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
testProvider(providerName) {
|
|
const provider = this.providers.find(p => p.name === providerName);
|
|
if (!provider) return;
|
|
|
|
// Show testing in progress
|
|
if (window.authManager) {
|
|
window.authManager.showToast(`Testing ${providerName} connection...`, 'info');
|
|
}
|
|
|
|
// Simulate API test
|
|
setTimeout(() => {
|
|
// In a real app, this would test the provider connection via API
|
|
const success = Math.random() > 0.3; // 70% success rate for demo
|
|
|
|
if (window.authManager) {
|
|
window.authManager.showToast(
|
|
`${providerName} connection ${success ? 'successful' : 'failed'}`,
|
|
success ? 'success' : 'error'
|
|
);
|
|
}
|
|
|
|
// Refresh connection tests
|
|
this.loadConnectionTests();
|
|
}, 1500);
|
|
}
|
|
|
|
testAllProviders() {
|
|
if (window.authManager) {
|
|
window.authManager.showToast('Testing all providers...', 'info');
|
|
}
|
|
|
|
// Test each provider sequentially
|
|
this.providers.forEach((provider, index) => {
|
|
setTimeout(() => {
|
|
this.testProvider(provider.name);
|
|
}, index * 2000); // Stagger tests
|
|
});
|
|
}
|
|
|
|
copyToClipboard(text) {
|
|
navigator.clipboard.writeText(text).catch(err => {
|
|
console.error('Failed to copy:', err);
|
|
});
|
|
}
|
|
|
|
refresh() {
|
|
this.loadProviderStats();
|
|
this.loadProvidersList();
|
|
this.loadModelsList();
|
|
this.loadConnectionTests();
|
|
}
|
|
}
|
|
|
|
// Initialize providers page when needed
|
|
window.initProviders = async () => {
|
|
window.providersPage = new ProvidersPage();
|
|
};
|
|
|
|
// Export for use in other modules
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = ProvidersPage;
|
|
} |