Skip to main content
Developer tutorial. Requires Node.js, React, TypeScript, and command-line experience.
Upload a contract and get back a severity-ranked list of risky clauses with suggested redlines, all in a single-page React app that talks to the GC AI API.
Contract Risk Heatmap app showing a PDF preview and identified risks sorted by severity with category badges, clause references, and suggested redlines

What you’ll build

A drag-and-drop webapp where a user pastes their API key, uploads a PDF contract, and sees a heatmap of risks sorted by severity. Each risk includes a clause reference, category, and suggested redline. The whole thing runs on two API calls: one to upload the file, one to analyze it.

API endpoints

Want to skip ahead? View on GitHub or run on Replit.

Prerequisites

Build the app

1

Scaffold the project

Create a new Vite + React + TypeScript project and install Tailwind CSS:
Open vite.config.ts and add the Tailwind plugin plus an API proxy for development:
vite.config.ts
The proxy forwards /api requests to GC AI during development so you don’t hit CORS issues from localhost.Now clear out the starter code. We’ll write everything from scratch:
Replace src/index.css with the Tailwind import and a minimal color theme:
src/index.css
Create two empty files. These are the only two source files we’ll write:
Run npm run dev to confirm the scaffold works before we start building.
2

Build the API client

The API client goes in src/api.ts. It wraps the GC AI endpoints into three functions: validateApiKey, uploadFile, and analyzeContract.Start with the types. ContractRisk is the shape we’ll ask the model to return:
src/api.ts
Next, a small helper that attaches the API key to every request and throws on non-2xx responses:
src/api.ts
Key validation. A lightweight GET /folders?limit=1 call checks whether the API key is valid without creating any resources:
src/api.ts
File upload. uploadFile posts the file, then polls GET /files/:id every 2 seconds until the status is ready. The caller just awaits the result:
src/api.ts
Contract analysis. analyzeContract sends a chat completion with wait=0, which returns a job ID immediately instead of blocking. It polls the job until the model finishes, then parses the structured JSON out of the response:
src/api.ts
That’s the whole API layer. Three exported functions: validateApiKey(key), uploadFile(key, file), and analyzeContract(key, fileId).
3

App shell: header and footer

Now we start building src/App.tsx. This first pass just sets up the header (with the API key input), an empty main area, and a footer:
src/App.tsx
The key validates on blur and on page load (if one was saved to localStorage from a previous session). A green checkmark or red X shows up inline so you know right away whether the key is good.
App header showing Contract Risk Heatmap title, API key input with green checkmark, and file upload drop zone
4

File upload

Add a FileUpload component above the App function. It supports both drag-and-drop and click-to-browse:
src/App.tsx (above App)
5

Document preview

Add a DocumentPreview component that renders the uploaded PDF using react-pdf. We use this instead of a native iframe embed so the preview works in sandboxed environments like Replit:
src/App.tsx (above App)
This renders from the browser’s local File object, not from anything returned by the API. The preview shows up the moment the user drops a file, even while the upload is still running.
6

Risk heatmap

Add the RiskHeatmap component that renders the analysis results. Each risk gets a card with a colored left border for severity, a category badge, a clause reference, and a suggested redline:
src/App.tsx (above App)
Risks are sorted high to low. The SeveritySummary at the top acts as a legend, with colored count badges that match the left-border colors on the cards below.
7

Wire it up

The last piece is the state machine and the conditional rendering that wires everything together.Define the status type and status messages above the App function:
src/App.tsx (above App)
Each variant of Status carries only the data the UI needs for that state. TypeScript narrows the type in each rendering branch, so you can’t accidentally access risks while still uploading.Add the state and handler inside App:
Finally, fill in the <main> element:
The UI shows one thing at a time based on the current status: a prompt to enter a key, the file upload zone, a spinner, or the results. The DocumentPreview stays mounted across the uploading/analyzing/done transitions so it doesn’t flash.Run npm run dev, paste your API key, and drop a contract PDF. You should see the risk heatmap appear after about 30-60 seconds.
Finished app showing a PDF preview of the uploaded contract and risk heatmap cards sorted by severity
Finished app showing a PDF preview of the uploaded contract and risk heatmap cards sorted by severity

Source code

GitHub

Clone the repo and run locally

Replit

Run it in the browser