NitinBot001 commited on
Commit
731fbf6
Β·
verified Β·
1 Parent(s): 6bdcef9

Upload index.html

Browse files
Files changed (1) hide show
  1. app/static/index.html +249 -0
app/static/index.html ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Anthropic ↔ OpenAI Proxy</title>
7
+ <style>
8
+ *{box-sizing:border-box;margin:0;padding:0}
9
+ body{font-family:'Segoe UI',sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh}
10
+ .container{max-width:900px;margin:0 auto;padding:2rem}
11
+ h1{font-size:1.8rem;font-weight:700;color:#7dd3fc;margin-bottom:.3rem}
12
+ .subtitle{color:#94a3b8;margin-bottom:2rem;font-size:.9rem}
13
+ .card{background:#1e293b;border-radius:12px;padding:1.5rem;margin-bottom:1.5rem;border:1px solid #334155}
14
+ .card h2{font-size:1.1rem;font-weight:600;color:#bae6fd;margin-bottom:1rem}
15
+ .tabs{display:flex;gap:.5rem;margin-bottom:1.5rem}
16
+ .tab{padding:.5rem 1.2rem;border-radius:8px;border:1px solid #334155;background:#1e293b;color:#94a3b8;cursor:pointer;font-size:.9rem;transition:all .2s}
17
+ .tab.active{background:#0284c7;color:#fff;border-color:#0284c7}
18
+ .form-group{margin-bottom:1rem}
19
+ label{display:block;font-size:.85rem;color:#94a3b8;margin-bottom:.4rem}
20
+ 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}
21
+ input:focus{border-color:#0284c7}
22
+ .btn{padding:.6rem 1.4rem;border-radius:8px;border:none;cursor:pointer;font-size:.9rem;font-weight:600;transition:all .2s}
23
+ .btn-primary{background:#0284c7;color:#fff}.btn-primary:hover{background:#0369a1}
24
+ .btn-danger{background:#dc2626;color:#fff;padding:.4rem .9rem;font-size:.8rem}.btn-danger:hover{background:#b91c1c}
25
+ .btn-secondary{background:#334155;color:#e2e8f0}.btn-secondary:hover{background:#475569}
26
+ .proxy-card{background:#0f172a;border:1px solid #334155;border-radius:10px;padding:1.2rem;margin-bottom:1rem}
27
+ .proxy-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.8rem}
28
+ .proxy-name{font-weight:600;color:#7dd3fc}
29
+ .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}
30
+ .copy-btn{background:#0284c7;border:none;color:#fff;padding:.3rem .7rem;border-radius:5px;cursor:pointer;font-size:.75rem;white-space:nowrap}
31
+ .copy-btn:hover{background:#0369a1}
32
+ .meta{font-size:.78rem;color:#64748b;margin-top:.6rem}
33
+ .alert{padding:.8rem 1rem;border-radius:8px;margin-bottom:1rem;font-size:.9rem}
34
+ .alert-error{background:#450a0a;border:1px solid #dc2626;color:#fca5a5}
35
+ .alert-success{background:#052e16;border:1px solid #16a34a;color:#86efac}
36
+ .hidden{display:none}
37
+ .mapping-row{display:flex;gap:.5rem;margin-bottom:.5rem}
38
+ .mapping-row input{flex:1}
39
+ .header-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem}
40
+ .user-badge{background:#0284c7;padding:.3rem .8rem;border-radius:20px;font-size:.8rem;font-weight:600}
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <div class="container">
45
+ <div class="header-row">
46
+ <div>
47
+ <h1>πŸ”„ Anthropic ↔ OpenAI Proxy</h1>
48
+ <p class="subtitle">Use any OpenAI-compatible backend with Anthropic SDK</p>
49
+ </div>
50
+ <div id="user-info" class="hidden">
51
+ <span class="user-badge" id="username-badge"></span>
52
+ <button class="btn btn-secondary" style="margin-left:.5rem" onclick="logout()">Logout</button>
53
+ </div>
54
+ </div>
55
+
56
+ <div id="auth-section">
57
+ <div class="tabs">
58
+ <button class="tab active" onclick="switchTab('login')">Login</button>
59
+ <button class="tab" onclick="switchTab('signup')">Sign Up</button>
60
+ </div>
61
+ <div class="card">
62
+ <div id="auth-alert" class="hidden"></div>
63
+ <div id="login-form">
64
+ <h2>Login</h2>
65
+ <div class="form-group"><label>Username</label><input id="l-username" placeholder="your_username"/></div>
66
+ <div class="form-group"><label>Password</label><input id="l-password" type="password" placeholder="β€’β€’β€’β€’β€’β€’"/></div>
67
+ <button class="btn btn-primary" onclick="login()">Login</button>
68
+ </div>
69
+ <div id="signup-form" class="hidden">
70
+ <h2>Create Account</h2>
71
+ <div class="form-group"><label>Username</label><input id="s-username" placeholder="your_username"/></div>
72
+ <div class="form-group"><label>Email</label><input id="s-email" type="email" placeholder="you@example.com"/></div>
73
+ <div class="form-group"><label>Password</label><input id="s-password" type="password" placeholder="Min 6 characters"/></div>
74
+ <button class="btn btn-primary" onclick="signup()">Create Account</button>
75
+ </div>
76
+ </div>
77
+ </div>
78
+
79
+ <div id="dashboard-section" class="hidden">
80
+ <div class="card">
81
+ <h2>βž• Add New Proxy</h2>
82
+ <div id="create-alert" class="hidden"></div>
83
+ <div class="form-group"><label>Proxy Name</label><input id="p-name" placeholder="My LLM Server"/></div>
84
+ <div class="form-group"><label>OpenAI-Compatible Base URL</label><input id="p-url" placeholder="http://localhost:8000/v1"/></div>
85
+ <div class="form-group"><label>API Key</label><input id="p-key" type="password" placeholder="sk-... (or any string)"/></div>
86
+ <div class="form-group">
87
+ <label>Model Mapping (Anthropic β†’ Your model) <span style="color:#64748b;font-size:.8rem">optional</span></label>
88
+ <div id="mapping-rows"></div>
89
+ <button class="btn btn-secondary" style="font-size:.8rem;padding:.4rem .8rem;margin-top:.4rem" onclick="addMappingRow()">+ Add Mapping</button>
90
+ </div>
91
+ <button class="btn btn-primary" onclick="createProxy()" style="margin-top:.5rem">Create Proxy</button>
92
+ </div>
93
+
94
+ <div class="card">
95
+ <h2>πŸ”— Your Proxy Endpoints</h2>
96
+ <div id="proxies-alert" class="hidden"></div>
97
+ <div id="proxies-list"><p style="color:#64748b;font-size:.9rem">Loading...</p></div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+
102
+ <script>
103
+ const API = '';
104
+ let TOKEN = localStorage.getItem('token') || '';
105
+
106
+ function setAlert(id, msg, type='error') {
107
+ const el = document.getElementById(id);
108
+ el.className = `alert alert-${type}`;
109
+ el.textContent = msg;
110
+ el.classList.remove('hidden');
111
+ setTimeout(() => el.classList.add('hidden'), 5000);
112
+ }
113
+
114
+ function switchTab(tab) {
115
+ document.querySelectorAll('.tab').forEach((t,i) => t.classList.toggle('active', (tab==='login'?0:1)===i));
116
+ document.getElementById('login-form').classList.toggle('hidden', tab !== 'login');
117
+ document.getElementById('signup-form').classList.toggle('hidden', tab !== 'signup');
118
+ document.getElementById('auth-alert').classList.add('hidden');
119
+ }
120
+
121
+ async function apiFetch(path, opts={}) {
122
+ const headers = {'Content-Type':'application/json', ...(opts.headers||{})};
123
+ if (TOKEN) headers['Authorization'] = `Bearer ${TOKEN}`;
124
+ const res = await fetch(API + path, {...opts, headers});
125
+ const data = await res.json().catch(() => ({}));
126
+ if (!res.ok) throw new Error(data.detail || 'Request failed');
127
+ return data;
128
+ }
129
+
130
+ async function login() {
131
+ try {
132
+ const data = await apiFetch('/auth/login', {
133
+ method:'POST',
134
+ body: JSON.stringify({username: document.getElementById('l-username').value, password: document.getElementById('l-password').value})
135
+ });
136
+ TOKEN = data.access_token;
137
+ localStorage.setItem('token', TOKEN);
138
+ showDashboard(data.user.username);
139
+ } catch(e) { setAlert('auth-alert', e.message); }
140
+ }
141
+
142
+ async function signup() {
143
+ try {
144
+ const data = await apiFetch('/auth/signup', {
145
+ method:'POST',
146
+ body: JSON.stringify({username: document.getElementById('s-username').value, email: document.getElementById('s-email').value, password: document.getElementById('s-password').value})
147
+ });
148
+ TOKEN = data.access_token;
149
+ localStorage.setItem('token', TOKEN);
150
+ showDashboard(data.user.username);
151
+ } catch(e) { setAlert('auth-alert', e.message); }
152
+ }
153
+
154
+ function logout() {
155
+ TOKEN = ''; localStorage.removeItem('token');
156
+ document.getElementById('dashboard-section').classList.add('hidden');
157
+ document.getElementById('auth-section').classList.remove('hidden');
158
+ document.getElementById('user-info').classList.add('hidden');
159
+ }
160
+
161
+ function showDashboard(username) {
162
+ document.getElementById('auth-section').classList.add('hidden');
163
+ document.getElementById('dashboard-section').classList.remove('hidden');
164
+ document.getElementById('user-info').classList.remove('hidden');
165
+ document.getElementById('username-badge').textContent = 'πŸ‘€ ' + username;
166
+ loadProxies();
167
+ }
168
+
169
+ function addMappingRow(anthr='', oai='') {
170
+ const row = document.createElement('div');
171
+ row.className = 'mapping-row';
172
+ 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>`;
173
+ document.getElementById('mapping-rows').appendChild(row);
174
+ }
175
+
176
+ function getMappings() {
177
+ const rows = document.querySelectorAll('#mapping-rows .mapping-row');
178
+ const mapping = {};
179
+ rows.forEach(r => {
180
+ const inputs = r.querySelectorAll('input');
181
+ if (inputs[0].value && inputs[1].value) mapping[inputs[0].value.trim()] = inputs[1].value.trim();
182
+ });
183
+ return mapping;
184
+ }
185
+
186
+ async function createProxy() {
187
+ try {
188
+ const data = await apiFetch('/proxies', {
189
+ method:'POST',
190
+ body: JSON.stringify({
191
+ name: document.getElementById('p-name').value,
192
+ openai_base_url: document.getElementById('p-url').value,
193
+ openai_api_key: document.getElementById('p-key').value,
194
+ model_mapping: getMappings(),
195
+ })
196
+ });
197
+ setAlert('create-alert', `Proxy "${data.name}" created!`, 'success');
198
+ document.getElementById('p-name').value='';
199
+ document.getElementById('p-url').value='';
200
+ document.getElementById('p-key').value='';
201
+ document.getElementById('mapping-rows').innerHTML='';
202
+ loadProxies();
203
+ } catch(e) { setAlert('create-alert', e.message); }
204
+ }
205
+
206
+ async function loadProxies() {
207
+ const list = document.getElementById('proxies-list');
208
+ try {
209
+ const proxies = await apiFetch('/proxies');
210
+ if (!proxies.length) { list.innerHTML='<p style="color:#64748b;font-size:.9rem">No proxies yet. Create one above.</p>'; return; }
211
+ list.innerHTML = proxies.map(p => `
212
+ <div class="proxy-card">
213
+ <div class="proxy-header">
214
+ <span class="proxy-name">${p.name}</span>
215
+ <button class="btn btn-danger" onclick="deleteProxy(${p.id})">Delete</button>
216
+ </div>
217
+ <label style="font-size:.78rem;color:#64748b">Proxy URL (use as Anthropic base_url)</label>
218
+ <div class="url-box">
219
+ <span>${p.proxy_url}</span>
220
+ <button class="copy-btn" onclick="copyText('${p.proxy_url}',this)">Copy</button>
221
+ </div>
222
+ <div class="meta">
223
+ Forwards to: <code style="color:#7dd3fc">${p.openai_base_url}</code> &nbsp;|&nbsp; Created: ${new Date(p.created_at).toLocaleDateString()}
224
+ </div>
225
+ </div>
226
+ `).join('');
227
+ } catch(e) { list.innerHTML=`<p style="color:#fca5a5">${e.message}</p>`; }
228
+ }
229
+
230
+ async function deleteProxy(id) {
231
+ if (!confirm('Delete this proxy?')) return;
232
+ try {
233
+ await apiFetch(`/proxies/${id}`, {method:'DELETE'});
234
+ loadProxies();
235
+ } catch(e) { setAlert('proxies-alert', e.message); }
236
+ }
237
+
238
+ function copyText(text, btn) {
239
+ navigator.clipboard.writeText(text);
240
+ btn.textContent='Copied!';
241
+ setTimeout(() => btn.textContent='Copy', 2000);
242
+ }
243
+
244
+ if (TOKEN) {
245
+ apiFetch('/auth/me').then(u => showDashboard(u.username)).catch(() => { TOKEN=''; localStorage.removeItem('token'); });
246
+ }
247
+ </script>
248
+ </body>
249
+ </html>