Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ddf0f3f
feat(*): credentials management for org, project
Ayush8923 Apr 10, 2026
d65eb09
fix(*): use the page header component
Ayush8923 Apr 10, 2026
3b8ad54
fix(*): some cleanups
Ayush8923 Apr 11, 2026
3b54093
fix(*): added styling
Ayush8923 Apr 11, 2026
7f392ba
feat(*): added the middleware so that not anyone can access the setti…
Ayush8923 Apr 11, 2026
3e09217
fix(*): added scope for adding more pathname in middleware
Ayush8923 Apr 11, 2026
9d05d60
fix(*): if the user is authenticate then they will able to access the…
Ayush8923 Apr 11, 2026
e6062b9
Merge branch 'feat/email-validation-flow' into feat/credentials-manag…
Ayush8923 Apr 11, 2026
1bb615d
fix(*): remove the unwanted js comment
Ayush8923 Apr 11, 2026
b445e79
fix(*): update the setting config name and use the tailwind class ins…
Ayush8923 Apr 12, 2026
376a1a8
fix(*): few cleanups
Ayush8923 Apr 12, 2026
c2ae99b
fix(*): update the auth cookies js comment
Ayush8923 Apr 12, 2026
1b207f9
fix(*): update the middleware js comment
Ayush8923 Apr 12, 2026
93bf616
fix(*): use the append method instead of set
Ayush8923 Apr 12, 2026
370e93f
fix(*): evaluation UX inconsistency
Ayush8923 Apr 14, 2026
54c88d9
Merge branch 'feat/email-validation-flow' into feat/credentials-manag…
Ayush8923 Apr 14, 2026
00c3e35
fix(*): remove the unwanted instructions files
Ayush8923 Apr 15, 2026
8d43229
Merge branch 'feat/email-validation-flow' into feat/credentials-manag…
Ayush8923 Apr 15, 2026
ae04095
Merge branch 'feat/credentials-management' into fix/evaluation-ui-inc…
Ayush8923 Apr 16, 2026
61a6802
fix(*): update the claude.md
Ayush8923 Apr 16, 2026
626ef92
fix(*): cleanups regarding the tailwind and inline style
Ayush8923 Apr 16, 2026
bfb5437
fix(*): few updates and fixes
Ayush8923 Apr 16, 2026
56f733f
fix(*): few updates and fixes
Ayush8923 Apr 16, 2026
dcaa724
fix(*): remove the unused file
Ayush8923 Apr 16, 2026
8e65e96
fix(*): remove the js comments
Ayush8923 Apr 16, 2026
33363dd
fix(*): fix infinite scroll
Ayush8923 Apr 16, 2026
66fbf41
Merge branch 'feat/credentials-management' of https://github.com/Proj…
Ayush8923 Apr 16, 2026
06e8645
Merge branch 'feat/credentials-management' into fix/evaluation-ui-inc…
Ayush8923 Apr 16, 2026
fadbdd8
Merge branch 'feat/email-validation-flow' into fix/evaluation-ui-inco…
Ayush8923 Apr 17, 2026
c5802f4
fix(*): cleanups and updates
Ayush8923 Apr 17, 2026
bd72e38
fix(*): added the cursor pointer class
Ayush8923 Apr 17, 2026
31b4fab
fix(*): revert the column inconsistency changes
Ayush8923 Apr 17, 2026
29f542e
fix(*): cleanups
Ayush8923 Apr 17, 2026
f9c7dbf
fix(*): remove the js comments and reset the state after create the e…
Ayush8923 Apr 17, 2026
20d0672
fix(*): cleanups in icons
Ayush8923 Apr 17, 2026
519d447
fix(*): added the classname
Ayush8923 Apr 17, 2026
00d3b77
fix(*): update the claude.md file
Ayush8923 Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
npm run dev # Start dev server at http://localhost:3000
npm run lint # Run ESLint
npm run build # Production build
npm run format # Format TS/tsx with Prettier
```

## Architecture Overview

### Tech Stack

- **Framework**: Next.js 16.0.7 (App Router)
- **React 19.2.0** + **TypeScript** (strict mode)
- **Routing**: Next.js App Router (react-router-dom is listed as a dependency but currently unused)
- **Styling**: Tailwind CSS 4.x + custom color system and styles defined in `/app/globals.css` for cases not supported by Tailwind
- **Data Fetching**: Native Fetch API + SWR 2.3.6 (used selectively where required)
- **Date/Time**: date-fns 4.1.0, date-fns-tz 3.2.0
- **ESLint**: Used for maintaining code quality, enforcing consistent coding standards, and catching potential issues during development and build time

### Directory Structure

| Path | Purpose |
| ------------------------------ | --------------------------------------------------------------------------------------------- |
| `app/(auth)/` | Authentication-related routes (e.g., invite, verify flows) |
| `app/(main)/` | Main application routes (dashboard-level features like datasets, evaluations, settings, etc.) |
| `app/api/` | Backend API route handlers (Next.js route handlers acting as BFF layer) |
| `app/components/` | App-scoped components used within routes/Pages |
| `app/components/icons/` | Hand-authored React icon components |
| `app/hooks/` | Custom React hooks specific to app features |
| `app/lib/` | Core shared logic and utilities across the application |
| `app/lib/context/` | React context providers (global state handling) |
| `app/lib/store/` | State management logic (custom/global store) |
| `app/lib/types/` | TypeScript type definitions (shared across modules) |
| `app/lib/utils/` | Domain-specific utility modules (e.g., evaluation, guardrails) |
| `app/lib/data/` | Static data and validators (e.g., guardrails validators) |
| `app/lib/apiClient.ts` | Centralized API client for forwarding requests to the backend |
| `app/lib/authCookie.ts` | Authentication cookie utilities (get/set/remove tokens) |
| `app/lib/configFetchers.ts` | API fetchers related to configuration modules |
| `app/lib/constants.ts` | Global constants used across the app |
| `app/lib/guardrailsClient.ts` | Client-side API helpers for guardrails features |
| `app/lib/models.ts` | Data models/interfaces for structured data handling |
| `app/lib/navConfig.ts` | Navigation configuration (sidebar/menu structure) |
| `app/lib/promptEditorUtils.ts` | Utility functions for prompt editor logic |
| `app/lib/utils.ts` | General utility/helper functions |
| `public/favicon.ico` | Application favicon |

## Import Aliases

[tsconfig.json](./tsconfig.json) sets paths: `{ "@/*": ["./*"] }`, so imports are resolved from the project root using the `@/` prefix. Use:

```
import { apiClient } from '@/app/lib/apiClient';
import { Providers } from '@/app/components/providers';
import { APP_NAME } from '@/app/lib/constants';
```

SVGs follow Next.js defaults (imported as static assets via next/image or referenced from /public).

## Routing & Role-Based Access

Routing uses the **Next.js App Router** exclusively. Routes are organized via route groups:

- `app/(auth)/` - unauthenticated flows (`/invite`, `/verify`)
- `app/(main)/` — authenticated app surface (`/evaluations`, `/datasets`, `/configurations`, `/guardrails`, `/knowledge-base`, `/settings`, etc.)

Role gating lives in middleware.ts and reads a kaapi_role cookie with two values:

- `user` - standard authenticated user
- `superuser` - admin; required for `/settings/*`

The cookie is issued server-side by [authCookie.ts](app/lib/authCookie.ts) after login/verify based on user.is_superuser. Middleware classifies each request into one of:

- `PUBLIC_ROUTES` — open to everyone (`/evaluations`, `/invite`, `/verify`, `/coming-soon/*`)
- `GUEST_ONLY_ROUTES` — unauthenticated only (`/keystore`); authenticated users are redirected to `/evaluations`
- `/settings/*` — superuser only
- Everything else — any authenticated user

There is no dynamic/custom role system; only the two static roles above.

## Toast Notifications

Toasts are managed via a React Context provider ([Toast.tsx](app/components/Toast.tsx)), mounted once in [Providers.tsx](app/components/providers/Providers.tsx). Consume them from any client component:

```
import { useToast } from '@/app/components/Toast';
// or the re-export: import { useToast } from '@/app/hooks/useToast';

function MyComponent() {
const toast = useToast();

toast.success('Saved successfully'); // success toast
toast.error('Something went wrong'); // error toast
toast.warning('Heads up'); // warning toast
toast.info('FYI'); // info toast

// Optional: override the default 5000ms auto-dismiss
toast.success('Saved', 3000);

// Low-level API (type + duration)
toast.addToast('Custom message', 'success', 4000);
}
```

## Authentication [AuthContext.tsx](app/lib/context/AuthContext.tsx)

There is no `AuthService` class. Auth state is owned by a React Context provider (`AuthProvider`) mounted in [Providers.tsx](app/components/providers/Providers.tsx), and consumed via the `useAuth()` hook:

```
import { useAuth } from '@/app/lib/context/AuthContext';

function MyComponent() {
const {
isAuthenticated, isHydrated,
session, currentUser, googleProfile,
apiKeys, activeKey, addKey, removeKey, setKeys,
loginWithToken, logout,
} = useAuth();
}
```

## App Context [AppContext.tsx](app/lib/context/AppContext.tsx)

Sidebar state is managed via `AppProvider`, consumed with `useApp()`:

```
import { useApp } from '@/app/lib/context/AppContext';

const { sidebarCollapsed, setSidebarCollapsed, toggleSidebar } = useApp();
```

## API Client & Error Handling

The BFF layer uses [apiClient.ts](app/lib/apiClient.ts) which forwards requests from Next.js route handlers to the backend at `BACKEND_URL` (defaults to `http://localhost:8000`). Key patterns:

- **Server-side (route handlers)**: Use `apiClient(request, endpoint, options)` — it relays `X-API-KEY` and `Cookie` headers automatically and returns `{ status, data, headers }`.
- **Client-side**: Use `clientFetch(endpoint, options)` — handles token refresh on 401, dispatches `AUTH_EXPIRED_EVENT` when refresh fails, and throws with a message extracted from `error`, `message`, or `detail` fields in the response body.
- **Error extraction**: `extractErrorMessage(body, fallback)` reads `body.error || body.message || body.detail` — follow this pattern when adding new API routes.
- **Auth expiry**: On 401 with failed refresh, a `CustomEvent(AUTH_EXPIRED_EVENT)` is dispatched on `window`, which `AuthContext` listens to for automatic logout.
2 changes: 1 addition & 1 deletion app/(main)/configurations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { colors } from "@/app/lib/colors";
import { usePaginatedList, useInfiniteScroll } from "@/app/hooks";
import ConfigCard from "@/app/components/ConfigCard";
import Loader, { LoaderBox } from "@/app/components/Loader";
import { EvalJob } from "@/app/components/types";
import { EvalJob } from "@/app/lib/types/evaluation";
import {
ConfigPublic,
ConfigVersionItems,
Expand Down
101 changes: 18 additions & 83 deletions app/(main)/evaluations/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ import { useRouter, useParams } from "next/navigation";
import { apiFetch } from "@/app/lib/apiClient";
import { useAuth } from "@/app/lib/context/AuthContext";
import { useApp } from "@/app/lib/context/AppContext";
import {
import type {
EvalJob,
AssistantConfig,
GroupedTraceItem,
} from "@/app/lib/types/evaluation";
import {
hasSummaryScores,
isNewScoreObjectV2,
getScoreObject,
normalizeToIndividualScores,
GroupedTraceItem,
isGroupedFormat,
} from "@/app/components/types";
} from "@/app/lib/utils/evaluation";
import ConfigModal from "@/app/components/ConfigModal";
import Sidebar from "@/app/components/Sidebar";
import DetailedResultsTable from "@/app/components/DetailedResultsTable";
import DetailedResultsTable from "@/app/components/evaluations/DetailedResultsTable";
import { colors } from "@/app/lib/colors";
import { useToast } from "@/app/components/Toast";
import Loader from "@/app/components/Loader";
Expand Down Expand Up @@ -126,7 +128,6 @@ export default function EvaluationReport() {
if (isAuthenticated && jobId) fetchJobDetails();
}, [isAuthenticated, jobId, fetchJobDetails]);

// Export grouped format CSV
const exportGroupedCSV = (traces: GroupedTraceItem[]) => {
if (!job) return;
try {
Expand Down Expand Up @@ -391,9 +392,9 @@ export default function EvaluationReport() {
>
<ChevronLeftIcon />
</button>
<div className="min-w-0 flex items-center gap-3">
<div className="min-w-0 flex-1 flex items-center gap-3 overflow-hidden">
<h1
className="text-base font-semibold truncate"
className="text-base font-semibold truncate min-w-0"
style={{
color: colors.text.primary,
letterSpacing: "-0.01em",
Expand All @@ -411,99 +412,33 @@ export default function EvaluationReport() {
</div>
</div>

{/* Actions */}
<div className="flex items-center gap-3 flex-shrink-0">
<div className="flex items-center gap-3 flex-shrink-0 relative z-10">
<div
className="inline-flex rounded-lg p-0.5"
style={{ backgroundColor: colors.bg.secondary }}
>
<button
type="button"
onClick={() => setExportFormat("row")}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all cursor-pointer"
style={{
backgroundColor:
exportFormat === "row"
? colors.bg.primary
: "transparent",
color:
exportFormat === "row"
? colors.text.primary
: colors.text.primary,
boxShadow:
exportFormat === "row"
? "0 1px 2px rgba(0,0,0,0.08)"
: "none",
border:
exportFormat === "row"
? `1px solid ${colors.border}`
: "1px solid transparent",
}}
onMouseEnter={(e) => {
if (exportFormat !== "row") {
e.currentTarget.style.backgroundColor =
"rgba(0,0,0,0.04)";
e.currentTarget.style.boxShadow =
"0 0 0 1px rgba(0,0,0,0.06)";
}
}}
onMouseLeave={(e) => {
if (exportFormat !== "row") {
e.currentTarget.style.backgroundColor = "transparent";
e.currentTarget.style.boxShadow = "none";
}
}}
data-selected={exportFormat === "row"}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all cursor-pointer border border-transparent text-text-primary hover:bg-black/4 hover:shadow-[0_0_0_1px_rgba(0,0,0,0.06)] data-[selected=true]:bg-bg-primary data-[selected=true]:border-border data-[selected=true]:shadow-[0_1px_2px_rgba(0,0,0,0.08)] data-[selected=true]:hover:bg-bg-primary data-[selected=true]:hover:shadow-[0_1px_2px_rgba(0,0,0,0.08)]"
>
<MenuIcon className="w-3.5 h-3.5" />
<MenuIcon className="w-3.5 h-3.5 pointer-events-none" />
Individual Rows
</button>
<button
type="button"
onClick={() => setExportFormat("grouped")}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all cursor-pointer"
style={{
backgroundColor:
exportFormat === "grouped"
? colors.bg.primary
: "transparent",
color:
exportFormat === "grouped"
? colors.text.primary
: colors.text.primary,
boxShadow:
exportFormat === "grouped"
? "0 1px 2px rgba(0,0,0,0.08)"
: "none",
border:
exportFormat === "grouped"
? `1px solid ${colors.border}`
: "1px solid transparent",
}}
onMouseEnter={(e) => {
if (exportFormat !== "grouped") {
e.currentTarget.style.backgroundColor =
"rgba(0,0,0,0.04)";
e.currentTarget.style.boxShadow =
"0 0 0 1px rgba(0,0,0,0.06)";
}
}}
onMouseLeave={(e) => {
if (exportFormat !== "grouped") {
e.currentTarget.style.backgroundColor = "transparent";
e.currentTarget.style.boxShadow = "none";
}
}}
data-selected={exportFormat === "grouped"}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all cursor-pointer border border-transparent text-text-primary hover:bg-black/4 hover:shadow-[0_0_0_1px_rgba(0,0,0,0.06)] data-[selected=true]:bg-bg-primary data-[selected=true]:border-border data-[selected=true]:shadow-[0_1px_2px_rgba(0,0,0,0.08)] data-[selected=true]:hover:bg-bg-primary data-[selected=true]:hover:shadow-[0_1px_2px_rgba(0,0,0,0.08)]"
>
<GroupIcon />
<GroupIcon className="pointer-events-none" />
Group by Questions
</button>
</div>
<button
onClick={() => setIsConfigModalOpen(true)}
className="px-3 py-1.5 rounded-md text-xs font-medium border"
style={{
backgroundColor: "transparent",
borderColor: colors.border,
color: colors.text.primary,
}}
className="px-3 py-1.5 rounded-md text-xs font-medium border bg-transparent border-border text-text-primary"
>
View Config
</button>
Expand Down
8 changes: 4 additions & 4 deletions app/(main)/evaluations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,8 @@ function SimplifiedEvalContent() {
const [duplicationFactor, setDuplicationFactor] = useState("1");
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
const [isUploading, setIsUploading] = useState(false);

// Stored datasets
const [storedDatasets, setStoredDatasets] = useState<Dataset[]>([]);
const [isDatasetsLoading, setIsDatasetsLoading] = useState(false);

// Evaluation config state
const [selectedDatasetId, setSelectedDatasetId] = useState<string>(() => {
return searchParams.get("dataset") || "";
});
Expand Down Expand Up @@ -235,6 +231,10 @@ function SimplifiedEvalContent() {
});

setIsEvaluating(false);
setExperimentName("");
setSelectedDatasetId("");
setSelectedConfigId("");
setSelectedConfigVersion(0);
toast.success(`Evaluation created!`);
return true;
} catch (error: unknown) {
Expand Down
13 changes: 13 additions & 0 deletions app/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { ReactNode } from "react";

interface CodeBlockProps {
children: ReactNode;
}

export default function CodeBlock({ children }: CodeBlockProps) {
return (
<div className="text-sm font-mono px-3 py-2.5 rounded-md whitespace-pre-wrap max-h-60 overflow-y-auto leading-[1.6] bg-bg-secondary text-text-primary">
{children}
</div>
);
}
Loading
Loading