Skip to content

Repository files navigation

Critique

Critique*

A website that judges you based on your profiles.

Feed it your GitHub, LeetCode, Codeforces handle or your résumé, pick a tone, and get an LLM-generated verdict — from a gentle summary all the way to a full roast.

Live demo →

Next.js React TypeScript Tailwind Groq


Table of contents


What it does

Critique is a single-page Next.js app. You choose what to be judged on (a source) and how harshly (a tone). The app fetches the raw data server-side, flattens it into a compact text summary, and hands it to a Llama 3.3 70B model on Groq with a tone-conditioned prompt. The verdict streams into a modal as Markdown with a typewriter effect, and can be copied or exported as an image.

No database. No accounts (except an optional Spotify OAuth handshake). Nothing is stored — every critique is generated fresh and lives only in React state.


Tones

The tone is injected directly into the system prompt, so the same data can produce five very different verdicts.

Tone What you get
summarise A neutral, factual rundown
evaluate Measured assessment with strengths and weaknesses
guide Actionable advice on what to do next
criticism Constructive criticism, no sugar-coating
roast Comedy at your expense

Three of these (roast, evaluate, guide) also swap the animated page background via AnimatePresence, so the UI shifts mood along with the model.


Sources

Source Input Data pulled Auth
GitHub Username Profile, 15 most recently updated repos, open vs. merged PR counts, computed merge rate GITHUB_TOKEN (server-side)
LeetCode Username Solved counts by difficulty, contest rating, global ranking — via LeetCode's GraphQL endpoint None
Codeforces Handle user.info profile data None
Résumé PDF upload or URL Full text extraction via pdf2json, first 7000 chars sent to the model. Google Drive share links are auto-normalised to direct-download URLs None

Two more integrations exist in the codebase but are not currently wired into the mode selector: Spotify (top 15 artists, via NextAuth OAuth with the user-top-read scope) and MyAnimeList (top 20 completed anime by score). The server actions and lib functions are complete — they just need a Mode enum entry and a UI branch.


Architecture

Everything runs inside Next.js. There is no separate backend — the "API layer" is a set of React Server Actions that execute on the server and are called directly from the client component.

flowchart TB
    subgraph client["Browser — app/page.tsx (Client Component)"]
        UI["Mode + Tone selector<br/>Input / file upload"]
        MODAL["CritiqueModal<br/>Typewriter Markdown · copy · export PNG"]
    end

    subgraph server["Next.js Server — Server Actions"]
        AG["actions/github.ts"]
        AL["actions/leetcode.ts"]
        AC["actions/codeforces.ts"]
        AR["actions/resume.ts"]
        AS["actions/spotify.ts<br/><i>(not wired)</i>"]
        AA["actions/anime.ts<br/><i>(not wired)</i>"]
    end

    subgraph lib["lib/ — data fetchers"]
        LG["github.ts"]
        LL["leetcode.ts"]
        LC["codeforces.ts"]
        LR["resume.ts (pdf2json)"]
        LS["spotify.ts"]
        LA["anime.ts"]
    end

    LLM["lib/llm.ts<br/>Prompt builder"]
    GROQ["Groq API<br/>llama-3.3-70b-versatile"]

    subgraph ext["External APIs"]
        E1["api.github.com"]
        E2["leetcode.com/graphql"]
        E3["codeforces.com/api"]
        E4["PDF file or URL"]
        E5["api.spotify.com"]
        E6["api.myanimelist.net"]
    end

    UI -->|"invoke action"| AG & AL & AC & AR
    AG --> LG --> E1
    AL --> LL --> E2
    AC --> LC --> E3
    AR --> LR --> E4
    AS --> LS --> E5
    AA --> LA --> E6

    AG & AL & AC & AR & AS & AA -->|"flattened text + tone"| LLM
    LLM --> GROQ
    GROQ -->|"Markdown"| MODAL
Loading

Why server actions

