text-watermark-microscope / src /components /WatermarkPanel.svelte
roomnumber103's picture
Add LLM Text Watermark Microscope
c126239 verified
Raw
History Blame Contribute Delete
4.61 kB
<script lang="ts">
import {
wizards,
activeMethod,
setStage,
highestUnlocked,
downloadExport,
METHOD_META,
WATERMARKS,
type Stage,
} from '../lib/stores/app-state';
import KSplit from './stages/kirchenbauer/Split.svelte';
import KBias from './stages/kirchenbauer/Bias.svelte';
import KResult from './stages/kirchenbauer/Result.svelte';
import SClusters from './stages/ksemstamp/Clusters.svelte';
import SRejection from './stages/ksemstamp/Rejection.svelte';
import SResult from './stages/ksemstamp/Result.svelte';
import TRValues from './stages/textseal/RValues.svelte';
import TGumbel from './stages/textseal/Gumbel.svelte';
import TEvidence from './stages/textseal/Evidence.svelte';
import EditDetect from './stages/EditDetect.svelte';
const method = $derived($activeMethod);
const meta = $derived(METHOD_META[method]);
const wizard = $derived($wizards[method]);
const unlocked = $derived(highestUnlocked(wizard));
const STAGE_NUMS: Stage[] = [1, 2, 3, 4];
const NUM_LABEL = ['1', '2', '3', '4'];
function lockedReason(s: Stage): string {
if (s <= unlocked) return '';
if (s === 2) return 'Enter a seed in stage 1 first';
return 'Generate the watermarked text first';
}
</script>
<section class="panel wm">
<div class="methods">
{#each WATERMARKS as m}
<button class="method" class:active={m === method} onclick={() => activeMethod.set(m)}>
{METHOD_META[m].title}
<span class="badge">{METHOD_META[m].year}</span>
</button>
{/each}
<button
class="export"
title="Download seed, parameters, trace and scores as JSON"
disabled={!wizard.result}
onclick={() => downloadExport(method)}>export JSON</button
>
</div>
<p class="tagline dim small">{meta.tagline}</p>
<div class="stages">
{#each STAGE_NUMS as s, i}
{@const locked = s > unlocked}
<button
class="stage-tab"
class:active={wizard.stage === s}
class:locked
disabled={locked}
title={lockedReason(s)}
onclick={() => setStage(method, s)}
>
<span class="num">{NUM_LABEL[i]}</span>
{meta.stages[i]}
</button>
{/each}
</div>
<div class="body">
{#if wizard.error}
<p class="err small mono">{wizard.error}</p>
{/if}
{#if method === 'kirchenbauer'}
{#if wizard.stage === 1}
<KSplit {wizard} />
{:else if wizard.stage === 2}
<KBias {wizard} />
{:else if wizard.stage === 3}
<KResult {wizard} />
{:else}
<EditDetect {wizard} />
{/if}
{:else if method === 'ksemstamp'}
{#if wizard.stage === 1}
<SClusters {wizard} />
{:else if wizard.stage === 2}
<SRejection {wizard} />
{:else if wizard.stage === 3}
<SResult {wizard} />
{:else}
<EditDetect {wizard} />
{/if}
{:else if wizard.stage === 1}
<TRValues {wizard} />
{:else if wizard.stage === 2}
<TGumbel {wizard} />
{:else if wizard.stage === 3}
<TEvidence {wizard} />
{:else}
<EditDetect {wizard} />
{/if}
</div>
</section>
<style>
.wm {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 0;
}
.methods {
display: flex;
gap: 6px;
align-items: center;
flex-wrap: wrap;
}
.method {
display: flex;
align-items: center;
gap: 6px;
font-weight: 550;
}
.method.active {
border-color: var(--accent);
background: var(--accent-soft);
color: var(--accent);
}
.export {
margin-left: auto;
font-size: 12.5px;
padding: 4px 10px;
}
.tagline {
margin: 0;
}
.stages {
display: flex;
gap: 4px;
border-bottom: 1px solid var(--border);
padding-bottom: 8px;
flex-wrap: wrap;
}
.stage-tab {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
padding: 4px 10px;
border-color: transparent;
background: transparent;
}
.stage-tab .num {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--panel-3);
font-size: 11px;
font-weight: 700;
}
.stage-tab.active {
background: var(--accent-soft);
border-color: var(--accent);
color: var(--accent);
}
.stage-tab.active .num {
background: var(--accent);
color: #fff;
}
.stage-tab.locked {
opacity: 0.4;
}
.body {
min-height: 260px;
}
.err {
color: var(--red);
white-space: pre-wrap;
}
</style>