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:
5
src/app.d.ts
vendored
5
src/app.d.ts
vendored
@@ -8,6 +8,11 @@ declare global {
|
|||||||
// interface PageState {}
|
// interface PageState {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Umami analytics tracker (injected by script tag)
|
||||||
|
const umami: {
|
||||||
|
track(event: string, data?: Record<string, string | number | boolean>): void;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
<svelte:head>
|
<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>" />
|
<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>
|
</svelte:head>
|
||||||
|
|
||||||
{@render children()}
|
{@render children()}
|
||||||
@@ -78,6 +78,10 @@
|
|||||||
async function handleConvert() {
|
async function handleConvert() {
|
||||||
if (!canConvert) return;
|
if (!canConvert) return;
|
||||||
|
|
||||||
|
if (typeof umami !== 'undefined') {
|
||||||
|
umami.track('convert_click', { style: selectedStyleId, intensity });
|
||||||
|
}
|
||||||
|
|
||||||
loading = true;
|
loading = true;
|
||||||
error = '';
|
error = '';
|
||||||
outputText = '';
|
outputText = '';
|
||||||
@@ -108,14 +112,26 @@
|
|||||||
systemPrompt = result.systemPrompt;
|
systemPrompt = result.systemPrompt;
|
||||||
userMessage = result.userMessage;
|
userMessage = result.userMessage;
|
||||||
modelLabel = result.modelLabel;
|
modelLabel = result.modelLabel;
|
||||||
|
|
||||||
|
if (typeof umami !== 'undefined') {
|
||||||
|
umami.track('convert_success', { style: selectedStyleId, intensity, model: result.modelLabel });
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err instanceof Error ? err.message : 'Something went wrong';
|
error = err instanceof Error ? err.message : 'Something went wrong';
|
||||||
|
|
||||||
|
if (typeof umami !== 'undefined') {
|
||||||
|
umami.track('convert_error', { style: selectedStyleId, intensity, error });
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCopy() {
|
async function handleCopy() {
|
||||||
|
if (typeof umami !== 'undefined') {
|
||||||
|
umami.track('copy_result', { style: selectedStyleId });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(outputText);
|
await navigator.clipboard.writeText(outputText);
|
||||||
copied = true;
|
copied = true;
|
||||||
@@ -157,6 +173,7 @@
|
|||||||
bind:value={selectedCategoryId}
|
bind:value={selectedCategoryId}
|
||||||
onchange={onCategoryChange}
|
onchange={onCategoryChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
data-umami-event="select_category"
|
||||||
>
|
>
|
||||||
<option value="">Choose a category...</option>
|
<option value="">Choose a category...</option>
|
||||||
{#each categories as cat}
|
{#each categories as cat}
|
||||||
@@ -167,7 +184,7 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="style">Style</label>
|
<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}
|
{#if !selectedCategoryId}
|
||||||
<option value="">Select a category first...</option>
|
<option value="">Select a category first...</option>
|
||||||
{:else if availableStyles.length === 0}
|
{:else if availableStyles.length === 0}
|
||||||
@@ -196,6 +213,7 @@
|
|||||||
step="1"
|
step="1"
|
||||||
bind:value={intensity}
|
bind:value={intensity}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
data-umami-event="adjust_intensity"
|
||||||
/>
|
/>
|
||||||
<span class="slider-end">Maximum</span>
|
<span class="slider-end">Maximum</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,7 +258,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="prompt-section">
|
<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
|
{showPrompt ? '▼' : '▶'} Show prompt
|
||||||
</button>
|
</button>
|
||||||
{#if showPrompt}
|
{#if showPrompt}
|
||||||
|
|||||||
Reference in New Issue
Block a user