<!--
Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
-->
{% extends "base.html" %}

{% block title %}My Providers - AISBF Dashboard{% endblock %}

{% block content %}
<h2 style="margin-bottom: 30px;">My Providers</h2>

{% if success %}
<div class="alert alert-success">{{ success }}</div>
{% endif %}

{% if error %}
<div class="alert alert-error">{{ error }}</div>
{% endif %}

<!-- New Provider Form (hidden by default) -->
<div id="new-provider-form" style="display: none; background: var(--bg-panel); border: 2px solid #4a9eff; border-radius: 8px; padding: 20px; margin-bottom: 20px;">
    <h3 style="margin: 0 0 20px 0; color: #4a9eff;">Add New Provider</h3>
    
    <div class="form-group">
        <label>Provider Key (unique identifier, e.g., "gemini", "openai", "kiro")</label>
        <input type="text" id="new-provider-key" placeholder="Enter provider key" style="width: 100%;">
        <small style="color: var(--color-muted); display: block; margin-top: 5px;">This will be used as the provider ID in the configuration and API endpoints</small>
    </div>
    
    <div class="form-group">
        <label>Provider Type</label>
        <select id="new-provider-type" onchange="updateNewProviderDefaults()">
            <option value="openai">OpenAI</option>
            <option value="google">Google</option>
            <option value="anthropic">Anthropic</option>
            <option value="ollama">Ollama</option>
            <option value="kiro">Kiro (Amazon Q Developer)</option>
            <option value="claude">Claude (OAuth2)</option>
            <option value="kilocode">Kilocode (OAuth2)</option>
            <option value="qwen">Qwen (OAuth2)</option>
            <option value="codex">Codex (OpenAI OAuth2)</option>
        </select>
        <small style="color: var(--color-muted); display: block; margin-top: 5px;">Select the type of provider to configure appropriate settings</small>
    </div>
    
    <div id="new-provider-type-info" style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
        <p style="margin: 0; color: var(--color-muted);" id="new-provider-type-description">
            OpenAI-compatible provider. Uses standard API key authentication.
        </p>
    </div>

    <div id="new-provider-claude-warning" style="background: #ffdd57; border: 2px solid #ff6b35; border-radius: 8px; padding: 20px; margin-bottom: 15px; display: none;">
        <h4 style="margin: 0 0 10px 0; color: #d63031;">⚠️ Important Notice for Claude Provider</h4>
        <p style="margin: 0; color: #2d3436;">
            Claude.ai policies state that unofficial clients are not allowed. By using AISBF as a client in <strong>HTTP API / OAuth2 mode</strong>, you acknowledge that:
            <br><br>• While we do our best to mimic the claude-code CLI, using an unofficial client can lead to account suspension or cancellation
            <br>• Claude.ai detects prompt patterns to identify unofficial client usage, so it may not work with complex agents and prompts
            <br>• You use this software at your own risk and responsibility
            <br>• We are not affiliated with Anthropic and cannot guarantee compatibility or continued functionality
            <br><br>✅ <strong>CLI mode is different:</strong> when "Use Claude CLI mode" is enabled, AISBF proxies requests through the official <code>claude</code> binary using <code>claude -p</code>, which is the intended programmatic use of the official Anthropic CLI and is permitted by Claude's terms of service.
        </p>
    </div>
    
    <div style="display: flex; gap: 10px;">
        <button type="button" class="btn" onclick="confirmAddProvider()">Create Provider</button>
        <button type="button" class="btn btn-secondary" onclick="cancelAddProvider()">Cancel</button>
    </div>
</div>

<div style="margin-bottom: 15px; display: flex; gap: 10px; align-items: center;">
    <input type="text" id="providers-search" placeholder="Search providers by key or name..."
           oninput="providerSearchFilter = this.value; currentProviderPage = 0; renderProvidersList();"
           style="flex:1;padding:8px;border:1px solid var(--color-border);border-radius:3px;background:var(--bg-page);color:var(--color-text);font-size:14px;">
    <span id="providers-count" style="color:var(--color-muted);font-size:13px;white-space:nowrap;"></span>
</div>

<div id="providers-list" style="margin-bottom: 10px;">
    <!-- Provider list will be rendered here -->
</div>

<div id="providers-pagination" style="margin-bottom: 20px;"></div>

<button type="button" class="btn" id="add-provider-btn" onclick="showAddProviderForm()" style="margin-top: 10px;">Add Provider</button>
<div style="display: flex; gap: 10px; margin-top: 20px;">
    <button type="button" class="btn" onclick="saveProviders()">Save Configuration</button>
    <a href="{{ url_for(request, '/dashboard') }}" class="btn btn-secondary">Cancel</a>
</div>

<style>
.provider-item {
    animation: fadeIn 0.3s;
}

.provider-header:hover {
    background: var(--bg-accent);
}

@keyframes fadeIn {
    from { opacity: 0; transform: translateY(-10px); }
    to { opacity: 1; transform: translateY(0); }
}

.form-group {
    margin-bottom: 15px;
}

.form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: 500;
    color: var(--color-text);
}

.form-group input[type="text"],
.form-group input[type="password"],
.form-group input[type="number"],
.form-group select {
    width: 100%;
    padding: 8px;
    border: 1px solid var(--bg-accent);
    border-radius: 3px;
    font-size: 14px;
    background: var(--bg-page);
    color: var(--color-text);
}

.form-group input[type="checkbox"] {
    margin-right: 5px;
}
</style>
{% endblock %}

{% block extra_js %}
<script>
const CLAUDE_CLI_MODE = {{ 'true' if claude_cli_mode else 'false' }};
const IS_LOCAL_CLIENT = {{ 'true' if is_local_client else 'false' }};
const BASE_PATH = {{ (request.scope.get('root_path', '') or '') | tojson }};

// Global cache settings cache (pun intended)
let cacheSettings = [];

async function setCacheSetting(provider_id, model_name, enabled) {
    try {
        const response = await fetch('{{ url_for(request, "/dashboard/api/cache-settings") }}', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                provider_id: provider_id || null,
                model_name: model_name || null,
                cache_enabled: enabled
            })
        });
        
        if (response.ok) {
            showToast('Cache setting updated', 'success');
            // Refresh cache settings
            await loadCacheSettings();
        } else {
            showToast('Failed to update cache setting', 'danger');
        }
    } catch (error) {
        console.error('Error updating cache setting:', error);
        showToast('Error updating cache setting', 'danger');
    }
}

async function loadCacheSettings() {
    try {
        const response = await fetch('{{ url_for(request, "/dashboard/api/cache-settings") }}', {
            credentials: 'same-origin'
        });
        const data = await response.json();
        cacheSettings = data.settings || [];
    } catch (error) {
        console.error('Error loading cache settings:', error);
    }
}

// Check if cache is enabled for a provider/model
function isCacheEnabled(provider_id, model_name = null) {
    // Check model-level setting first
    if (model_name) {
        const modelSetting = cacheSettings.find(s => s.provider_id === provider_id && s.model_name === model_name);
        if (modelSetting) return modelSetting.cache_enabled;
    }
    
    // Check provider-level setting
    const providerSetting = cacheSettings.find(s => s.provider_id === provider_id && s.model_name === null);
    if (providerSetting) return providerSetting.cache_enabled;
    
    // Check global setting
    const globalSetting = cacheSettings.find(s => s.provider_id === null && s.model_name === null);
    if (globalSetting) return globalSetting.cache_enabled;
    
    // Default: enabled
    return true;
}

function showToast(message, type) {
    const alertDiv = document.createElement('div');
    alertDiv.style.cssText = `
        position: fixed;
        top: 20px;
        left: 50%;
        transform: translateX(-50%);
        z-index: 10000;
        min-width: 400px;
        max-width: calc(100vw - 16px);
        padding: 20px 30px;
        border-radius: 6px;
        box-shadow: 0 4px 16px rgba(0,0,0,0.4);
        ${type === 'success' ? 'background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%); color: white; border: 2px solid #27ae60;' : ''}
        ${type === 'danger'  ? 'background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); color: white; border: 2px solid #e74c3c;' : ''}
        ${type === 'warning' ? 'background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%); color: white; border: 2px solid #f39c12;' : ''}
    `;
    const icon = type === 'success' ? 'fa-check-circle' : type === 'danger' ? 'fa-times-circle' : 'fa-exclamation-triangle';
    alertDiv.innerHTML = `<i class="fas ${icon}" style="font-size: 24px; margin-right: 10px; vertical-align: middle;"></i><span style="vertical-align: middle;">${message}</span>`;
    if (!document.getElementById('alertAnimations')) {
        const style = document.createElement('style');
        style.id = 'alertAnimations';
        style.textContent = `
            @keyframes slideDown { from { opacity:0; transform:translateX(-50%) translateY(-20px); } to { opacity:1; transform:translateX(-50%) translateY(0); } }
            @keyframes slideUp   { from { opacity:1; transform:translateX(-50%) translateY(0); } to { opacity:0; transform:translateX(-50%) translateY(-20px); } }
        `;
        document.head.appendChild(style);
    }
    alertDiv.style.animation = 'slideDown 0.3s ease-out';
    document.body.appendChild(alertDiv);
    setTimeout(() => {
        alertDiv.style.animation = 'slideUp 0.3s ease-out';
        setTimeout(() => alertDiv.remove(), 300);
    }, 4000);
}

function escHtmlAttr(s) {
    return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}

async function apiCall(method, url, body) {
    const opts = { method, headers: { 'Content-Type': 'application/json' } };
    if (body !== undefined) opts.body = JSON.stringify(body);
    const r = await fetch(url, opts);
    return r.json();
}

let providersData = {};
let expandedProviders = new Set();
let currentProviderPage = 0;
const PROVIDERS_PAGE_SIZE = 10;
let providerSearchFilter = '';
let rawProviders = {{ user_providers_json | replace("</script>", "<\\/script>") | safe }};

