Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 31 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copy to .env.local and fill in real values:
# cp .env.example .env.local

# -----------------------------------------------------------------------------
# Auth (server-only — never expose these to the client)
# -----------------------------------------------------------------------------
KEYCLOAK_CLIENT_ID=your-keycloak-client-id
KEYCLOAK_CLIENT_SECRET=your-keycloak-client-secret
AUTH_ISSUER=https://your-keycloak-host/auth/realms/YourRealm
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=generate-a-long-random-secret-at-least-8-chars
END_SESSION_URL=https://your-keycloak-host/auth/realms/YourRealm/protocol/openid-connect/logout
REFRESH_TOKEN_URL=https://your-keycloak-host/auth/realms/YourRealm/protocol/openid-connect/token

# Optional: server-side GraphQL proxy target (falls back to NEXT_PUBLIC_BACKEND_URL)
# BACKEND_URL=http://localhost:9001

# -----------------------------------------------------------------------------
# Public / client (safe to expose — baked into the browser bundle)
# -----------------------------------------------------------------------------
NEXT_PUBLIC_BACKEND_URL=http://localhost:9001/graphql
NEXT_PUBLIC_BACKEND_BASE_URL=http://localhost:9001/
NEXT_PUBLIC_DATASPACE_API_URL=https://your-dataspace-api.example.com
NEXT_PUBLIC_DATASPACE_HOST=https://your-dataspace-host.example.com
NEXT_PUBLIC_AI_MAKER_URL=https://your-ai-maker.example.com/dashboard

# Optional
# NEXT_PUBLIC_PLATFORM_URL=http://localhost:3000
# NEXT_PUBLIC_GA_ID=
# NEXT_PUBLIC_DATASPACE_URL=
# NEXT_PUBLIC_IMAGE_REMOTE_HOSTS=
73 changes: 73 additions & 0 deletions .github/workflows/pre-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Build Status

on:
push:
branches: ['dev']
pull_request:
branches: ['dev']

