From 770da45f30c5ce159f4d7e8c0490c8deeed499f1 Mon Sep 17 00:00:00 2001 From: sage106 <146715764+sage106@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:21:14 +0530 Subject: [PATCH 01/11] feat: Add home-maintenance-triage kit --- kits/home-maintenance-triage/README.md | 160 +++ kits/home-maintenance-triage/agent.md | 72 ++ .../home-maintenance-triage/apps/.env.example | 7 + kits/home-maintenance-triage/apps/.gitignore | 34 + .../apps/next.config.ts | 7 + .../apps/package-lock.json | 963 ++++++++++++++++++ .../home-maintenance-triage/apps/package.json | 22 + .../apps/src/app/api/triage/route.ts | 89 ++ .../apps/src/app/globals.css | 588 +++++++++++ .../apps/src/app/layout.tsx | 36 + .../apps/src/app/page.tsx | 423 ++++++++ .../apps/tsconfig.json | 21 + .../constitutions/default.md | 21 + .../flows/home-maintenance-triage.ts | 154 +++ .../home-maintenance-triage/lamatic.config.ts | 27 + .../home-maintenance-triage_generate-text.ts | 12 + ...maintenance-triage_generate-text_system.md | 34 + 17 files changed, 2670 insertions(+) create mode 100644 kits/home-maintenance-triage/README.md create mode 100644 kits/home-maintenance-triage/agent.md create mode 100644 kits/home-maintenance-triage/apps/.env.example create mode 100644 kits/home-maintenance-triage/apps/.gitignore create mode 100644 kits/home-maintenance-triage/apps/next.config.ts create mode 100644 kits/home-maintenance-triage/apps/package-lock.json create mode 100644 kits/home-maintenance-triage/apps/package.json create mode 100644 kits/home-maintenance-triage/apps/src/app/api/triage/route.ts create mode 100644 kits/home-maintenance-triage/apps/src/app/globals.css create mode 100644 kits/home-maintenance-triage/apps/src/app/layout.tsx create mode 100644 kits/home-maintenance-triage/apps/src/app/page.tsx create mode 100644 kits/home-maintenance-triage/apps/tsconfig.json create mode 100644 kits/home-maintenance-triage/constitutions/default.md create mode 100644 kits/home-maintenance-triage/flows/home-maintenance-triage.ts create mode 100644 kits/home-maintenance-triage/lamatic.config.ts create mode 100644 kits/home-maintenance-triage/model-configs/home-maintenance-triage_generate-text.ts create mode 100644 kits/home-maintenance-triage/prompts/home-maintenance-triage_generate-text_system.md diff --git a/kits/home-maintenance-triage/README.md b/kits/home-maintenance-triage/README.md new file mode 100644 index 000000000..26cc07ea9 --- /dev/null +++ b/kits/home-maintenance-triage/README.md @@ -0,0 +1,160 @@ +# Home Maintenance Triage Agent + +![Built with Lamatic](https://img.shields.io/badge/Built%20with-Lamatic-5B21B6?style=flat-square) +![agentkit-challenge](https://img.shields.io/badge/challenge-agentkit-F59E0B?style=flat-square) +![Type: Kit](https://img.shields.io/badge/type-kit-0EA5E9?style=flat-square) + +> Instantly diagnose any home problem with AI — know if it's an emergency, who to call, and what to do right now. + +--- + +## 1. The Problem + +Homeowners encounter unexpected issues constantly — a water stain, a sparking outlet, a strange smell from the furnace. The uncertainty about severity causes two equally bad outcomes: **ignoring something genuinely dangerous** (a slow gas leak, active flooding) or **panicking over something trivial** (a small nail hole, surface condensation). + +There's no fast, intelligent, always-available way to answer the three questions every homeowner immediately asks: +1. Is this an emergency? +2. Can I fix it myself? +3. Who do I call? + +--- + +## 2. What This Kit Does + +Given a **text description** of a home issue (and an optional photo URL), this agent returns a structured triage report: + +| Field | Description | +|-------|-------------| +| **Severity** | `low` / `moderate` / `high` / `emergency` | +| **Urgency** | Plain-language timeframe ("Act immediately" vs "Address within days") | +| **DIY Feasible** | Whether the homeowner can safely handle it themselves | +| **Professional Type** | Exactly which specialist to call (plumber, electrician, HVAC, etc.) | +| **Safe Next Steps** | 2–5 concrete, safe actions to take right now | +| **Do NOT Do** | Explicit list of dangerous self-repair attempts to avoid | +| **Reasoning** | Why the AI assessed it this way | +| **Disclaimer** | Always included — this is informational, not a professional inspection | + +### Built-in Safety Rules +- **Automatic emergency escalation** for: sparking wiring, gas smells, active flooding, visible structural failure, smoke +- **Never gives DIY instructions** for electrical, gas, or structural — always routes to a licensed professional +- **Defaults to caution** when the image or description is ambiguous + +--- + +## 3. How It Works + +``` +User Input (text + optional image URL) + ↓ +API Request (GraphQL trigger) + ↓ +Vision LLM (analyzes image + description with safety-first system prompt) + ↓ +Structured JSON Response + ↓ +Next.js UI renders urgency badge + action cards +``` + +One flow, one LLM node (vision-capable model), clean JSON output. + +--- + +## 4. Setup + +### Prerequisites +- [Lamatic.ai](https://lamatic.ai) account (free tier works) +- A vision-capable LLM model configured in Lamatic Studio (e.g. GPT-4o, Gemini 1.5 Pro, Claude 3.5 Sonnet) +- Node.js 18+ for the frontend + +### Step 1 — Set up the Lamatic Flow + +1. Log in to [studio.lamatic.ai](https://studio.lamatic.ai) +2. Create a new project and a new flow +3. Build the flow with these nodes: + - **API Request** node (trigger, accepts `imageUrl` and `issueDescription`) + - **Generate Text** (LLM) node — select a **vision-capable** model, paste the system prompt from `prompts/home-maintenance-triage_generate-text_system.md` + - **API Response** node — maps `output` to `{{LLMNode.output.generatedResponse}}` +4. Deploy the flow and copy the **Flow ID** + +### Step 2 — Configure the Frontend + +```bash +cd apps +cp .env.example .env.local +``` + +Fill in `.env.local`: + +```env +LAMATIC_PROJECT_ENDPOINT=https://your-project.lamatic.ai +LAMATIC_PROJECT_ID=your-project-id +LAMATIC_PROJECT_API_KEY=your-api-key +NEXT_PUBLIC_LAMATIC_FLOW_ID=your-flow-id +``` + +### Step 3 — Run Locally + +```bash +cd apps +npm install +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000) + +### Step 4 — Deploy to Vercel (optional) + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/sage106/AgentKit/tree/main/kits/home-maintenance-triage/apps) + +Set the same four environment variables in your Vercel project settings. + +--- + +## 5. Example + +**Input:** +```json +{ + "issueDescription": "There's a wall outlet that sparked when I plugged something in and I can smell something burning near it", + "imageUrl": "https://example.com/outlet-photo.jpg" +} +``` + +**Output:** +```json +{ + "category": "electrical", + "severity": "emergency", + "urgency": "Stop and act immediately", + "professionalNeeded": true, + "professionalType": "licensed electrician", + "safeNextSteps": [ + "Turn off power to that circuit at the breaker if you can safely reach it", + "Keep everyone away from the outlet", + "Do not plug anything else into nearby outlets", + "Call a licensed electrician immediately — do not wait" + ], + "doNotDo": [ + "Do not touch the outlet", + "Do not attempt to open or inspect the outlet yourself", + "Do not ignore the burning smell" + ], + "reasoning": "Sparking combined with a burning smell indicates an active electrical fault and potential fire hazard. This requires immediate professional attention.", + "disclaimer": "This is an informational assessment, not a professional inspection. For anything electrical, gas-related, or structural, or if you are unsure, contact a licensed professional." +} +``` + +--- + +## 6. Disclaimer + +This tool provides **informational triage only**. It is not a substitute for a licensed inspector, electrician, plumber, or structural engineer. Always defer to a professional for any electrical, gas, structural, or emergency situation. + +--- + +## 7. Assumptions & Tradeoffs + +- **Single flow**: Kept deliberately simple — one flow, one LLM call, clean JSON. No RAG or multi-step pipeline needed for triage. +- **Vision support**: The LLM node must be a vision-capable model. Text-only models will still work for description-only queries but won't analyze images. +- **No image storage**: Image URLs are passed directly to the vision model — no file uploads or storage required. +- **Caution-first defaults**: The system prompt instructs the model to always round up on severity when uncertain, ensuring user safety over false reassurance. diff --git a/kits/home-maintenance-triage/agent.md b/kits/home-maintenance-triage/agent.md new file mode 100644 index 000000000..a4aaf7aa1 --- /dev/null +++ b/kits/home-maintenance-triage/agent.md @@ -0,0 +1,72 @@ +# Home Maintenance Triage + +## Identity +Home Maintenance Triage is an AI agent that helps people understand how +serious a home problem is and what to do about it safely — before they call +(or avoid calling) a professional. It analyzes a photo of the issue together +with a short description and returns a structured assessment: category, +severity, urgency, whether a licensed professional is needed, and safe +immediate next steps. + +## Capabilities +Given an image URL and an issue description, the agent returns: +- **category** — what kind of problem this is (water damage, electrical, structural, mold, pest, cosmetic, other) +- **severity** — low / moderate / high / emergency +- **urgency** — a plain-language timeframe for action +- **professionalNeeded** — whether a licensed professional is required +- **professionalType** — which kind, if applicable +- **safeNextSteps** — concrete, safe actions the person can take right now +- **doNotDo** — things to explicitly avoid doing themselves +- **reasoning** — why the agent assessed it this way +- **disclaimer** — a standing note that this is informational, not a professional inspection + +## Boundaries +- This agent never gives confident DIY repair instructions for electrical, + gas, or structural issues — these always route to "call a professional." +- It defaults to the more cautious assessment whenever the photo or + description is ambiguous — it never downplays a potential hazard to seem + more helpful. +- It is not a substitute for a licensed inspector, electrician, or plumber, + and it says so in every response. +- It does not handle medical or personal-injury emergencies. + +## Example +**Input:** photo of a brown stain spreading across a ceiling, description: "noticed it a week ago, seems to be growing" +**Output (abbreviated):** +```json +{ + "category": "water damage", + "severity": "moderate", + "urgency": "Address within the next few days", + "professionalNeeded": true, + "professionalType": "plumber", + "safeNextSteps": [ + "Check the room/floor above for a leaking pipe or fixture", + "Place a container under the stain if it's actively dripping", + "Take photos to track whether the stain grows" + ], + "doNotDo": ["Do not cut into the ceiling to inspect it yourself"], + "reasoning": "Growing stain suggests an active, ongoing leak rather than a one-time incident.", + "disclaimer": "This is an informational assessment, not a professional inspection. For anything electrical, gas-related, or structural, or if you are unsure, contact a licensed professional." +} +``` + +**Input:** description: "wall outlet is sparking and I can smell something burning" +**Output (abbreviated):** +```json +{ + "category": "electrical", + "severity": "emergency", + "urgency": "Stop and act immediately", + "professionalNeeded": true, + "professionalType": "licensed electrician", + "safeNextSteps": [ + "Turn off power to that circuit at the breaker if you can safely reach it", + "Keep everyone away from the outlet", + "Call a licensed electrician immediately" + ], + "doNotDo": ["Do not touch the outlet", "Do not attempt to repair this yourself"], + "reasoning": "Sparking combined with a burning smell indicates an active electrical hazard and fire risk.", + "disclaimer": "This is an informational assessment, not a professional inspection. For anything electrical, gas-related, or structural, or if you are unsure, contact a licensed professional." +} +``` diff --git a/kits/home-maintenance-triage/apps/.env.example b/kits/home-maintenance-triage/apps/.env.example new file mode 100644 index 000000000..c806eea68 --- /dev/null +++ b/kits/home-maintenance-triage/apps/.env.example @@ -0,0 +1,7 @@ +# Lamatic credentials (from Settings → API Keys in Lamatic Studio) +LAMATIC_PROJECT_ENDPOINT= +LAMATIC_PROJECT_ID= +LAMATIC_PROJECT_API_KEY= + +# Your deployed flow ID (from Flow → Details Panel → Flow ID) +NEXT_PUBLIC_LAMATIC_FLOW_ID= diff --git a/kits/home-maintenance-triage/apps/.gitignore b/kits/home-maintenance-triage/apps/.gitignore new file mode 100644 index 000000000..0d83b4331 --- /dev/null +++ b/kits/home-maintenance-triage/apps/.gitignore @@ -0,0 +1,34 @@ +# dependencies +node_modules/ +.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/kits/home-maintenance-triage/apps/next.config.ts b/kits/home-maintenance-triage/apps/next.config.ts new file mode 100644 index 000000000..e9ffa3083 --- /dev/null +++ b/kits/home-maintenance-triage/apps/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/kits/home-maintenance-triage/apps/package-lock.json b/kits/home-maintenance-triage/apps/package-lock.json new file mode 100644 index 000000000..57f7b6be3 --- /dev/null +++ b/kits/home-maintenance-triage/apps/package-lock.json @@ -0,0 +1,963 @@ +{ + "name": "home-maintenance-triage", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "home-maintenance-triage", + "version": "0.1.0", + "dependencies": { + "next": "15.5.23", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "typescript": "^5" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@next/env": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.23.tgz", + "integrity": "sha512-Mv3Z9hVbFcPnoLevsZ6rnX1TBtyHb5E17yN7HTPDXSXxeNsGBjUFrdbjRXKKXIOhfth7/cg6Ay7PZ2UFawaWsQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.23.tgz", + "integrity": "sha512-SrEwOROH/rhA03F59hHtdhgtfZMWGzr5duDBWgRQt2rS3mJhqMKOcnNx6txOd0/i3E3D3uFKYFvyHsEiwQxzag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.23.tgz", + "integrity": "sha512-f0FpFbG2EhDCuptBGcfrLcYMDuQAhe6m1QA4VVfXFrIBoFXvXt/olGbBkYkloKlXQtmhuzvtdYyuu/6zf07GIg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.23.tgz", + "integrity": "sha512-WlNtfepUXKX2u2ZsJZ8c3c8+tJSRZqsYzoMwLOY72A8ucKCCgxgNhiePA3qzFYahVWrwcQd8jOeJmBinc+VFVQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.23.tgz", + "integrity": "sha512-W/6qKk7UG93mg14PmQC+2urt69MIdwTBLNQ6MJyeC4wOCIHCjz+VfgssvS1pK7mgYBtLC1g6VKNoHD9xB0WWGg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.23.tgz", + "integrity": "sha512-vzefI32mi6VMk96RaTAyxApgfGbiFzQBXVsekEjsDv1fr48mlABTWx0sUYhaYCBHWqCalxmz3DxbxFcbFvzNtw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.23.tgz", + "integrity": "sha512-qppK/3dTGOTI+aoWWBZc3DshFIhrzgL8guATlaN9V6M1QJxbkP/rhEZ22tdICsQ/2WWXopMZ2Jokzj2u3uKY3Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.23.tgz", + "integrity": "sha512-Wc29KFOdT7XBcII3Vtmw7aoU8Uk3Mes/FNJfhFeSHdYBFJWMcR/DsI8U9BCPUhq/uycsUVuqSKGthW15tLsigA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.23.tgz", + "integrity": "sha512-/C7wRW4fa9s/PKA18zGPPpVmx8ycgVpP8yOxro4gzGTzjPJdscbAP3ODeFvgiIovxD176Z2J/SXO9t8PJKHLeQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "15.5.23", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.23.tgz", + "integrity": "sha512-Gvd2WKgvxIXCGotxcI1im/Uf3rS3J3oZGw0g/uskg6AVBZhyE3aAbujkYWzS3xLmEPEtTLfkaVQUKK0KMTSIkA==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.23", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.23", + "@next/swc-darwin-x64": "15.5.23", + "@next/swc-linux-arm64-gnu": "15.5.23", + "@next/swc-linux-arm64-musl": "15.5.23", + "@next/swc-linux-x64-gnu": "15.5.23", + "@next/swc-linux-x64-musl": "15.5.23", + "@next/swc-win32-arm64-msvc": "15.5.23", + "@next/swc-win32-x64-msvc": "15.5.23", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/kits/home-maintenance-triage/apps/package.json b/kits/home-maintenance-triage/apps/package.json new file mode 100644 index 000000000..39db05bec --- /dev/null +++ b/kits/home-maintenance-triage/apps/package.json @@ -0,0 +1,22 @@ +{ + "name": "home-maintenance-triage", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "15.5.23", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "typescript": "^5" + } +} diff --git a/kits/home-maintenance-triage/apps/src/app/api/triage/route.ts b/kits/home-maintenance-triage/apps/src/app/api/triage/route.ts new file mode 100644 index 000000000..c3d549c7f --- /dev/null +++ b/kits/home-maintenance-triage/apps/src/app/api/triage/route.ts @@ -0,0 +1,89 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { issueDescription, imageUrl, homeType, issueLocation } = body; + + if (!issueDescription || typeof issueDescription !== "string") { + return NextResponse.json( + { error: "issueDescription is required" }, + { status: 400 } + ); + } + + const endpoint = process.env.LAMATIC_PROJECT_ENDPOINT; + const projectId = process.env.LAMATIC_PROJECT_ID; + const apiKey = process.env.LAMATIC_PROJECT_API_KEY; + const flowId = process.env.NEXT_PUBLIC_LAMATIC_FLOW_ID; + + if (!endpoint || !projectId || !apiKey || !flowId) { + return NextResponse.json( + { error: "Lamatic credentials are not configured. Check your .env file." }, + { status: 500 } + ); + } + + const query = ` + query RunFlow($flowId: String!, $payload: JSON!) { + runFlow(flowId: $flowId, payload: $payload) { + output + } + } + `; + + const payload: Record = { issueDescription }; + if (imageUrl) payload.imageUrl = imageUrl; + if (homeType) payload.homeType = homeType; + if (issueLocation) payload.issueLocation = issueLocation; + + const response = await fetch(`${endpoint}/api/flow`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + "x-project-id": projectId, + }, + body: JSON.stringify({ + query, + variables: { flowId, payload }, + }), + }); + + if (!response.ok) { + const text = await response.text(); + console.error("Lamatic API error:", text); + return NextResponse.json( + { error: "Failed to reach Lamatic API. Check your endpoint and credentials." }, + { status: 502 } + ); + } + + const data = await response.json(); + const rawOutput: string | undefined = data?.data?.runFlow?.output; + + if (!rawOutput) { + return NextResponse.json( + { error: "No output returned from the flow. Ensure it is deployed." }, + { status: 500 } + ); + } + + // Parse the JSON string returned by the LLM node + let parsed; + try { + parsed = JSON.parse(rawOutput); + } catch { + // Return raw if it's not valid JSON + parsed = { raw: rawOutput }; + } + + return NextResponse.json({ result: parsed }); + } catch (err) { + console.error("Triage API error:", err); + return NextResponse.json( + { error: "An unexpected error occurred." }, + { status: 500 } + ); + } +} diff --git a/kits/home-maintenance-triage/apps/src/app/globals.css b/kits/home-maintenance-triage/apps/src/app/globals.css new file mode 100644 index 000000000..44cc7e591 --- /dev/null +++ b/kits/home-maintenance-triage/apps/src/app/globals.css @@ -0,0 +1,588 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); + +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + --bg-primary: #0a0a0f; + --bg-secondary: #12121a; + --bg-card: #1a1a26; + --bg-card-hover: #1e1e2e; + --border: rgba(255, 255, 255, 0.08); + --border-hover: rgba(255, 255, 255, 0.15); + + --text-primary: #f0f0ff; + --text-secondary: #9090b0; + --text-muted: #5a5a7a; + + --accent-amber: #f59e0b; + --accent-amber-light: #fbbf24; + --accent-amber-dim: rgba(245, 158, 11, 0.15); + + --emergency: #ef4444; + --emergency-dim: rgba(239, 68, 68, 0.15); + --urgent: #f97316; + --urgent-dim: rgba(249, 115, 22, 0.15); + --soon: #eab308; + --soon-dim: rgba(234, 179, 8, 0.15); + --low: #22c55e; + --low-dim: rgba(34, 197, 94, 0.15); + + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 24px; + + --shadow-card: 0 4px 24px rgba(0, 0, 0, 0.4); + --shadow-glow: 0 0 40px rgba(245, 158, 11, 0.08); + + --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +html, body { + min-height: 100vh; + background-color: var(--bg-primary); + color: var(--text-primary); + font-family: var(--font-inter), 'Inter', system-ui, sans-serif; + line-height: 1.6; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ─── Page Layout ─────────────────────────────── */ +.page { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* ─── Header ──────────────────────────────────── */ +.header { + padding: 1.25rem 2rem; + border-bottom: 1px solid var(--border); + background: rgba(10, 10, 15, 0.8); + backdrop-filter: blur(12px); + position: sticky; + top: 0; + z-index: 100; +} + +.header-inner { + max-width: 900px; + margin: 0 auto; + display: flex; + align-items: center; + gap: 0.75rem; +} + +.header-icon { + font-size: 1.5rem; + line-height: 1; +} + +.header-title { + font-size: 1.1rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.02em; +} + +.header-badge { + margin-left: auto; + font-size: 0.7rem; + font-weight: 600; + color: var(--accent-amber); + background: var(--accent-amber-dim); + border: 1px solid rgba(245, 158, 11, 0.3); + padding: 0.25rem 0.6rem; + border-radius: 100px; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +/* ─── Main Content ─────────────────────────────── */ +.main { + flex: 1; + max-width: 900px; + margin: 0 auto; + width: 100%; + padding: 3rem 2rem 4rem; +} + +/* ─── Hero ────────────────────────────────────── */ +.hero { + text-align: center; + margin-bottom: 3rem; +} + +.hero h1 { + font-size: clamp(2rem, 5vw, 3rem); + font-weight: 800; + letter-spacing: -0.04em; + line-height: 1.1; + margin-bottom: 1rem; + background: linear-gradient(135deg, #f0f0ff 0%, var(--accent-amber-light) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero p { + font-size: 1.05rem; + color: var(--text-secondary); + max-width: 520px; + margin: 0 auto; + line-height: 1.7; +} + +/* ─── Input Card ──────────────────────────────── */ +.input-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + padding: 2rem; + box-shadow: var(--shadow-card); + margin-bottom: 2rem; + transition: border-color var(--transition); +} + +.input-card:focus-within { + border-color: var(--border-hover); +} + +.form-group { + margin-bottom: 1.25rem; +} + +.form-label { + display: block; + font-size: 0.8rem; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.07em; + margin-bottom: 0.5rem; +} + +.form-label span { + color: var(--text-muted); + font-weight: 400; + text-transform: none; + letter-spacing: 0; +} + +textarea.form-input { + width: 100%; + min-height: 120px; + resize: vertical; + font-family: inherit; +} + +.form-input { + width: 100%; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 0.875rem 1rem; + color: var(--text-primary); + font-size: 0.95rem; + font-family: inherit; + line-height: 1.5; + transition: border-color var(--transition), box-shadow var(--transition); + outline: none; +} + +.form-input::placeholder { + color: var(--text-muted); +} + +.form-input:focus { + border-color: rgba(245, 158, 11, 0.5); + box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.08); +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} + +/* ─── Submit Button ───────────────────────────── */ +.submit-btn { + width: 100%; + padding: 1rem; + background: linear-gradient(135deg, var(--accent-amber) 0%, #d97706 100%); + color: #0a0a0f; + border: none; + border-radius: var(--radius-md); + font-size: 1rem; + font-weight: 700; + font-family: inherit; + cursor: pointer; + transition: opacity var(--transition), transform var(--transition), box-shadow var(--transition); + letter-spacing: -0.01em; + margin-top: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.submit-btn:hover:not(:disabled) { + opacity: 0.92; + transform: translateY(-1px); + box-shadow: 0 8px 24px rgba(245, 158, 11, 0.3); +} + +.submit-btn:active:not(:disabled) { + transform: translateY(0); +} + +.submit-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* ─── Loading ────────────────────────────────── */ +.spinner { + width: 18px; + height: 18px; + border: 2.5px solid rgba(0, 0, 0, 0.3); + border-top-color: #0a0a0f; + border-radius: 50%; + animation: spin 0.7s linear infinite; + flex-shrink: 0; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* ─── Error ──────────────────────────────────── */ +.error-box { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.3); + border-radius: var(--radius-md); + padding: 1rem 1.25rem; + color: #fca5a5; + font-size: 0.9rem; + margin-bottom: 1.5rem; + display: flex; + align-items: flex-start; + gap: 0.5rem; +} + +/* ─── Result Card ─────────────────────────────── */ +.result-section { + animation: fadeSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +@keyframes fadeSlideIn { + from { + opacity: 0; + transform: translateY(16px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.result-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1.5rem; + flex-wrap: wrap; +} + +.result-header h2 { + font-size: 1.3rem; + font-weight: 700; + letter-spacing: -0.02em; + flex: 1; +} + +/* ─── Urgency Badge ───────────────────────────── */ +.urgency-badge { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.4rem 0.9rem; + border-radius: 100px; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; + border: 1px solid; +} + +.urgency-emergency { + color: var(--emergency); + background: var(--emergency-dim); + border-color: rgba(239, 68, 68, 0.35); +} + +.urgency-high { + color: var(--urgent); + background: var(--urgent-dim); + border-color: rgba(249, 115, 22, 0.35); +} + +.urgency-moderate { + color: var(--soon); + background: var(--soon-dim); + border-color: rgba(234, 179, 8, 0.35); +} + +.urgency-low { + color: var(--low); + background: var(--low-dim); + border-color: rgba(34, 197, 94, 0.35); +} + +/* ─── Info Grid ──────────────────────────────── */ +.info-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-bottom: 1.5rem; +} + +.info-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 1.25rem; + transition: border-color var(--transition), transform var(--transition); +} + +.info-card:hover { + border-color: var(--border-hover); + transform: translateY(-1px); +} + +.info-card-icon { + font-size: 1.5rem; + margin-bottom: 0.5rem; + line-height: 1; +} + +.info-card-label { + font-size: 0.7rem; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 0.25rem; +} + +.info-card-value { + font-size: 0.95rem; + font-weight: 600; + color: var(--text-primary); + line-height: 1.3; +} + +.info-card-value.diy-yes { + color: var(--low); +} + +.info-card-value.diy-no { + color: var(--urgent); +} + +/* ─── List Cards ─────────────────────────────── */ +.list-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 1.25rem 1.5rem; + margin-bottom: 1rem; +} + +.list-card-header { + display: flex; + align-items: center; + gap: 0.6rem; + margin-bottom: 0.875rem; +} + +.list-card-icon { + font-size: 1.1rem; + line-height: 1; +} + +.list-card-title { + font-size: 0.85rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.01em; +} + +.list-card ul { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.list-card li { + font-size: 0.9rem; + color: var(--text-secondary); + padding-left: 1.25rem; + position: relative; + line-height: 1.5; +} + +.list-card li::before { + content: '→'; + position: absolute; + left: 0; + color: var(--accent-amber); + font-size: 0.8rem; +} + +.list-card.danger li::before { + color: var(--emergency); + content: '✕'; +} + +/* ─── Reasoning Box ──────────────────────────── */ +.reasoning-box { + background: var(--bg-card); + border: 1px solid var(--border); + border-left: 3px solid var(--accent-amber); + border-radius: var(--radius-md); + padding: 1rem 1.25rem; + margin-bottom: 1rem; +} + +.reasoning-label { + font-size: 0.7rem; + font-weight: 700; + color: var(--accent-amber); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 0.35rem; +} + +.reasoning-text { + font-size: 0.9rem; + color: var(--text-secondary); + line-height: 1.6; +} + +/* ─── Disclaimer ────────────────────────────── */ +.disclaimer-box { + background: rgba(255,255,255,0.02); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 0.875rem 1.25rem; + display: flex; + align-items: flex-start; + gap: 0.5rem; +} + +.disclaimer-icon { + font-size: 0.85rem; + margin-top: 0.05rem; + flex-shrink: 0; + color: var(--text-muted); +} + +.disclaimer-text { + font-size: 0.78rem; + color: var(--text-muted); + line-height: 1.5; +} + +/* ─── Examples ───────────────────────────────── */ +.examples-section { + margin-bottom: 2.5rem; +} + +.examples-label { + font-size: 0.75rem; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 0.75rem; + display: block; +} + +.examples-grid { + display: flex; + gap: 0.625rem; + flex-wrap: wrap; +} + +.example-chip { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 100px; + padding: 0.4rem 0.875rem; + font-size: 0.82rem; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition); + font-family: inherit; +} + +.example-chip:hover { + border-color: rgba(245, 158, 11, 0.4); + color: var(--accent-amber-light); + background: var(--accent-amber-dim); +} + +/* ─── Footer ─────────────────────────────────── */ +.footer { + text-align: center; + padding: 1.5rem 2rem; + border-top: 1px solid var(--border); +} + +.footer p { + font-size: 0.78rem; + color: var(--text-muted); +} + +.footer a { + color: var(--accent-amber); + text-decoration: none; +} + +.footer a:hover { + text-decoration: underline; +} + +/* ─── Responsive ─────────────────────────────── */ +@media (max-width: 640px) { + .main { + padding: 2rem 1rem 3rem; + } + + .header { + padding: 1rem; + } + + .input-card { + padding: 1.25rem; + } + + .form-row { + grid-template-columns: 1fr; + } + + .info-grid { + grid-template-columns: 1fr; + } + + .hero h1 { + font-size: 1.8rem; + } +} diff --git a/kits/home-maintenance-triage/apps/src/app/layout.tsx b/kits/home-maintenance-triage/apps/src/app/layout.tsx new file mode 100644 index 000000000..873d28acd --- /dev/null +++ b/kits/home-maintenance-triage/apps/src/app/layout.tsx @@ -0,0 +1,36 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); + +export const metadata: Metadata = { + title: "Home Maintenance Triage | AI-Powered Home Issue Diagnosis", + description: + "Describe any home issue and get an instant AI triage: urgency level, DIY feasibility, safety hazards, which professional to call, and immediate action steps.", + keywords: [ + "home maintenance", + "AI triage", + "home repair", + "DIY", + "emergency", + "plumber", + "electrician", + ], + openGraph: { + title: "Home Maintenance Triage Agent", + description: + "Instantly diagnose any home problem with AI — know if it's an emergency, who to call, and what to do right now.", + type: "website", + }, +}; + +export default function RootLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/kits/home-maintenance-triage/apps/src/app/page.tsx b/kits/home-maintenance-triage/apps/src/app/page.tsx new file mode 100644 index 000000000..b6a4c9298 --- /dev/null +++ b/kits/home-maintenance-triage/apps/src/app/page.tsx @@ -0,0 +1,423 @@ +"use client"; + +import { useState, useRef } from "react"; + +// ─── Types ──────────────────────────────────────────────────────────────────── + +interface TriageResult { + category?: string; + severity?: "low" | "moderate" | "high" | "emergency"; + urgency?: string; + professionalNeeded?: boolean; + professionalType?: string | null; + safeNextSteps?: string[]; + doNotDo?: string[]; + reasoning?: string; + disclaimer?: string; + raw?: string; +} + +// ─── Constants ──────────────────────────────────────────────────────────────── + +const EXAMPLES = [ + "My ceiling has a brown water stain that's been slowly growing for a week", + "Wall outlet sparked and I can smell something burning", + "There's a strong smell of rotten eggs near the stove", + "My AC unit is making a loud grinding noise and blowing warm air", + "I noticed black mold spots in the bathroom corner behind the toilet", + "My toilet keeps running and doesn't stop after flushing", +]; + +const SEVERITY_CONFIG = { + emergency: { + label: "🚨 Emergency", + className: "urgency-emergency", + }, + high: { + label: "🔴 Urgent", + className: "urgency-high", + }, + moderate: { + label: "🟡 Soon", + className: "urgency-moderate", + }, + low: { + label: "🟢 Low", + className: "urgency-low", + }, +}; + +// ─── Main Page ──────────────────────────────────────────────────────────────── + +export default function HomePage() { + const [issueDescription, setIssueDescription] = useState(""); + const [imageUrl, setImageUrl] = useState(""); + const [homeType, setHomeType] = useState(""); + const [issueLocation, setIssueLocation] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + const resultRef = useRef(null); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (!issueDescription.trim()) return; + + setLoading(true); + setError(null); + setResult(null); + + try { + const res = await fetch("/api/triage", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + issueDescription: issueDescription.trim(), + imageUrl: imageUrl.trim() || undefined, + homeType: homeType || undefined, + issueLocation: issueLocation.trim() || undefined, + }), + }); + + const data = await res.json(); + + if (!res.ok || data.error) { + setError(data.error ?? "Something went wrong. Please try again."); + } else { + setResult(data.result); + setTimeout( + () => + resultRef.current?.scrollIntoView({ + behavior: "smooth", + block: "start", + }), + 100 + ); + } + } catch { + setError("Network error. Please check your connection and try again."); + } finally { + setLoading(false); + } + } + + function fillExample(example: string) { + setIssueDescription(example); + setResult(null); + setError(null); + } + + const severityCfg = result?.severity + ? SEVERITY_CONFIG[result.severity] ?? SEVERITY_CONFIG.low + : null; + + return ( +
+ {/* ── Header ── */} +
+
+ 🏠 + Home Maintenance Triage + Powered by Lamatic AI +
+
+ + {/* ── Main ── */} +
+ {/* Hero */} +
+

Diagnose Any Home Issue Instantly

+

+ Describe what's wrong and our AI tells you if it's an + emergency, who to call, and exactly what to do right now. +

+
+ + {/* Example chips */} +
+ Try an example +
+ {EXAMPLES.map((ex) => ( + + ))} +
+
+ + {/* Input Form */} +
+
+
+ +