// Convert user providers format to the format expected by the UI
rawProviders.forEach(provider => {
    providersData[provider.provider_id] = provider.config;
});

// Chunk size: 512KB chunks for maximum compatibility with restrictive proxies
const CHUNK_SIZE = 512 * 1024;

// Generic chunked upload handler for all providers
async function uploadFileChunked(providerKey, fileType, file, configObject) {
    if (!file) return;
    
    // Ensure provider is expanded so status element exists
    if (!expandedProviders.has(providerKey)) {
        toggleProvider(providerKey);
    }
    
    // Map config object names to status element prefixes
    const statusPrefixMap = {
        'kiro_config': 'kiro',
        'kilo_config': 'kilo',
        'claude_config': 'claude',
        'qwen_config': 'qwen',
        'codex_config': 'codex'
    };
    
    const statusPrefix = statusPrefixMap[configObject] || configObject.replace('_config', '');
    const statusEl = document.getElementById(`${statusPrefix}-upload-status-${providerKey}`);
    statusEl.innerHTML = `<div style="color: #4a9eff;">Uploading file: 0%</div>`;
    
    try {
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        
        for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {
            const start = (chunkNumber - 1) * CHUNK_SIZE;
            const end = Math.min(start + CHUNK_SIZE, file.size);
            const chunk = file.slice(start, end);
            
            const progress = Math.round((chunkNumber / totalChunks) * 100);
            statusEl.innerHTML = `<div style="color: #4a9eff;">Uploading file: ${progress}%</div>`;
            
            const formData = new FormData();
            formData.append('provider_key', providerKey);
            formData.append('file_type', fileType);
            formData.append('file_name', file.name);
            formData.append('total_size', file.size);
            formData.append('chunk_number', chunkNumber);
            formData.append('total_chunks', totalChunks);
            formData.append('file', chunk);
            
            const response = await fetch(BASE_PATH + '/dashboard/providers/upload-auth-file/chunk', {
                method: 'POST',
                body: formData
            });
            
            const result = await response.json();
            
            if (!result.success) {
                throw new Error(result.error || 'Upload failed');
            }
            
            // When upload is complete
            if (result.complete) {
                const savedPath = result.file_path || result.stored_filename || file.name;
                statusEl.innerHTML = `<div style="color: #4ade80; display: flex; align-items: center; gap: 6px;"><span style="font-size: 1.1em;">&#10003;</span> Saved: <code style="background: var(--bg-panel); padding: 1px 6px; border-radius: 3px; font-size: 0.9em;">${savedPath}</code></div>`;
                showToast(`File uploaded successfully: ${savedPath}`, 'success');

                // Update the config with the full file path (not just filename)
                if (!providersData[providerKey][configObject]) {
                    providersData[providerKey][configObject] = {};
                }
                // Use file_path from server response (tilde format: ~/.aisbf/...)
                providersData[providerKey][configObject][fileType] = result.file_path || result.stored_filename;
                setTimeout(() => renderProvidersList(), 2500);
                return;
            }
        }
    } catch (e) {
        statusEl.innerHTML = `<div style="color: #f87171;">Upload failed: ${e.message}</div>`;
        showToast(`Upload failed: ${e.message}`, 'danger');
    }
}

// Upload handler for Kiro credential files
async function uploadKiroFile(providerKey, fileType, file) {
    await uploadFileChunked(providerKey, fileType, file, 'kiro_config');
}

// Upload handler for Kilo credential files
async function uploadKiloFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'kilo_config');
}

// Upload handler for Claude credential files
async function uploadClaudeFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'claude_config');
}

// Upload handler for Claude CLI credentials file (.credentials.json from ~/.claude/)
async function uploadClaudeCliFile(providerKey, file) {
    if (!file) return;
    if (!expandedProviders.has(providerKey)) toggleProvider(providerKey);
    const statusEl = document.getElementById(`claude-cli-upload-status-${providerKey}`);
    if (!statusEl) return;
    statusEl.innerHTML = '<div style="color: #4a9eff;">Uploading CLI credentials: 0%</div>';
    try {
        const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
        for (let chunkNumber = 1; chunkNumber <= totalChunks; chunkNumber++) {
            const start = (chunkNumber - 1) * CHUNK_SIZE;
            const chunk = file.slice(start, Math.min(start + CHUNK_SIZE, file.size));
            const progress = Math.round((chunkNumber / totalChunks) * 100);
            statusEl.innerHTML = `<div style="color: #4a9eff;">Uploading CLI credentials: ${progress}%</div>`;
            const formData = new FormData();
            formData.append('provider_key', providerKey);
            formData.append('file_type', 'cli_credentials');
            formData.append('file_name', file.name);
            formData.append('total_size', file.size);
            formData.append('chunk_number', chunkNumber);
            formData.append('total_chunks', totalChunks);
            formData.append('file', chunk);
            const response = await fetch(BASE_PATH + '/dashboard/providers/upload-auth-file/chunk', { method: 'POST', body: formData });
            const result = await response.json();
            if (!result.success) throw new Error(result.error || 'Upload failed');
            if (result.complete) {
                statusEl.innerHTML = `<div style="color: #4ade80; display: flex; align-items: center; gap: 6px;"><span style="font-size: 1.1em;">&#10003;</span> CLI credentials saved: <code style="background: var(--bg-panel); padding: 1px 6px; border-radius: 3px; font-size: 0.9em;">${file.name}</code></div>`;
                showToast(`CLI credentials saved: ${file.name}`, 'success');
                return;
            }
        }
    } catch (e) {
        statusEl.innerHTML = `<div style="color: #f87171;">Upload failed: ${e.message}</div>`;
        showToast(`Upload failed: ${e.message}`, 'danger');
    }
}

// Upload handler for Qwen credential files
async function uploadQwenFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'qwen_config');
}

// Upload handler for Codex credential files
async function uploadCodexFile(providerKey, file) {
    await uploadFileChunked(providerKey, 'credentials_file', file, 'codex_config');
}