Every external call needs a secret (GITHUB_TOKEN, GROQ_API_KEY, MAL_CLIENT_ID) or would be blocked by CORS (LeetCode's GraphQL endpoint). Marking the lib/ and actions/ files "use server" keeps all of that off the client entirely — the browser only ever sends a username and receives Markdown back.


Request lifecycle

sequenceDiagram
    actor User
    participant Page as page.tsx
    participant Action as Server Action
    participant API as External API
    participant LLM as lib/llm.ts
    participant Groq as Groq

    User->>Page: Pick mode + tone, enter handle
    Page->>Page: setIsLoading(true)
    Page->>Action: analyzeGithub({ username, tone })

    Action->>API: fetch profile + repos + PR search
    API-->>Action: JSON

    alt fetch failed or user not found
        Action-->>Page: throw Error
        Page-->>User: toast.error(...)
    else success
        Action->>Action: Flatten JSON into plain-text summary
        Action->>LLM: generateResponse(data, tone)
        LLM->>Groq: chat.completions.create(prompt)
        Groq-->>LLM: Markdown verdict
        LLM-->>Action: string
        Action-->>Page: string
        Page->>Page: handleSuccess() → open modal
        Page-->>User: Typewriter render, copy / export image
    end
Loading

The prompt contract

lib/llm.ts is the single choke point for every source. It wraps whatever text the action produced in a fixed instruction set:

  • Hold the requested tone for the whole response
  • No greetings, no preamble, no emojis, no em dashes
  • Treat the supplied data as ground truth, and judge only the content — never the format or length
  • Output Markdown, using ### headings per category with bullets underneath

Because every action funnels through this one function, adding a new source means writing a fetcher, flattening its data into a string, and nothing else.


Project structure

project-critique/
├── app/
│   ├── page.tsx                     # Single-page UI: mode + tone state, all handlers
│   ├── layout.tsx                   # Fonts (Grenze Gotisch, Space Mono), Toaster, Analytics
│   ├── globals.css                  # Tailwind 4 theme tokens
│   ├── api/auth/[...nextauth]/      # NextAuth — Spotify provider, JWT → session accessToken
│   └── actions/                     # Server actions: fetch → flatten → generateResponse
│       ├── github.ts
│       ├── leetcode.ts
│       ├── codeforces.ts
│       ├── resume.ts
│       ├── spotify.ts
│       └── anime.ts
│
├── lib/
│   ├── llm.ts                       # Groq client + prompt template (all tones)
│   ├── github.ts                    # REST: profile + repos
│   ├── leetcode.ts                  # GraphQL: submit stats + contest ranking
│   ├── codeforces.ts                # REST: user.info
│   ├── resume.ts                    # pdf2json parser + Drive URL normaliser
│   ├── spotify.ts                   # REST: top artists
│   ├── anime.ts                     # REST: MAL completed list
│   └── utils.ts                     # cn() helper
│
├── components/
│   ├── critiqueModal.tsx            # Dialog + typewriter Markdown + html-to-image export
│   ├── providers.tsx                # SessionProvider + ThemeProvider
│   ├── roastBackground.tsx          # Animated background — roast tone
│   ├── evaluateBackground.tsx       # Animated background — evaluate tone
│   ├── guideBackground.tsx          # Animated background — guide tone
│   └── ui/                          # shadcn/ui primitives
│
├── types/next-auth.d.ts             # Augments Session with accessToken
└── public/                          # Logo, local font files

Getting started

Prerequisites: Node.js 20+ and npm.

git clone https://github.com/iammdzaidalam/project-critique.git
cd project-critique
npm install
cp .env.example .env.local   # then fill in the values below
npm run dev

Open http://localhost:3000.

Script Does
npm run dev Dev server with hot reload
npm run build Production build
npm run start Serve the production build
npm run lint ESLint

Environment variables

Create .env.local in the project root:

# Required — the model that writes every critique
GROQ_API_KEY=

# Required for the GitHub mode (raises rate limits, enables PR search)
GITHUB_TOKEN=

# Required only if you wire up the Spotify mode
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=

# Required only if you wire up the anime mode
MAL_CLIENT_ID=
Variable Where to get it
GROQ_API_KEY console.groq.com — free tier is plenty
GITHUB_TOKEN GitHub → Settings → Developer settings → Personal access tokens. No scopes needed for public data
SPOTIFY_CLIENT_ID / SECRET developer.spotify.com/dashboard. Add http://localhost:3000/api/auth/callback/spotify as a redirect URI
NEXTAUTH_SECRET Generate with openssl rand -base64 32
MAL_CLIENT_ID myanimelist.net/apiconfig

LeetCode, Codeforces and résumé parsing need no credentials.


Deployment

Deployed on Vercel. Import the repo, add the environment variables above under Settings → Environment Variables, and deploy — server actions and the NextAuth route work out of the box on Vercel's Node runtime.

If you deploy elsewhere, note that pdf2json requires a Node runtime (not Edge), and NEXTAUTH_URL must match your production origin.


Roadmap

  • Wire Spotify and MyAnimeList into the mode selector
  • Stream the LLM response token-by-token instead of typing out a completed string
  • Rate limiting on the server actions
  • Shareable critique permalinks
  • Compare mode — two profiles, one verdict

Built by @iammdzaidalam

About

Critique, a website that judges you based on your profiles.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages