From 743637d2a4064402a5dba6c514103e530e20d695 Mon Sep 17 00:00:00 2001 From: Anubhav Tandon Date: Mon, 23 Mar 2026 22:40:47 +0530 Subject: [PATCH 1/4] feat: add Cold Email Personalization agent kit Adds a new automation kit that generates hyper-personalized cold outreach emails for college students applying to engineering internships. Takes LinkedIn/profile data and student context as inputs and produces a subject line, email body, and personalization hook via a Gemini-powered Lamatic flow. Made-with: Cursor --- .DS_Store | Bin 6148 -> 0 bytes .../cold-email-personalization/.env.example | 6 + .../cold-email-personalization/.gitignore | 27 + .../cold-email-personalization/.npmrc | 1 + .../cold-email-personalization/README.md | 99 + .../actions/orchestrate.ts | 71 + .../app/globals.css | 125 + .../cold-email-personalization/app/layout.tsx | 45 + .../cold-email-personalization/app/page.tsx | 305 + .../components.json | 21 + .../components/header.tsx | 47 + .../components/theme-provider.tsx | 11 + .../components/ui/accordion.tsx | 66 + .../components/ui/alert-dialog.tsx | 157 + .../components/ui/alert.tsx | 66 + .../components/ui/aspect-ratio.tsx | 11 + .../components/ui/avatar.tsx | 53 + .../components/ui/badge.tsx | 46 + .../components/ui/breadcrumb.tsx | 109 + .../components/ui/button-group.tsx | 83 + .../components/ui/button.tsx | 60 + .../components/ui/calendar.tsx | 213 + .../components/ui/card.tsx | 92 + .../components/ui/carousel.tsx | 241 + .../components/ui/chart.tsx | 353 ++ .../components/ui/checkbox.tsx | 32 + .../components/ui/collapsible.tsx | 33 + .../components/ui/command.tsx | 184 + .../components/ui/context-menu.tsx | 252 + .../components/ui/dialog.tsx | 143 + .../components/ui/drawer.tsx | 135 + .../components/ui/dropdown-menu.tsx | 257 + .../components/ui/empty.tsx | 104 + .../components/ui/field.tsx | 244 + .../components/ui/form.tsx | 167 + .../components/ui/hover-card.tsx | 44 + .../components/ui/input-group.tsx | 169 + .../components/ui/input-otp.tsx | 77 + .../components/ui/input.tsx | 21 + .../components/ui/item.tsx | 193 + .../components/ui/kbd.tsx | 28 + .../components/ui/label.tsx | 24 + .../components/ui/menubar.tsx | 276 + .../components/ui/navigation-menu.tsx | 166 + .../components/ui/pagination.tsx | 127 + .../components/ui/popover.tsx | 48 + .../components/ui/progress.tsx | 31 + .../components/ui/radio-group.tsx | 45 + .../components/ui/resizable.tsx | 56 + .../components/ui/scroll-area.tsx | 58 + .../components/ui/select.tsx | 185 + .../components/ui/separator.tsx | 28 + .../components/ui/sheet.tsx | 139 + .../components/ui/sidebar.tsx | 726 +++ .../components/ui/skeleton.tsx | 13 + .../components/ui/slider.tsx | 63 + .../components/ui/sonner.tsx | 25 + .../components/ui/spinner.tsx | 16 + .../components/ui/switch.tsx | 31 + .../components/ui/table.tsx | 116 + .../components/ui/tabs.tsx | 66 + .../components/ui/textarea.tsx | 18 + .../components/ui/toast.tsx | 129 + .../components/ui/toaster.tsx | 35 + .../components/ui/toggle-group.tsx | 73 + .../components/ui/toggle.tsx | 47 + .../components/ui/tooltip.tsx | 61 + .../components/ui/use-mobile.tsx | 19 + .../components/ui/use-toast.ts | 191 + .../cold-email-personalization/config.json | 26 + .../cold-email-personalisation/README.md | 48 + .../cold-email-personalisation/config.json | 121 + .../cold-email-personalisation/inputs.json | 26 + .../cold-email-personalisation/meta.json | 9 + .../hooks/use-mobile.ts | 19 + .../hooks/use-toast.ts | 191 + .../lib/lamatic-client.ts | 20 + .../lib/parse-cold-email-result.ts | 173 + .../cold-email-personalization/lib/utils.ts | 6 + .../next.config.mjs | 20 + .../cold-email-personalization/orchestrate.js | 34 + .../package-lock.json | 5204 +++++++++++++++++ .../cold-email-personalization/package.json | 80 + .../postcss.config.mjs | 8 + .../cold-email-personalization/tsconfig.json | 41 + 85 files changed, 13229 insertions(+) delete mode 100644 .DS_Store create mode 100644 kits/automation/cold-email-personalization/.env.example create mode 100644 kits/automation/cold-email-personalization/.gitignore create mode 100644 kits/automation/cold-email-personalization/.npmrc create mode 100644 kits/automation/cold-email-personalization/README.md create mode 100644 kits/automation/cold-email-personalization/actions/orchestrate.ts create mode 100644 kits/automation/cold-email-personalization/app/globals.css create mode 100644 kits/automation/cold-email-personalization/app/layout.tsx create mode 100644 kits/automation/cold-email-personalization/app/page.tsx create mode 100644 kits/automation/cold-email-personalization/components.json create mode 100644 kits/automation/cold-email-personalization/components/header.tsx create mode 100644 kits/automation/cold-email-personalization/components/theme-provider.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/accordion.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/alert-dialog.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/alert.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/aspect-ratio.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/avatar.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/badge.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/breadcrumb.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/button-group.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/button.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/calendar.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/card.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/carousel.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/chart.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/checkbox.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/collapsible.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/command.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/context-menu.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/dialog.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/drawer.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/dropdown-menu.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/empty.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/field.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/form.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/hover-card.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/input-group.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/input-otp.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/input.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/item.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/kbd.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/label.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/menubar.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/navigation-menu.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/pagination.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/popover.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/progress.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/radio-group.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/resizable.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/scroll-area.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/select.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/separator.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/sheet.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/sidebar.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/skeleton.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/slider.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/sonner.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/spinner.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/switch.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/table.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/tabs.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/textarea.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/toast.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/toaster.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/toggle-group.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/toggle.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/tooltip.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/use-mobile.tsx create mode 100644 kits/automation/cold-email-personalization/components/ui/use-toast.ts create mode 100644 kits/automation/cold-email-personalization/config.json create mode 100644 kits/automation/cold-email-personalization/flows/cold-email-personalisation/README.md create mode 100644 kits/automation/cold-email-personalization/flows/cold-email-personalisation/config.json create mode 100644 kits/automation/cold-email-personalization/flows/cold-email-personalisation/inputs.json create mode 100644 kits/automation/cold-email-personalization/flows/cold-email-personalisation/meta.json create mode 100644 kits/automation/cold-email-personalization/hooks/use-mobile.ts create mode 100644 kits/automation/cold-email-personalization/hooks/use-toast.ts create mode 100644 kits/automation/cold-email-personalization/lib/lamatic-client.ts create mode 100644 kits/automation/cold-email-personalization/lib/parse-cold-email-result.ts create mode 100644 kits/automation/cold-email-personalization/lib/utils.ts create mode 100644 kits/automation/cold-email-personalization/next.config.mjs create mode 100644 kits/automation/cold-email-personalization/orchestrate.js create mode 100644 kits/automation/cold-email-personalization/package-lock.json create mode 100644 kits/automation/cold-email-personalization/package.json create mode 100644 kits/automation/cold-email-personalization/postcss.config.mjs create mode 100644 kits/automation/cold-email-personalization/tsconfig.json diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0-.lamatic.dev/graphql`) | +| `LAMATIC_PROJECT_ID` | Project ID from Lamatic Settings | +| `LAMATIC_API_KEY` | API key from Lamatic Settings → API Keys | + +--- + +## Run locally + +```bash +cd kits/automation/cold-email-personalization +npm install +cp .env.example .env.local +# Edit .env.local with your values +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000). + +--- + +## Deploy (Vercel) + +1. Import this repository and set **Root Directory** to `kits/automation/cold-email-personalization`. +2. Add the same environment variables in the Vercel project settings. +3. Deploy. + +--- + +## Flow inputs (API payload) + +The app sends these keys to `executeWorkflow`: + +- `profile_data` — pasted profile / LinkedIn context +- `prospect_name`, `prospect_role`, `company_name` +- `product_description` — your student pitch +- `value_proposition` — why you’re a fit +- `call_to_action` — what you’re asking for + +**Outputs:** `subject_line`, `email_body`, `personalized_hook` (parsed from the API result or from `generatedResponse` JSON when needed). + +--- + +## Troubleshooting + +### Error: schema `type` / `properties` / `required` in the API result (no real email text) + +If `executeWorkflow` returns something like: + +```json +{ + "type": "object", + "properties": { + "subject_line": { "type": "string" }, + "email_body": { "type": "string" }, + "personalized_hook": { "type": "string" } + }, + "required": [...] +} +``` + +then the **API Response** node in Lamatic is emitting the **JSON Schema** only, not the **values** from **Generate Text**. + +**Fix in Lamatic Studio** + +1. Open the flow → **API Response** node → **Config** (and any **Mapping** / binding UI). +2. For each output field (`subject_line`, `email_body`, `personalized_hook`), bind the value to the **Generate Text** node — e.g. map from parsed LLM JSON or from `generatedResponse` (you may need a small parse/transform step in Studio if the UI requires it). +3. The schema defines the *shape*; each field must still have a *data source* from the LLM step. +4. **Save** and **redeploy** the flow. + +Until the live API returns real strings for those three keys (or a parseable `generatedResponse` blob), this app cannot show the email. + +--- + +## License + +MIT — see repository root [LICENSE](https://github.com/Lamatic/AgentKit/blob/main/LICENSE). diff --git a/kits/automation/cold-email-personalization/actions/orchestrate.ts b/kits/automation/cold-email-personalization/actions/orchestrate.ts new file mode 100644 index 00000000..ab9f9c19 --- /dev/null +++ b/kits/automation/cold-email-personalization/actions/orchestrate.ts @@ -0,0 +1,71 @@ +"use server" + +import { lamaticClient } from "@/lib/lamatic-client" +import { parseColdEmailResult } from "@/lib/parse-cold-email-result" +import { config } from "../orchestrate.js" + +export type ColdEmailInput = { + profile_data: string + prospect_name: string + prospect_role: string + company_name: string + product_description: string + value_proposition: string + call_to_action: string +} + +const flow = config.flows.coldEmail + +export async function personalizeColdEmail(input: ColdEmailInput): Promise<{ + success: boolean + data?: ReturnType + error?: string +}> { + try { + const workflowInput = Object.keys(flow.inputSchema).reduce( + (acc, key) => { + acc[key] = input[key as keyof ColdEmailInput] + return acc + }, + {} as Record, + ) + + if (!flow.workflowId) { + throw new Error("Workflow ID not configured (AUTOMATION_COLD_EMAIL).") + } + + const response = await lamaticClient.executeFlow(flow.workflowId, workflowInput) + + if (response.status === "error" || response.result == null) { + throw new Error( + response.message ?? "Workflow returned an error or empty result.", + ) + } + + if (process.env.NODE_ENV === "development") { + console.log( + "[cold-email] executeFlow raw response:", + JSON.stringify(response, null, 2).slice(0, 8000), + ) + } + + const data = parseColdEmailResult(response.result) + + return { success: true, data } + } catch (error) { + console.error("[cold-email] Error:", error) + + let errorMessage = "Unknown error occurred" + if (error instanceof Error) { + errorMessage = error.message + if (error.message.includes("fetch failed")) { + errorMessage = + "Network error: Unable to connect to Lamatic. Check your connection and try again." + } else if (error.message.includes("API key") || error.message.includes("401")) { + errorMessage = "Authentication error: Check LAMATIC_API_KEY and project settings." + } + } + + return { success: false, error: errorMessage } + } +} diff --git a/kits/automation/cold-email-personalization/app/globals.css b/kits/automation/cold-email-personalization/app/globals.css new file mode 100644 index 00000000..dc2aea17 --- /dev/null +++ b/kits/automation/cold-email-personalization/app/globals.css @@ -0,0 +1,125 @@ +@import 'tailwindcss'; +@import 'tw-animate-css'; + +@custom-variant dark (&:is(.dark *)); + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.145 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.145 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.985 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.269 0 0); + --input: oklch(0.269 0 0); + --ring: oklch(0.439 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(0.269 0 0); + --sidebar-ring: oklch(0.439 0 0); +} + +@theme inline { + --font-sans: 'Geist', 'Geist Fallback'; + --font-mono: 'Geist Mono', 'Geist Mono Fallback'; + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/kits/automation/cold-email-personalization/app/layout.tsx b/kits/automation/cold-email-personalization/app/layout.tsx new file mode 100644 index 00000000..4489638d --- /dev/null +++ b/kits/automation/cold-email-personalization/app/layout.tsx @@ -0,0 +1,45 @@ +import type { Metadata } from 'next' +import { Geist, Geist_Mono } from 'next/font/google' +import { Analytics } from '@vercel/analytics/next' +import './globals.css' + +const _geist = Geist({ subsets: ["latin"] }); +const _geistMono = Geist_Mono({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: 'v0 App', + description: 'Created with v0', + generator: 'v0.app', + icons: { + icon: [ + { + url: '/icon-light-32x32.png', + media: '(prefers-color-scheme: light)', + }, + { + url: '/icon-dark-32x32.png', + media: '(prefers-color-scheme: dark)', + }, + { + url: '/icon.svg', + type: 'image/svg+xml', + }, + ], + apple: '/apple-icon.png', + }, +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + {children} + + + + ) +} diff --git a/kits/automation/cold-email-personalization/app/page.tsx b/kits/automation/cold-email-personalization/app/page.tsx new file mode 100644 index 00000000..f649ff94 --- /dev/null +++ b/kits/automation/cold-email-personalization/app/page.tsx @@ -0,0 +1,305 @@ +"use client" + +import type React from "react" +import { useState } from "react" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Input } from "@/components/ui/input" +import { Card } from "@/components/ui/card" +import { Label } from "@/components/ui/label" +import { Loader2, Sparkles, Mail, Copy, Check, Home } from "lucide-react" +import { personalizeColdEmail } from "@/actions/orchestrate" +import { Header } from "@/components/header" + +const initialForm = { + profile_data: "", + prospect_name: "", + prospect_role: "", + company_name: "", + product_description: "", + value_proposition: "", + call_to_action: "", +} + +export default function ColdEmailPage() { + const [form, setForm] = useState(initialForm) + const [isLoading, setIsLoading] = useState(false) + const [result, setResult] = useState<{ + subject_line: string + email_body: string + personalized_hook: string + } | null>(null) + const [error, setError] = useState("") + const [copiedField, setCopiedField] = useState(null) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + const required = [ + "profile_data", + "prospect_name", + "company_name", + "product_description", + "value_proposition", + "call_to_action", + ] as const + for (const key of required) { + if (!form[key].trim()) { + setError(`Please fill in ${key.replace(/_/g, " ")}`) + return + } + } + + setIsLoading(true) + setError("") + setResult(null) + + try { + const response = await personalizeColdEmail(form) + if (response.success && response.data) { + setResult(response.data) + } else { + setError(response.error || "Generation failed") + } + } catch (err) { + setError(err instanceof Error ? err.message : "An error occurred") + } finally { + setIsLoading(false) + } + } + + const handleReset = () => { + setResult(null) + setForm(initialForm) + setError("") + setCopiedField(null) + } + + const copyText = async (label: string, text: string) => { + try { + await navigator.clipboard.writeText(text) + setCopiedField(label) + setTimeout(() => setCopiedField(null), 2000) + } catch { + console.error("Copy failed") + } + } + + return ( +
+
+ +
+ {!result && ( +
+
+
+

+ Cold Email Personalization +

+

+ Paste a LinkedIn-style profile and your context. Get a subject line, email body, and + personalization hook for engineering internship outreach. +

+
+ + +
+
+ +