function renderProvidersList() {
    const container = document.getElementById('providers-list');
    if (!container) return;

    const allKeys = Object.keys(providersData);
    const filteredKeys = providerSearchFilter
        ? allKeys.filter(k => k.toLowerCase().includes(providerSearchFilter.toLowerCase()) ||
                              (providersData[k].name || '').toLowerCase().includes(providerSearchFilter.toLowerCase()))
        : allKeys;

    const total = filteredKeys.length;
    const totalPages = Math.max(1, Math.ceil(total / PROVIDERS_PAGE_SIZE));
    if (currentProviderPage >= totalPages) currentProviderPage = totalPages - 1;

    const pageKeys = filteredKeys.slice(currentProviderPage * PROVIDERS_PAGE_SIZE, (currentProviderPage + 1) * PROVIDERS_PAGE_SIZE);

    const countEl = document.getElementById('providers-count');
    if (countEl) countEl.textContent = `${total} provider${total !== 1 ? 's' : ''}`;

    container.innerHTML = '';
    if (pageKeys.length === 0) {
        container.innerHTML = `<p style="color:var(--color-muted);">${window.i18n.t('providers.no_providers')}</p>`;
    } else {
        pageKeys.forEach(key => {
            const provider = providersData[key];
            const providerItem = document.createElement('div');
            providerItem.className = 'provider-item';
            providerItem.style.cssText = 'border: 1px solid var(--color-border); margin-bottom: 10px; border-radius: 5px; background: var(--bg-page);';

            const isExpanded = expandedProviders.has(key);
            const safeKey = key.replace(/\\/g, '\\\\').replace(/'/g, "\\'");

            providerItem.innerHTML = `
                <div class="provider-header" onclick="toggleProvider('${safeKey}')" style="padding: 15px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; user-select: none;">
                    <div style="display: flex; align-items: center; gap: 10px;">
                        <span style="font-size: 18px;">${isExpanded ? '▼' : '▶'}</span>
                        <strong style="font-size: 16px;">${escHtmlAttr(key)}</strong>
                        <span style="color: var(--color-muted); font-size: 14px;">(${escHtmlAttr(provider.name || key)})</span>
                    </div>
                    <button type="button" class="btn btn-secondary" onclick="event.stopPropagation(); removeProvider('${safeKey}')" style="background: #dc3545; padding: 5px 15px;">${window.i18n.t('providers.remove')}</button>
                </div>
                <div id="provider-details-${escHtmlAttr(key)}" style="display: ${isExpanded ? 'block' : 'none'}; padding: 20px; border-top: 1px solid var(--color-border); background: var(--bg-panel);">
                </div>
            `;

            container.appendChild(providerItem);

            if (isExpanded) {
                renderProviderDetails(key);
            }
        });
    }

    const paginationEl = document.getElementById('providers-pagination');
    if (paginationEl) {
        if (totalPages <= 1) {
            paginationEl.innerHTML = '';
        } else {
            let btns = '';
            btns += `<button class="btn btn-secondary" onclick="goToProviderPage(${currentProviderPage - 1})" ${currentProviderPage === 0 ? 'disabled' : ''} style="padding:5px 12px;">← Prev</button>`;
            const start = Math.max(0, currentProviderPage - 2);
            const end = Math.min(totalPages - 1, currentProviderPage + 2);
            for (let p = start; p <= end; p++) {
                btns += `<button class="btn ${p === currentProviderPage ? '' : 'btn-secondary'}" onclick="goToProviderPage(${p})" style="padding:5px 10px;min-width:36px;">${p + 1}</button>`;
            }
            btns += `<button class="btn btn-secondary" onclick="goToProviderPage(${currentProviderPage + 1})" ${currentProviderPage >= totalPages - 1 ? 'disabled' : ''} style="padding:5px 12px;">Next →</button>`;
            paginationEl.innerHTML = `<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;">
                ${btns}
                <span style="color:var(--color-muted);font-size:13px;margin-left:8px;">Page ${currentProviderPage + 1} of ${totalPages}</span>
            </div>`;
        }
    }
}

function goToProviderPage(page) {
    currentProviderPage = page;
    expandedProviders.clear();
    renderProvidersList();
    window.scrollTo(0, 0);
}

function toggleProvider(key) {
    if (expandedProviders.has(key)) {
        expandedProviders.delete(key);
    } else {
        expandedProviders.add(key);
        const allKeys = Object.keys(providersData);
        const filteredKeys = providerSearchFilter
            ? allKeys.filter(k => k.toLowerCase().includes(providerSearchFilter.toLowerCase()) ||
                                  (providersData[k].name || '').toLowerCase().includes(providerSearchFilter.toLowerCase()))
            : allKeys;
        const idx = filteredKeys.indexOf(key);
        if (idx >= 0) currentProviderPage = Math.floor(idx / PROVIDERS_PAGE_SIZE);
    }
    renderProvidersList();
}

function renderProviderDetails(key) {
    const container = document.getElementById(`provider-details-${key}`);
    const provider = providersData[key];
    const safeKey = key.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
    const isKiroProvider = provider.type === 'kiro';
    const isClaudeProvider = provider.type === 'claude';
    const isKiloProvider = provider.type === 'kilocode';
    const isQwenProvider = provider.type === 'qwen';
    const isCodexProvider = provider.type === 'codex';
    
    // Initialize kiro_config if this is a kiro provider and doesn't have it
    if (isKiroProvider && !provider.kiro_config) {
        provider.kiro_config = {
            region: 'us-east-1',
            refresh_token: '',
            profile_arn: '',
            client_id: '',
            client_secret: ''
        };
    }
    
    // Initialize claude_config if this is a claude provider and doesn't have it
    if (isClaudeProvider && !provider.claude_config) {
        provider.claude_config = {};
    }
    
    // Initialize kilo_config if this is a kilocode provider and doesn't have it
    if (isKiloProvider && !provider.kilo_config) {
        provider.kilo_config = {
            api_base: 'https://api.kilo.ai/api/gateway'
        };
    }
    
    // Initialize qwen_config if this is a qwen provider and doesn't have it
    if (isQwenProvider && !provider.qwen_config) {
        provider.qwen_config = {
            api_key: '',
            region: 'china-beijing',
            workspace_id: 'Default Workspace'
        };
    }
    
    // Initialize codex_config if this is a codex provider and doesn't have it
    if (isCodexProvider && !provider.codex_config) {
        provider.codex_config = {
            issuer: 'https://auth.openai.com'
        };
    }
    
    const kiroConfig = provider.kiro_config || {};
    const claudeConfig = provider.claude_config || {};
    const kiloConfig = provider.kilo_config || {};
    const qwenConfig = provider.qwen_config || {};
    const codexConfig = provider.codex_config || {};
    
    // Build authentication fields based on provider type
    let authFieldsHtml = '';
    if (isKiroProvider) {
        // Kiro-specific authentication fields
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: #4a9eff;">Kiro Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Choose one authentication method: Kiro IDE credentials (creds_file), kiro-cli database (sqlite_db), or direct credentials (refresh_token + client_id/secret).
                </small>
                
                <div class="form-group">
                    <label>AWS Region</label>
                    <input type="text" value="${kiroConfig.region || 'us-east-1'}" onchange="updateKiroConfig('${key}', 'region', this.value)" placeholder="us-east-1">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">AWS region for Kiro API (default: us-east-1)</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">Direct Credentials</h5>
                <div class="form-group">
                    <label>Refresh Token</label>
                    <input type="password" value="${kiroConfig.refresh_token || ''}" onchange="updateKiroConfig('${key}', 'refresh_token', this.value)" placeholder="Enter refresh token">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Kiro refresh token for direct authentication</small>
                </div>
                
                <div class="form-group">
                    <label>Profile ARN</label>
                    <input type="text" value="${kiroConfig.profile_arn || ''}" onchange="updateKiroConfig('${key}', 'profile_arn', this.value)" placeholder="arn:aws:codewhisperer:us-east-1:...">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">AWS CodeWhisperer profile ARN (optional)</small>
                </div>
                
                <div class="form-group">
                    <label>Client ID (for AWS SSO OIDC)</label>
                    <input type="text" value="${kiroConfig.client_id || ''}" onchange="updateKiroConfig('${key}', 'client_id', this.value)" placeholder="OAuth client ID">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">OAuth client ID for AWS SSO OIDC authentication</small>
                </div>
                
                <div class="form-group">
                    <label>Client Secret (for AWS SSO OIDC)</label>
                    <input type="password" value="${kiroConfig.client_secret || ''}" onchange="updateKiroConfig('${key}', 'client_secret', this.value)" placeholder="OAuth client secret">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">OAuth client secret for AWS SSO OIDC authentication</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">Option 4: Upload Files</h5>
                <div class="form-group">
                    <label>Upload Credentials File</label>
                    <input type="file" id="kiro-creds-file-${key}" accept=".json" onchange="uploadKiroFile('${key}', 'creds_file', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Kiro IDE credentials JSON file</small>
                </div>
                <div class="form-group">
                    <label>Upload SQLite Database</label>
                    <input type="file" id="kiro-db-file-${key}" accept=".sqlite3,.db,.sqlite" onchange="uploadKiroFile('${key}', 'sqlite_db', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload kiro-cli SQLite database file</small>
                </div>
                <div id="kiro-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else if (isKiloProvider) {
        // Kilocode authentication fields - supports both API key and OAuth2
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: #4a9eff;">Kilocode Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Choose your authentication method: API Key (recommended for simplicity) or OAuth2 Device Authorization Grant.
                </small>
                
                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">Option 1: API Key / Auth Token</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_key')}</label>
                    <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter your Kilocode API key">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">If provided, API key authentication will be used instead of OAuth2</small>
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">Option 2: OAuth2 Authentication</h5>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    If no API key is provided, OAuth2 will be used automatically.
                </small>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_base')}</label>
                    <input type="text" value="${kiloConfig.api_base || 'https://api.kilo.ai/api/gateway'}" readonly style="background: var(--bg-panel); cursor: not-allowed;" placeholder="https://api.kilo.ai/api/gateway">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Kilocode API base URL (fixed)</small>
                </div>
                
                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateKilo('${key}')" style="background: #4a9eff;">
                        🔐 ${window.i18n.t('providers.authenticate_claude').replace('Claude', 'Kilocode OAuth2')}
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkKiloAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>
                
                <div id="kilo-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="kilo-creds-file-${key}" accept=".json" onchange="uploadKiloFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Kilocode OAuth2 credentials JSON file</small>
                </div>
                <div id="kilo-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else if (isClaudeProvider) {
        // Claude OAuth2 authentication fields
        const extensionInstalled = !!window.aisbfOAuth2Extension;
        const oauth2Available = IS_LOCAL_CLIENT || extensionInstalled;

        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: #4a9eff;">Claude OAuth2 Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Claude uses OAuth2 authentication. Click "Authenticate" to start the OAuth2 flow in your browser.
                </small>

                ${!IS_LOCAL_CLIENT && !extensionInstalled ? `
                <div style="background: #2a1a00; border: 2px solid #f59e0b; border-radius: 8px; padding: 15px; margin-bottom: 15px;">
                    <h5 style="margin: 0 0 8px 0; color: #f59e0b;">🧩 Chrome Extension Required</h5>
                    <p style="margin: 0 0 10px 0; color: #fcd34d; font-size: 0.9em;">
                        Claude's OAuth2 flow redirects your browser to <code style="background:#1a1000;padding:1px 4px;border-radius:3px;">http://localhost:54545/callback</code>
                        to deliver the auth token. When you access AISBF remotely, that localhost URL goes to
                        <em>your machine</em>, not the server — so the token never reaches AISBF.
                        <br><br>
                        The AISBF Chrome Extension intercepts that localhost callback in your browser and
                        forwards it to this server, completing the OAuth2 flow transparently.
                        Install it, reload this page, and the Authenticate button will appear.
                    </p>
                    <a href="${BASE_PATH}/dashboard/extension/download" download
                       style="display: inline-block; padding: 8px 16px; background: #2d6a4f; color: #fff; border-radius: 4px; text-decoration: none; font-weight: 600;">
                        ⬇️ Download Chrome Extension (.zip)
                    </a>
                    <small style="color: var(--color-muted); display: block; margin-top: 8px;">
                        In Chrome: go to <strong>chrome://extensions</strong>, enable Developer Mode, click "Load unpacked", and select the extracted folder.
                    </small>
                </div>
                ` : ''}

                ${oauth2Available ? `
                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateClaude('${key}')" style="background: #4a9eff;">
                        🔐 ${window.i18n.t('providers.authenticate_claude')}
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkClaudeAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>

                <div id="claude-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>

                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="claude-creds-file-${key}" accept=".json" onchange="uploadClaudeFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">${window.i18n.t('providers.upload_credentials_desc')}</small>
                </div>
                <div id="claude-upload-status-${key}" style="margin-top: 10px;"></div>
                ` : `
                <div id="claude-auth-status-${key}" style="display:none;"></div>
                <div id="claude-upload-status-${key}" style="display:none;"></div>
                `}

                ${CLAUDE_CLI_MODE ? `
                <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--color-border);">
                    <h5 style="margin: 0 0 8px 0; color: #4ade80;">${window.i18n.t('providers.cli_mode_active')}</h5>
                    <small style="color: #4ade80; display: block; margin-bottom: 14px;">
                        The claude CLI was detected at startup. When enabled, requests are piped
                        through the local claude binary instead of the HTTP API.
                    </small>

                    <div class="form-group" style="margin-bottom: 16px;">
                        <label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
                            <input type="checkbox"
                                   id="claude-use-cli-mode-${key}"
                                   ${claudeConfig.use_cli_mode ? 'checked' : ''}
                                   onchange="updateClaudeConfig('${key}', 'use_cli_mode', this.checked)"
                                   style="width: 18px; height: 18px; cursor: pointer; accent-color: #4ade80;">
                            <span style="color: var(--color-text); font-weight: 500;">${window.i18n.t('providers.use_cli_mode')}</span>
                        </label>
                        <small style="color: var(--color-muted); display: block; margin-top: 6px; padding-left: 28px;">
                            When checked and you have already authenticated via OAuth2 above,
                            your existing tokens will be used automatically — no separate file upload needed.
                            You can also upload an explicit <code style="background:var(--bg-panel);padding:1px 4px;border-radius:3px;">.credentials.json</code>
                            below to override.
                        </small>
                    </div>

                    <div class="form-group">
                        <label>${window.i18n.t('providers.upload_cli_creds')}</label>
                        <input type="file" id="claude-cli-creds-file-${key}" accept=".json" onchange="uploadClaudeCliFile('${key}', this.files[0])">
                        <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                            Optional — upload a <code style="background:var(--bg-panel);padding:1px 4px;border-radius:3px;">~/.claude/.credentials.json</code>
                            to use a specific account instead of your OAuth2 tokens.
                        </small>
                    </div>
                    <div id="claude-cli-upload-status-${key}" style="margin-top: 10px;"></div>
                </div>
                ` : ''}
            </div>

            <div style="background: #ffdd57; border: 2px solid #ff6b35; border-radius: 8px; padding: 20px; margin-bottom: 15px;">
                <h4 style="margin: 0 0 10px 0; color: #d63031;">⚠️ Important Notice</h4>
                <p style="margin: 0; color: #2d3436;">
                    Claude.ai policies state that unofficial clients are not allowed. By using AISBF in <strong>HTTP API / OAuth2 mode</strong>, you acknowledge that:
                    <br><br>• While we do our best to mimic the claude-code CLI, using an unofficial client can lead to account suspension or cancellation
                    <br>• Claude.ai detects prompt patterns to identify unofficial client usage, so it may not work with complex agents and prompts
                    <br>• You use this software at your own risk and responsibility
                    <br>• We are not affiliated with Anthropic and cannot guarantee compatibility or continued functionality
                    <br><br>✅ <strong>CLI mode is different:</strong> when "Use Claude CLI mode" is enabled, AISBF proxies requests through the official <code style="background:#e8e0b0;padding:1px 4px;border-radius:3px;">claude</code> binary using <code style="background:#e8e0b0;padding:1px 4px;border-radius:3px;">claude -p</code>, which is the intended programmatic use of the official Anthropic CLI and is permitted by Claude's terms of service.
                </p>
            </div>
        `;
    } else if (isQwenProvider) {
        // Qwen authentication fields - supports both API key and OAuth2
        authFieldsHtml = `
            <div style="background: #ff6b35; border: 2px solid #d63031; border-radius: 8px; padding: 20px; margin-bottom: 15px;">
                <h4 style="margin: 0 0 10px 0; color: #fff;">⚠️ Qwen OAuth2 Service Discontinued</h4>
                <p style="margin: 0; color: #fff;">
                    As of April 2026, Qwen has completely disabled OAuth2 subscriptions for Qwen Code.
                    OAuth2 tokens are no longer accepted by the DashScope API.
                    <br><br><strong>Please use API Key authentication instead.</strong>
                    <br><br>OAuth2 support is maintained in the code for potential future re-enablement, but it is currently non-functional.
                </p>
            </div>
            
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: #4a9eff;">Qwen Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Choose your authentication method: API Key (recommended for simplicity) or OAuth2 Device Authorization Grant.
                </small>

                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">Option 1: API Key (Recommended)</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_key')}</label>
                    <input type="password" value="${qwenConfig.api_key || ''}" onchange="updateQwenConfig('${key}', 'api_key', this.value)" placeholder="Enter your Qwen API key">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">If provided, API key authentication will be used instead of OAuth2</small>
                </div>

                <div id="qwen-region-config-${key}" style="display: ${qwenConfig.api_key ? 'block' : 'none'}; margin-top: 15px;">
                    <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">Region Configuration (API Key Mode)</h5>
                    <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                        Select your preferred region for API key authentication. Different regions have different endpoints.
                    </small>

                    <div class="form-group">
                        <label>Region</label>
                        <select onchange="updateQwenConfig('${key}', 'region', this.value)">
                            <option value="china-beijing" ${qwenConfig.region === 'china-beijing' || !qwenConfig.region ? 'selected' : ''}>China (Beijing) - dashscope.aliyuncs.com</option>
                            <option value="singapore" ${qwenConfig.region === 'singapore' ? 'selected' : ''}>Singapore - dashscope-intl.aliyuncs.com</option>
                            <option value="us-virginia" ${qwenConfig.region === 'us-virginia' ? 'selected' : ''}>US (Virginia) - dashscope-us.aliyuncs.com</option>
                            <option value="china-hongkong" ${qwenConfig.region === 'china-hongkong' ? 'selected' : ''}>China (Hong Kong) - cn-hongkong.dashscope.aliyuncs.com</option>
                            <option value="germany-frankfurt" ${qwenConfig.region === 'germany-frankfurt' ? 'selected' : ''}>Germany (Frankfurt) - Requires Workspace ID</option>
                        </select>
                        <small style="color: var(--color-muted); display: block; margin-top: 5px;">Select the region for your Qwen API endpoint</small>
                    </div>

                    <div id="qwen-workspace-config-${key}" style="display: ${qwenConfig.region === 'germany-frankfurt' ? 'block' : 'none'};">
                        <div class="form-group">
                            <label>Workspace ID</label>
                            <input type="text" value="${qwenConfig.workspace_id || 'Default Workspace'}" onchange="updateQwenConfig('${key}', 'workspace_id', this.value)" placeholder="Default Workspace">
                            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Workspace ID for Germany region (default: "Default Workspace")</small>
                        </div>
                    </div>
                </div>

                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">Option 2: OAuth2 Authentication (DISCONTINUED - NOT WORKING)</h5>
                <small style="color: #ff6b35; display: block; margin-bottom: 15px;">
                    ⚠️ OAuth2 authentication is no longer functional. Qwen has discontinued this service. Use API Key instead.
                </small>

                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateQwen('${key}')" style="background: #4a9eff;">
                        🔐 Authenticate with Qwen OAuth2
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkQwenAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>

                <div id="qwen-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>

                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="qwen-creds-file-${key}" accept=".json" onchange="uploadQwenFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Qwen OAuth2 credentials JSON file</small>
                </div>
                <div id="qwen-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else if (isCodexProvider) {
        // Codex OAuth2 authentication fields
        authFieldsHtml = `
            <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
                <h4 style="margin: 0 0 15px 0; color: #4a9eff;">Codex OAuth2 Authentication</h4>
                <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
                    Codex uses OAuth2 Device Authorization Grant (same protocol as OpenAI). Click "Authenticate" to start the OAuth2 flow.
                </small>
                
                <div class="form-group">
                    <label>${window.i18n.t('providers.api_key')} (${window.i18n.t('providers.optional')} - if provided, used instead of OAuth2)</label>
                    <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter OpenAI API key (optional)">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">If provided, API key authentication will be used instead of OAuth2</small>
                </div>
                
                <div class="form-group">
                    <label>OAuth2 Issuer URL</label>
                    <input type="text" value="${codexConfig.issuer || 'https://auth.openai.com'}" onchange="updateCodexConfig('${key}', 'issuer', this.value)" placeholder="https://auth.openai.com">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">OAuth2 issuer URL (default: https://auth.openai.com)</small>
                </div>
                
                <div style="margin-top: 15px;">
                    <button type="button" class="btn" onclick="authenticateCodex('${key}')" style="background: #4a9eff;">
                        🔐 Authenticate with Codex (Device Code)
                    </button>
                    <button type="button" class="btn btn-secondary" onclick="checkCodexAuth('${key}')" style="margin-left: 10px;">
                        ${window.i18n.t('providers.check_status')}
                    </button>
                </div>
                
                <div id="codex-auth-status-${key}" style="margin-top: 10px; padding: 10px; border-radius: 3px; display: none;">
                    <!-- Auth status will be displayed here -->
                </div>
                
                <h5 style="margin: 20px 0 10px 0; color: #8ec8ff;">${window.i18n.t('providers.upload_credentials_title')}</h5>
                <div class="form-group">
                    <label>${window.i18n.t('providers.upload_credentials_label')}</label>
                    <input type="file" id="codex-creds-file-${key}" accept=".json" onchange="uploadCodexFile('${key}', this.files[0])">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">Upload Codex OAuth2 credentials JSON file</small>
                </div>
                <div id="codex-upload-status-${key}" style="margin-top: 10px;"></div>
            </div>
        `;
    } else {
        // Standard API key authentication fields
        authFieldsHtml = `
            <div class="form-group">
                <label>
                    <input type="checkbox" ${provider.api_key_required ? 'checked' : ''} onchange="updateProvider('${key}', 'api_key_required', this.checked)">
                    API Key Required
                </label>
            </div>
            
            <div class="form-group">
                <label>API Key</label>
                <input type="password" value="${provider.api_key || ''}" onchange="updateProvider('${key}', 'api_key', this.value)" placeholder="Enter API key">
            </div>
        `;
    }
    
    container.innerHTML = `
        <div class="form-group">
            <label>${window.i18n.t('providers.provider_name')}</label>
            <input type="text" value="${provider.id}" onchange="updateProvider('${key}', 'id', this.value)" required>
        </div>
        
        <div class="form-group">
            <label>Name</label>
            <input type="text" value="${provider.name}" onchange="updateProvider('${key}', 'name', this.value)" required>
        </div>
        
        <div class="form-group">
            <label>Endpoint</label>
            <input type="text" value="${provider.endpoint}" onchange="updateProvider('${key}', 'endpoint', this.value)" ${(provider.type === 'kilocode' || provider.type === 'qwen' || provider.type === 'claude' || provider.type === 'codex') ? 'readonly style="background: var(--bg-panel); cursor: not-allowed;"' : ''} required>
            ${isKiroProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Typically: https://q.us-east-1.amazonaws.com</small>' : ''}
            ${provider.type === 'kilocode' ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Kilocode OAuth2 provider</small>' : ''}
            ${isQwenProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Qwen OAuth2 provider (https://dashscope.aliyuncs.com/compatible-mode/v1)</small>' : ''}
            ${isClaudeProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Claude OAuth2 provider (https://api.anthropic.com/v1)</small>' : ''}
            ${isCodexProvider ? '<small style="color: var(--color-muted); display: block; margin-top: 5px;">Fixed endpoint for Codex OAuth2 provider (https://api.openai.com/v1)</small>' : ''}
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.provider_type')}</label>
            <select onchange="updateProviderType('${key}', this.value)" required>
                <option value="google" ${provider.type === 'google' ? 'selected' : ''}>Google</option>
                <option value="openai" ${provider.type === 'openai' ? 'selected' : ''}>OpenAI</option>
                <option value="anthropic" ${provider.type === 'anthropic' ? 'selected' : ''}>Anthropic</option>
                <option value="ollama" ${provider.type === 'ollama' ? 'selected' : ''}>Ollama</option>
                <option value="kiro" ${provider.type === 'kiro' ? 'selected' : ''}>Kiro (Amazon Q Developer)</option>
                <option value="claude" ${provider.type === 'claude' ? 'selected' : ''}>Claude (OAuth2)</option>
                <option value="kilocode" ${provider.type === 'kilocode' ? 'selected' : ''}>Kilocode (OAuth2)</option>
                <option value="qwen" ${provider.type === 'qwen' ? 'selected' : ''}>Qwen (OAuth2)</option>
                <option value="codex" ${provider.type === 'codex' ? 'selected' : ''}>Codex (OpenAI OAuth2)</option>
            </select>
        </div>
        
        ${authFieldsHtml}
        
        <div style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin: 15px 0; border-left: 3px solid #4a9eff;">
            <h4 style="margin: 0 0 15px 0; color: #4a9eff;">Pricing Configuration</h4>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${provider.is_subscription ? 'checked' : ''} onchange="updateProvider('${key}', 'is_subscription', this.checked); togglePricingFields('${key}')">
                    Subscription-Based Provider (Free)
                </label>
                <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                    If checked, this provider is subscription-based and costs will be calculated as $0. Usage is still tracked for analytics.
                </small>
            </div>
            
            <div id="pricing-fields-${key}" style="display: ${provider.is_subscription ? 'none' : 'block'};">
                <div class="form-group">
                    <label>Price per Million Prompt Tokens (USD)</label>
                    <input type="number" step="0.01" min="0" value="${provider.price_per_million_prompt || ''}" onchange="updateProvider('${key}', 'price_per_million_prompt', parseFloat(this.value) || null)" placeholder="e.g., 15.00">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                        Leave empty to use default pricing. Examples: OpenAI GPT-4: $10, Anthropic Claude: $15, Google Gemini: $1.25
                    </small>
                </div>
                
                <div class="form-group">
                    <label>Price per Million Completion Tokens (USD)</label>
                    <input type="number" step="0.01" min="0" value="${provider.price_per_million_completion || ''}" onchange="updateProvider('${key}', 'price_per_million_completion', parseFloat(this.value) || null)" placeholder="e.g., 75.00">
                    <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                        Leave empty to use default pricing. Examples: OpenAI GPT-4: $30, Anthropic Claude: $75, Google Gemini: $5.00
                    </small>
                </div>
            </div>
        </div>

        <div class="form-group">
            <label>${window.i18n.t('providers.rate_limit')}</label>
            <input type="number" value="${provider.rate_limit || 0}" onchange="updateProvider('${key}', 'rate_limit', parseFloat(this.value))" step="0.1">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Time delay between requests to this provider</small>
        </div>
        
        <div class="form-group">
            <label>Default Rate Limit TPM (Tokens Per Minute)</label>
            <input type="number" value="${provider.default_rate_limit_TPM || ''}" onchange="updateProvider('${key}', 'default_rate_limit_TPM', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Default token limit per minute for models in this provider</small>
        </div>
        
        <div class="form-group">
            <label>Default Rate Limit TPH (Tokens Per Hour)</label>
            <input type="number" value="${provider.default_rate_limit_TPH || ''}" onchange="updateProvider('${key}', 'default_rate_limit_TPH', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Default token limit per hour for models in this provider</small>
        </div>
        
        <div class="form-group">
            <label>Default Rate Limit TPD (Tokens Per Day)</label>
            <input type="number" value="${provider.default_rate_limit_TPD || ''}" onchange="updateProvider('${key}', 'default_rate_limit_TPD', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Default token limit per day for models in this provider</small>
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.max_tokens')}</label>
            <input type="number" value="${provider.default_max_request_tokens || ''}" onchange="updateProvider('${key}', 'default_max_request_tokens', this.value ? parseInt(this.value) : null)" placeholder="Optional">
        </div>
        
        <div class="form-group">
            <label>${window.i18n.t('providers.context_size')}</label>
            <input type="number" value="${provider.default_context_size || ''}" onchange="updateProvider('${key}', 'default_context_size', this.value ? parseInt(this.value) : null)" placeholder="Optional">
        </div>
        
        <div class="form-group">
            <label>Default Condense Context (%)</label>
            <input type="number" value="${provider.default_condense_context || ''}" onchange="updateProvider('${key}', 'default_condense_context', this.value ? parseInt(this.value) : null)" placeholder="Optional (e.g., 80)">
        </div>
        
        <div class="form-group">
            <label>Default Condense Method (conversational, semantic, hierarchical, algorithmic)</label>
            <input type="text" value="${Array.isArray(provider.default_condense_method) ? provider.default_condense_method.join(', ') : (provider.default_condense_method || '')}" onchange="updateProviderCondenseMethod('${key}', this.value)" placeholder="e.g., semantic, conversational">
        </div>
        
        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.nsfw ? 'checked' : ''} onchange="updateProvider('${key}', 'nsfw', this.checked)">
                NSFW
            </label>
        </div>
        
        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.cache_enabled ? 'checked' : ''} onchange="setCacheSetting('${key}', null, this.checked)">
                Enable Prompt Caching
            </label>
            <small style="color: var(--color-subtle); font-size: 12px;">
                <strong>Provider-level cache override:</strong> Disabling this prevents the provider's native prompt 
                caching feature from being used, even if enabled in the provider configuration. 
                This takes priority over the global cache setting.
            </small>
        </div>
        
        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.privacy ? 'checked' : ''} onchange="updateProvider('${key}', 'privacy', this.checked)">
                Privacy
            </label>
        </div>

        <h4 style="margin-top: 20px; margin-bottom: 10px;">Native Caching</h4>
        <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
            Provider-native caching features (Anthropic cache_control, Google Context Caching, OpenAI and Kilo-compatible APIs) for cost reduction.
        </small>

        <div class="form-group">
            <label>
                <input type="checkbox" ${provider.enable_native_caching ? 'checked' : ''} onchange="updateProvider('${key}', 'enable_native_caching', this.checked)">
                Enable Native Caching
            </label>
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Enable provider-native caching for cost reduction (50-70% savings for supported providers)</small>
        </div>

        <div class="form-group">
            <label>Cache TTL (seconds)</label>
            <input type="number" value="${provider.cache_ttl || ''}" onchange="updateProvider('${key}', 'cache_ttl', this.value ? parseInt(this.value) : null)" placeholder="Optional (e.g., 3600)">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Cache time-to-live in seconds (Google Context Caching only)</small>
        </div>

        <div class="form-group">
            <label>Min Cacheable Tokens</label>
            <input type="number" value="${provider.min_cacheable_tokens || 1000}" onchange="updateProvider('${key}', 'min_cacheable_tokens', this.value ? parseInt(this.value) : 1000)" placeholder="1000">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Minimum token count for content to be cacheable (default: 1000)</small>
        </div>

        <div class="form-group">
            <label>Prompt Cache Key (OpenAI/Kilo)</label>
            <input type="text" value="${provider.prompt_cache_key || ''}" onchange="updateProvider('${key}', 'prompt_cache_key', this.value)" placeholder="Optional cache key for load balancer optimization">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">Optional cache key for OpenAI/Kilo load balancer routing optimization</small>
        </div>

        <h4 style="margin-top: 20px; margin-bottom: 10px;">${window.i18n.t('providers.models_section')}</h4>
        <small style="color: var(--color-muted); display: block; margin-bottom: 15px;">
            Configure specific models for this provider, or leave empty to automatically fetch all available models from the provider's API.
        </small>

        <div class="form-group" style="background: var(--bg-panel); padding: 15px; border-radius: 5px; margin-bottom: 15px; border-left: 3px solid #4a9eff;">
            <label>Model Filter (for auto-fetched models)</label>
            <input type="text" value="${provider.model_filter || ''}" onchange="updateProvider('${key}', 'model_filter', this.value)" placeholder="e.g., free, pro, flash">
            <small style="color: var(--color-muted); display: block; margin-top: 5px;">
                When no models are manually configured, only expose models whose ID contains this filter word (case-insensitive wildcard matching).
                <br>Example: "free" will match models like "model-name-free", "free-model", "gpt-4-free-tier", etc.
            </small>
        </div>

        <div id="models-${key}"></div>
        <div style="display: flex; gap: 10px; margin-top: 10px;">
            <button type="button" class="btn" onclick="getModelsFromProvider('${key}')" style="background: #4a9eff;">
                🔄 ${window.i18n.t('providers.fetch_models')}
            </button>
            <button type="button" class="btn btn-secondary" onclick="addModel('${key}')">${window.i18n.t('providers.add_model')}</button>
        </div>
        <div id="get-models-status-${key}" style="margin-top: 10px;"></div>

        <div style="margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--color-border); display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
            <button type="button" class="btn" onclick="saveProvider('${safeKey}')" style="background: #22c55e;">${window.i18n.t('providers.save_this')}</button>
            <span id="save-status-${key}" style="font-size: 13px; color: var(--color-muted);"></span>
        </div>
    `;

    renderModels(key);
}

function renderModels(providerKey) {
    const container = document.getElementById(`models-${providerKey}`);
    const provider = providersData[providerKey];
    
    if (!provider.models || provider.models.length === 0) {
        container.innerHTML = `<p style="color: var(--color-muted);">${window.i18n.t('providers.no_models')}</p>`;
        return;
    }
    
    container.innerHTML = '';
    provider.models.forEach((model, index) => {
        const modelDiv = document.createElement('div');
        modelDiv.style.cssText = 'border: 1px solid var(--color-border); padding: 15px; margin-bottom: 10px; border-radius: 3px; background: var(--bg-page);';
        
        modelDiv.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
                <strong>${window.i18n.t('providers.model_label')} ${index + 1}</strong>
                <button type="button" class="btn btn-secondary" onclick="removeModel('${providerKey}', ${index})" style="background: #dc3545; padding: 5px 10px; font-size: 12px;">${window.i18n.t('providers.remove')}</button>
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.model_name')}</label>
                <input type="text" value="${model.name}" onchange="updateModel('${providerKey}', ${index}, 'name', this.value)" required>
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.rate_limit')}</label>
                <input type="number" value="${model.rate_limit || 0}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit', parseFloat(this.value))" step="0.1">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.max_tokens')}</label>
                <input type="number" value="${model.max_request_tokens || ''}" onchange="updateModel('${providerKey}', ${index}, 'max_request_tokens', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>${window.i18n.t('providers.context_size')}</label>
                <input type="number" value="${model.context_size || ''}" onchange="updateModel('${providerKey}', ${index}, 'context_size', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>Rate Limit TPM (Tokens Per Minute)</label>
                <input type="number" value="${model.rate_limit_TPM || ''}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit_TPM', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>Rate Limit TPH (Tokens Per Hour)</label>
                <input type="number" value="${model.rate_limit_TPH || ''}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit_TPH', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>Rate Limit TPD (Tokens Per Day)</label>
                <input type="number" value="${model.rate_limit_TPD || ''}" onchange="updateModel('${providerKey}', ${index}, 'rate_limit_TPD', this.value ? parseInt(this.value) : null)" placeholder="Optional">
            </div>
            
            <div class="form-group">
                <label>Condense Context (%)</label>
                <input type="number" value="${model.condense_context || ''}" onchange="updateModel('${providerKey}', ${index}, 'condense_context', this.value ? parseInt(this.value) : null)" placeholder="Optional (default: 80)">
            </div>
            
            <div class="form-group">
                <label>Condense Method (conversational, semantic, hierarchical, algorithmic)</label>
                <input type="text" value="${Array.isArray(model.condense_method) ? model.condense_method.join(', ') : (model.condense_method || '')}" onchange="updateModelCondenseMethod('${providerKey}', ${index}, this.value)" placeholder="e.g., semantic, conversational">
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${model.nsfw ? 'checked' : ''} onchange="updateModel('${providerKey}', ${index}, 'nsfw', this.checked)">
                    NSFW
                </label>
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${model.privacy ? 'checked' : ''} onchange="updateModel('${providerKey}', ${index}, 'privacy', this.checked)">
                    Privacy
                </label>
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" ${model.cache_enabled ? 'checked' : ''} onchange="setCacheSetting('${providerKey}', '${model.name || model.id}', this.checked)">
                    Enable Prompt Caching
                </label>
                <small style="color: var(--color-subtle); font-size: 12px;">
                    <strong>Model-level cache override:</strong> Disabling this prevents prompt caching for this specific model.
                    Overrides both provider-level cache setting and global cache setting.
                </small>
            </div>
        `;
        
        container.appendChild(modelDiv);
    });
}



function showAddProviderForm() {
    document.getElementById('new-provider-form').style.display = 'block';
    document.getElementById('add-provider-btn').style.display = 'none';
    document.getElementById('new-provider-key').value = '';
    document.getElementById('new-provider-type').value = 'openai';
    updateNewProviderDefaults();
    document.getElementById('new-provider-key').focus();
}

function cancelAddProvider() {
    document.getElementById('new-provider-form').style.display = 'none';
    document.getElementById('add-provider-btn').style.display = 'inline-block';
}

function updateNewProviderDefaults() {
    const providerType = document.getElementById('new-provider-type').value;
    const descriptionEl = document.getElementById('new-provider-type-description');

    const descriptions = {
        'openai': 'OpenAI-compatible provider. Uses standard API key authentication. Endpoint: https://api.openai.com/v1',
        'google': 'Google AI provider (Gemini). Uses API key authentication. Endpoint: https://generativelanguage.googleapis.com/v1beta',
        'anthropic': 'Anthropic provider (Claude). Uses API key authentication. Endpoint: https://api.anthropic.com/v1',
        'ollama': 'Ollama local provider. No API key required by default. Endpoint: http://localhost:11434/api',
        'kiro': 'Kiro (Amazon Q Developer) provider. Uses Kiro credentials (IDE, CLI, or direct tokens). Endpoint: https://q.us-east-1.amazonaws.com',
        'claude': 'Claude Code provider. Uses OAuth2 authentication (browser-based login). Endpoint: https://api.anthropic.com/v1',
        'kilocode': 'Kilocode provider. Uses OAuth2 Device Authorization Grant. Endpoint: https://api.kilo.ai/api/gateway',
        'qwen': 'Qwen provider. Uses OAuth2 Device Authorization Grant or API key. Endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1',
        'codex': 'Codex provider. Uses OAuth2 Device Authorization Grant (same protocol as OpenAI). Endpoint: https://api.openai.com/v1'
    };

    descriptionEl.textContent = descriptions[providerType] || 'Standard provider configuration.';

    // Show/hide Claude warning
    const claudeWarningEl = document.getElementById('new-provider-claude-warning');
    if (providerType === 'claude') {
        claudeWarningEl.style.display = 'block';
    } else {
        claudeWarningEl.style.display = 'none';
    }
}

function togglePricingFields(key) {
    const provider = providersData[key];
    const pricingFields = document.getElementById(`pricing-fields-${key}`);
    if (pricingFields) {
        pricingFields.style.display = provider.is_subscription ? 'none' : 'block';
    }
}

// Helper function to format expiration time
function formatExpirationTime(expiresAt) {
    if (!expiresAt) return 'Unknown';
    
    const now = Math.floor(Date.now() / 1000);
    const secondsRemaining = expiresAt - now;
    
    if (secondsRemaining <= 0) {
        return 'Expired';
    }
    
    const days = Math.floor(secondsRemaining / 86400);
    const hours = Math.floor((secondsRemaining % 86400) / 3600);
    const minutes = Math.floor((secondsRemaining % 3600) / 60);
    const seconds = secondsRemaining % 60;
    
    if (days > 0) {
        return `${days} day${days > 1 ? 's' : ''} ${hours} hour${hours !== 1 ? 's' : ''}`;
    } else if (hours > 0) {
        return `${hours} hour${hours > 1 ? 's' : ''} ${minutes} minute${minutes !== 1 ? 's' : ''}`;
    } else if (minutes > 0) {
        return `${minutes} minute${minutes > 1 ? 's' : ''} ${seconds} second${seconds !== 1 ? 's' : ''}`;
    } else {
        return `${seconds} second${seconds > 1 ? 's' : ''}`;
    }
}

// OAuth authentication check functions
async function checkClaudeAuth(providerKey) {
    const statusDiv = document.getElementById(`claude-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = 'Checking Claude authentication status...';

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = `✅ Claude authentication is valid. Expires in: ${expirationText}`;
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = `❌ Claude authentication failed: ${result.error || 'Not authenticated'}`;
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = `❌ Error checking Claude auth: ${error.message}`;
    }
}

async function checkQwenAuth(providerKey) {
    const statusDiv = document.getElementById(`qwen-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = 'Checking Qwen authentication status...';

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = `✅ Qwen authentication is valid. Expires in: ${expirationText}`;
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = `❌ Qwen authentication failed: ${result.error || 'Not authenticated'}`;
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = `❌ Error checking Qwen auth: ${error.message}`;
    }
}

async function checkCodexAuth(providerKey) {
    const statusDiv = document.getElementById(`codex-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = 'Checking Codex authentication status...';

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = `✅ Codex authentication is valid. Expires in: ${expirationText}`;
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = `❌ Codex authentication failed: ${result.error || 'Not authenticated'}`;
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = `❌ Error checking Codex auth: ${error.message}`;
    }
}

async function checkKiloAuth(providerKey) {
    const statusDiv = document.getElementById(`kilo-auth-status-${providerKey}`);
    if (!statusDiv) return;

    statusDiv.style.display = 'block';
    statusDiv.style.background = '#f0f0f0';
    statusDiv.style.color = '#333';
    statusDiv.innerHTML = 'Checking Kilocode authentication status...';

    try {
        const response = await fetch(`${BASE_PATH}/dashboard/providers/${providerKey}/auth/check`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        const result = await response.json();

        if (response.ok && result.authenticated) {
            const expirationText = result.expires_at ? formatExpirationTime(result.expires_at) : 'Unknown';
            statusDiv.style.background = '#d4edda';
            statusDiv.style.color = '#155724';
            statusDiv.innerHTML = `✅ Kilocode authentication is valid. Expires in: ${expirationText}`;
        } else {
            statusDiv.style.background = '#f8d7da';
            statusDiv.style.color = '#721c24';
            statusDiv.innerHTML = `❌ Kilocode authentication failed: ${result.error || 'Not authenticated'}`;
        }
    } catch (error) {
        statusDiv.style.background = '#f8d7da';
        statusDiv.style.color = '#721c24';
        statusDiv.innerHTML = `❌ Error checking Kilocode auth: ${error.message}`;
    }
}

// OAuth authentication functions
async function authenticateClaude(key) {
    const statusEl = document.getElementById(`claude-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting OAuth2 authentication flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/claude/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key
            })
        });
        
        const data = await response.json();
        
        if (!data.success || !data.auth_url) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Failed to start authentication: ${data.error || 'Unknown error'}</p>`;
            return;
        }
        
        statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Opening authentication window...</p>';
        
        // Open OAuth2 URL in popup window
        const authWindow = window.open(data.auth_url, 'claude-auth', 'width=600,height=700');
        
        statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Please complete authentication in the popup window...</p>';
        
        // Poll for completion
        let pollCount = 0;
        const maxPolls = 60;
        let isCompleting = false;
        
        await new Promise(resolve => setTimeout(resolve, 8000));
        
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const statusResponse = await fetch(BASE_PATH + '/dashboard/claude/auth/callback-status');
                const statusData = await statusResponse.json();
                
                if (statusData.received) {
                    clearInterval(pollInterval);
                    
                    if (authWindow && !authWindow.closed) {
                        authWindow.close();
                    }
                    
                    if (statusData.error) {
                        statusEl.style.background = '#402010';
                        statusEl.style.border = '1px solid #ff4a4a';
                        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ OAuth2 error: ${statusData.error}</p>`;
                        return;
                    }
                    
                    if (isCompleting) return;
                    isCompleting = true;
                    
                    await new Promise(resolve => setTimeout(resolve, 1000));
                    
                    try {
                        statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Completing authentication...</p>';
                        
                        const completeResponse = await fetch(BASE_PATH + '/dashboard/claude/auth/complete', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json',
                            }
                        });
                        
                        const completeData = await completeResponse.json();
                        
                        if (completeData.success) {
                            statusEl.style.background = '#0f4020';
                            statusEl.style.border = '1px solid #4eff9e';
                            statusEl.innerHTML = '<p style="margin: 0; color: #4eff9e;">✓ Authentication successful! Credentials saved.</p>';
                        } else {
                            statusEl.style.background = '#402010';
                            statusEl.style.border = '1px solid #ff4a4a';
                            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Authentication incomplete. ${completeData.error || 'Please try again.'}</p>`;
                        }
                    } catch (error) {
                        statusEl.style.background = '#402010';
                        statusEl.style.border = '1px solid #ff4a4a';
                        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Error completing authentication: ${error.message}</p>`;
                    }
                }
            } catch (error) {
                console.error('Error checking callback status:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                if (authWindow && !authWindow.closed) {
                    authWindow.close();
                }
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authentication timeout. Please try again.</p>';
            }
        }, 2000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Error: ${error.message}</p>`;
    }
}

async function authenticateQwen(key) {
    const statusEl = document.getElementById(`qwen-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting Qwen OAuth2 Device Authorization flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/qwen/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key
            })
        });
        
        const data = await response.json();
        
        if (!data.success) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Failed to start authentication: ${data.error || 'Unknown error'}</p>`;
            return;
        }
        
        statusEl.style.background = 'var(--bg-panel)';
        statusEl.style.border = '1px solid #4a9eff';
        statusEl.innerHTML = `
            <div style="margin: 0;">
                <p style="margin: 0 0 10px 0; color: #4a9eff; font-weight: bold;">🔐 Qwen Device Authorization</p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Please visit: <a href="${data.verification_uri}" target="_blank" style="color: #4eff9e; text-decoration: underline;">${data.verification_uri}</a>
                </p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Enter code: <strong style="color: #4eff9e; font-size: 18px; letter-spacing: 2px;">${data.user_code}</strong>
                </p>
                <p style="margin: 0; color: var(--color-muted); font-size: 13px;">
                    Waiting for authorization... (expires in ${Math.floor(data.expires_in / 60)} minutes)
                </p>
            </div>
        `;
        
        try {
            window.open(data.verification_uri, 'qwen-auth', 'width=600,height=700');
        } catch (e) {
            console.error('Could not open auth window:', e);
        }
        
        let pollCount = 0;
        const maxPolls = Math.floor(data.expires_in / data.interval);
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const pollResponse = await fetch(BASE_PATH + '/dashboard/qwen/auth/poll', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const pollData = await pollResponse.json();
                
                if (pollData.status === 'completed') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#0f4020';
                    statusEl.style.border = '1px solid #4eff9e';
                    statusEl.innerHTML = '<p style="margin: 0; color: #4eff9e;">✓ Qwen authentication successful! Credentials saved.</p>';
                } else if (pollData.status === 'denied') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authorization denied by user.</p>';
                } else if (pollData.status === 'expired') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authorization code expired. Please try again.</p>';
                }
                
            } catch (error) {
                console.error('Error polling Qwen auth:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authentication timeout. Please try again.</p>';
            }
        }, data.interval * 1000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Error: ${error.message}</p>`;
    }
}

async function pollQwenAuth(providerKey, deviceCode) {
    // This function is no longer needed as polling is handled in authenticateQwen
}

async function authenticateCodex(key) {
    const statusEl = document.getElementById(`codex-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting Codex OAuth2 Device Authorization flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/codex/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key,
                issuer: providersData[key].codex_config?.issuer || 'https://auth.openai.com'
            })
        });
        
        const data = await response.json();
        
        if (!data.success) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Failed to start authentication: ${data.error || 'Unknown error'}</p>`;
            return;
        }
        
        statusEl.style.background = 'var(--bg-panel)';
        statusEl.style.border = '1px solid #4a9eff';
        statusEl.innerHTML = `
            <div style="margin: 0;">
                <p style="margin: 0 0 10px 0; color: #4a9eff; font-weight: bold;">🔐 Codex Device Authorization</p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Please visit: <a href="${data.verification_uri}" target="_blank" style="color: #4eff9e; text-decoration: underline;">${data.verification_uri}</a>
                </p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Enter code: <strong style="color: #4eff9e; font-size: 18px; letter-spacing: 2px;">${data.user_code}</strong>
                </p>
                <p style="margin: 0; color: var(--color-muted); font-size: 13px;">
                    Waiting for authorization... (expires in ${Math.floor(data.expires_in / 60)} minutes)
                </p>
            </div>
        `;
        
        try {
            window.open(data.verification_uri, 'codex-auth', 'width=600,height=700');
        } catch (e) {
            console.error('Could not open auth window:', e);
        }
        
        let pollCount = 0;
        const maxPolls = Math.floor(data.expires_in / data.interval);
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const pollResponse = await fetch(BASE_PATH + '/dashboard/codex/auth/poll', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const pollData = await pollResponse.json();
                
                if (pollData.status === 'approved') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#0f4020';
                    statusEl.style.border = '1px solid #4eff9e';
                    statusEl.innerHTML = '<p style="margin: 0; color: #4eff9e;">✓ Codex authentication successful! Credentials saved.</p>';
                } else if (pollData.status === 'denied') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authorization denied by user.</p>';
                } else if (pollData.status === 'expired') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authorization code expired. Please try again.</p>';
                }
                
            } catch (error) {
                console.error('Error polling Codex auth:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authentication timeout. Please try again.</p>';
            }
        }, data.interval * 1000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Error: ${error.message}</p>`;
    }
}

