| |
| |
| |
| |
|
|
| const dynamicLoader = { |
| apiBase: window.location.origin, |
| registeredModels: [], |
| |
| |
| |
| |
| async init() { |
| console.log('🚀 Initializing Dynamic Model Loader...'); |
| |
| |
| await this.refreshModelsList(); |
| |
| |
| this.setupEventListeners(); |
| |
| console.log('✅ Dynamic Model Loader initialized'); |
| }, |
| |
| setupEventListeners() { |
| |
| const manualForm = document.getElementById('manual-form'); |
| if (manualForm) { |
| manualForm.addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| await this.submitManualConfig(); |
| }); |
| } |
| }, |
| |
| |
| |
| |
| showPasteMode() { |
| this.closeAllModes(); |
| document.getElementById('paste-mode').style.display = 'block'; |
| document.getElementById('paste-input').focus(); |
| }, |
| |
| showManualMode() { |
| this.closeAllModes(); |
| document.getElementById('manual-mode').style.display = 'block'; |
| document.getElementById('manual-model-id').focus(); |
| }, |
| |
| showAutoMode() { |
| this.closeAllModes(); |
| document.getElementById('auto-mode').style.display = 'block'; |
| document.getElementById('auto-url').focus(); |
| }, |
| |
| closeAllModes() { |
| document.getElementById('paste-mode').style.display = 'none'; |
| document.getElementById('manual-mode').style.display = 'none'; |
| document.getElementById('auto-mode').style.display = 'none'; |
| }, |
| |
| closeTestPanel() { |
| document.getElementById('test-panel').style.display = 'none'; |
| }, |
| |
| |
| |
| |
| async processPastedConfig() { |
| const configText = document.getElementById('paste-input').value.trim(); |
| const autoDetect = document.getElementById('auto-detect-paste').checked; |
| |
| if (!configText) { |
| this.showError('Please paste a configuration'); |
| return; |
| } |
| |
| this.showInfo('Processing pasted configuration...'); |
| |
| try { |
| const response = await fetch(`${this.apiBase}/api/dynamic-models/paste-config`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| config_text: configText, |
| auto_detect: autoDetect |
| }) |
| }); |
| |
| const data = await response.json(); |
| |
| if (data.success) { |
| this.showSuccess(`Model "${data.data.model_id}" registered successfully!`); |
| await this.refreshModelsList(); |
| this.closeAllModes(); |
| document.getElementById('paste-input').value = ''; |
| } else { |
| this.showError(data.error || 'Failed to process configuration'); |
| } |
| } catch (error) { |
| this.showError(`Error: ${error.message}`); |
| console.error('Paste config error:', error); |
| } |
| }, |
| |
| async testPastedConfig() { |
| const configText = document.getElementById('paste-input').value.trim(); |
| |
| if (!configText) { |
| this.showError('Please paste a configuration'); |
| return; |
| } |
| |
| this.showInfo('Testing configuration...'); |
| |
| try { |
| |
| let parsedConfig; |
| try { |
| parsedConfig = JSON.parse(configText); |
| } catch { |
| this.showError('Invalid JSON. Please provide valid JSON configuration for testing.'); |
| return; |
| } |
| |
| const response = await fetch(`${this.apiBase}/api/dynamic-models/test-connection`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(parsedConfig) |
| }); |
| |
| const data = await response.json(); |
| |
| if (data.success && data.test_result.success) { |
| this.showSuccess(`✅ Connection successful! (${Math.round(data.test_result.response_time_ms)}ms)`); |
| } else { |
| this.showError(`❌ Connection failed: ${data.test_result.error || 'Unknown error'}`); |
| } |
| } catch (error) { |
| this.showError(`Test failed: ${error.message}`); |
| console.error('Test error:', error); |
| } |
| }, |
| |
| |
| |
| |
| async submitManualConfig() { |
| const config = { |
| model_id: document.getElementById('manual-model-id').value.trim(), |
| model_name: document.getElementById('manual-model-name').value.trim(), |
| base_url: document.getElementById('manual-base-url').value.trim(), |
| api_key: document.getElementById('manual-api-key').value.trim() || null, |
| api_type: document.getElementById('manual-api-type').value === 'auto' |
| ? null |
| : document.getElementById('manual-api-type').value, |
| endpoints: document.getElementById('manual-endpoint').value.trim() || null |
| }; |
| |
| const testFirst = document.getElementById('test-before-register').checked; |
| |
| if (testFirst) { |
| this.showInfo('Testing connection first...'); |
| |
| try { |
| const testResponse = await fetch(`${this.apiBase}/api/dynamic-models/test-connection`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(config) |
| }); |
| |
| const testData = await testResponse.json(); |
| |
| if (!testData.success || !testData.test_result.success) { |
| const proceed = confirm( |
| `Connection test failed: ${testData.test_result.error}\n\nDo you want to register anyway?` |
| ); |
| if (!proceed) return; |
| } |
| } catch (error) { |
| const proceed = confirm( |
| `Test failed: ${error.message}\n\nDo you want to register anyway?` |
| ); |
| if (!proceed) return; |
| } |
| } |
| |
| this.showInfo('Registering model...'); |
| |
| try { |
| const response = await fetch(`${this.apiBase}/api/dynamic-models/register`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(config) |
| }); |
| |
| const data = await response.json(); |
| |
| if (data.success) { |
| this.showSuccess(`Model "${config.model_id}" registered successfully!`); |
| await this.refreshModelsList(); |
| this.closeAllModes(); |
| document.getElementById('manual-form').reset(); |
| } else { |
| this.showError(data.message || 'Registration failed'); |
| } |
| } catch (error) { |
| this.showError(`Error: ${error.message}`); |
| console.error('Registration error:', error); |
| } |
| }, |
| |
| async testManualConfig() { |
| const config = { |
| model_id: document.getElementById('manual-model-id').value.trim(), |
| model_name: document.getElementById('manual-model-name').value.trim(), |
| base_url: document.getElementById('manual-base-url').value.trim(), |
| api_key: document.getElementById('manual-api-key').value.trim() || null, |
| api_type: document.getElementById('manual-api-type').value === 'auto' |
| ? null |
| : document.getElementById('manual-api-type').value |
| }; |
| |
| if (!config.base_url) { |
| this.showError('Please enter a base URL'); |
| return; |
| } |
| |
| this.showInfo('Testing connection...'); |
| |
| try { |
| const response = await fetch(`${this.apiBase}/api/dynamic-models/test-connection`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(config) |
| }); |
| |
| const data = await response.json(); |
| |
| if (data.success && data.test_result.success) { |
| this.showSuccess( |
| `✅ Connection successful!\n` + |
| `API Type: ${data.test_result.api_type}\n` + |
| `Response Time: ${Math.round(data.test_result.response_time_ms)}ms\n` + |
| `Capabilities: ${data.test_result.detected_capabilities.join(', ')}` |
| ); |
| } else { |
| this.showError( |
| `❌ Connection failed:\n${data.test_result.error || 'Unknown error'}` |
| ); |
| } |
| } catch (error) { |
| this.showError(`Test failed: ${error.message}`); |
| console.error('Test error:', error); |
| } |
| }, |
| |
| |
| |
| |
| async autoConfigureFromURL() { |
| const url = document.getElementById('auto-url').value.trim(); |
| |
| if (!url) { |
| this.showError('Please enter a URL'); |
| return; |
| } |
| |
| this.showInfo('Auto-configuring model...'); |
| |
| try { |
| const response = await fetch(`${this.apiBase}/api/dynamic-models/auto-configure`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ url }) |
| }); |
| |
| const data = await response.json(); |
| |
| if (data.success) { |
| this.showSuccess( |
| `✅ Model auto-configured and registered!\n` + |
| `Model ID: ${data.config.model_id}\n` + |
| `API Type: ${data.config.api_type}\n` + |
| `Endpoints discovered: ${Object.keys(data.config.endpoints?.endpoints || {}).length}` |
| ); |
| await this.refreshModelsList(); |
| this.closeAllModes(); |
| document.getElementById('auto-url').value = ''; |
| } else { |
| this.showError(data.error || 'Auto-configuration failed'); |
| } |
| } catch (error) { |
| this.showError(`Error: ${error.message}`); |
| console.error('Auto-configure error:', error); |
| } |
| }, |
| |
| |
| |
| |
| async refreshModelsList() { |
| const container = document.getElementById('models-list'); |
| |
| try { |
| const response = await fetch(`${this.apiBase}/api/dynamic-models/models`); |
| const data = await response.json(); |
| |
| if (data.success) { |
| this.registeredModels = data.models; |
| this.renderModelsList(data.models); |
| } else { |
| container.innerHTML = '<p class="error-text">Failed to load models</p>'; |
| } |
| } catch (error) { |
| console.error('Failed to load models:', error); |
| container.innerHTML = '<p class="error-text">Error loading models</p>'; |
| } |
| }, |
| |
| renderModelsList(models) { |
| const container = document.getElementById('models-list'); |
| |
| if (models.length === 0) { |
| container.innerHTML = ` |
| <div class="empty-state"> |
| <p>No models registered yet</p> |
| <p class="text-secondary">Click one of the quick action buttons above to register your first model</p> |
| </div> |
| `; |
| return; |
| } |
| |
| container.innerHTML = models.map(model => ` |
| <div class="model-card" data-model-id="${model.model_id}"> |
| <div class="model-header"> |
| <div class="model-info"> |
| <h4>${this.escapeHtml(model.model_name)}</h4> |
| <span class="model-type">${model.api_type || 'unknown'}</span> |
| </div> |
| <div class="model-actions"> |
| <button |
| class="btn-icon" |
| title="Test model" |
| onclick="dynamicLoader.openTestModel('${model.model_id}')" |
| > |
| <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg> |
| </button> |
| <button |
| class="btn-icon" |
| title="View details" |
| onclick="dynamicLoader.viewModelDetails('${model.model_id}')" |
| > |
| <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg> |
| </button> |
| <button |
| class="btn-icon btn-danger" |
| title="Delete model" |
| onclick="dynamicLoader.deleteModel('${model.model_id}')" |
| > |
| <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg> |
| </button> |
| </div> |
| </div> |
| <div class="model-details"> |
| <div><strong>ID:</strong> ${this.escapeHtml(model.model_id)}</div> |
| <div><strong>URL:</strong> ${this.escapeHtml(model.base_url)}</div> |
| ${model.api_key ? '<div><strong>Auth:</strong> Yes (API key set)</div>' : ''} |
| </div> |
| <div class="model-meta"> |
| <span>Created: ${new Date(model.created_at).toLocaleString()}</span> |
| ${model.last_used_at ? `<span>Last used: ${new Date(model.last_used_at).toLocaleString()}</span>` : ''} |
| <span>Uses: ${model.use_count || 0}</span> |
| </div> |
| </div> |
| `).join(''); |
| }, |
| |
| |
| |
| |
| openTestModel(modelId) { |
| |
| const select = document.getElementById('test-model-select'); |
| select.innerHTML = this.registeredModels.map(m => |
| `<option value="${m.model_id}" ${m.model_id === modelId ? 'selected' : ''}>${m.model_name}</option>` |
| ).join(''); |
| |
| |
| document.getElementById('test-panel').style.display = 'block'; |
| document.getElementById('test-panel').scrollIntoView({ behavior: 'smooth' }); |
| }, |
| |
| async executeTest() { |
| const modelId = document.getElementById('test-model-select').value; |
| const endpoint = document.getElementById('test-endpoint').value.trim(); |
| const payloadText = document.getElementById('test-payload').value.trim(); |
| |
| if (!modelId) { |
| this.showError('Please select a model'); |
| return; |
| } |
| |
| let payload; |
| try { |
| payload = JSON.parse(payloadText || '{}'); |
| } catch { |
| this.showError('Invalid JSON payload'); |
| return; |
| } |
| |
| this.showInfo('Testing model...'); |
| |
| const resultDiv = document.getElementById('test-result'); |
| resultDiv.innerHTML = '<div class="spinner"></div><p>Running test...</p>'; |
| |
| try { |
| const response = await fetch( |
| `${this.apiBase}/api/dynamic-models/models/${modelId}/use`, |
| { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ endpoint, payload }) |
| } |
| ); |
| |
| const data = await response.json(); |
| |
| if (data.success) { |
| this.showSuccess(`Test completed in ${Math.round(data.data.response_time_ms)}ms`); |
| resultDiv.innerHTML = ` |
| <div class="success-banner">✅ Test Successful</div> |
| <div><strong>Response Time:</strong> ${Math.round(data.data.response_time_ms)}ms</div> |
| <div><strong>Response Data:</strong></div> |
| <pre>${JSON.stringify(data.data.data, null, 2)}</pre> |
| `; |
| } else { |
| this.showError('Test failed'); |
| resultDiv.innerHTML = ` |
| <div class="error-banner">❌ Test Failed</div> |
| <div><strong>Error:</strong> ${data.error}</div> |
| `; |
| } |
| } catch (error) { |
| this.showError(`Test error: ${error.message}`); |
| resultDiv.innerHTML = ` |
| <div class="error-banner">❌ Error</div> |
| <div>${error.message}</div> |
| `; |
| } |
| }, |
| |
| viewModelDetails(modelId) { |
| const model = this.registeredModels.find(m => m.model_id === modelId); |
| if (!model) return; |
| |
| alert(` |
| Model Details: |
| -------------- |
| ID: ${model.model_id} |
| Name: ${model.model_name} |
| API Type: ${model.api_type} |
| Base URL: ${model.base_url} |
| Created: ${new Date(model.created_at).toLocaleString()} |
| Use Count: ${model.use_count || 0} |
| Auto-detected: ${model.auto_detected ? 'Yes' : 'No'} |
| |
| Config: |
| ${JSON.stringify(model.config, null, 2)} |
| |
| Endpoints: |
| ${JSON.stringify(model.endpoints, null, 2)} |
| `.trim()); |
| }, |
| |
| async deleteModel(modelId) { |
| if (!confirm(`Are you sure you want to delete model "${modelId}"?`)) { |
| return; |
| } |
| |
| try { |
| const response = await fetch( |
| `${this.apiBase}/api/dynamic-models/models/${modelId}`, |
| { method: 'DELETE' } |
| ); |
| |
| const data = await response.json(); |
| |
| if (data.success) { |
| this.showSuccess(`Model "${modelId}" deleted`); |
| await this.refreshModelsList(); |
| } else { |
| this.showError('Failed to delete model'); |
| } |
| } catch (error) { |
| this.showError(`Error: ${error.message}`); |
| } |
| }, |
| |
| |
| |
| |
| showSuccess(message) { |
| this.showMessage(message, 'success'); |
| }, |
| |
| showError(message) { |
| this.showMessage(message, 'error'); |
| }, |
| |
| showInfo(message) { |
| this.showMessage(message, 'info'); |
| }, |
| |
| showMessage(message, type = 'info') { |
| const container = document.getElementById('status-messages'); |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `status-message ${type}`; |
| messageDiv.textContent = message; |
| |
| container.appendChild(messageDiv); |
| |
| setTimeout(() => { |
| messageDiv.remove(); |
| }, 5000); |
| }, |
| |
| |
| |
| |
| escapeHtml(text) { |
| const div = document.createElement('div'); |
| div.textContent = text; |
| return div.innerHTML; |
| } |
| }; |
|
|
| |
| if (document.readyState === 'loading') { |
| document.addEventListener('DOMContentLoaded', () => dynamicLoader.init()); |
| } else { |
| dynamicLoader.init(); |
| } |
|
|
| |
| window.dynamicLoader = dynamicLoader; |
|
|
|
|