File size: 3,078 Bytes
c126239 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | <script lang="ts">
/**
* The decoding as it happens: each sampled token appears the moment the
* worker chooses it, already carrying the mark the watermark gave it.
*/
import type { WizardState } from '../../lib/stores/app-state';
let { wizard }: { wizard: WizardState } = $props();
const tokens = $derived(wizard.liveTokens);
const running = $derived(wizard.status === 'running');
const greens = $derived(tokens.filter((t) => t.green === true).length);
const key2 = $derived(tokens.filter((t) => t.keyId === 2).length);
let box: HTMLDivElement | undefined = $state();
let pinned = $state(true);
// Follow the tail while it writes, unless the reader has scrolled away.
$effect(() => {
void tokens.length;
if (box && pinned) box.scrollTop = box.scrollHeight;
});
function onScroll() {
if (!box) return;
pinned = box.scrollHeight - box.scrollTop - box.clientHeight < 24;
}
function tokenClass(green?: boolean, keyId?: 1 | 2): string {
if (green === true) return 'tok-green';
if (green === false) return 'tok-red';
if (keyId === 2) return 'key2';
return '';
}
</script>
{#if running || tokens.length > 0}
<div class="live">
<div class="head small dim">
<span class:pulse={running}>{running ? 'sampling' : 'sampled'}</span>
<span class="mono">{tokens.length} tokens</span>
{#if wizard.algorithm === 'kirchenbauer' && tokens.length > 0}
<span class="mono">
{greens} green ({((greens / tokens.length) * 100).toFixed(0)}%)
</span>
{/if}
{#if wizard.algorithm === 'textseal' && tokens.length > 0}
<span class="mono">key₂ ×{key2}</span>
{/if}
{#if wizard.retries > 0}
<span class="mono">{wizard.retries} sentence retries</span>
{/if}
{#if !pinned}
<button class="follow" onclick={() => { pinned = true; if (box) box.scrollTop = box.scrollHeight; }}>
follow
</button>
{/if}
</div>
<div class="text-box plain" bind:this={box} onscroll={onScroll}>
{#each tokens as t (t.index)}<span
class={tokenClass(t.green, t.keyId)}
title={`step ${t.index}${t.entropy !== undefined ? ` · H=${t.entropy.toFixed(2)}` : ''}${t.keyId ? ` · key${t.keyId}` : ''}`}
>{t.text}</span
>{/each}{#if running}<span class="caret">|</span>{/if}
</div>
</div>
{/if}
<style>
.live {
display: flex;
flex-direction: column;
gap: 6px;
}
.head {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.follow {
padding: 1px 8px;
font-size: 11.5px;
}
.text-box {
white-space: pre-wrap;
font-size: 14px;
}
.key2 {
background: var(--amber-bg);
box-shadow: inset 0 -2px 0 #e6cd94;
border-radius: 2px;
}
.caret {
color: var(--accent);
animation: blink 1s steps(2, start) infinite;
}
@keyframes blink {
to {
visibility: hidden;
}
}
.pulse::after {
content: '…';
animation: blink 1.2s steps(3, start) infinite;
}
</style>
|