From f7d0e42290d969255e8c568980de1ed42e06e66d Mon Sep 17 00:00:00 2001 From: Arda Ceylan Date: Sun, 31 May 2026 21:41:46 +0200 Subject: [PATCH 01/10] feat: implement review-analyzer chrome extension kit --- kits/review-analyzer/README.md | 84 + kits/review-analyzer/agent.md | 58 + kits/review-analyzer/apps/.env.example | 4 + kits/review-analyzer/apps/.gitignore | 27 + kits/review-analyzer/apps/.npmrc | 1 + kits/review-analyzer/apps/README.md | 155 + .../apps/actions/orchestrate.ts | 57 + kits/review-analyzer/apps/app/globals.css | 136 + kits/review-analyzer/apps/app/layout.tsx | 32 + kits/review-analyzer/apps/app/page.tsx | 157 + kits/review-analyzer/apps/app/popup/page.tsx | 221 + kits/review-analyzer/apps/components.json | 21 + .../apps/components/header.tsx | 41 + .../apps/components/theme-provider.tsx | 11 + .../apps/components/ui/accordion.tsx | 66 + .../apps/components/ui/alert-dialog.tsx | 157 + .../apps/components/ui/alert.tsx | 66 + .../apps/components/ui/aspect-ratio.tsx | 11 + .../apps/components/ui/avatar.tsx | 53 + .../apps/components/ui/badge.tsx | 46 + .../apps/components/ui/breadcrumb.tsx | 109 + .../apps/components/ui/button-group.tsx | 83 + .../apps/components/ui/button.tsx | 60 + .../apps/components/ui/calendar.tsx | 213 + .../apps/components/ui/card.tsx | 92 + .../apps/components/ui/carousel.tsx | 247 + .../apps/components/ui/chart.tsx | 353 ++ .../apps/components/ui/checkbox.tsx | 32 + .../apps/components/ui/collapsible.tsx | 33 + .../apps/components/ui/command.tsx | 184 + .../apps/components/ui/context-menu.tsx | 252 + .../apps/components/ui/dialog.tsx | 143 + .../apps/components/ui/drawer.tsx | 135 + .../apps/components/ui/dropdown-menu.tsx | 257 + .../apps/components/ui/empty.tsx | 104 + .../apps/components/ui/field.tsx | 244 + .../apps/components/ui/form.tsx | 167 + .../apps/components/ui/hover-card.tsx | 44 + .../apps/components/ui/input-group.tsx | 169 + .../apps/components/ui/input-otp.tsx | 77 + .../apps/components/ui/input.tsx | 21 + .../apps/components/ui/item.tsx | 193 + .../apps/components/ui/kbd.tsx | 28 + .../apps/components/ui/label.tsx | 24 + .../apps/components/ui/menubar.tsx | 276 + .../apps/components/ui/navigation-menu.tsx | 166 + .../apps/components/ui/pagination.tsx | 127 + .../apps/components/ui/popover.tsx | 48 + .../apps/components/ui/progress.tsx | 31 + .../apps/components/ui/radio-group.tsx | 45 + .../apps/components/ui/resizable.tsx | 56 + .../apps/components/ui/scroll-area.tsx | 58 + .../apps/components/ui/select.tsx | 185 + .../apps/components/ui/separator.tsx | 28 + .../apps/components/ui/sheet.tsx | 139 + .../apps/components/ui/sidebar.tsx | 726 +++ .../apps/components/ui/skeleton.tsx | 13 + .../apps/components/ui/slider.tsx | 63 + .../apps/components/ui/sonner.tsx | 25 + .../apps/components/ui/spinner.tsx | 16 + .../apps/components/ui/switch.tsx | 31 + .../apps/components/ui/table.tsx | 116 + .../apps/components/ui/tabs.tsx | 66 + .../apps/components/ui/textarea.tsx | 18 + .../apps/components/ui/toast.tsx | 129 + .../apps/components/ui/toaster.tsx | 35 + .../apps/components/ui/toggle-group.tsx | 73 + .../apps/components/ui/toggle.tsx | 47 + .../apps/components/ui/tooltip.tsx | 59 + .../apps/components/ui/use-mobile.tsx | 19 + .../apps/extension/background.js | 12 + .../review-analyzer/apps/extension/content.js | 60 + .../apps/extension/manifest.json | 28 + .../review-analyzer/apps/extension/popup.html | 203 + kits/review-analyzer/apps/extension/popup.js | 116 + kits/review-analyzer/apps/hooks/use-mobile.ts | 19 + kits/review-analyzer/apps/hooks/use-toast.ts | 191 + .../apps/lib/lamatic-client.ts | 20 + kits/review-analyzer/apps/lib/utils.ts | 6 + kits/review-analyzer/apps/next.config.mjs | 11 + kits/review-analyzer/apps/orchestrate.js | 25 + kits/review-analyzer/apps/package-lock.json | 5542 +++++++++++++++++ kits/review-analyzer/apps/package.json | 82 + kits/review-analyzer/apps/postcss.config.mjs | 8 + .../apps/public/apple-icon.png | Bin 0 -> 2626 bytes .../apps/public/icon-dark-32x32.png | Bin 0 -> 585 bytes .../apps/public/icon-light-32x32.png | Bin 0 -> 1265 bytes kits/review-analyzer/apps/public/icon.svg | 26 + .../apps/public/lamatic-logo.png | Bin 0 -> 8777 bytes .../apps/public/placeholder-logo.png | Bin 0 -> 568 bytes .../apps/public/placeholder-logo.svg | 1 + .../apps/public/placeholder.svg | 1 + kits/review-analyzer/apps/tsconfig.json | 43 + kits/review-analyzer/config.json | 21 + kits/review-analyzer/constitutions/default.md | 24 + kits/review-analyzer/flows/review-analyzer.ts | 154 + kits/review-analyzer/lamatic.config.ts | 21 + .../model-configs/review-analyzer_llm-node.ts | 5 + .../review-analyzer_llm-node_system.md | 22 + .../prompts/review-analyzer_llm-node_user.md | 7 + review-analyzer-plan.md | 130 + review-analyzer-walkthrough.md | 85 + 102 files changed, 14126 insertions(+) create mode 100644 kits/review-analyzer/README.md create mode 100644 kits/review-analyzer/agent.md create mode 100644 kits/review-analyzer/apps/.env.example create mode 100644 kits/review-analyzer/apps/.gitignore create mode 100644 kits/review-analyzer/apps/.npmrc create mode 100644 kits/review-analyzer/apps/README.md create mode 100644 kits/review-analyzer/apps/actions/orchestrate.ts create mode 100644 kits/review-analyzer/apps/app/globals.css create mode 100644 kits/review-analyzer/apps/app/layout.tsx create mode 100644 kits/review-analyzer/apps/app/page.tsx create mode 100644 kits/review-analyzer/apps/app/popup/page.tsx create mode 100644 kits/review-analyzer/apps/components.json create mode 100644 kits/review-analyzer/apps/components/header.tsx create mode 100644 kits/review-analyzer/apps/components/theme-provider.tsx create mode 100644 kits/review-analyzer/apps/components/ui/accordion.tsx create mode 100644 kits/review-analyzer/apps/components/ui/alert-dialog.tsx create mode 100644 kits/review-analyzer/apps/components/ui/alert.tsx create mode 100644 kits/review-analyzer/apps/components/ui/aspect-ratio.tsx create mode 100644 kits/review-analyzer/apps/components/ui/avatar.tsx create mode 100644 kits/review-analyzer/apps/components/ui/badge.tsx create mode 100644 kits/review-analyzer/apps/components/ui/breadcrumb.tsx create mode 100644 kits/review-analyzer/apps/components/ui/button-group.tsx create mode 100644 kits/review-analyzer/apps/components/ui/button.tsx create mode 100644 kits/review-analyzer/apps/components/ui/calendar.tsx create mode 100644 kits/review-analyzer/apps/components/ui/card.tsx create mode 100644 kits/review-analyzer/apps/components/ui/carousel.tsx create mode 100644 kits/review-analyzer/apps/components/ui/chart.tsx create mode 100644 kits/review-analyzer/apps/components/ui/checkbox.tsx create mode 100644 kits/review-analyzer/apps/components/ui/collapsible.tsx create mode 100644 kits/review-analyzer/apps/components/ui/command.tsx create mode 100644 kits/review-analyzer/apps/components/ui/context-menu.tsx create mode 100644 kits/review-analyzer/apps/components/ui/dialog.tsx create mode 100644 kits/review-analyzer/apps/components/ui/drawer.tsx create mode 100644 kits/review-analyzer/apps/components/ui/dropdown-menu.tsx create mode 100644 kits/review-analyzer/apps/components/ui/empty.tsx create mode 100644 kits/review-analyzer/apps/components/ui/field.tsx create mode 100644 kits/review-analyzer/apps/components/ui/form.tsx create mode 100644 kits/review-analyzer/apps/components/ui/hover-card.tsx create mode 100644 kits/review-analyzer/apps/components/ui/input-group.tsx create mode 100644 kits/review-analyzer/apps/components/ui/input-otp.tsx create mode 100644 kits/review-analyzer/apps/components/ui/input.tsx create mode 100644 kits/review-analyzer/apps/components/ui/item.tsx create mode 100644 kits/review-analyzer/apps/components/ui/kbd.tsx create mode 100644 kits/review-analyzer/apps/components/ui/label.tsx create mode 100644 kits/review-analyzer/apps/components/ui/menubar.tsx create mode 100644 kits/review-analyzer/apps/components/ui/navigation-menu.tsx create mode 100644 kits/review-analyzer/apps/components/ui/pagination.tsx create mode 100644 kits/review-analyzer/apps/components/ui/popover.tsx create mode 100644 kits/review-analyzer/apps/components/ui/progress.tsx create mode 100644 kits/review-analyzer/apps/components/ui/radio-group.tsx create mode 100644 kits/review-analyzer/apps/components/ui/resizable.tsx create mode 100644 kits/review-analyzer/apps/components/ui/scroll-area.tsx create mode 100644 kits/review-analyzer/apps/components/ui/select.tsx create mode 100644 kits/review-analyzer/apps/components/ui/separator.tsx create mode 100644 kits/review-analyzer/apps/components/ui/sheet.tsx create mode 100644 kits/review-analyzer/apps/components/ui/sidebar.tsx create mode 100644 kits/review-analyzer/apps/components/ui/skeleton.tsx create mode 100644 kits/review-analyzer/apps/components/ui/slider.tsx create mode 100644 kits/review-analyzer/apps/components/ui/sonner.tsx create mode 100644 kits/review-analyzer/apps/components/ui/spinner.tsx create mode 100644 kits/review-analyzer/apps/components/ui/switch.tsx create mode 100644 kits/review-analyzer/apps/components/ui/table.tsx create mode 100644 kits/review-analyzer/apps/components/ui/tabs.tsx create mode 100644 kits/review-analyzer/apps/components/ui/textarea.tsx create mode 100644 kits/review-analyzer/apps/components/ui/toast.tsx create mode 100644 kits/review-analyzer/apps/components/ui/toaster.tsx create mode 100644 kits/review-analyzer/apps/components/ui/toggle-group.tsx create mode 100644 kits/review-analyzer/apps/components/ui/toggle.tsx create mode 100644 kits/review-analyzer/apps/components/ui/tooltip.tsx create mode 100644 kits/review-analyzer/apps/components/ui/use-mobile.tsx create mode 100644 kits/review-analyzer/apps/extension/background.js create mode 100644 kits/review-analyzer/apps/extension/content.js create mode 100644 kits/review-analyzer/apps/extension/manifest.json create mode 100644 kits/review-analyzer/apps/extension/popup.html create mode 100644 kits/review-analyzer/apps/extension/popup.js create mode 100644 kits/review-analyzer/apps/hooks/use-mobile.ts create mode 100644 kits/review-analyzer/apps/hooks/use-toast.ts create mode 100644 kits/review-analyzer/apps/lib/lamatic-client.ts create mode 100644 kits/review-analyzer/apps/lib/utils.ts create mode 100644 kits/review-analyzer/apps/next.config.mjs create mode 100644 kits/review-analyzer/apps/orchestrate.js create mode 100644 kits/review-analyzer/apps/package-lock.json create mode 100644 kits/review-analyzer/apps/package.json create mode 100644 kits/review-analyzer/apps/postcss.config.mjs create mode 100644 kits/review-analyzer/apps/public/apple-icon.png create mode 100644 kits/review-analyzer/apps/public/icon-dark-32x32.png create mode 100644 kits/review-analyzer/apps/public/icon-light-32x32.png create mode 100644 kits/review-analyzer/apps/public/icon.svg create mode 100644 kits/review-analyzer/apps/public/lamatic-logo.png create mode 100644 kits/review-analyzer/apps/public/placeholder-logo.png create mode 100644 kits/review-analyzer/apps/public/placeholder-logo.svg create mode 100644 kits/review-analyzer/apps/public/placeholder.svg create mode 100644 kits/review-analyzer/apps/tsconfig.json create mode 100644 kits/review-analyzer/config.json create mode 100644 kits/review-analyzer/constitutions/default.md create mode 100644 kits/review-analyzer/flows/review-analyzer.ts create mode 100644 kits/review-analyzer/lamatic.config.ts create mode 100644 kits/review-analyzer/model-configs/review-analyzer_llm-node.ts create mode 100644 kits/review-analyzer/prompts/review-analyzer_llm-node_system.md create mode 100644 kits/review-analyzer/prompts/review-analyzer_llm-node_user.md create mode 100644 review-analyzer-plan.md create mode 100644 review-analyzer-walkthrough.md diff --git a/kits/review-analyzer/README.md b/kits/review-analyzer/README.md new file mode 100644 index 00000000..976d5e88 --- /dev/null +++ b/kits/review-analyzer/README.md @@ -0,0 +1,84 @@ +# Review Analyzer Kit + +An AI-powered Chrome Extension that scrapes product reviews from e-commerce sites (like Amazon) and uses [Lamatic.ai](https://lamatic.ai) to synthesize customer consensus, extract key pros/cons, and calculate a Trust Score to identify fake or low-effort reviews. + +## 🚀 Overview +Review Analyzer is built with Next.js and Tailwind CSS, communicating with a Lamatic AI flow backend. By loading the Chrome Extension, users can trigger review scraping with a single click. The scraped data is securely passed to a Next.js server, which runs the reasoning agent workflow on Lamatic AI to generate the summary dashboard. + +## 🛠️ Folder Structure +``` +kits/review-analyzer/ +├── lamatic.config.ts # Metadata definition +├── config.json # Legacy config to pass CI validation +├── agent.md # Agent capability & setup document +├── README.md # This guide +├── constitutions/ +│ └── default.md # LLM behavior guide +├── flows/ +│ └── review-analyzer.ts # Flow config exported from Lamatic Studio +└── apps/ # Next.js web application & Chrome Extension + ├── package.json # Next.js dependencies + ├── app/ # Next.js page router (iframe popup view) + ├── lib/ # Library files (lamatic client) + ├── actions/ # Server Actions calling Lamatic flow + └── extension/ # Chrome Extension (manifest, scripts, popup iframe) +``` + +--- + +## 🔑 Setup & Installation + +### Step 1: Clone the Repo & Install Dependencies +First, clone your fork and install Next.js dependencies: +```bash +cd kits/review-analyzer/apps +npm install +``` + +### Step 2: Set up Environment Variables +Create a `.env.local` file inside the `apps` directory with the following keys: +```bash +# Lamatic Flow ID +REVIEW_ANALYZER_FLOW_ID="your-flow-id" + +# Lamatic Credentials (Get from Settings in Lamatic Studio) +LAMATIC_API_URL="https://studio.lamatic.ai/api/your-project-url" +LAMATIC_PROJECT_ID="your-project-id" +LAMATIC_API_KEY="your-api-key" +``` + +### Step 3: Run the Next.js App +Start the Next.js server locally to act as the backend API bridge for the Chrome Extension: +```bash +npm run dev +``` +The Next.js dashboard will be running at `http://localhost:3000`. + +### Step 4: Install the Chrome Extension +1. Open Google Chrome and navigate to `chrome://extensions/`. +2. Enable **Developer mode** using the toggle switch in the top-right corner. +3. Click the **Load unpacked** button in the top-left corner. +4. Select the `kits/review-analyzer/apps/extension` folder from your local directory. +5. The extension is now active in your browser. + +--- + +## 🧠 Lamatic Flow Workflow Setup + +In the [Lamatic Cloud Studio](https://studio.lamatic.ai): +1. Create a new flow named `review-analyzer`. +2. **Define the Input**: Set the trigger to API (GraphQL). The input schema should take an array of strings (e.g. `reviews: [String]`). +3. **Add LLM Node**: Use Claude 3.5 Sonnet or GPT-4o. Set the system prompt to analyze the reviews, compile a consensus summary, isolate key pros & cons, and detect suspicious patterns (such as exact text duplication, excessively short phrasing, or repetitive reviews) to assign a **Trust Score (0 to 100)**. +4. **Define the Output**: Map the output to a GraphQL schema returning the following fields: + - `summary`: string + - `pros`: string[] + - `cons`: string[] + - `trustScore`: int + - `trustLabel`: string + - `analysisDetail`: string +5. **Deploy**: Deploy the flow to retrieve your Flow ID and Project details, and populate `.env.local`. + +--- + +## 🤝 Contributing +Feel free to open issues or PRs! To submit your changes to the AgentKit repo, ensure your PR is labeled with `agentkit-challenge`. diff --git a/kits/review-analyzer/agent.md b/kits/review-analyzer/agent.md new file mode 100644 index 00000000..85f7f155 --- /dev/null +++ b/kits/review-analyzer/agent.md @@ -0,0 +1,58 @@ +# Agent Identity: Review Analyzer + +## Overview +Review Analyzer is an AI-powered assistant designed to help e-commerce shoppers make informed decisions instantly. By scraping and analyzing hundreds of reviews from product pages, it delivers a clear consensus, highlights critical pros & cons, and calculates a Trust Score to identify fake or sponsored comments. + +## Purpose +The sheer volume of reviews on sites like Amazon makes it impossible for shoppers to read everything, and many reviews are generated by bots, incentivized programs, or contains low-effort filler. Review Analyzer: +- **Saves Time**: Aggregates and synthesizes hundreds of reviews into a few readable sentences. +- **Exposes Hidden Flaws**: Isolates negative feedback that might be buried under five-star ratings. +- **Protects from Fraud**: Audits reviews for bot-like phrasing, repetitive patterns, or sponsored templates to score review trust. + +## Flows + +### 1. `review-analyzer` Flow +This is the primary flow triggered by the Chrome Extension. It takes scraped reviews, processes them using a large language model (Claude 3.5 Sonnet or GPT-4o) through Lamatic AI, and returns a structured analysis. + +- **Trigger**: API Trigger (GraphQL execution). +- **Processing**: + - Summarizes the overall customer sentiment and consensus. + - Extracts lists of distinct Pros and Cons. + - Examines individual review properties (length, repetitive words, generic bot-like phrasing) to calculate an aggregate Trust Score. +- **Output**: JSON payload returning: + - `summary`: The general consensus summary. + - `pros`: A list of product benefits. + - `cons`: A list of product drawbacks or complaints. + - `trustScore`: Score from 0 to 100 indicating review reliability. + - `trustLabel`: Sentiment classification of trust (e.g. "Highly Trusted", "Needs Review", "Unreliable"). + - `analysisDetail`: Explanation of why the trust score was assigned. + +## Guardrails +- **Input Limit**: The flow handles a maximum of 50 reviews per call to optimize response time and LLM token limits. +- **Content Security**: Content is filtered to strip HTML tags and scripts before LLM processing to prevent prompt injection. +- **Language Boundaries**: If reviews are in languages other than English, the LLM will automatically translate the summary and pros/cons into English while processing. + +## Environment Setup +The Next.js bridge app requires the following environment variables: + +| Variable Name | Description | Source | +|---|---|---| +| `REVIEW_ANALYZER_FLOW_ID` | Flow ID of the deployed Lamatic flow | Lamatic Studio Flow page | +| `LAMATIC_API_URL` | Endpoint URL for the Lamatic project | Lamatic Studio API Settings | +| `LAMATIC_PROJECT_ID` | Project ID in Lamatic | Lamatic Studio Settings | +| `LAMATIC_API_KEY` | Secret API key for authentication | Lamatic Studio API Settings | + +## Quickstart +1. Set up the environment variables in `apps/.env.local`. +2. Deploy your workflow in the Lamatic AI Studio and export it to `flows/review-analyzer.ts`. +3. Run `npm install` and `npm run dev` in the `apps/` directory. +4. Load the Chrome Extension folder `apps/extension` into your Chrome browser via `chrome://extensions`. +5. Visit any Amazon or e-commerce product page, click the extension icon, and view the analysis. + +## Common Failure Modes + +| Symptom | Cause | Resolution | +|---|---|---| +| Trust Score is always 100 | LLM prompt failed to detect patterns or reviews are too sparse | Ensure the scraped reviews are correctly parsed and passed. Add more diverse reviews to the page. | +| Extension popup shows connection error | Next.js server is not running or env variables are incorrect | Ensure Next.js is running at the configured URL (`http://localhost:3000`) and the API credentials are valid. | +| Scraper yields empty results | Target site updated their DOM/classnames | Update the selectors in `content.js` to match the new review container class names on the target e-commerce site. | diff --git a/kits/review-analyzer/apps/.env.example b/kits/review-analyzer/apps/.env.example new file mode 100644 index 00000000..f8010cc4 --- /dev/null +++ b/kits/review-analyzer/apps/.env.example @@ -0,0 +1,4 @@ +REVIEW_ANALYZER_FLOW_ID = "YOUR_FLOW_ID" +LAMATIC_API_URL = "YOUR_LAMATIC_API_URL" +LAMATIC_PROJECT_ID = "YOUR_LAMATIC_PROJECT_ID" +LAMATIC_API_KEY = "YOUR_LAMATIC_API_KEY" diff --git a/kits/review-analyzer/apps/.gitignore b/kits/review-analyzer/apps/.gitignore new file mode 100644 index 00000000..b28ea810 --- /dev/null +++ b/kits/review-analyzer/apps/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/kits/review-analyzer/apps/.npmrc b/kits/review-analyzer/apps/.npmrc new file mode 100644 index 00000000..521a9f7c --- /dev/null +++ b/kits/review-analyzer/apps/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/kits/review-analyzer/apps/README.md b/kits/review-analyzer/apps/README.md new file mode 100644 index 00000000..cdaa3f41 --- /dev/null +++ b/kits/review-analyzer/apps/README.md @@ -0,0 +1,155 @@ +# Reddit Scout by Lamatic.ai + +

