feat: add Umami analytics with tagged events

Add Umami web analytics tracking script to the layout head, and
instrument all interactive elements with event tracking:

Declarative (data-umami-event attributes):
- Category selector: select_category
- Style selector: select_style
- Intensity slider: adjust_intensity
- Prompt toggle: toggle_prompt (with open/close action)

Programmatic (umami.track with metadata):
- convert_click: { style, intensity }
- convert_success: { style, intensity, model }
- convert_error: { style, intensity, error }
- copy_result: { style }
This commit is contained in:
2026-04-13 01:21:20 -04:00
parent eaa1544e66
commit c96d97e154
3 changed files with 26 additions and 2 deletions

5
src/app.d.ts vendored
View File

@@ -8,6 +8,11 @@ declare global {
// interface PageState {}
// interface Platform {}
}
// Umami analytics tracker (injected by script tag)
const umami: {
track(event: string, data?: Record<string, string | number | boolean>): void;
};
}
export {};

View File

@@ -6,6 +6,7 @@
<svelte:head>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>✨</text></svg>" />
<script defer src="https://wa.santhoshj.com/script.js" data-website-id="1004961a-2b1f-4c45-95a6-78059acac472"></script>
</svelte:head>
{@render children()}

View File

@@ -78,6 +78,10 @@
async function handleConvert() {
if (!canConvert) return;
if (typeof umami !== 'undefined') {
umami.track('convert_click', { style: selectedStyleId, intensity });
}
loading = true;
error = '';
outputText = '';
@@ -108,14 +112,26 @@
systemPrompt = result.systemPrompt;
userMessage = result.userMessage;
modelLabel = result.modelLabel;
if (typeof umami !== 'undefined') {
umami.track('convert_success', { style: selectedStyleId, intensity, model: result.modelLabel });
}
} catch (err) {
error = err instanceof Error ? err.message : 'Something went wrong';
if (typeof umami !== 'undefined') {
umami.track('convert_error', { style: selectedStyleId, intensity, error });
}
} finally {
loading = false;
}
}
async function handleCopy() {
if (typeof umami !== 'undefined') {
umami.track('copy_result', { style: selectedStyleId });
}
try {
await navigator.clipboard.writeText(outputText);
copied = true;
@@ -157,6 +173,7 @@
bind:value={selectedCategoryId}
onchange={onCategoryChange}
disabled={loading}
data-umami-event="select_category"
>
<option value="">Choose a category...</option>
{#each categories as cat}
@@ -167,7 +184,7 @@
<div class="form-group">
<label for="style">Style</label>
<select id="style" bind:value={selectedStyleId} disabled={loading || !selectedCategoryId}>
<select id="style" bind:value={selectedStyleId} disabled={loading || !selectedCategoryId} data-umami-event="select_style">
{#if !selectedCategoryId}
<option value="">Select a category first...</option>
{:else if availableStyles.length === 0}
@@ -196,6 +213,7 @@
step="1"
bind:value={intensity}
disabled={loading}
data-umami-event="adjust_intensity"
/>
<span class="slider-end">Maximum</span>
</div>
@@ -240,7 +258,7 @@
</div>
<div class="prompt-section">
<button class="prompt-toggle" onclick={() => (showPrompt = !showPrompt)}>
<button class="prompt-toggle" onclick={() => (showPrompt = !showPrompt)} data-umami-event="toggle_prompt" data-umami-event-action={showPrompt ? 'close' : 'open'}>
{showPrompt ? '▼' : '▶'} Show prompt
</button>
{#if showPrompt}