Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ build/
*.egg-info/
dist/
.pytest_cache/
*.ipynb
.ipynb_checkpoints/
*.coverage
htmlcov/

# AI service specific
services/ai-service/data/evaluations/
services/ai-service/data/reports/
services/ai-service/test.ipynb

# Temporary files
*.tmp
Expand Down
2 changes: 2 additions & 0 deletions .github/.copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Treat all backends as APIs; do not couple services together.
- The AI service is stateless and must not call application APIs directly.
- always use pnpm and pnpx for package management
- don't write unnecessary markdown files or documentation, prefer inline code comments and self documenting code where possible.

## Monorepo Structure

Expand Down Expand Up @@ -32,6 +33,7 @@
- Use Tailwind CSS for styling.
- Prefer shadcn/ui components where applicable.
- Do not create ad-hoc UI components in app folders.
- [tanstack-start](https://tanstack.com/start/llms.txt) is the frontend application framework

## Forms & State

Expand Down
76 changes: 76 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Build and Push Docker Images

on:
push:
branches:
- main
- dev
tags:
- 'v*'
pull_request:
branches:
- main
- dev
workflow_dispatch:

env:
REGISTRY: docker.io

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

strategy:
matrix:
include:
- dockerfile: ai.dockerfile
image: governance-agent-ai_service
context: .
- dockerfile: next.dockerfile
image: governance-agent-frontend
context: .

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64

- name: Image digest
run: echo "Image pushed with digest ${{ steps.build.outputs.digest }}"
41 changes: 39 additions & 2 deletions ai.dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,55 @@
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
# Build stage
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder

WORKDIR /app

ENV UV_COMPILE_BYTECODE=1
ENV PYTHONPATH=/app/src
ENV UV_INDEX_STRATEGY=unsafe-best-match
ENV UV_HTTP_TIMEOUT=300

COPY services/ai-service/pyproject.toml ./
# Copy dependency files for caching
COPY services/ai-service/pyproject.toml services/ai-service/uv.lock ./

# Install CPU-only PyTorch to reduce image size dramatically
RUN uv pip install --system --no-cache \
torch==2.5.1+cpu \
--index-url https://download.pytorch.org/whl/cpu

# Install other dependencies
RUN uv sync --no-install-project --no-dev

COPY services/ai-service/ ./

RUN uv sync --no-dev

# Remove unnecessary files
RUN find /app/.venv -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
RUN find /app/.venv -type f -name "*.pyc" -delete
RUN find /app/.venv -type f -name "*.pyo" -delete
RUN find /app/.venv -name "tests" -type d -exec rm -rf {} + 2>/dev/null || true
RUN find /app/.venv -name "test" -type d -exec rm -rf {} + 2>/dev/null || true

# Runtime stage
FROM python:3.13-slim-bookworm

WORKDIR /app

ENV PYTHONPATH=/app/src
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

# Install runtime dependencies only
RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*

# Copy only the virtual environment and application code
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/src /app/src
COPY --from=builder /app/run.py /app/run.py
COPY --from=builder /app/main.py /app/main.py

# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
Expand Down
5 changes: 4 additions & 1 deletion apps/tanstack-start/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"format": "prettier --check . --ignore-path ../../.gitignore --ignore-path .prettierignore",
"lint": "eslint --flag unstable_native_nodejs_ts_config",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
"with-env": "dotenv -e ../../.env.local --"
},
"dependencies": {
"@fontsource-variable/geist": "^5.2.8",
Expand All @@ -19,6 +19,7 @@
"@governance/auth": "workspace:*",
"@governance/db": "workspace:*",
"@governance/ui": "workspace:*",
"@radix-ui/themes": "^3.3.0",
"@t3-oss/env-core": "^0.13.8",
"@tanstack/react-form": "catalog:",
"@tanstack/react-query": "catalog:",
Expand All @@ -29,6 +30,8 @@
"@trpc/client": "catalog:",
"@trpc/server": "catalog:",
"@trpc/tanstack-react-query": "catalog:",
"@workos-inc/widgets": "^1.8.0",
"@workos-inc/widgets-i18n": "^0.1.0",
"@workos/authkit-tanstack-react-start": "^0.5.0",
"nitro": "3.0.1-alpha.1",
"react": "catalog:react19",
Expand Down
42 changes: 22 additions & 20 deletions apps/tanstack-start/src/component/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { Link, useLocation } from "@tanstack/react-router";

export function NotFound() {
const location = useLocation();
console.log("NotFound triggered for path:", location.pathname);
const location = useLocation();
console.log("NotFound triggered for path:", location.pathname);

return (
<div className="flex flex-col items-center justify-center min-h-screen bg-background text-foreground p-4">
<h1 className="text-9xl font-extrabold tracking-widest text-primary">404</h1>
<div className="bg-primary px-2 text-sm rounded rotate-12 absolute text-primary-foreground">
Page Not Found
</div>
<div className="mt-8 text-center">
<div className="text-xl font-medium mb-4">
Oops! The page you asked for doesn't exist.
</div>
<Link
to="/"
className="px-6 py-3 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors duration-200"
>
Go Home
</Link>
</div>
return (
<div className="bg-background text-foreground flex min-h-screen flex-col items-center justify-center p-4">
<h1 className="text-primary text-9xl font-extrabold tracking-widest">
404
</h1>
<div className="bg-primary text-primary-foreground absolute rotate-12 rounded px-2 text-sm">
Page Not Found
</div>
<div className="mt-8 text-center">
<div className="mb-4 text-xl font-medium">
Oops! The page you asked for doesn't exist.
</div>
);
<Link
to="/"
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-6 py-3 transition-colors duration-200"
>
Go Home
</Link>
</div>
</div>
);
}
4 changes: 2 additions & 2 deletions apps/tanstack-start/src/component/auth-showcase.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useAuth } from "@workos/authkit-tanstack-react-start/client";
import { getSignInUrl } from "@workos/authkit-tanstack-react-start";
import { createServerFn } from "@tanstack/react-start";
import { getSignInUrl } from "@workos/authkit-tanstack-react-start";
import { useAuth } from "@workos/authkit-tanstack-react-start/client";