+ + Live Demo + +

+ +**Reddit Scout** is an AI-powered product review research tool built with [Lamatic.ai](https://lamatic.ai). It searches Reddit for real user opinions and generates structured, scannable review summaries for any product or topic. + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Lamatic/AgentKit&root-directory=kits/agentic/reddit-scout&env=REDDIT_SCOUT_FLOW_ID,LAMATIC_API_URL,LAMATIC_PROJECT_ID,LAMATIC_API_KEY&envDescription=Your%20Lamatic%20Reddit%20Scout%20keys%20are%20required.) + +--- + +## The Problem + +When researching a product before buying, the most honest opinions live on Reddit — not in influencer reviews or star ratings. But finding and reading through dozens of Reddit threads is time-consuming. People manually Google "site:reddit.com [product] reviews", open 10 tabs, read hundreds of comments, and try to mentally synthesize what the consensus is. This takes 15-20 minutes per product and is hard to do consistently. + +**Nobody has built a focused tool that automates this specific workflow.** + +## The Approach + +Reddit Scout is a single-purpose tool: type a product name, get a structured review summary from Reddit. + +**How it works:** + +1. User enters a product or topic (e.g., "HP Victus", "Sony WH-1000XM5") +2. An LLM generates Reddit-scoped search queries +3. Google Serper API finds the most relevant Reddit threads +4. Serper's scrape endpoint extracts full thread content (posts + comments) +5. A second LLM analyzes all content and produces a structured summary + +**Tech stack:** +- **Lamatic AI** — orchestrates the entire flow (search, scrape, analyze) +- **Google Serper API** — Reddit-scoped web search and thread scraping +- **Next.js** — clean, fast frontend +- **ReactMarkdown** — renders AI output as formatted markdown + +## The Result + +A clean, single-page app where users type a product name and get a structured review in seconds: + +- **Overall Sentiment** — positive/negative split at a glance +- **What Users Love** — key pros from real users +- **Common Complaints** — honest downsides +- **Notable User Quotes** — direct quotes with context +- **Frequently Discussed Features** — what keeps coming up +- **Final Verdict** — direct recommendation + +**Live demo:** [reddit-scout-tawny.vercel.app](https://reddit-scout-tawny.vercel.app/) + +## Tradeoffs & Assumptions + +- **Scrape vs. full crawl:** We use Serper's scrape endpoint (single-page) instead of Firecrawl (which doesn't support Reddit). This means we get thread content but not all nested comments — we capture the most relevant discussion. +- **10 threads per search:** We scrape the top 10 Reddit results. This balances speed (~20s) with coverage. More threads would mean longer wait times. +- **No caching:** Every search hits the API fresh. Adding a cache layer would reduce costs for repeated queries but wasn't needed for this scope. +- **Single product search:** No comparison mode (e.g., "X vs Y") — keeping the scope narrow and the output clean. + +--- + +## Lamatic Setup + +Before running this project, you must build and deploy the flow in Lamatic, then wire its config into this codebase. + +**Pre: Build in Lamatic** +1. Sign in or sign up at https://lamatic.ai +2. Create a project +3. Click "+ New Flow" and select "Flow" +4. Build the Reddit Scout flow: + - Trigger Node (GraphQL) with input `{ "query": "string" }` + - LLM Node: Generate Reddit search query + - Web Search Node (Serper): Search Google scoped to Reddit + - Code Node: Extract URLs from search results + - Batch Node: Iterate over URLs + - Web Search Node (Serper Scrape): Scrape each Reddit thread + - Code Node: Extract all scraped text + - LLM Node: Analyze and generate structured review summary + - Response Node: Return with output mapping `{ "answer": "{{LLMNode.output.generatedResponse}}" }` +5. Configure providers and API credentials (Serper API key) +6. Deploy the flow in Lamatic and obtain your .env keys + +**Post: Wire into this repo** +1. Create a `.env` file and set the keys +2. Install and run locally: + - `npm install` + - `npm run dev` +3. Deploy (Vercel recommended): + - Import your repo, set the project's Root Directory to `kits/agentic/reddit-scout` + - Add env vars in Vercel (same as your `.env`) + - Deploy and test your live URL + +--- + +## Key Features +- Searches Reddit for product reviews using Google Serper API +- Scrapes full Reddit thread content via Serper scrape endpoint +- AI-generated structured review summaries with sentiment analysis +- Pros/cons extraction and notable user quotes +- Clean markdown rendering of results + +--- + +## Required Keys and Config + +| Item | Purpose | Where to Get It | +|------|---------|-----------------| +| Lamatic API Key | Authentication for Lamatic AI APIs | [lamatic.ai](https://lamatic.ai) | +| Serper API Key | Google Search API for Reddit search | [serper.dev](https://serper.dev) | + +### 1. Environment Variables + +Create `.env.local` with: + +```bash +REDDIT_SCOUT_FLOW_ID = "YOUR_FLOW_ID" +LAMATIC_API_URL = "YOUR_API_ENDPOINT" +LAMATIC_PROJECT_ID = "YOUR_PROJECT_ID" +LAMATIC_API_KEY = "YOUR_API_KEY" +``` + +### 2. Install & Run + +```bash +npm install +npm run dev +# Open http://localhost:3000 +``` + +--- + +## Repo Structure + +``` +/actions + └── orchestrate.ts # Lamatic workflow orchestration +/app + └── page.tsx # Main search form UI +/components + ├── header.tsx # Header component + └── ui # shadcn/ui components +/lib + └── lamatic-client.ts # Lamatic SDK client +/public + └── lamatic-logo.png # Lamatic branding +/flows + └── reddit-scout/ # Exported Lamatic flow +/package.json # Dependencies & scripts +``` + +--- + +## License + +MIT License + diff --git a/kits/review-analyzer/apps/actions/orchestrate.ts b/kits/review-analyzer/apps/actions/orchestrate.ts new file mode 100644 index 00000000..fff2b9d2 --- /dev/null +++ b/kits/review-analyzer/apps/actions/orchestrate.ts @@ -0,0 +1,57 @@ +"use server" + +import { lamaticClient } from "@/lib/lamatic-client" + +const FLOW_ID = process.env.REVIEW_ANALYZER_FLOW_ID + +export async function analyzeReviews( + reviews: string[] +): Promise<{ + success: boolean + data?: string + error?: string +}> { + try { + if (!FLOW_ID) { + throw new Error("REVIEW_ANALYZER_FLOW_ID environment variable is not set") + } + + console.log("[Review Analyzer] Invoking flow with reviews count:", reviews.length) + + const inputs: Record = { reviews } + + console.log("[Review Analyzer] Sending inputs:", inputs) + + const resData = await lamaticClient.executeFlow(FLOW_ID, inputs) + console.log("[Review Analyzer] Raw response:", resData) + + const result = resData?.result?.result + + if (!result) { + throw new Error("No analysis result found in response") + } + + return { + success: true, + data: result as string, + } + } catch (error) { + console.error("[Review Analyzer] 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 AI. Please check your internet connection." + } else if (error.message.includes("API key")) { + errorMessage = "Authentication error: Please verify your API credentials." + } + } + + return { + success: false, + error: errorMessage, + } + } +} diff --git a/kits/review-analyzer/apps/app/globals.css b/kits/review-analyzer/apps/app/globals.css new file mode 100644 index 00000000..3047e4de --- /dev/null +++ b/kits/review-analyzer/apps/app/globals.css @@ -0,0 +1,136 @@ +@import 'tailwindcss'; +@import 'tw-animate-css'; +@plugin '@tailwindcss/typography'; + +@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: 'DM Sans', system-ui, sans-serif; + --font-serif: 'DM Serif Display', Georgia, serif; + --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; + } +} + +@keyframes pulse-dot { + 0%, 100% { transform: scale(1); opacity: 1; } + 50% { transform: scale(1.4); opacity: 0.7; } +} + +@keyframes fade-stage { + 0% { opacity: 0; transform: translateY(4px); } + 100% { opacity: 1; transform: translateY(0); } +} diff --git a/kits/review-analyzer/apps/app/layout.tsx b/kits/review-analyzer/apps/app/layout.tsx new file mode 100644 index 00000000..0aabdd8b --- /dev/null +++ b/kits/review-analyzer/apps/app/layout.tsx @@ -0,0 +1,32 @@ +import type { Metadata } from 'next' +import { DM_Sans, DM_Serif_Display } from 'next/font/google' +import { Analytics } from '@vercel/analytics/next' +import './globals.css' + +const dmSans = DM_Sans({ subsets: ["latin"], variable: "--font-sans" }) +const dmSerif = DM_Serif_Display({ subsets: ["latin"], weight: "400", variable: "--font-serif" }) + +export const metadata: Metadata = { + title: 'Review Analyzer — Lamatic AI', + description: 'Analyze product reviews and determine authenticity using Lamatic AI', + icons: { + icon: '/lamatic-logo.png', + shortcut: '/lamatic-logo.png', + apple: '/lamatic-logo.png', + }, +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + {children} + + + + ) +} diff --git a/kits/review-analyzer/apps/app/page.tsx b/kits/review-analyzer/apps/app/page.tsx new file mode 100644 index 00000000..b41f4772 --- /dev/null +++ b/kits/review-analyzer/apps/app/page.tsx @@ -0,0 +1,157 @@ +"use client" + +import { ShieldCheck, ShieldAlert, Sparkles, AlertTriangle, ArrowRight, Chrome, Terminal, PlayCircle } from "lucide-react" + +export default function Home() { + return ( +
+ {/* Background glow effects */} +
+
+ + {/* Navigation */} + + + {/* Hero Section */} +
+
+ + Lamatic AgentKit Challenge +
+ +

+ The AI-Powered E-Commerce Shield For Shoppers +

+

+ Stop scrolling through hundreds of comments. Instantly detect fake reviews, extract honest pros/cons, and get a Trust Score using Chrome & Lamatic AI. +

+ + + + {/* Feature Cards Grid */} +
+ {/* Card 1 */} +
+
+ +
+

Consensus Synthesis

+

+ Synthesize hundreds of user reviews into a brief, readable paragraph mapping the absolute consensus of real buyers. +

+
+ + {/* Card 2 */} +
+
+ +
+

Authenticity Audit

+

+ Audits and assigns an aggregate Trust Score from 0 to 100 based on word patterns, repetitive feedback templates, and low-effort spam. +

+
+ + {/* Card 3 */} +
+
+ +
+

Chrome Action

+

+ Scrapes review panels directly inside your active tab, bypassing strict server-side bot-blocking protections securely. +

+
+
+ + {/* Setup Steps Section */} +
+

+ Quickstart Setup +

+ +
+ {/* Step 1 */} +
+
+ 1 +
+
+

Create Environment Variables

+

+ Create a `.env.local` file inside the Next.js app directory (`kits/review-analyzer/apps/`) and populate your Lamatic API Keys: +

+
+                  {`REVIEW_ANALYZER_FLOW_ID="your-flow-id"\nLAMATIC_API_URL="https://studio.lamatic.ai/api/your-project"\nLAMATIC_PROJECT_ID="your-project-id"\nLAMATIC_API_KEY="your-api-key"`}
+                
+
+
+ + {/* Step 2 */} +
+
+ 2 +
+
+

Run Next.js locally

+

+ Install node dependencies and launch the dev environment: +

+
+                  cd kits/review-analyzer/apps && npm install && npm run dev
+                
+
+
+ + {/* Step 3 */} +
+
+ 3 +
+
+

Install the Chrome Extension

+
    +
  1. Navigate to chrome://extensions/ in Chrome.
  2. +
  3. Enable **Developer mode** using the toggle in the top-right.
  4. +
  5. Click **Load unpacked** in the top-left.
  6. +
  7. Choose the folder: kits/review-analyzer/apps/extension.
  8. +
  9. Open any product page and click the extension icon to run analysis!
  10. +
+
+
+
+
+
+
+ ) +} diff --git a/kits/review-analyzer/apps/app/popup/page.tsx b/kits/review-analyzer/apps/app/popup/page.tsx new file mode 100644 index 00000000..bead6016 --- /dev/null +++ b/kits/review-analyzer/apps/app/popup/page.tsx @@ -0,0 +1,221 @@ +"use client" + +import { useEffect, useState } from "react" +import { analyzeReviews } from "@/actions/orchestrate" +import { ShieldCheck, ShieldAlert, AlertTriangle, MessageSquare, ThumbsUp, ThumbsDown, Sparkles } from "lucide-react" + +interface AnalysisResult { + summary: string + pros: string[] + cons: string[] + trustScore: number + trustLabel: string + analysisDetail: string +} + +export default function PopupPage() { + const [loading, setLoading] = useState(true) + const [loadingStep, setLoadingStep] = useState(0) + const [reviews, setReviews] = useState([]) + const [result, setResult] = useState(null) + const [error, setError] = useState(null) + + // 1. Send signal that iframe is ready to receive data + useEffect(() => { + if (typeof window !== "undefined") { + // Notify parent Chrome extension that the iframe is ready + window.parent.postMessage({ type: "IFRAME_READY" }, "*") + } + }, []) + + // 2. Listen for scraped reviews from the parent Chrome extension + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + const message = event.data + if (message && message.type === "REVIEWS_SCRAPED") { + console.log("[Next.js] Received reviews:", message.reviews) + setReviews(message.reviews) + triggerAnalysis(message.reviews) + } + } + + window.addEventListener("message", handleMessage) + return () => window.removeEventListener("message", handleMessage) + }, []) + + // 3. Loading animation steps + useEffect(() => { + if (!loading) return + const interval = setInterval(() => { + setLoadingStep((prev) => (prev + 1) % 5) + }, 2000) + return () => clearInterval(interval) + }, [loading]) + + const triggerAnalysis = async (reviewsData: string[]) => { + setLoading(true) + setError(null) + try { + const response = await analyzeReviews(reviewsData) + if (response.success && response.data) { + // Parse the LLM JSON response + const parsed = JSON.parse(response.data) as AnalysisResult + setResult(parsed) + } else { + setError(response.error || "Failed to analyze reviews.") + } + } catch (err) { + console.error(err) + setError("An unexpected error occurred during analysis.") + } finally { + setLoading(false) + } + } + + // Get color mapping for Trust Score + const getTrustColors = (score: number) => { + if (score >= 80) return { text: "text-emerald-400", border: "stroke-emerald-500", bg: "bg-emerald-500/10", label: "text-emerald-400", icon: ShieldCheck } + if (score >= 50) return { text: "text-amber-400", border: "stroke-amber-500", bg: "bg-amber-500/10", label: "text-amber-400", icon: AlertTriangle } + return { text: "text-rose-400", border: "stroke-rose-500", bg: "bg-rose-500/10", label: "text-rose-400", icon: ShieldAlert } + } + + const trustInfo = result ? getTrustColors(result.trustScore) : null + const TrustIcon = trustInfo?.icon || ShieldCheck + + const loadingStepsText = [ + "Parsing scraped reviews...", + "Analyzing consensus & sentiment...", + "Auditing spelling & patterns...", + "Detecting artificial behavior...", + "Calculating trust score..." + ] + + return ( +
+ {loading && ( +
+
+
+
+
+

+ {loadingStepsText[loadingStep]} +

+
+ )} + + {error && ( +
+ +

Analysis Failed

+

{error}

+ +
+ )} + + {!loading && !error && result && ( +
+ + {/* Header Stat & Circle Gauge Row */} +
+
+ Analysis Dataset + + + {reviews.length} Reviews + +
+ + {/* Circular Gauge */} +
+ + + + +
+ {result.trustScore} + Trust +
+
+
+ + {/* Trust Level Indicator */} +
+ +
+ Agent Verdict + {result.trustLabel} +

{result.analysisDetail}

+
+
+ + {/* Consensus Summary */} +
+ + + Consensus Summary + +
+

{result.summary}

+
+
+ + {/* Pros & Cons Section */} +
+ {/* Pros */} +
+ + Pros + +
    + {result.pros.map((pro, index) => ( +
  • + + {pro} +
  • + ))} + {result.pros.length === 0 && ( +
  • None mentioned
  • + )} +
+
+ + {/* Cons */} +
+ + Cons + +
    + {result.cons.map((con, index) => ( +
  • + + {con} +
  • + ))} + {result.cons.length === 0 && ( +
  • None mentioned
  • + )} +
+
+
+ +
+ )} +
+ ) +} diff --git a/kits/review-analyzer/apps/components.json b/kits/review-analyzer/apps/components.json new file mode 100644 index 00000000..4ee62ee1 --- /dev/null +++ b/kits/review-analyzer/apps/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/kits/review-analyzer/apps/components/header.tsx b/kits/review-analyzer/apps/components/header.tsx new file mode 100644 index 00000000..b32b6da4 --- /dev/null +++ b/kits/review-analyzer/apps/components/header.tsx @@ -0,0 +1,41 @@ +import Link from "next/link" +import Image from "next/image" + +export function Header() { + return ( +
+
+ + Lamatic + + Lamatic / Review Analyzer + + + +
+
+ ) +} diff --git a/kits/review-analyzer/apps/components/theme-provider.tsx b/kits/review-analyzer/apps/components/theme-provider.tsx new file mode 100644 index 00000000..55c2f6eb --- /dev/null +++ b/kits/review-analyzer/apps/components/theme-provider.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as React from 'react' +import { + ThemeProvider as NextThemesProvider, + type ThemeProviderProps, +} from 'next-themes' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/kits/review-analyzer/apps/components/ui/accordion.tsx b/kits/review-analyzer/apps/components/ui/accordion.tsx new file mode 100644 index 00000000..e538a33b --- /dev/null +++ b/kits/review-analyzer/apps/components/ui/accordion.tsx @@ -0,0 +1,66 @@ +'use client' + +import * as React from 'react' +import * as AccordionPrimitive from '@radix-ui/react-accordion' +import { ChevronDownIcon } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Accordion({ + ...props +}: React.ComponentProps) { + return +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180', + className, + )} + {...props} + > + {children} + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/kits/review-analyzer/apps/components/ui/alert-dialog.tsx b/kits/review-analyzer/apps/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..97044526 --- /dev/null +++ b/kits/review-analyzer/apps/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +'use client' + +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' + +import { cn } from '@/lib/utils' +import { buttonVariants } from '@/components/ui/button' + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/kits/review-analyzer/apps/components/ui/alert.tsx b/kits/review-analyzer/apps/components/ui/alert.tsx new file mode 100644 index 00000000..e6751abe --- /dev/null +++ b/kits/review-analyzer/apps/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + { + variants: { + variant: { + default: 'bg-card text-card-foreground', + destructive: + 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/kits/review-analyzer/apps/components/ui/aspect-ratio.tsx b/kits/review-analyzer/apps/components/ui/aspect-ratio.tsx new file mode 100644 index 00000000..40bb1208 --- /dev/null +++ b/kits/review-analyzer/apps/components/ui/aspect-ratio.tsx @@ -0,0 +1,11 @@ +'use client' + +import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/kits/review-analyzer/apps/components/ui/avatar.tsx b/kits/review-analyzer/apps/components/ui/avatar.tsx new file mode 100644 index 00000000..aa98465a --- /dev/null +++ b/kits/review-analyzer/apps/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +'use client' + +import * as React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' + +import { cn } from '@/lib/utils' + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/kits/review-analyzer/apps/components/ui/badge.tsx b/kits/review-analyzer/apps/components/ui/badge.tsx new file mode 100644 index 00000000..fc4126b7 --- /dev/null +++ b/kits/review-analyzer/apps/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span' + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/kits/review-analyzer/apps/components/ui/breadcrumb.tsx b/kits/review-analyzer/apps/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..1750ff26 --- /dev/null +++ b/kits/review-analyzer/apps/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { ChevronRight, MoreHorizontal } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) { + return