Spaces:
Running
Running
improve ux
Browse files- index.html +39 -1
- wasm-demo.js +73 -2
index.html
CHANGED
@@ -71,6 +71,8 @@
|
|
71 |
font-family: 'Telegraf', sans-serif;
|
72 |
cursor: pointer;
|
73 |
transition: all .2s ease;
|
|
|
|
|
74 |
}
|
75 |
.contact-btn:hover {
|
76 |
background: var(--black);
|
@@ -253,6 +255,38 @@
|
|
253 |
flex-shrink: 0;
|
254 |
}
|
255 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
footer {
|
257 |
font-size: 1.2rem;
|
258 |
text-align: center;
|
@@ -351,7 +385,7 @@
|
|
351 |
<!-- Navbar -->
|
352 |
<nav class="navbar">
|
353 |
<div class="logo">ZAMA</div>
|
354 |
-
<
|
355 |
</nav>
|
356 |
|
357 |
<!-- Hero -->
|
@@ -433,6 +467,10 @@ sequenceDiagram
|
|
433 |
<p id="srvComputing" class="status" aria-live="polite" hidden>Server computing...</p>
|
434 |
<p id="srvProgress" class="status" aria-live="polite" hidden></p>
|
435 |
</div>
|
|
|
|
|
|
|
|
|
436 |
<div class="controls" style="margin-top: auto;">
|
437 |
<button id="btnSend" class="btn" disabled>📡 Send</button>
|
438 |
<span id="spin" class="loader" hidden aria-label="Working"></span>
|
|
|
71 |
font-family: 'Telegraf', sans-serif;
|
72 |
cursor: pointer;
|
73 |
transition: all .2s ease;
|
74 |
+
text-decoration: none;
|
75 |
+
display: inline-block;
|
76 |
}
|
77 |
.contact-btn:hover {
|
78 |
background: var(--black);
|
|
|
255 |
flex-shrink: 0;
|
256 |
}
|
257 |
|
258 |
+
/* Progress bar styles */
|
259 |
+
.progress-container {
|
260 |
+
width: 100%;
|
261 |
+
background: var(--grey-200);
|
262 |
+
border: 2px solid var(--black);
|
263 |
+
height: 24px;
|
264 |
+
position: relative;
|
265 |
+
overflow: hidden;
|
266 |
+
display: none;
|
267 |
+
}
|
268 |
+
|
269 |
+
.progress-bar {
|
270 |
+
height: 100%;
|
271 |
+
background: var(--black);
|
272 |
+
width: 0%;
|
273 |
+
transition: width 0.5s ease;
|
274 |
+
position: relative;
|
275 |
+
}
|
276 |
+
|
277 |
+
.progress-text {
|
278 |
+
position: absolute;
|
279 |
+
top: 50%;
|
280 |
+
left: 50%;
|
281 |
+
transform: translate(-50%, -50%);
|
282 |
+
color: var(--black);
|
283 |
+
font-size: 0.9rem;
|
284 |
+
font-weight: 600;
|
285 |
+
z-index: 1;
|
286 |
+
mix-blend-mode: difference;
|
287 |
+
filter: invert(1);
|
288 |
+
}
|
289 |
+
|
290 |
footer {
|
291 |
font-size: 1.2rem;
|
292 |
text-align: center;
|
|
|
385 |
<!-- Navbar -->
|
386 |
<nav class="navbar">
|
387 |
<div class="logo">ZAMA</div>
|
388 |
+
<a href="https://www.zama.ai/contact" class="contact-btn" target="_blank" rel="noopener noreferrer">Contact us</a>
|
389 |
</nav>
|
390 |
|
391 |
<!-- Hero -->
|
|
|
467 |
<p id="srvComputing" class="status" aria-live="polite" hidden>Server computing...</p>
|
468 |
<p id="srvProgress" class="status" aria-live="polite" hidden></p>
|
469 |
</div>
|
470 |
+
<div class="progress-container" id="progressContainer">
|
471 |
+
<div class="progress-bar" id="progressBar"></div>
|
472 |
+
<div class="progress-text" id="progressText">0%</div>
|
473 |
+
</div>
|
474 |
<div class="controls" style="margin-top: auto;">
|
475 |
<button id="btnSend" class="btn" disabled>📡 Send</button>
|
476 |
<span id="spin" class="loader" hidden aria-label="Working"></span>
|
wasm-demo.js
CHANGED
@@ -11,6 +11,7 @@ let keygenWorker;
|
|
11 |
let encryptWorker;
|
12 |
let sessionUid;
|
13 |
let taskId;
|
|
|
14 |
|
15 |
// Memory-efficient base64 encoding for large Uint8Array
|
16 |
function uint8ToBase64(uint8) {
|
@@ -30,6 +31,60 @@ const $ = id => document.getElementById(id);
|
|
30 |
const enable = (id, ok=true) => $(id).disabled = !ok;
|
31 |
const show = (id, visible=true) => $(id).hidden = !visible;
|
32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
// Hide all spinners immediately
|
34 |
show('keygenSpin', false);
|
35 |
show('spin', false);
|
@@ -66,7 +121,7 @@ show('tokenizerSpin', false);
|
|
66 |
show('encIcon', true);
|
67 |
enable('btnEncrypt', true);
|
68 |
enable('btnSend');
|
69 |
-
$('encStatus').textContent =
|
70 |
} else if (e.data.type === 'error') {
|
71 |
console.error('[Main] Encryption error:', e.data.error);
|
72 |
show('encryptSpin', false);
|
@@ -160,6 +215,7 @@ $('btnEncrypt').onclick = async () => {
|
|
160 |
try {
|
161 |
console.log('[Main] Tokenizing text:', text);
|
162 |
const tokenIds = llama3Tokenizer.encode(text);
|
|
|
163 |
console.log('[Main] Token IDs:', tokenIds);
|
164 |
|
165 |
encryptWorker.postMessage({ type: 'encrypt', tokenIds });
|
@@ -179,6 +235,8 @@ async function pollTaskStatus(currentTaskId, currentUid) {
|
|
179 |
console.error(`[Poll] Error fetching status: ${statusResponse.status} ${errorText}`);
|
180 |
$('srvStatus').textContent = `Status check error: ${statusResponse.status}`;
|
181 |
show('spin', false);
|
|
|
|
|
182 |
return null;
|
183 |
}
|
184 |
|
@@ -187,11 +245,14 @@ async function pollTaskStatus(currentTaskId, currentUid) {
|
|
187 |
$('srvStatus').textContent = `Status: ${statusData.status} - ${statusData.details}`;
|
188 |
|
189 |
if (statusData.status === 'success' || statusData.status === 'completed') {
|
|
|
190 |
return statusData;
|
191 |
} else if (['failure', 'revoked', 'unknown', 'error'].includes(statusData.status.toLowerCase())) {
|
192 |
console.error('[Poll] Task failed or unrecoverable:', statusData);
|
193 |
$('srvStatus').textContent = `Task failed: ${statusData.status}`;
|
194 |
show('spin', false);
|
|
|
|
|
195 |
return null;
|
196 |
} else {
|
197 |
setTimeout(() => pollTaskStatus(currentTaskId, currentUid).then(finalStatus => {
|
@@ -205,6 +266,8 @@ async function pollTaskStatus(currentTaskId, currentUid) {
|
|
205 |
console.error('[Poll] Polling exception:', e);
|
206 |
$('srvStatus').textContent = `Polling error: ${e.message}`;
|
207 |
show('spin', false);
|
|
|
|
|
208 |
return null;
|
209 |
}
|
210 |
}
|
@@ -234,6 +297,8 @@ async function getTaskResult(currentTaskId, currentUid, taskName) {
|
|
234 |
} finally {
|
235 |
show('spin', false);
|
236 |
$('srvComputing').hidden = true;
|
|
|
|
|
237 |
}
|
238 |
}
|
239 |
|
@@ -252,6 +317,10 @@ $('btnSend').onclick = async () => {
|
|
252 |
$('srvStatus').textContent = 'Submitting task…';
|
253 |
$('srvComputing').hidden = true;
|
254 |
window.taskStartTime = performance.now();
|
|
|
|
|
|
|
|
|
255 |
|
256 |
try {
|
257 |
const formData = new FormData();
|
@@ -290,6 +359,8 @@ $('btnSend').onclick = async () => {
|
|
290 |
$('srvStatus').textContent = `Task submission error: ${e.message} (${duration}s)`;
|
291 |
show('spin', false);
|
292 |
$('srvComputing').hidden = true;
|
|
|
|
|
293 |
}
|
294 |
};
|
295 |
|
@@ -306,4 +377,4 @@ $('btnDecrypt').onclick = () => {
|
|
306 |
console.error('[Main] Decryption error:', e);
|
307 |
$('decResult').textContent = `Decryption failed: ${e.message}`;
|
308 |
}
|
309 |
-
};
|
|
|
11 |
let encryptWorker;
|
12 |
let sessionUid;
|
13 |
let taskId;
|
14 |
+
let currentTokenCount = 0; // Track token count for progress estimation
|
15 |
|
16 |
// Memory-efficient base64 encoding for large Uint8Array
|
17 |
function uint8ToBase64(uint8) {
|
|
|
31 |
const enable = (id, ok=true) => $(id).disabled = !ok;
|
32 |
const show = (id, visible=true) => $(id).hidden = !visible;
|
33 |
|
34 |
+
// Progress bar functions
|
35 |
+
function showProgress() {
|
36 |
+
const container = $('progressContainer');
|
37 |
+
const bar = $('progressBar');
|
38 |
+
const text = $('progressText');
|
39 |
+
|
40 |
+
container.style.display = 'block';
|
41 |
+
bar.style.width = '0%';
|
42 |
+
text.textContent = '0%';
|
43 |
+
}
|
44 |
+
|
45 |
+
function hideProgress() {
|
46 |
+
const container = $('progressContainer');
|
47 |
+
container.style.display = 'none';
|
48 |
+
}
|
49 |
+
|
50 |
+
function updateProgress(percent) {
|
51 |
+
const bar = $('progressBar');
|
52 |
+
const text = $('progressText');
|
53 |
+
|
54 |
+
bar.style.width = percent + '%';
|
55 |
+
text.textContent = Math.round(percent) + '%';
|
56 |
+
}
|
57 |
+
|
58 |
+
// Progress animation based on token count
|
59 |
+
let progressInterval;
|
60 |
+
function startProgressAnimation(tokenCount) {
|
61 |
+
const estimatedTimePerToken = 30000; // 30 seconds per token in milliseconds
|
62 |
+
const totalEstimatedTime = tokenCount * estimatedTimePerToken;
|
63 |
+
const startTime = Date.now();
|
64 |
+
|
65 |
+
progressInterval = setInterval(() => {
|
66 |
+
const elapsed = Date.now() - startTime;
|
67 |
+
const progress = Math.min((elapsed / totalEstimatedTime) * 100, 95); // Cap at 95% until actual completion
|
68 |
+
updateProgress(progress);
|
69 |
+
|
70 |
+
// Update status message with time estimate
|
71 |
+
const remainingTime = Math.max(0, totalEstimatedTime - elapsed);
|
72 |
+
const remainingSeconds = Math.ceil(remainingTime / 1000);
|
73 |
+
if (remainingSeconds > 0) {
|
74 |
+
$('srvProgress').textContent = `Processing ${tokenCount} tokens... (~${remainingSeconds}s remaining)`;
|
75 |
+
show('srvProgress', true);
|
76 |
+
}
|
77 |
+
}, 100); // Update every 100ms for smooth animation
|
78 |
+
}
|
79 |
+
|
80 |
+
function stopProgressAnimation() {
|
81 |
+
if (progressInterval) {
|
82 |
+
clearInterval(progressInterval);
|
83 |
+
progressInterval = null;
|
84 |
+
}
|
85 |
+
show('srvProgress', false);
|
86 |
+
}
|
87 |
+
|
88 |
// Hide all spinners immediately
|
89 |
show('keygenSpin', false);
|
90 |
show('spin', false);
|
|
|
121 |
show('encIcon', true);
|
122 |
enable('btnEncrypt', true);
|
123 |
enable('btnSend');
|
124 |
+
$('encStatus').textContent = `Your text is encrypted 🔒 (${currentTokenCount} tokens)`;
|
125 |
} else if (e.data.type === 'error') {
|
126 |
console.error('[Main] Encryption error:', e.data.error);
|
127 |
show('encryptSpin', false);
|
|
|
215 |
try {
|
216 |
console.log('[Main] Tokenizing text:', text);
|
217 |
const tokenIds = llama3Tokenizer.encode(text);
|
218 |
+
currentTokenCount = tokenIds.length; // Store token count
|
219 |
console.log('[Main] Token IDs:', tokenIds);
|
220 |
|
221 |
encryptWorker.postMessage({ type: 'encrypt', tokenIds });
|
|
|
235 |
console.error(`[Poll] Error fetching status: ${statusResponse.status} ${errorText}`);
|
236 |
$('srvStatus').textContent = `Status check error: ${statusResponse.status}`;
|
237 |
show('spin', false);
|
238 |
+
stopProgressAnimation();
|
239 |
+
hideProgress();
|
240 |
return null;
|
241 |
}
|
242 |
|
|
|
245 |
$('srvStatus').textContent = `Status: ${statusData.status} - ${statusData.details}`;
|
246 |
|
247 |
if (statusData.status === 'success' || statusData.status === 'completed') {
|
248 |
+
updateProgress(100); // Complete the progress bar
|
249 |
return statusData;
|
250 |
} else if (['failure', 'revoked', 'unknown', 'error'].includes(statusData.status.toLowerCase())) {
|
251 |
console.error('[Poll] Task failed or unrecoverable:', statusData);
|
252 |
$('srvStatus').textContent = `Task failed: ${statusData.status}`;
|
253 |
show('spin', false);
|
254 |
+
stopProgressAnimation();
|
255 |
+
hideProgress();
|
256 |
return null;
|
257 |
} else {
|
258 |
setTimeout(() => pollTaskStatus(currentTaskId, currentUid).then(finalStatus => {
|
|
|
266 |
console.error('[Poll] Polling exception:', e);
|
267 |
$('srvStatus').textContent = `Polling error: ${e.message}`;
|
268 |
show('spin', false);
|
269 |
+
stopProgressAnimation();
|
270 |
+
hideProgress();
|
271 |
return null;
|
272 |
}
|
273 |
}
|
|
|
297 |
} finally {
|
298 |
show('spin', false);
|
299 |
$('srvComputing').hidden = true;
|
300 |
+
stopProgressAnimation();
|
301 |
+
hideProgress();
|
302 |
}
|
303 |
}
|
304 |
|
|
|
317 |
$('srvStatus').textContent = 'Submitting task…';
|
318 |
$('srvComputing').hidden = true;
|
319 |
window.taskStartTime = performance.now();
|
320 |
+
|
321 |
+
// Show and start progress animation
|
322 |
+
showProgress();
|
323 |
+
startProgressAnimation(currentTokenCount);
|
324 |
|
325 |
try {
|
326 |
const formData = new FormData();
|
|
|
359 |
$('srvStatus').textContent = `Task submission error: ${e.message} (${duration}s)`;
|
360 |
show('spin', false);
|
361 |
$('srvComputing').hidden = true;
|
362 |
+
stopProgressAnimation();
|
363 |
+
hideProgress();
|
364 |
}
|
365 |
};
|
366 |
|
|
|
377 |
console.error('[Main] Decryption error:', e);
|
378 |
$('decResult').textContent = `Decryption failed: ${e.message}`;
|
379 |
}
|
380 |
+
};
|