async function pollCodexAuth(providerKey, deviceCode) {
    // This function is no longer needed as polling is handled in authenticateCodex
}

async function authenticateKilo(key) {
    const statusEl = document.getElementById(`kilo-auth-status-${key}`);
    statusEl.style.display = 'block';
    statusEl.style.background = 'var(--bg-panel)';
    statusEl.style.border = '1px solid #4a9eff';
    statusEl.innerHTML = '<p style="margin: 0; color: #4a9eff;">🔄 Starting Kilocode OAuth2 Device Authorization flow...</p>';
    
    try {
        const response = await fetch(BASE_PATH + '/dashboard/kilo/auth/start', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key
            })
        });
        
        const data = await response.json();
        
        if (!data.success) {
            statusEl.style.background = '#402010';
            statusEl.style.border = '1px solid #ff4a4a';
            statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Failed to start authentication: ${data.error || 'Unknown error'}</p>`;
            return;
        }
        
        statusEl.style.background = 'var(--bg-panel)';
        statusEl.style.border = '1px solid #4a9eff';
        statusEl.innerHTML = `
            <div style="margin: 0;">
                <p style="margin: 0 0 10px 0; color: #4a9eff; font-weight: bold;">🔐 Kilocode Device Authorization</p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Please visit: <a href="${data.verification_uri}" target="_blank" style="color: #4eff9e; text-decoration: underline;">${data.verification_uri}</a>
                </p>
                <p style="margin: 0 0 10px 0; color: var(--color-text);">
                    Enter code: <strong style="color: #4eff9e; font-size: 18px; letter-spacing: 2px;">${data.user_code}</strong>
                </p>
                <p style="margin: 0; color: var(--color-muted); font-size: 13px;">
                    Waiting for authorization... (expires in ${Math.floor(data.expires_in / 60)} minutes)
                </p>
            </div>
        `;
        
        try {
            window.open(data.verification_uri, 'kilo-auth', 'width=600,height=700');
        } catch (e) {
            console.error('Could not open auth window:', e);
        }
        
        let pollCount = 0;
        const maxPolls = Math.floor(data.expires_in / data.interval);
        const pollInterval = setInterval(async () => {
            pollCount++;
            
            try {
                const pollResponse = await fetch(BASE_PATH + '/dashboard/kilo/auth/poll', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    }
                });
                
                const pollData = await pollResponse.json();
                
                if (pollData.status === 'completed') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#0f4020';
                    statusEl.style.border = '1px solid #4eff9e';
                    statusEl.innerHTML = '<p style="margin: 0; color: #4eff9e;">✓ Kilocode authentication successful! Credentials saved.</p>';
                } else if (pollData.status === 'denied') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authorization denied by user.</p>';
                } else if (pollData.status === 'expired') {
                    clearInterval(pollInterval);
                    statusEl.style.background = '#402010';
                    statusEl.style.border = '1px solid #ff4a4a';
                    statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authorization code expired. Please try again.</p>';
                }
                
            } catch (error) {
                console.error('Error polling Kilo auth:', error);
            }
            
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                statusEl.style.background = '#402010';
                statusEl.style.border = '1px solid #ff4a4a';
                statusEl.innerHTML = '<p style="margin: 0; color: #ff4a4a;">✗ Authentication timeout. Please try again.</p>';
            }
        }, data.interval * 1000);
        
    } catch (error) {
        statusEl.style.background = '#402010';
        statusEl.style.border = '1px solid #ff4a4a';
        statusEl.innerHTML = `<p style="margin: 0; color: #ff4a4a;">✗ Error: ${error.message}</p>`;
    }
}

async function pollKiloAuth(providerKey, deviceCode) {
    // This function is no longer needed as polling is handled in authenticateKilo
}

async function saveProvider(key) {
    const statusEl = document.getElementById(`save-status-${key}`);
    if (statusEl) statusEl.textContent = window.i18n.t('providers.saving');
    try {
        const result = await apiCall('POST', '/dashboard/api/provider', {
            provider_id: key,
            config: providersData[key]
        });
        if (result.success) {
            if (statusEl) { statusEl.textContent = window.i18n.t('providers.saved'); statusEl.style.color = '#4ade80'; setTimeout(() => { statusEl.textContent = ''; }, 3000); }
        } else {
            if (statusEl) { statusEl.textContent = 'Error: ' + (result.error || 'Unknown'); statusEl.style.color = '#f87171'; }
        }
    } catch (e) {
        if (statusEl) { statusEl.textContent = 'Error: ' + e.message; statusEl.style.color = '#f87171'; }
    }
}

async function removeProvider(key) {
    if (await showDangerConfirm(`Remove provider "${key}"?`, 'Remove Provider')) {
        try {
            const result = await apiCall('DELETE', '/dashboard/api/provider/' + encodeURIComponent(key));
            if (!result.success) {
                showAlert('Error removing provider: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
                return;
            }
        } catch (e) {
            showAlert('Error removing provider: ' + e.message, 'Error', '❌', 'danger');
            return;
        }
        delete providersData[key];
        expandedProviders.delete(key);
        renderProvidersList();
    }
}

function updateProvider(key, field, value) {
    providersData[key][field] = value;
}

function updateKiroConfig(key, field, value) {
    if (!providersData[key].kiro_config) {
        providersData[key].kiro_config = {};
    }
    providersData[key].kiro_config[field] = value;
}

function updateClaudeConfig(key, field, value) {
    if (!providersData[key].claude_config) {
        providersData[key].claude_config = {};
    }
    providersData[key].claude_config[field] = value;
}

function updateKiloConfig(key, field, value) {
    if (!providersData[key].kilo_config) {
        providersData[key].kilo_config = {};
    }
    providersData[key].kilo_config[field] = value;
}

function updateQwenConfig(key, field, value) {
    if (!providersData[key].qwen_config) {
        providersData[key].qwen_config = {};
    }
    providersData[key].qwen_config[field] = value;
}

function updateCodexConfig(key, field, value) {
    if (!providersData[key].codex_config) {
        providersData[key].codex_config = {};
    }
    providersData[key].codex_config[field] = value;
}

function updateProviderType(key, value) {
    providersData[key].type = value;
    const defaultEndpoint = getDefaultEndpoint(value);
    if (defaultEndpoint) {
        providersData[key].endpoint = defaultEndpoint;
    }
    // Re-render to update the configuration fields
    renderProvidersList();
}

function updateProviderCondenseMethod(key, value) {
    const trimmed = value.trim();
    if (!trimmed) {
        providersData[key].default_condense_method = null;
        return;
    }

    // Check if it's a comma-separated list
    if (trimmed.includes(',')) {
        providersData[key].default_condense_method = trimmed.split(',').map(s => s.trim()).filter(s => s);
    } else {
        providersData[key].default_condense_method = trimmed;
    }
}

function addModel(key) {
    if (!providersData[key].models) {
        providersData[key].models = [];
    }

    providersData[key].models.push({
        name: '',
        rate_limit: 0
    });

    renderModels(key);
}

async function removeModel(providerKey, index) {
    if (await showDangerConfirm('Remove this model?', 'Remove Model')) {
        providersData[providerKey].models.splice(index, 1);
        renderModels(providerKey);
    }
}

async function confirmAddProvider() {
    const key = document.getElementById('new-provider-key').value.trim();
    const type = document.getElementById('new-provider-type').value;

    if (!key) {
        showAlert('Please enter a provider key', 'Missing Key', '⚠️', 'warn');
        return;
    }

    if (providersData[key]) {
        showAlert('Provider key already exists', 'Duplicate Key', '⚠️', 'warn');
        return;
    }

    // Create new provider with defaults
    providersData[key] = {
        id: key,
        name: key.charAt(0).toUpperCase() + key.slice(1),
        endpoint: getDefaultEndpoint(type),
        type: type,
        api_key_required: type !== 'ollama' && type !== 'kiro' && type !== 'claude' && type !== 'kilocode' && type !== 'qwen' && type !== 'codex',
        rate_limit: 0,
        models: []
    };

    // Add type-specific config
    if (type === 'kiro') {
        providersData[key].kiro_config = {
            region: 'us-east-1',
            refresh_token: '',
            profile_arn: '',
            client_id: '',
            client_secret: ''
        };
    } else if (type === 'claude') {
        providersData[key].claude_config = {};
    } else if (type === 'kilocode') {
        providersData[key].kilo_config = {
            api_base: 'https://api.kilo.ai/api/gateway'
        };
    } else if (type === 'qwen') {
        providersData[key].qwen_config = {
            api_key: '',
            region: 'china-beijing',
            workspace_id: 'Default Workspace'
        };
    } else if (type === 'codex') {
        providersData[key].codex_config = {
            issuer: 'https://auth.openai.com'
        };
    }

    try {
        const result = await apiCall('POST', '/dashboard/api/provider', {
            provider_id: key,
            config: providersData[key]
        });
        if (!result.success) {
            showAlert('Error creating provider: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
            delete providersData[key];
            return;
        }
    } catch (e) {
        showAlert('Error creating provider: ' + e.message, 'Error', '❌', 'danger');
        delete providersData[key];
        return;
    }

    expandedProviders.add(key);
    cancelAddProvider();
    renderProvidersList();
}

function getDefaultEndpoint(type) {
    const defaults = {
        'openai': 'https://api.openai.com/v1',
        'google': 'https://generativelanguage.googleapis.com/v1beta',
        'anthropic': 'https://api.anthropic.com/v1',
        'ollama': 'http://localhost:11434/api',
        'kiro': 'https://q.us-east-1.amazonaws.com',
        'claude': 'https://api.anthropic.com/v1',
        'kilocode': 'https://api.kilo.ai/api/gateway',
        'qwen': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
        'codex': 'https://api.openai.com/v1'
    };
    return defaults[type] || '';
}

async function getModelsFromProvider(key) {
    const statusDiv = document.getElementById(`get-models-status-${key}`);
    if (!statusDiv) return;

    statusDiv.innerHTML = 'Fetching models...';

    try {
        const response = await fetch(BASE_PATH + '/dashboard/providers/get-models', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                provider_key: key,
                provider: providersData[key]
            })
        });

        const result = await response.json();

        if (response.ok && result.models) {
            providersData[key].models = result.models;
            renderModels(key);
            statusDiv.innerHTML = `✅ Found ${result.models.length} models`;
        } else {
            statusDiv.innerHTML = `❌ Error: ${result.error || 'Failed to fetch models'}`;
        }
    } catch (error) {
        statusDiv.innerHTML = `❌ Error: ${error.message}`;
    }
}

function updateModel(providerKey, index, field, value) {
    providersData[providerKey].models[index][field] = value;
}

function updateModelCondenseMethod(providerKey, index, value) {
    const trimmed = value.trim();
    if (!trimmed) {
        providersData[providerKey].models[index].condense_method = null;
        return;
    }

    // Check if it's a comma-separated list
    if (trimmed.includes(',')) {
        providersData[providerKey].models[index].condense_method = trimmed.split(',').map(s => s.trim()).filter(s => s);
    } else {
        providersData[providerKey].models[index].condense_method = trimmed;
    }
}

async function saveProviders() {
    try {
        const response = await fetch(BASE_PATH + '/dashboard/providers', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: 'config=' + encodeURIComponent(JSON.stringify(providersData, null, 2))
        });

        if (response.ok) {
            window.location.href = BASE_PATH + '/dashboard/providers?success=1';
        } else {
            showAlert('Error saving configuration', 'Error', '❌', 'danger');
        }
    } catch (error) {
        showAlert('Error: ' + error.message, 'Error', '❌', 'danger');
    }
}

async function initProviders() {
    await loadCacheSettings();
    renderProvidersList();
}

// Defer until i18n is ready so translated strings are available
function _doInitialRender() { initProviders(); }
if (window.i18n) { _doInitialRender(); } else { document.addEventListener('i18n:ready', _doInitialRender, {once: true}); }

</script>
{% endblock %}
