Theflame47 commited on
Commit
c58005c
·
verified ·
1 Parent(s): 3334b2b

Create Deployment_UI_BE.py

Browse files
Files changed (1) hide show
  1. Deployment_UI_BE.py +244 -0
Deployment_UI_BE.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deployment_UI.py — UI-only (binds to /api/* provided by Deployment_UI_BE.py)
2
+ from fastapi import APIRouter
3
+ from fastapi.responses import HTMLResponse
4
+
5
+ router = APIRouter()
6
+
7
+ @router.get("/Deployment_UI", response_class=HTMLResponse)
8
+ def deployment_ui_page():
9
+ html_head = """
10
+ <!doctype html>
11
+ <html><head><meta charset="utf-8"/><title>Deployment UI</title>
12
+ <style>
13
+ body { font-family: system-ui, sans-serif; margin: 24px; display: flex; flex-direction: column; height: 90vh; }
14
+ a.button { display:inline-block; padding:8px 14px; margin-bottom:10px; border:1px solid #ccc; border-radius:6px; text-decoration:none; color:#000; background:#f7f7f7; }
15
+ a.button:hover { background:#eee; }
16
+ #chat-window { flex:1; border:1px solid #ccc; border-radius:8px; padding:12px; overflow-y:auto; background:#fafafa; margin-bottom:10px; }
17
+ #input-area { display:flex; gap:8px; margin-bottom:10px; }
18
+ #user-input { flex:1; padding:10px; border:1px solid #ccc; border-radius:8px; }
19
+ #send-btn { padding:10px 16px; border:none; border-radius:8px; background:#0078d7; color:#fff; cursor:pointer; }
20
+ #send-btn:hover { background:#005fa3; }
21
+ #control-bar { display:flex; gap:8px; margin:0 0 10px; }
22
+ #start-btn, #stop-btn, #endall-btn { padding:8px 14px; border:1px solid #ccc; border-radius:8px; background:#f7f7f7; cursor:pointer; }
23
+ #start-btn:hover, #stop-btn:hover, #endall-btn:hover { background:#eee; }
24
+ #log-toggle { cursor:pointer; font-size:14px; color:#0078d7; text-decoration:underline; margin-bottom:6px; align-self:flex-start; }
25
+ #logs { border:1px dashed #bbb; border-radius:8px; padding:10px; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:12px; background:#fff; max-height:220px; overflow-y:auto; transition:max-height .25s ease, opacity .25s ease; opacity:1; }
26
+ #logs.collapsed { max-height:0; padding:0; opacity:0; overflow:hidden; border:none; }
27
+ .log-line { margin:0 0 6px; white-space:pre-wrap; }
28
+ .log-raw { color:#333; } .log-ok { color:#2d7c2d; } .log-err { color:#9d1c1c; }
29
+ #results { border:1px solid #ddd; border-radius:8px; padding:10px; background:#fff; margin-top:10px; }
30
+ .result { margin:8px 0; }
31
+ .thumb { max-width: 420px; border:1px solid #ccc; border-radius:6px; }
32
+ .meta { font-size:12px; color:#555; margin-top:4px; }
33
+ .download { display:inline-block; margin-top:6px; }
34
+ /* loading mask */
35
+ #boot-mask{position:fixed;inset:0;background:rgba(255,255,255,.85);display:none;
36
+ align-items:center;justify-content:center;flex-direction:column;z-index:9999}
37
+ #boot-msg{margin-top:12px;color:#000;font-weight:600}
38
+ .spinner{width:36px;height:36px;border:3px solid #ddd;border-top-color:#0078d7;border-radius:50%;
39
+ animation:spin 1s linear infinite}
40
+ @keyframes spin{to{transform:rotate(360deg)}}
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <a href="/trythis" class="button">← Back</a>
45
+ <div id="chat-window"></div>
46
+ <div id="input-area">
47
+ <input type="text" id="user-input" placeholder="Describe an image to generate…" />
48
+ <button id="send-btn">Send</button>
49
+ </div>
50
+ <div id="control-bar">
51
+ <button id="start-btn">Create inst.</button>
52
+ <button id="stop-btn">Stop</button>
53
+ <button id="endall-btn">End All</button>
54
+ </div>
55
+ <div id="log-toggle">Hide Logs</div>
56
+ <div id="logs" aria-live="polite"></div>
57
+ <div id="results"></div>
58
+ <div id="boot-mask" aria-live="polite" role="status">
59
+ <div class="spinner"></div>
60
+ <div id="boot-msg">Starting…</div>
61
+ </div>
62
+ <script>
63
+ """
64
+ html_tail = """
65
+ const logs = document.getElementById('logs');
66
+ const toggleBtn = document.getElementById('log-toggle');
67
+ const startBtn = document.getElementById('start-btn');
68
+ const stopBtn = document.getElementById('stop-btn');
69
+ const endAllBtn = document.getElementById('endall-btn');
70
+ const sendBtn = document.getElementById('send-btn');
71
+ const userInput = document.getElementById('user-input');
72
+ const results = document.getElementById('results');
73
+ const chat = document.getElementById('chat-window');
74
+ const bootMask = document.getElementById('boot-mask');
75
+ const bootMsg = document.getElementById('boot-msg');
76
+ let running = false, ready = false;
77
+ let MODEL_BASE = null, PREDICT_ROUTE = null;
78
+
79
+ // NEW: auto-ingest blob from FE query param on page load
80
+ (async () => {
81
+ try {
82
+ const u = new URL(window.location.href);
83
+ const blobUrl = u.searchParams.get('blob_url') || '/modelblob.json';
84
+ const r = await fetch(`/api/ingest/from_landing?blob_url=${encodeURIComponent(blobUrl)}`, { method: 'POST' });
85
+ const t = await r.text();
86
+ console.log('ingest-from-landing', r.status, t.slice(0,200));
87
+ } catch (e) {
88
+ console.warn('ingest bootstrap failed:', e);
89
+ }
90
+ })();
91
+
92
+ function showBoot(msg){ bootMsg.textContent = msg || 'Starting…'; bootMask.style.display = 'flex'; }
93
+ function setBoot(msg){ bootMsg.textContent = msg || 'Working…'; }
94
+ function hideBoot(){ bootMask.style.display = 'none'; }
95
+ function log(msg, cls='log-raw') {
96
+ const line = document.createElement('div');
97
+ line.className = 'log-line ' + cls;
98
+ line.textContent = String(msg);
99
+ logs.appendChild(line);
100
+ logs.scrollTop = logs.scrollHeight;
101
+ }
102
+ toggleBtn.addEventListener('click', () => {
103
+ const collapsed = logs.classList.toggle('collapsed');
104
+ toggleBtn.textContent = collapsed ? "Show Logs" : "Hide Logs";
105
+ });
106
+ async function post(path, payload) {
107
+ const r = await fetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload || {}) });
108
+ const text = await r.text();
109
+ log(text, r.ok ? 'log-ok' : 'log-err');
110
+ try { return { ok: r.ok, json: JSON.parse(text) }; } catch { return { ok: r.ok, json: { _raw: text } }; }
111
+ }
112
+ async function del(path) {
113
+ const r = await fetch(path, { method: 'DELETE' });
114
+ const text = await r.text();
115
+ log(text, r.ok ? 'log-ok' : 'log-err');
116
+ try { return { ok: r.ok, json: JSON.parse(text) }; } catch { return { ok: r.ok, json: { _raw: text } }; }
117
+ }
118
+ // create + wait for true readiness (isReady)
119
+ async function begin() {
120
+ if (running) return;
121
+ running = true; ready = false;
122
+ showBoot('Creating instance…');
123
+ const create = await post('/api/compute/create_instance');
124
+ if (!create.ok) { running = false; hideBoot(); return; }
125
+ const ok = await ensureReady(true);
126
+ hideBoot();
127
+ }
128
+ async function stopOnce() {
129
+ await del('/api/compute/delete_instance');
130
+ hideBoot(); setBoot(''); ready = false; running = false;
131
+ }
132
+ async function endAll() {
133
+ await stopOnce();
134
+ MODEL_BASE = null; PREDICT_ROUTE = null;
135
+ }
136
+ function appendResult(job_id, b64, timings) {
137
+ const div = document.createElement('div'); div.className = 'result';
138
+ const img = document.createElement('img'); img.className = 'thumb'; img.src = 'data:image/png;base64,' + b64;
139
+ const a = document.createElement('a'); a.className = 'download'; a.textContent = 'Download'; a.href = img.src; a.download = `job_${job_id}.png`;
140
+ const meta = document.createElement('div'); meta.className = 'meta'; meta.textContent = `job ${job_id} | ${JSON.stringify(timings || {})}`;
141
+ div.append(img, document.createTextNode(' '), a, meta); results.prepend(div);
142
+ }
143
+ // chat helpers
144
+ function escapeHtml(s){ return String(s).replace(/[&<>"']/g, m => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[m])); }
145
+ function addBlock(sender, html) {
146
+ const wrap = document.createElement('div');
147
+ wrap.style.margin = '8px 0';
148
+ wrap.innerHTML = `<div class="meta"><strong>${sender}:</strong></div><div>${html}</div>`;
149
+ chat.appendChild(wrap);
150
+ chat.scrollTop = chat.scrollHeight;
151
+ return wrap;
152
+ }
153
+ function addUser(text) { return addBlock('You', `<div>${escapeHtml(text)}</div>`); }
154
+ function addModel(text) { return addBlock('Model', `<div>${escapeHtml(text)}</div>`); }
155
+ function addModelImg(b64){
156
+ const wrap = addBlock('Model', '');
157
+ const img = document.createElement('img');
158
+ img.className = 'thumb';
159
+ img.src = 'data:image/png;base64,' + b64;
160
+ wrap.lastElementChild.appendChild(img);
161
+ return wrap;
162
+ }
163
+ function addLoader() {
164
+ const wrap = addBlock('Model', `<div id="spinner" style="display:inline-block">Loading…</div>`);
165
+ return wrap;
166
+ }
167
+ function looksBase64(s){ return /^[A-Za-z0-9+/=\\s]+$/.test(s||'') && String(s||'').length > 100; }
168
+ // poll for true readiness provided by BE via cachedState.isReady
169
+ async function ensureReady(verbose=false) {
170
+ for (let i = 0; i < 60; i++) {
171
+ const r = await fetch('/api/compute/wait_instance');
172
+ const j = await r.json();
173
+ const cs = j.cachedState || {};
174
+ const status = (cs.status || '').toUpperCase();
175
+ if (verbose) {
176
+ if (cs.isReady === true) setBoot('Ready');
177
+ else if (status === 'RUNNING' && cs.base) setBoot('Warming model…');
178
+ else setBoot(`Status: ${status || '…'}`);
179
+ }
180
+ if (cs.base && cs.predictRoute && cs.isReady === true) {
181
+ MODEL_BASE = cs.base;
182
+ PREDICT_ROUTE = cs.predictRoute.startsWith('/') ? cs.predictRoute : `/${cs.predictRoute}`;
183
+ log(`PROMPT_ENDPOINT ${MODEL_BASE}${PREDICT_ROUTE}`, 'log-ok');
184
+ ready = true;
185
+ return true;
186
+ }
187
+ log(`READY_POLL base=${cs.base ? 'yes' : 'no'} route=${cs.predictRoute || ''} isReady=${cs.isReady === true} status=${status}`, 'log-raw');
188
+ await new Promise(res => setTimeout(res, 1000));
189
+ }
190
+ ready = false;
191
+ return false;
192
+ }
193
+ // backend hop for prompts
194
+ async function sendViaBackend(prompt) {
195
+ const r = await fetch('/api/middleware/infer', {
196
+ method: 'POST',
197
+ headers: { 'content-type': 'application/json' },
198
+ body: JSON.stringify({ prompt })
199
+ });
200
+ const text = await r.text();
201
+ log(`POST /api/middleware/infer → ${r.status}`, r.ok ? 'log-ok' : 'log-err');
202
+ try { return { ok: r.ok, json: JSON.parse(text) }; } catch { return { ok: r.ok, json: { _raw: text } }; }
203
+ }
204
+ async function sendMessage() {
205
+ const prompt = (userInput.value || '').trim();
206
+ if (!prompt) return;
207
+ if (!ready) { addModel('Instance not ready yet. Try Start, or wait a moment.'); return; }
208
+ addUser(prompt);
209
+ const loader = addLoader();
210
+ userInput.disabled = true;
211
+ try {
212
+ const { ok, json } = await sendViaBackend(prompt);
213
+ loader.remove();
214
+ if (!ok && json?.error) { addModel(`Error: ${json.error}`); return; }
215
+ if (json?.image_b64) {
216
+ addModelImg(json.image_b64);
217
+ if (json.timings) appendResult(String(Date.now()), json.image_b64, json.timings);
218
+ } else if (typeof json?.output === 'string' && looksBase64(json.output)) {
219
+ addModelImg(json.output);
220
+ } else if (typeof json?.output === 'string') {
221
+ addModel(json.output);
222
+ } else if (json?._raw) {
223
+ addModel(json._raw);
224
+ } else {
225
+ addModel(JSON.stringify(json || {}, null, 2));
226
+ }
227
+ } catch (e) {
228
+ loader.remove();
229
+ addModel(`Error: ${String(e)}`);
230
+ } finally {
231
+ userInput.disabled = false; userInput.value = '';
232
+ userInput.focus();
233
+ }
234
+ }
235
+ document.getElementById('send-btn').addEventListener('click', sendMessage);
236
+ document.getElementById('user-input').addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); sendMessage(); } });
237
+ document.getElementById('start-btn').addEventListener('click', begin);
238
+ document.getElementById('stop-btn').addEventListener('click', stopOnce);
239
+ document.getElementById('endall-btn').addEventListener('click', endAll);
240
+ (function init(){})();
241
+ </script>
242
+ </body></html>
243
+ """
244
+ return HTMLResponse(content=html_head + html_tail)