Litellm-proxy / app /static /index.html
NitinBot001's picture
Upload index.html
731fbf6 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Anthropic ↔ OpenAI Proxy</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Segoe UI',sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh}
.container{max-width:900px;margin:0 auto;padding:2rem}
h1{font-size:1.8rem;font-weight:700;color:#7dd3fc;margin-bottom:.3rem}
.subtitle{color:#94a3b8;margin-bottom:2rem;font-size:.9rem}
.card{background:#1e293b;border-radius:12px;padding:1.5rem;margin-bottom:1.5rem;border:1px solid #334155}
.card h2{font-size:1.1rem;font-weight:600;color:#bae6fd;margin-bottom:1rem}
.tabs{display:flex;gap:.5rem;margin-bottom:1.5rem}
.tab{padding:.5rem 1.2rem;border-radius:8px;border:1px solid #334155;background:#1e293b;color:#94a3b8;cursor:pointer;font-size:.9rem;transition:all .2s}
.tab.active{background:#0284c7;color:#fff;border-color:#0284c7}
.form-group{margin-bottom:1rem}
label{display:block;font-size:.85rem;color:#94a3b8;margin-bottom:.4rem}
input{width:100%;padding:.6rem .9rem;background:#0f172a;border:1px solid #334155;border-radius:8px;color:#e2e8f0;font-size:.95rem;outline:none;transition:border .2s}
input:focus{border-color:#0284c7}
.btn{padding:.6rem 1.4rem;border-radius:8px;border:none;cursor:pointer;font-size:.9rem;font-weight:600;transition:all .2s}
.btn-primary{background:#0284c7;color:#fff}.btn-primary:hover{background:#0369a1}
.btn-danger{background:#dc2626;color:#fff;padding:.4rem .9rem;font-size:.8rem}.btn-danger:hover{background:#b91c1c}
.btn-secondary{background:#334155;color:#e2e8f0}.btn-secondary:hover{background:#475569}
.proxy-card{background:#0f172a;border:1px solid #334155;border-radius:10px;padding:1.2rem;margin-bottom:1rem}
.proxy-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.8rem}
.proxy-name{font-weight:600;color:#7dd3fc}
.url-box{background:#1e293b;border:1px solid #334155;border-radius:6px;padding:.6rem .9rem;font-family:monospace;font-size:.85rem;color:#a5f3fc;word-break:break-all;display:flex;justify-content:space-between;align-items:center;gap:.5rem}
.copy-btn{background:#0284c7;border:none;color:#fff;padding:.3rem .7rem;border-radius:5px;cursor:pointer;font-size:.75rem;white-space:nowrap}
.copy-btn:hover{background:#0369a1}
.meta{font-size:.78rem;color:#64748b;margin-top:.6rem}
.alert{padding:.8rem 1rem;border-radius:8px;margin-bottom:1rem;font-size:.9rem}
.alert-error{background:#450a0a;border:1px solid #dc2626;color:#fca5a5}
.alert-success{background:#052e16;border:1px solid #16a34a;color:#86efac}
.hidden{display:none}
.mapping-row{display:flex;gap:.5rem;margin-bottom:.5rem}
.mapping-row input{flex:1}
.header-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem}
.user-badge{background:#0284c7;padding:.3rem .8rem;border-radius:20px;font-size:.8rem;font-weight:600}
</style>
</head>
<body>
<div class="container">
<div class="header-row">
<div>
<h1>🔄 Anthropic ↔ OpenAI Proxy</h1>
<p class="subtitle">Use any OpenAI-compatible backend with Anthropic SDK</p>
</div>
<div id="user-info" class="hidden">
<span class="user-badge" id="username-badge"></span>
<button class="btn btn-secondary" style="margin-left:.5rem" onclick="logout()">Logout</button>
</div>
</div>
<div id="auth-section">
<div class="tabs">
<button class="tab active" onclick="switchTab('login')">Login</button>
<button class="tab" onclick="switchTab('signup')">Sign Up</button>
</div>
<div class="card">
<div id="auth-alert" class="hidden"></div>
<div id="login-form">
<h2>Login</h2>
<div class="form-group"><label>Username</label><input id="l-username" placeholder="your_username"/></div>
<div class="form-group"><label>Password</label><input id="l-password" type="password" placeholder="••••••"/></div>
<button class="btn btn-primary" onclick="login()">Login</button>
</div>
<div id="signup-form" class="hidden">
<h2>Create Account</h2>
<div class="form-group"><label>Username</label><input id="s-username" placeholder="your_username"/></div>
<div class="form-group"><label>Email</label><input id="s-email" type="email" placeholder="you@example.com"/></div>
<div class="form-group"><label>Password</label><input id="s-password" type="password" placeholder="Min 6 characters"/></div>
<button class="btn btn-primary" onclick="signup()">Create Account</button>
</div>
</div>
</div>
<div id="dashboard-section" class="hidden">
<div class="card">
<h2>➕ Add New Proxy</h2>
<div id="create-alert" class="hidden"></div>
<div class="form-group"><label>Proxy Name</label><input id="p-name" placeholder="My LLM Server"/></div>
<div class="form-group"><label>OpenAI-Compatible Base URL</label><input id="p-url" placeholder="http://localhost:8000/v1"/></div>
<div class="form-group"><label>API Key</label><input id="p-key" type="password" placeholder="sk-... (or any string)"/></div>
<div class="form-group">
<label>Model Mapping (Anthropic → Your model) <span style="color:#64748b;font-size:.8rem">optional</span></label>
<div id="mapping-rows"></div>
<button class="btn btn-secondary" style="font-size:.8rem;padding:.4rem .8rem;margin-top:.4rem" onclick="addMappingRow()">+ Add Mapping</button>
</div>
<button class="btn btn-primary" onclick="createProxy()" style="margin-top:.5rem">Create Proxy</button>
</div>
<div class="card">
<h2>🔗 Your Proxy Endpoints</h2>
<div id="proxies-alert" class="hidden"></div>
<div id="proxies-list"><p style="color:#64748b;font-size:.9rem">Loading...</p></div>
</div>
</div>
</div>
<script>
const API = '';
let TOKEN = localStorage.getItem('token') || '';
function setAlert(id, msg, type='error') {
const el = document.getElementById(id);
el.className = `alert alert-${type}`;
el.textContent = msg;
el.classList.remove('hidden');
setTimeout(() => el.classList.add('hidden'), 5000);
}
function switchTab(tab) {
document.querySelectorAll('.tab').forEach((t,i) => t.classList.toggle('active', (tab==='login'?0:1)===i));
document.getElementById('login-form').classList.toggle('hidden', tab !== 'login');
document.getElementById('signup-form').classList.toggle('hidden', tab !== 'signup');
document.getElementById('auth-alert').classList.add('hidden');
}
async function apiFetch(path, opts={}) {
const headers = {'Content-Type':'application/json', ...(opts.headers||{})};
if (TOKEN) headers['Authorization'] = `Bearer ${TOKEN}`;
const res = await fetch(API + path, {...opts, headers});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.detail || 'Request failed');
return data;
}
async function login() {
try {
const data = await apiFetch('/auth/login', {
method:'POST',
body: JSON.stringify({username: document.getElementById('l-username').value, password: document.getElementById('l-password').value})
});
TOKEN = data.access_token;
localStorage.setItem('token', TOKEN);
showDashboard(data.user.username);
} catch(e) { setAlert('auth-alert', e.message); }
}
async function signup() {
try {
const data = await apiFetch('/auth/signup', {
method:'POST',
body: JSON.stringify({username: document.getElementById('s-username').value, email: document.getElementById('s-email').value, password: document.getElementById('s-password').value})
});
TOKEN = data.access_token;
localStorage.setItem('token', TOKEN);
showDashboard(data.user.username);
} catch(e) { setAlert('auth-alert', e.message); }
}
function logout() {
TOKEN = ''; localStorage.removeItem('token');
document.getElementById('dashboard-section').classList.add('hidden');
document.getElementById('auth-section').classList.remove('hidden');
document.getElementById('user-info').classList.add('hidden');
}
function showDashboard(username) {
document.getElementById('auth-section').classList.add('hidden');
document.getElementById('dashboard-section').classList.remove('hidden');
document.getElementById('user-info').classList.remove('hidden');
document.getElementById('username-badge').textContent = '👤 ' + username;
loadProxies();
}
function addMappingRow(anthr='', oai='') {
const row = document.createElement('div');
row.className = 'mapping-row';
row.innerHTML = `<input placeholder="claude-3-opus-20240229" value="${anthr}"/><span style="color:#94a3b8;display:flex;align-items:center">→</span><input placeholder="gpt-4o" value="${oai}"/><button class="btn btn-danger" onclick="this.parentElement.remove()">✕</button>`;
document.getElementById('mapping-rows').appendChild(row);
}
function getMappings() {
const rows = document.querySelectorAll('#mapping-rows .mapping-row');
const mapping = {};
rows.forEach(r => {
const inputs = r.querySelectorAll('input');
if (inputs[0].value && inputs[1].value) mapping[inputs[0].value.trim()] = inputs[1].value.trim();
});
return mapping;
}
async function createProxy() {
try {
const data = await apiFetch('/proxies', {
method:'POST',
body: JSON.stringify({
name: document.getElementById('p-name').value,
openai_base_url: document.getElementById('p-url').value,
openai_api_key: document.getElementById('p-key').value,
model_mapping: getMappings(),
})
});
setAlert('create-alert', `Proxy "${data.name}" created!`, 'success');
document.getElementById('p-name').value='';
document.getElementById('p-url').value='';
document.getElementById('p-key').value='';
document.getElementById('mapping-rows').innerHTML='';
loadProxies();
} catch(e) { setAlert('create-alert', e.message); }
}
async function loadProxies() {
const list = document.getElementById('proxies-list');
try {
const proxies = await apiFetch('/proxies');
if (!proxies.length) { list.innerHTML='<p style="color:#64748b;font-size:.9rem">No proxies yet. Create one above.</p>'; return; }
list.innerHTML = proxies.map(p => `
<div class="proxy-card">
<div class="proxy-header">
<span class="proxy-name">${p.name}</span>
<button class="btn btn-danger" onclick="deleteProxy(${p.id})">Delete</button>
</div>
<label style="font-size:.78rem;color:#64748b">Proxy URL (use as Anthropic base_url)</label>
<div class="url-box">
<span>${p.proxy_url}</span>
<button class="copy-btn" onclick="copyText('${p.proxy_url}',this)">Copy</button>
</div>
<div class="meta">
Forwards to: <code style="color:#7dd3fc">${p.openai_base_url}</code> &nbsp;|&nbsp; Created: ${new Date(p.created_at).toLocaleDateString()}
</div>
</div>
`).join('');
} catch(e) { list.innerHTML=`<p style="color:#fca5a5">${e.message}</p>`; }
}
async function deleteProxy(id) {
if (!confirm('Delete this proxy?')) return;
try {
await apiFetch(`/proxies/${id}`, {method:'DELETE'});
loadProxies();
} catch(e) { setAlert('proxies-alert', e.message); }
}
function copyText(text, btn) {
navigator.clipboard.writeText(text);
btn.textContent='Copied!';
setTimeout(() => btn.textContent='Copy', 2000);
}
if (TOKEN) {
apiFetch('/auth/me').then(u => showDashboard(u.username)).catch(() => { TOKEN=''; localStorage.removeItem('token'); });
}
</script>
</body>
</html>