import { Button } from "@governance/ui/button";

Expand Down
101 changes: 49 additions & 52 deletions apps/tanstack-start/src/component/index/IndexCard.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,60 @@
import { Link } from "@tanstack/react-router";

import { cn } from "@governance/ui";

interface IndexCardProps {
title: string;
subtitle: string;
isActive: boolean;
href?: string;
title: string;
subtitle: string;
isActive: boolean;
href?: string;
}

export function IndexCard({ title, subtitle, isActive, href }: IndexCardProps) {
const CardContent = (
<div
className={cn(
"group relative overflow-hidden rounded-2xl border-2 p-8 transition-all duration-300",
isActive
? "border-ndmo-blue-medium bg-gradient-to-br from-white to-ndmo-blue-pale hover:shadow-2xl hover:scale-105 cursor-pointer"
: "border-ndmo-gray-light bg-ndmo-gray-light/30 cursor-not-allowed opacity-60"
)}
>
{/* Active Indicator */}
{isActive && (
<div className="absolute top-6 right-6">
<div className="relative">
<div className="h-4 w-4 rounded-full bg-ndmo-green pulse-indicator" />
<div className="absolute inset-0 h-4 w-4 rounded-full bg-ndmo-green/30 animate-ping" />
</div>
</div>
)}

{/* Content */}
<div className="space-y-3">
<h2 className="text-3xl font-bold text-ndmo-blue-dark">
{title}
</h2>
<p className="text-sm font-medium text-ndmo-gray-medium">
{subtitle}
</p>
</div>

{/* Active Label */}
{isActive && (
<div className="mt-6 flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-ndmo-green" />
<span className="text-xs font-semibold text-ndmo-green uppercase tracking-wide">
Active
</span>
</div>
)}

{/* Hover Effect */}
{isActive && (
<div className="absolute inset-0 bg-gradient-to-br from-ndmo-blue-medium/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
)}
const CardContent = (
<div
className={cn(
"group relative overflow-hidden rounded-2xl border-2 p-8 transition-all duration-300",
isActive
? "border-ndmo-blue-medium to-ndmo-blue-pale cursor-pointer bg-gradient-to-br from-white hover:scale-105 hover:shadow-2xl"
: "border-ndmo-gray-light bg-ndmo-gray-light/30 cursor-not-allowed opacity-60",
)}
>
{/* Active Indicator */}
{isActive && (
<div className="absolute top-6 right-6">
<div className="relative">
<div className="bg-ndmo-green pulse-indicator h-4 w-4 rounded-full" />
<div className="bg-ndmo-green/30 absolute inset-0 h-4 w-4 animate-ping rounded-full" />
</div>
</div>
);
)}

{/* Content */}
<div className="space-y-3">
<h2 className="text-ndmo-blue-dark text-3xl font-bold">{title}</h2>
<p className="text-ndmo-gray-medium text-sm font-medium">{subtitle}</p>
</div>

{/* Active Label */}
{isActive && (
<div className="mt-6 flex items-center gap-2">
<div className="bg-ndmo-green h-2 w-2 rounded-full" />
<span className="text-ndmo-green text-xs font-semibold tracking-wide uppercase">
Active
</span>
</div>
)}

{/* Hover Effect */}
{isActive && (
<div className="from-ndmo-blue-medium/5 absolute inset-0 bg-gradient-to-br to-transparent opacity-0 transition-opacity group-hover:opacity-100" />
)}
</div>
);

if (isActive && href) {
return <Link to={href}>{CardContent}</Link>;
}
if (isActive && href) {
return <Link to={href}>{CardContent}</Link>;
}

return CardContent;
return CardContent;
}
Loading
Loading