Dashboard Architecture
The dashboard is a Next.js 14 App Router application that provides a browser-based interface for AI Legal UK's analysis skills. It supports demo mode (fixture data, zero API cost) and live mode (real supported provider API calls with the user's own key).
Technology Stack
| Layer | Technology | Purpose |
|---|---|---|
| Framework | Next.js 14 (App Router) | Server-side rendering, API routes, file-system routing |
| UI Components | shadcn/ui + Radix UI primitives | Accessible, composable component library |
| Styling | Tailwind CSS + CSS variables | Utility-first styling with theme support (light/dark) |
| Language | TypeScript | Type safety across client and server |
| API Clients | @anthropic-ai/sdk plus fetch-based OpenAI Responses adapter | Provider-backed live analysis |
| State | localStorage (client-side) | Reviews, API key, mode preference |
Page Structure (9 Pages)
All pages use "use client" directives for client-side interactivity.
| Path | Component | Description |
|---|---|---|
/ | app/page.tsx | Main dashboard with skill cards, mode toggle, and quick-start actions |
/review | app/review/page.tsx | Upload and analyse a document -- file picker, skill selector, progress display |
/review/[id] | app/review/[id]/page.tsx | Completed review with score gauge, risk heatmap, clause cards, recommendations |
/compare | app/compare/page.tsx | Side-by-side contract comparison with diff highlighting |
/legislation | app/legislation/page.tsx | Browse and search UK legislation via the MCP server |
/skills | app/skills/page.tsx | Full skill catalog with descriptions and categories |
/generate | app/generate/page.tsx | Document generator for NDAs, terms, agreements, board packs |
/history | app/history/page.tsx | Past review history loaded from localStorage |
/settings | app/settings/page.tsx | API key configuration, mode preferences, theme settings |
API Routes (6 Endpoints)
| Endpoint | Method | Description |
|---|---|---|
/api/review | POST | Main analysis endpoint. Returns SSE stream when Accept: text/event-stream, otherwise buffered JSON |
/api/compare | POST | Contract comparison. Supports SSE streaming and JSON fallback |
/api/generate | POST | Document generation (NDA, terms, agreements, board packs) |
/api/anthropic/validate | POST | Validates an Anthropic key for the current settings UI; review execution also accepts OpenAI through the provider adapter |
/api/document/extract | POST | Server-side text extraction from uploaded files (multipart/form-data) |
/api/legislation/search | GET | Proxies search requests to legislation.gov.uk |
Core Library Modules (dashboard/lib/)
api.ts -- Central API Integration
The largest and most critical module. Contains the provider-neutral prompt orchestration plus Anthropic and OpenAI review adapters.
SKILL_PROMPTS Map (26 Skills)
A Record<string, string> mapping skill IDs to their analysis prompts. Each prompt defines the persona, assessment criteria, and output format for a specific skill:
const SKILL_PROMPTS: Record<string, string> = {
"legal-review": `You are an expert UK commercial contract lawyer...`,
"legal-employment": `You are an expert UK employment lawyer...`,
"legal-ir35": `You are an expert UK tax and employment status specialist...`,
"legal-compliance": `You are an expert UK regulatory compliance auditor...`,
"legal-risks": `...`,
"legal-missing": `...`,
"legal-plain": `...`,
"legal-freelancer": `...`,
"legal-property": `...`,
"legal-corporate": `...`,
"legal-negotiate": `...`,
"legal-aml": `...`,
"legal-consumer": `...`,
"legal-esg": `...`,
"legal-dispute": `...`,
"legal-ai-compliance": `...`,
"legal-benchmark": `...`,
"legal-due-diligence": `...`,
"legal-regulatory-calendar": `...`,
"legal-legislation-tracker": `...`,
"legal-gdpr": `...`,
"legal-tenancy": `...`,
"legal-ip": `...`,
"legal-debt": `...`,
"legal-immigration": `...`,
"legal-wills": `...`,
};REVIEW_OUTPUT_SCHEMA -- Shared Review Schema
A shared JSON schema named submit_review standardises structured review output across providers. The schema defines all possible fields across all review types:
| Section | Fields |
|---|---|
| Common | type (enum: contract/employment/ir35/compliance), documentName, score, grade, summary, clauses[], recommendations[], metadata |
| IR35-specific | ir35Score, status (inside/outside/borderline), confidence, factors[], riskIndicators[], contractAmendments[], financialExposure |
| Employment-specific | era2025Dashboard[], equalityActMatrix[], obligations[], financialExposure |
| Compliance-specific | frameworks[], checkItems[] |
Required fields: type, documentName, summary, metadata.
SKILL_TO_TYPE Mapping
Maps every skill in the live-review allowlist (defined in dashboard/lib/api-route-utils.ts as VALID_LIVE_REVIEW_SKILLS) to one of four review types. The allowlist itself is the single authoritative source of "which skills can run live"; the table below is a snapshot for reference only:
| Review Type | Skills |
|---|---|
contract | legal-review, legal-risks, legal-missing, legal-plain, legal-freelancer, legal-property, legal-corporate, legal-negotiate, legal-dispute, legal-benchmark, legal-due-diligence, legal-tenancy, legal-ip, legal-debt, legal-wills |
employment | legal-employment |
ir35 | legal-ir35 |
compliance | legal-compliance, legal-aml, legal-consumer, legal-esg, legal-ai-compliance, legal-regulatory-calendar, legal-legislation-tracker, legal-gdpr, legal-immigration |
runSkillAnalysis(apiKey, skillId, documentText)
Non-streaming analysis function (kept for backward compatibility):
- Looks up the skill prompt from
SKILL_PROMPTS - Selects the requested provider adapter and passes the user API key
- Calls the provider adapter with the shared prompt, document text, and review schema
- Extracts the provider structured output
- Calls
buildReview()to construct a typedReviewobject - Falls back to a generic review wrapper when a provider returns unstructured text
streamSkillAnalysis(apiKey, skillId, documentText)
Returns a ReadableStream<Uint8Array> of SSE events for real-time progress:
- Uses Anthropic streaming where available; non-streaming providers fall back to buffered analysis
- Emits
progressevents as chunks arrive (mapped to stages: "Analysing clauses...", "Reviewing provisions...", "Scoring risks...", "Finalising recommendations...") - Progress percent scales from 15% to 85% based on chunk count
- On completion, emits
progressat 100% followed byresultwith the complete Review JSON - On error, emits
errorevent with the message
// SSE event format
function sseEvent(event: string, data: unknown): string {
return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
}api-route-utils.ts -- Request Validation
Shared validation logic for all API route handlers.
| Export | Purpose |
|---|---|
VALID_LIVE_REVIEW_SKILLS | Readonly array of skill IDs enabled for live analysis — the canonical allowlist consulted on every POST /api/review |
MAX_LIVE_DOCUMENT_CHARS | 50_000 -- maximum character count for uploaded documents |
ANTHROPIC_VALIDATE_URL | https://api.anthropic.com/v1/messages |
ANTHROPIC_VALIDATE_MODEL | Default: claude-sonnet-4-20250514. Override with the ANALYSIS_MODEL env var. |
validateReviewRouteBody() | Validates API key (must start with sk-), skill (must be in allowlist), text (non-empty, within char limit) |
normalizeApiKey() | Trims whitespace from API key strings |
mapReviewRouteError() | Maps error messages to appropriate HTTP status codes (400, 401, 429, 500) |
buildAnthropicValidationRequest() | Constructs a minimal API request to validate a key |
Validation Flow
Request body → normalizeApiKey() → validateReviewRouteBody()
├─ 401: Missing/invalid API key
├─ 400: Invalid skill or missing text
├─ 413: Document exceeds 50,000 chars
└─ 200: Valid (returns apiKey, skill, text)document-extraction.ts -- Text Extraction
Server-side text extraction supporting 4 file formats:
| Format | Primary Method | Fallback |
|---|---|---|
.txt | Raw UTF-8 read | -- |
.md | Raw UTF-8 read | -- |
.docx | textutil -convert txt -stdout (macOS) | python3 -c "from docx import Document..." (python-docx) |
.pdf | pdftotext -layout (poppler) | python3 -c "from pypdf import PdfReader..." (pypdf) |
The module auto-detects available backends at startup and caches the result. If PDF text extraction yields insufficient text (scanned document), it falls back to OCR.
document-ocr.ts -- OCR Fallback
OCR fallback for scanned PDFs. Triggered when pdftotext or pypdf returns minimal text.
| Provider | Status | Configuration |
|---|---|---|
openai | Implemented | OPENAI_API_KEY + OCR_OPENAI_MODEL env vars |
google | Stub | Recognised but not implemented |
aws | Stub | Recognised but not implemented |
azure | Stub | Recognised but not implemented |
tesseract | Stub | Recognised but not implemented |
Provider selection: OCR_PROVIDER or AI_LEGAL_UK_OCR_PROVIDER env var.
review-utils.ts -- Review Lifecycle
Helpers for creating, updating, and transforming review objects. Includes scoring helpers and review merge logic.
storage.ts -- Client-Side Persistence
Uses localStorage for all client-side state:
| Key | Content | Type |
|---|---|---|
ai-legal-uk-mode | Current mode | "demo" or "live" |
ai-legal-uk-reviews | Saved review objects | Review[] (JSON-serialised) |
ai-legal-uk-api-key | User.s provider API key | string |
Cross-tab synchronisation via a custom ai-legal-uk:storage-sync event and the native storage event.
| Function | Purpose |
|---|---|
getMode() / setMode() | Read/write demo or live mode |
getApiKey() / setApiKey() / clearApiKey() | Manage the API key |
getReviews() / saveReview() | Read all reviews / merge and save a new review |
subscribeToStorageSync() | Subscribe to storage change events across tabs |
skill-registry.json / skills-data.ts -- Skill Catalog
dashboard/lib/skill-registry.json is the canonical registry for all 38 skills. skills-data.ts derives the dashboard Skill[] array from that registry so routes, input contracts, and live-review capability metadata stay in one place:
interface Skill {
id: string; // e.g., "legal-review"
name: string; // e.g., "Contract Review"
command: string; // e.g., "/legal review"
category: SkillCategory;
description: string;
route: "/review" | "/compare" | "/generate" | "/history" | "/skills";
inputContract: "text" | "comparison" | "generation" | "report" | "utility";
liveReview: boolean;
}Categories: Contract Analysis, Property Law, Document Generation, Compliance & Reporting, Employment & Corporate, Consumer & ESG, Platform Tools, Business Intelligence, Specialist.
types.ts -- Type Definitions
The Review union type and all supporting interfaces:
type Review = ContractReview | EmploymentReview | IR35Assessment | ComplianceAudit| Interface | Key Fields |
|---|---|
ContractReview | type: 'contract', score, grade, clauses[], recommendations[], metadata |
EmploymentReview | type: 'employment', era2025Dashboard[], equalityActMatrix[], obligations[], financialExposure |
IR35Assessment | type: 'ir35', ir35Score, status (inside/outside/borderline), confidence, factors[] |
ComplianceAudit | type: 'compliance', frameworks[], checkItems[] |
Supporting types: Clause, Recommendation, DocumentMetadata, ERA2025Check, EqualityActCheck, Obligation, FinancialExposure, IR35Factor, FrameworkScore, ComplianceCheckItem.
legislation.ts and linkify-statutes.tsx
legislation.ts-- Statute data and types for the legislation search UI (LegislationSearchResult,LegislationDetail)linkify-statutes.tsx-- React component that automatically converts statute references (e.g., "Companies Act 2006") in analysis text into clickable links to legislation.gov.uk
UI Components (dashboard/components/)
Layout Components
| Component | Details |
|---|---|
| Sidebar | 280px fixed width on desktop. Collapsible as a Radix Sheet on mobile. Contains skill categories with navigation links. |
| Header | Top bar with mode toggle (demo/live) and theme toggle (light/dark). Shows current page title. |
Analysis Components
| Component | Details |
|---|---|
| FileUpload | Drag-and-drop zone + file picker + paste support. Accepts PDF, DOCX, TXT, MD. Shows upload progress. |
| ScoreGauge | SVG circular gauge displaying score 0--100. Animated on mount. Colour-coded: green (75+), amber (50--74), red (below 50). |
| RiskHeatmap | Interactive grid showing risk distribution across clauses. Cells colour-coded by severity. Clickable to jump to clause detail. |
| ClauseCard | Expandable card for individual clause analysis. Shows clause reference, title, risk score badge, issues list, and recommendation. Copy-to-clipboard support. |
Control Components
| Component | Details |
|---|---|
| ModeToggle | Switches between demo (fixture data) and live (API) modes. When switching to live, validates the API key by calling /api/anthropic/validate. Shows error if key is invalid. |
| ThemeToggle | Light/dark theme switcher using CSS variables and next-themes. Persists preference. |
Theming
The dashboard uses CSS variables for theming, allowing light and dark modes:
- Colour variables defined in
globals.cssusing HSL values - Theme switching via
next-themeswithclassstrategy - shadcn/ui components automatically respect the active theme
- All custom components use Tailwind CSS utilities that reference the CSS variables
API Key Security Model
Important Security Design
The dashboard server does not store or hold any ANTHROPIC_API_KEY. The user's API key is stored exclusively in their browser's localStorage and sent with each request body. Route handlers pass it directly to the Anthropic SDK without any server-side persistence.
Browser (localStorage) → Request body → Route handler → Provider adapter → Model APIThis design means:
- No server-side secrets management required
- Each user brings their own API key and billing
- The server cannot access analysis data after the request completes
- API keys never touch disk on the server side
- No user accounts or authentication needed