jobs:
build:
runs-on: ubuntu-latest
# Only attach the "development" environment on push/merge — not on PRs.
# PRs still build & test without unlocking deployment env / triggering env status.
environment: ${{ github.event_name == 'push' && 'development' || '' }}
env:
NEXT_TELEMETRY_DISABLED: 1
# On push: vars/secrets come from the development environment.
# On PR: fall back to CI placeholders so next build / env.ts URL checks pass.
KEYCLOAK_CLIENT_ID: ${{ secrets.KEYCLOAK_CLIENT_ID || 'ci-client' }}
KEYCLOAK_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET || 'ci-secret-placeholder' }}
AUTH_ISSUER: ${{ secrets.AUTH_ISSUER || 'https://example.com/auth/realms/ci' }}
NEXTAUTH_URL: ${{ vars.NEXTAUTH_URL || secrets.NEXTAUTH_URL || 'http://localhost:3000' }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET || 'ci-nextauth-secret-min-8' }}
END_SESSION_URL: ${{ secrets.END_SESSION_URL || 'https://example.com/logout' }}
REFRESH_TOKEN_URL: ${{ secrets.REFRESH_TOKEN_URL || 'https://example.com/token' }}
BACKEND_URL: ${{ vars.BACKEND_URL || secrets.BACKEND_URL || 'http://localhost:9001' }}
NEXT_PUBLIC_BACKEND_URL: ${{ vars.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:9001/graphql' }}
NEXT_PUBLIC_BACKEND_BASE_URL: ${{ vars.NEXT_PUBLIC_BACKEND_BASE_URL || 'http://localhost:9001/' }}
NEXT_PUBLIC_DATASPACE_API_URL: ${{ vars.NEXT_PUBLIC_DATASPACE_API_URL || secrets.NEXT_PUBLIC_DATASPACE_API_URL || 'http://localhost:9001' }}
NEXT_PUBLIC_DATASPACE_HOST: ${{ vars.NEXT_PUBLIC_DATASPACE_HOST || secrets.NEXT_PUBLIC_DATASPACE_HOST || 'localhost' }}
NEXT_PUBLIC_AI_MAKER_URL: ${{ vars.NEXT_PUBLIC_AI_MAKER_URL || secrets.NEXT_PUBLIC_AI_MAKER_URL || 'http://localhost:3000' }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22.x
cache: 'npm'

- uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-

- id: npm_ci
run: npm ci --force
continue-on-error: true

- if: steps.npm_ci.outcome == 'failure'
run: npm install --force

- name: Type check
run: npx tsc --noEmit

- name: Test with coverage
run: npm run test:coverage

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
fail_ci_if_error: false

- name: Build
run: npm run build

- if: steps.npm_ci.outcome == 'failure'
run: |
echo "::error::npm ci failed: Run \`npm install --force\` locally and commit the updated package-lock.json"
exit 1
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Next.js](https://img.shields.io/badge/Next.js-14.2+-black.svg)](https://nextjs.org/)
[![React](https://img.shields.io/badge/React-18.2+-blue.svg)](https://reactjs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
[![codecov](https://codecov.io/gh/CivicDataLab/ParakhAI-frontend/graph/badge.svg)](https://app.codecov.io/gh/CivicDataLab/ParakhAI-frontend)

ParakhAI is a platform for evaluating AI models, creating prompt datasets, and running expert-led evaluations against those models.

Expand All @@ -17,7 +18,7 @@ ParakhAI is a platform for evaluating AI models, creating prompt datasets, and r

## Tech Stack

- **Framework:** Next.js
- **Framework:** Next.js
- **UI:** React + TypeScript + Tailwind CSS
- **Data:** GraphQL
- **Auth:** NextAuth + Keycloak
Expand Down
61 changes: 20 additions & 41 deletions features/ai-maker/components/manual-evaluation/ModuleSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use client";
'use client';

import { Tag, Text, Tooltip } from "opub-ui";
import React from "react";
import type { ModuleProgress } from "./types";
import styles from "../styles.module.scss";
import React from 'react';
import { Tag, Text, Tooltip } from 'opub-ui';
import styles from '../evaluations/styles.module.scss';
import type { ModuleProgress } from './types';

interface ModuleSelectorProps {
modules: string[];
Expand All @@ -14,9 +14,9 @@ interface ModuleSelectorProps {
}

const MODULE_DESCRIPTIONS: Record<string, string> = {
BIAS_FAIRNESS: "Checks whether model perpetuates stereotypes",
HALLUCINATION_MISINFORMATION: "Detects false or unsafe outputs",
PRIVACY_SAFETY: "Ensures personal data is not exposed",
BIAS_FAIRNESS: 'Checks whether model perpetuates stereotypes',
HALLUCINATION_MISINFORMATION: 'Detects false or unsafe outputs',
PRIVACY_SAFETY: 'Ensures personal data is not exposed',
};

const ModuleSelector: React.FC<ModuleSelectorProps> = ({
Expand All @@ -26,14 +26,12 @@ const ModuleSelector: React.FC<ModuleSelectorProps> = ({
onSelectModule,
getModuleDisplayName,
}) => {
const getProgressForModule = (
moduleName: string
): ModuleProgress | undefined => {
const getProgressForModule = (moduleName: string): ModuleProgress | undefined => {
return moduleProgress.find((p) => p.module === moduleName);
};

const getModuleDescription = (moduleName: string): string => {
return MODULE_DESCRIPTIONS[moduleName] || "";
return MODULE_DESCRIPTIONS[moduleName] || '';
};

return (
Expand All @@ -42,11 +40,10 @@ const ModuleSelector: React.FC<ModuleSelectorProps> = ({
Select Module to Test
</Text>
<Text variant="bodySm" className="text-gray-500 mb-4 block">
Choose a module to begin testing. The evaluation requires at least 3
test cases in total.
Choose a module to begin testing. The evaluation requires at least 3 test cases in total.
</Text>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{modules.map((module) => {
const progress = getProgressForModule(module);
const testCount = progress?.testCaseCount || 0;
Expand All @@ -61,51 +58,33 @@ const ModuleSelector: React.FC<ModuleSelectorProps> = ({
onClick={() => onSelectModule(module)}
className={`${styles.moduleselectioncard} text-left transition-all ${
isSelected
? "border-2 border-purple-600 bg-purple-50"
? 'border-purple-600 bg-purple-50 border-2'
: isComplete
? "border border-green-300 bg-green-50"
: ""
? 'border border-green-300 bg-green-50'
: ''
}`}
>
<Text
variant="bodyMd"
fontWeight="semibold"
className="mb-2 text-[#0A0704] block"
>
<Text variant="bodyMd" fontWeight="semibold" className="mb-2 block text-[#0A0704]">
{getModuleDisplayName(module)}
</Text>

<Text variant="bodySm" className="text-gray-600 mb-4 block">
{getModuleDescription(module)}
</Text>

<div
className={`${styles.moduleStatusTags} flex flex-wrap gap-2`}
>
<div className={`${styles.moduleStatusTags} flex flex-wrap gap-2`}>
<Tooltip content={`${testCount} Test Cases`}>
<Tag
variation="filled"
fillColor="#E5E7EB"
textColor="#374151"
>
<Tag variation="filled" fillColor="#E5E7EB" textColor="#374151">
{testCount} Test Cases
</Tag>
</Tooltip>
<Tooltip content={`${failedCount} Failed`}>
<Tag
variation="filled"
fillColor="#EC4899"
textColor="#FFFFFF"
>
<Tag variation="filled" fillColor="#EC4899" textColor="#FFFFFF">
{failedCount} Failed
</Tag>
</Tooltip>
<Tooltip content={`${passedCount} Passed`}>
<Tag
variation="filled"
fillColor="#D9F99D"
textColor="#15803D"
>
<Tag variation="filled" fillColor="#D9F99D" textColor="#15803D">
{passedCount} Passed
</Tag>
</Tooltip>
Expand Down
Loading
Loading