# Project export: GrantHope

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: GrantHope uses AI to streamline the grant discovery process for rare disease nonprofits. We eliminate the entry barrier for charities and allow them to focus on their mission rather than fund-hunting.
- Devpost: https://devpost.com/software/granthope
- GitHub: https://github.com/Ulto85/GrantHope
- Video: https://www.youtube.com/embed/CNzw3uE9tFA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Aayan Rizvi (16 commits), AlexHockett (1 commits), Brad Ma (1 commits), CalSaksham (1 commits)

## Devpost submission (written by the team)

### Inspiration

Rare disease nonprofits face a unique challenge - they need funding to support their vital missions, but often lack the resources to effectively search for grants. Through interactions advocacy groups, we witnessed firsthand how organizations spend countless hours manually searching for funding opportunities. This inspired us to create GrantHope, a platform that automates grant discovery and matching, allowing these organizations to focus on what truly matters - supporting their communities and advancing research.

### What it does

GrantHope transforms the grant search process for rare disease nonprofits through intelligent automation. Organizations create detailed profiles about their mission, focus area, and needs. Our AI then scans and analyzes grant opportunities, matching them based on relevance and requirements. The platform provides information on deadlines, validates eligibility criteria, and helps organizations prioritize the most promising opportunities. This streamlined approach turns what was once a time-consuming manual process into an efficient, automated workflow.

### How we built it

Our platform combines advanced AI capabilities with a microservices architecture to deliver efficient and scalable grant matching. We built the frontend using Next.js 14 with shadcn/ui components for a modern, responsive interface. The backend utilizes Python-based microservices with Fetch AI's uAgents, integrating Google Gemini for intelligent matching and Tavily API for comprehensive grant searches. This tech stack enables us to process complex grant requirements and match them with organization profiles accurately. The Agent workflow is split into three parts. First a prompt planner generates search queries that cover a wide variety of grants (broad:"rare device funding" all the way to specific: "VHL research grants"). This is then given to a Searcher Agent which finds links to all grants matching the search queries. Lastly a validation agent looks through each link, extracts a broad overview of the grant opportunity, the approximate funding range, and. the deadline. It also sees if the grant matches the NGO's mission so only highly relevant grants are shown to the user. Here are our agents on AgentVerse: Searcher Agent: https://agentverse.ai/agents/details/agent1qfdclr87pf02w5g7nc4l6yprperm8cyguvwl3d7mydq9jm6gsduj7px0p7x/editor Prompt Planner Agent: https://agentverse.ai/agents/details/agent1qtafwqfdzzsh5t5vddnc7n9vz4d59dktpg7rlgvxk5lslpd2yqq76zpzu7f/editor Web Page Validation/Extraction Agent: https://agentverse.ai/agents/details/agent1qgkkgvqxkfe5s3dpkmz0jsxw2ac7t4g8c93mzpad8xate5n64fu7j03h5lv/editor

### Challenges we ran into

During development, we encountered several significant hurdles. Initially, we started with Reflex for our frontend but realized it didn't provide the performance and flexibility we needed, leading to a complete pivot to Next.js. Adapting our uAgents microservices to work as REST APIs proved challenging, requiring careful consideration of API design. We also faced interesting technical challenges in balancing the precision of our AI matching algorithm with the need to cast a wide enough net for grant opportunities.

### Accomplishments we're proud of

Our greatest achievement lies in creating a platform that genuinely simplifies the grant discovery process for rare disease nonprofits. We successfully implemented an intelligent matching system that adapts to each organization's unique profile while maintaining high accuracy. The clean, intuitive interface masks the complex technology underneath, making it accessible to users regardless of their technical expertise. Our microservices architecture ensures the platform can scale as we add more features and support more organizations.

### What we learned

This project provided invaluable insights into both technical and domain-specific challenges. We deepened our understanding of Next.js architecture and React state management while mastering the intricacies of AI prompt engineering for specific contexts. The process of building microservices taught us important lessons about system design and API integration. Perhaps most importantly, we gained a deeper appreciation for the challenges faced by rare disease nonprofits and the impact technology can have in supporting their missions.

### What's next

Looking ahead, we envision GrantHope evolving into a comprehensive grant management platform. We plan to implement features for tracking grant applications through their lifecycle, facilitating collaboration between organizations, and providing automated progress tracking. Machine learning will continue to improve our matching algorithms based on user feedback and application outcomes. We're also exploring integrations with grant submission portals to streamline the application process further by autofilling these applications. Our ultimate goal is to create an end-to-end solution that empowers rare disease nonprofits to secure the funding they need to make a difference in their communities. However we would also like to look into further streamlining other aspects of a nonprofit's lifecycle, including their budgets, legal work, and much more

## README (from the GitHub repository)

## [Devpost Link](https://devpost.com/software/granthope)

## Inspiration

Rare disease nonprofits face a unique challenge - they need funding to support their vital missions, but often lack the resources to effectively search for grants. Through interactions advocacy groups, we witnessed firsthand how organizations spend countless hours manually searching for funding opportunities. This inspired us to create GrantHope, a platform that automates grant discovery and matching, allowing these organizations to focus on what truly matters - supporting their communities and advancing research.

## What it does

GrantHope transforms the grant search process for rare disease nonprofits through intelligent automation. Organizations create detailed profiles about their mission, focus area, and needs. Our AI then continuously scans and analyzes grant opportunities, matching them based on relevance and requirements. The platform provides real-time updates on deadlines, validates eligibility criteria, and helps organizations prioritize the most promising opportunities. This streamlined approach turns what was once a time-consuming manual process into an efficient, automated workflow.

## How we built it

Our platform combines advanced AI capabilities with a microservices architecture to deliver efficient and scalable grant matching. We built the frontend using Next.js 14 with shadcn/ui components for a modern, responsive interface. The backend utilizes Python-based microservices with Fetch AI's uAgents, integrating Google Gemini for intelligent matching and Tavily API for comprehensive grant searches. This tech stack enables us to process complex grant requirements and match them with organization profiles accurately. The Agent workflow is split into three parts. First a prompt planner generates search queries that cover a wide variety of grants (broad:"rare device funding"  all the way to specific: "VHL research grants"). This is then given to a Searcher Agent which finds links to all grants matching the search queries. Lastly a validation agent looks through each link, extracts a broad overview of the grant opportunity, the approximate funding range, and. the deadline. It also sees if the grant matches the NGO's mission so only highly relevant grants are shown to the user.

Here are our agents on AgentVerse:

Searcher Agent: https://agentverse.ai/agents/details/agent1qfdclr87pf02w5g7nc4l6yprperm8cyguvwl3d7mydq9jm6gsduj7px0p7x/profile

Prompt Planner Agent: https://agentverse.ai/agents/details/agent1qtafwqfdzzsh5t5vddnc7n9vz4d59dktpg7rlgvxk5lslpd2yqq76zpzu7f/profile

Web Page Validation/Extraction Agent: https://agentverse.ai/agents/details/agent1qgkkgvqxkfe5s3dpkmz0jsxw2ac7t4g8c93mzpad8xate5n64fu7j03h5lv/profile


## Challenges we ran into

During development, we encountered several significant hurdles. Initially, we started with Reflex for our frontend but realized it didn't provide the performance and flexibility we needed, leading to a complete pivot to Next.js. Adapting our uAgents microservices to work as REST APIs proved challenging, requiring careful consideration of state management and API design. We also faced interesting technical challenges in balancing the precision of our AI matching algorithm with the need to cast a wide enough net for grant opportunities. Rate limits across multiple APIs forced us to implement clever caching and request management strategies.

## Accomplishments that we're proud of

Our greatest achievement lies in creating a platform that genuinely simplifies the grant discovery process for rare disease nonprofits. We successfully implemented an intelligent matching system that adapts to each organization's unique profile while maintaining high accuracy. The clean, intuitive interface masks the complex technology underneath, making it accessible to users regardless of their technical expertise. Our microservices architecture ensures the platform can scale as we add more features and support more organizations.

## What we learned

This project provided invaluable insights into both technical and domain-specific challenges. We deepened our understanding of Next.js architecture and React state management while mastering the intricacies of AI prompt engineering for specific contexts. The process of building microservices taught us important lessons about system design and API integration. Perhaps most importantly, we gained a deeper appreciation for the challenges faced by rare disease nonprofits and the impact technology can have in supporting their missions.

## What's next for GrantHope

Looking ahead, we envision GrantHope evolving into a comprehensive grant management platform. We plan to implement features for tracking grant applications through their lifecycle, facilitating collaboration between organizations, and providing automated progress tracking. Machine learning will continue to improve our matching algorithms based on user feedback and application outcomes. We're also exploring integrations with grant submission portals to streamline the application process further by autofilling these applications. Our ultimate goal is to create an end-to-end solution that empowers rare disease nonprofits to secure the funding they need to make a difference in their communities. However we would also like to look into further streamlining other aspects of a nonprofit's lifecycle, including their budgets, legal work, and much more


## Detected evidence (automated analysis)

Indexed codebase: 21 recognized source files, 53 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- LangChain (technology) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (33 of 33)

```
.DS_Store
.env
.gitignore
granthope/.eslintrc.json
granthope/.gitignore
granthope/components.json
granthope/next.config.mjs
granthope/package.json
granthope/postcss.config.mjs
granthope/README.md
granthope/src/app/dashboard/page.tsx
granthope/src/app/globals.css
granthope/src/app/layout.tsx
granthope/src/app/login/page.tsx
granthope/src/app/page.tsx
granthope/src/app/register/page.tsx
granthope/src/components/ui/alert.tsx
granthope/src/components/ui/button.tsx
granthope/src/components/ui/card.tsx
granthope/src/components/ui/dialog.tsx
granthope/src/components/ui/form.tsx
granthope/src/components/ui/input.tsx
granthope/src/components/ui/label.tsx
granthope/src/components/ui/textarea.tsx
granthope/src/lib/utils.ts
granthope/tailwind.config.ts
granthope/tsconfig.json
private_keys.json
prompt_plan_agent.py
README.md
requirements.txt
search_agent.py
validate_webpage_agent.py
```

### Dependencies

- granthope/package.json: @hookform/resolvers@^3.9.0, @radix-ui/react-dialog@^1.1.2, @radix-ui/react-label@^2.1.0, @radix-ui/react-slot@^1.1.0, @types/node@^20, @types/react@^18, @types/react-dom@^18, axios@^1.7.7, class-variance-authority@^0.7.0, clsx@^2.1.1, eslint@^8, eslint-config-next@14.2.15, lucide-react@^0.453.0, next@14.2.15, postcss@^8, react@^18, react-dom@^18, react-hook-form@^7.53.1, tailwind-merge@^2.5.4, tailwindcss@^3.4.1, tailwindcss-animate@^1.0.7, typescript@^5, zod@^3.23.8
- requirements.txt: aiohappyeyeballs@==2.4.3, aiohttp@==3.10.10, aiosignal@==1.3.1, alembic@==1.13.3, annotated-types@==0.7.0, anyio@==4.6.2.post1, apispec@==6.6.1, async-timeout@==4.0.3, attrs@==24.2.0, backports.tarfile@==1.2.0, beautifulsoup4@==4.12.3, bech32@==1.2.0, bidict@==0.23.1, build@==1.2.2.post1, cachetools@==5.5.0, certifi@==2024.8.30, charset-normalizer@==3.4.0, click@==8.1.7, cosmpy@==0.9.2, dataclasses-json@==0.6.7, docutils@==0.21.2, ecdsa@==0.19.0, exceptiongroup@==1.2.2, fastapi@==0.115.2, frozenlist@==1.4.1, google-ai-generativelanguage@==0.6.10, google-api-core@==2.21.0, google-api-python-client@==2.149.0, google-auth@==2.35.0, google-auth-httplib2@==0.2.0, google-generativeai@==0.8.3, googleapis-common-protos@==1.65.0, grpcio@==1.67.0, grpcio-status@==1.67.0, gunicorn@==23.0.0, h11@==0.14.0, httpcore@==1.0.6, httplib2@==0.22.0, httpx@==0.27.2, idna@==3.10, importlib_metadata@==8.5.0, jaraco.classes@==3.4.0, jaraco.context@==6.0.1, jaraco.functools@==4.1.0, Jinja2@==3.1.4, jsonpatch@==1.33, jsonpointer@==3.0.0, jsonschema@==4.23.0, jsonschema-specifications@==2024.10.1, keyring@==25.4.1, langchain@==0.3.4, langchain-community@==0.3.3, langchain-core@==0.3.12, langchain-text-splitters@==0.3.0, langsmith@==0.1.136, lazy_loader@==0.4, Mako@==1.3.5, markdown-it-py@==3.0.0, MarkupSafe@==3.0.2, marshmallow@==3.23.0, mdurl@==0.1.2, more-itertools@==10.5.0, msgpack@==1.1.0, multidict@==6.1.0, mypy-extensions@==1.0.0, nh3@==0.2.18, numpy@==1.26.4, orjson@==3.10.9, packaging@==24.1, pipdeptree@==2.16.2, pkginfo@==1.10.0, platformdirs@==4.3.6, propcache@==0.2.0, proto-plus@==1.24.0, protobuf@==4.25.5, psutil@==6.1.0, pyasn1@==0.6.1, pyasn1_modules@==0.4.1, pycryptodome@==3.21.0, pydantic@==2.8.2, pydantic_core@==2.20.1, pydantic-settings@==2.6.0, Pygments@==2.18.0, pyparsing@==3.2.0, pyproject_hooks@==1.2.0, python-dateutil@==2.9.0.post0, python-dotenv@==1.0.1, python-engineio@==4.10.1, python-multipart@==0.0.12, python-socketio@==5.11.4, PyYAML@==6.0.2, readme_renderer@==44.0, redis@==5.1.1, referencing@==0.35.1, reflex@==0.6.3, reflex-chakra@==0.6.2, reflex-hosting-cli@==0.1.13, regex@==2024.9.11, requests@==2.32.3, requests-toolbelt@==1.0.0, rfc3986@==2.0.0, rich@==13.9.2, rpds-py@==0.20.0, rsa@==4.9, shellingham@==1.5.4, simple-websocket@==1.1.0, six@==1.16.0, sniffio@==1.3.1, soupsieve@==2.6, SQLAlchemy@==2.0.36, sqlmodel@==0.0.22, starlette@==0.40.0, starlette-admin@==0.14.1, tabulate@==0.9.0, tavily-python@==0.5.0, tenacity@==9.0.0, tiktoken@==0.8.0, tomli@==2.0.2, tomlkit@==0.13.2, tqdm@==4.66.5, twine@==5.1.1, typer@==0.12.5, typing_extensions@==4.12.2, typing-inspect@==0.9.0, uagents@==0.16.2, uagents-ai-engine@==0.5.0, uritemplate@==4.1.1, urllib3@==2.2.3, uvicorn@==0.30.6, websockets@==10.4, wrapt@==1.16.0, wsproto@==1.2.0, yarl@==1.15.5, zipp@==3.20.2

### Recent commits (newest first)

- Update README.md
- Create README.md
- Update page.tsx
- pruned again
- pruned
- updateds
- Update page.tsx
- updated styling
- created next js app
- Add logo
- did stuff
- edited stuff
- Update validate_webpage_agent.py
- added summarization for rag purposes
- web_search_agent
- added files
- updated search
- Updated agents and fast api
- Adding frontend empty file
- update search agent and the test endpoint

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
aiohappyeyeballs==2.4.3
aiohttp==3.10.10
aiosignal==1.3.1
alembic==1.13.3
annotated-types==0.7.0
anyio==4.6.2.post1
apispec==6.6.1
async-timeout==4.0.3
attrs==24.2.0
backports.tarfile==1.2.0
beautifulsoup4==4.12.3
bech32==1.2.0
bidict==0.23.1
build==1.2.2.post1
cachetools==5.5.0
certifi==2024.8.30
charset-normalizer==3.4.0
click==8.1.7
cosmpy==0.9.2
dataclasses-json==0.6.7
docutils==0.21.2
ecdsa==0.19.0
exceptiongroup==1.2.2
fastapi==0.115.2
frozenlist==1.4.1
google-ai-generativelanguage==0.6.10
google-api-core==2.21.0
google-api-python-client==2.149.0
google-auth==2.35.0
google-auth-httplib2==0.2.0
google-generativeai==0.8.3
googleapis-common-protos==1.65.0
grpcio==1.67.0
grpcio-status==1.67.0
gunicorn==23.0.0
h11==0.14.0
httpcore==1.0.6
httplib2==0.22.0
httpx==0.27.2
idna==3.10
importlib_metadata==8.5.0
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.1.0
Jinja2==3.1.4
jsonpatch==1.33
jsonpointer==3.0.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
keyring==25.4.1
langchain==0.3.4
langchain-community==0.3.3
langchain-core==0.3.12
langchain-text-splitters==0.3.0
langsmith==0.1.136
lazy_loader==0.4
Mako==1.3.5
markdown-it-py==3.0.0
MarkupSafe==3.0.2
marshmallow==3.23.0
mdurl==0.1.2
more-itertools==10.5.0
msgpack==1.1.0
multidict==6.1.0
mypy-extensions==1.0.0
nh3==0.2.18
numpy==1.26.4
orjson==3.10.9
packaging==24.1
pipdeptree==2.16.2
pkginfo==1.10.0
platformdirs==4.3.6
propcache==0.2.0
proto-plus==1.24.0
protobuf==4.25.5
psutil==6.1.0
pyasn1==0.6.1
pyasn1_modules==0.4.1
pycryptodome==3.21.0
pydantic==2.8.2
pydantic-settings==2.6.0
pydantic_core==2.20.1
Pygments==2.18.0
pyparsing==3.2.0
pyproject_hooks==1.2.0
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-engineio==4.10.1
python-multipart==0.0.12
python-socketio==5.11.4
PyYAML==6.0.2
readme_renderer==44.0
redis==5.1.1
referencing==0.35.1
reflex==0.6.3
reflex-chakra==0.6.2
reflex-hosting-cli==0.1.13
regex==2024.9.11
requests==2.32.3
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==13.9.2
rpds-py==0.20.0
rsa==4.9
shellingham==1.5.4
simple-websocket==1.1.0
six==1.16.0
sniffio==1.3.1
soupsieve==2.6
SQLAlchemy==2.0.36
sqlmodel==0.0.22
starlette==0.40.0
starlette-admin==0.14.1
tabulate==0.9.0
tavily-python==0.5.0
tenacity==9.0.0
tiktoken==0.8.0
tomli==2.0.2
tomlkit==0.13.2
tqdm==4.66.5
twine==5.1.1
typer==0.12.5
typing-inspect==0.9.0
typing_extensions==4.12.2
uagents==0.16.2
uagents-ai-engine==0.5.0
uritemplate==4.1.1
urllib3==2.2.3
uvicorn==0.30.6
websockets==10.4
wrapt==1.16.0
wsproto==1.2.0
yarl==1.15.5
zipp==3.20.2

```

### granthope/package.json

```
{
  "name": "granthope",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.0",
    "@radix-ui/react-dialog": "^1.1.2",
    "@radix-ui/react-label": "^2.1.0",
    "@radix-ui/react-slot": "^1.1.0",
    "axios": "^1.7.7",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "lucide-react": "^0.453.0",
    "next": "14.2.15",
    "react": "^18",
    "react-dom": "^18",
    "react-hook-form": "^7.53.1",
    "tailwind-merge": "^2.5.4",
    "tailwindcss-animate": "^1.0.7",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.15",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### granthope/src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";

const geistSans = localFont({
  src: "./fonts/GeistVF.woff",
  variable: "--font-geist-sans",
  weight: "100 900",
});
const geistMono = localFont({
  src: "./fonts/GeistMonoVF.woff",
  variable: "--font-geist-mono",
  weight: "100 900",
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

```

### granthope/src/app/page.tsx

```typescript
'use client';
'use client';
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import Link from 'next/link';
import { Heart, BarChart3, Clock, Target } from 'lucide-react'; 
export default function LandingPage() {
  return (
    <div className="min-h-screen bg-gradient-to-b from-white to-blue-50">
    <div className="container mx-auto px-4">
      <nav className="py-8 flex justify-between items-center">
        <div className="flex items-center gap-2">
          <Heart className="text-blue-600" />
          <h1 className="text-2xl font-bold">GrantHope</h1>
        </div>
        <div className="space-x-4">
          <Link href="/login">
            <Button variant="ghost">Login</Button>
          </Link>
          <Link href="/register">
            <Button className="bg-blue-600 hover:bg-blue-700">Get Started</Button>
          </Link>
        </div>
      </nav>
  
      <main className="py-20">
        <div className="max-w-4xl mx-auto text-center">
          <h2 className="text-6xl font-bold mb-6">
            Find Grants for Your
            <span className="text-blue-600"> Rare Disease</span> Nonprofit
          </h2>
          <p className="text-xl text-gray-600 mb-12">
            Stop spending hours searching for funding. Let GrantHope connect you with 
            <span className="text-blue-600 font-medium"> relevant grants</span> so you can focus on what matters most - your mission.
          </p>
  
          <div className="grid md:grid-cols-3 gap-8 mt-16">
 <Card className="p-8 hover:shadow-lg transition-all text-center">
   <div className="flex justify-center">
     <BarChart3 className="h-12 w-12 text-blue-600 mb-6" />
   </div>
   <h3 className="text-xl font-bold mb-3">Smart <span className="text-blue-600">Search</span></h3>
   <p className="text-gray-600">AI-powered grant matching tailored to your organization</p>
 </Card>
 
 <Card className="p-8 hover:shadow-lg transition-all text-center">
   <div className="flex justify-center">
     <Target className="h-12 w-12 text-blue-600 mb-6" />
   </div>
   <h3 className="text-xl font-bold mb-3">Time <span className="text-blue-600">Saving</span></h3>
   <p className="text-gray-600">Automated screening saves hours of manual searching</p>
 </Card>
 
 <Card className="p-8 hover:shadow-lg transition-all text-center">
   <div className="flex justify-center">
     <Clock className="h-12 w-12 text-blue-600 mb-6" />
   </div>
   <h3 className="text-xl font-bold mb-3">Focused <span className="text-blue-600">Results</span></h3>
   <p className="text-gray-600">Only see grants that match your specific needs</p>
 </Card>
</div>
        </div>
      </main>
    </div>
  </div>
  );
}

```

### granthope/src/app/login/page.tsx

```typescript
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import Link from 'next/link';
import { Heart, ArrowRight, Lock, Mail } from 'lucide-react';

export default function LoginPage() {
  const [credentials, setCredentials] = useState({ email: '', password: '' });
  const router = useRouter();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    localStorage.setItem('user', JSON.stringify(credentials));
    router.push('/dashboard');
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-b from-white to-blue-50">
 <Card className="w-[450px] p-6">
   <CardHeader>
     <div className="flex justify-center mb-6">
       <Heart className="text-blue-600 h-12 w-12" />
     </div>
     <div className="text-3xl font-bold text-center">Welcome Back to <span className="text-blue-600">GrantHope</span></div>
     <p className="text-gray-600 text-center mt-2">Continue your mission of making a difference</p>
   </CardHeader>
   <CardContent>
     <form onSubmit={handleSubmit} className="space-y-6">
       <div className="space-y-2">
         <Input
           type="email"
           placeholder="Email"
           className="h-12"
           value={credentials.email}
           onChange={(e) => setCredentials({...credentials, email: e.target.value})}
         />
       </div>
       <div className="space-y-2">
         <Input
           type="password"
           placeholder="Password"
           className="h-12"
           value={credentials.password}
           onChange={(e) => setCredentials({...credentials, password: e.target.value})}
         />
       </div>
       <Button type="submit" className="w-full h-12 bg-blue-600 hover:bg-blue-700">
         Sign In
       </Button>
       <div className="text-center text-gray-600">
         Don't have an account? <Link href="/register" className="text-blue-600 hover:underline font-medium">Register</Link>
       </div>
     </form>
   </CardContent>
 </Card>
</div>
  );
}
```

### granthope/src/app/register/page.tsx

```typescript
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Heart, Building2, Target, Lock } from 'lucide-react';
import Link from 'next/link';

export default function RegisterPage() {
  const [user, setUser] = useState({
    email: '',
    password: '',
    confirmPassword: '',
    organizationName: '',
    disease: '',
    mission: '',
    description: '',
    location: ''
  });
  const router = useRouter();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (user.password !== user.confirmPassword) {
      alert('Passwords do not match!');
      return;
    }
    localStorage.setItem('user', JSON.stringify(user));
    router.push('/dashboard');
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gradient-to-b from-white to-blue-50 p-4">
    <Card className="w-[700px] p-6">
      <CardHeader>
        <div className="flex justify-center mb-6">
          <Heart className="text-blue-600 h-12 w-12" />
        </div>
        <div className="text-3xl font-bold text-center">Join <span className="text-blue-600">GrantHope</span></div>
        <p className="text-gray-600 text-center mt-2">Start connecting with the perfect grants for your mission</p>
      </CardHeader>
      <CardContent>
        <form onSubmit={handleSubmit} className="space-y-6">
          <div className="flex items-center gap-2 px-3 py-2 bg-blue-50 rounded-lg mb-6">
            <Building2 className="text-blue-600 h-5 w-5" />
            <p className="text-sm text-gray-600">Organization Details</p>
          </div>
          
          <div className="grid md:grid-cols-2 gap-4">
            <div className="space-y-2">
              <Input
                type="text"
                placeholder="Organization Name"
                className="h-12"
                value={user.organizationName}
                onChange={(e) => setUser({...user, organizationName: e.target.value})}
              />
            </div>
            <div className="space-y-2">
              <Input
                type="email"
                placeholder="Email"
                className="h-12"
                value={user.email}
                onChange={(e) => setUser({...user, email: e.target.value})}
              />
            </div>
          </div>
   
          <div className="space-y-2">
            <Input
              type="text"
              placeholder="Disease Focus"
              className="h-12"
              value={user.disease}
              onChange={(e) => setUser({...user, disease: e.target.value})}
            />
          </div>
   
          <div className="flex items-center gap-2 px-3 py-2 bg-blue-50 rounded-lg">
            <Target className="text-blue-600 h-5 w-5" />
            <p className="text-sm text-gray-600">Mission & Description</p>
          </div>
   
          <Textarea
            placeholder="Organization Mission"
            className="min-h-[100px]"
            value={user.mission}
            onChange={(e) => setUser({...user, mission: e.target.value})}
          />
          <Textarea
            placeholder="Organization Description"
            className="min-h-[100px]"
            value={user.description}
            onChange={(e) => setUser({...user, description: e.target.value})}
          />
   
          <div className="flex items-center gap-2 px-3 py-2 bg-blue-50 rounded-lg">
            <Lock className="text-blue-600 h-5 w-5" />
            <p className="text-sm text-gray-600">Security Details</p>
          </div>
   
          <div className="grid md:grid-cols-2 gap-4">
            <Input
              type="text"
              placeholder="Location"
              className="h-12"
              value={user.location}
              onChange={(e) => setUser({...user, location: e.target.value})}
            />
            <Input
              type="password"
              placeholder="Password"
              className="h-12"
              value={user.password}
              onChange={(e) => setUser({...user, password: e.target.value})}
            />
          </div>
   
          <Input
            type="password"
            placeholder="Confirm Password"
            className="h-12"
            value={user.confirmPassword}
            onChange={(e) => setUser({...user, confirmPassword: e.target.value})}
          />
   
          <Button type="submit" className="w-full h-12 bg-blue-600 hover:bg-blue-700">
            Create Account
          </Button>
          
          <div className="text-center text-gray-600">
            Already have an account? <Link href="/login" className="text-blue-600 hover:underline font-medium">Login</Link>
          </div>
        </form>
      </CardContent>
    </Card>
   </div>
  );
}
```

### granthope/src/app/dashboard/page.tsx

```typescript
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";

import { Heart, Building2, Search, ExternalLink, Loader2 } from 'lucide-react';
// Sample data for demo purposes
const SAMPLE_GRANTS = [
    {
      text: "VHL Research Grant Program - Offering support for innovative research projects focused on von Hippel-Lindau disease mechanisms, treatments, and potential cures.",
      approximateRange: "$50,000 - $100,000",
      deadline: "March 15, 2025",
      isValid: true,
      url: "https://example.com/vhl-research-grant"
    },
    {
      text: "Rare Disease Treatment Innovation Grant - Supporting organizations working on rare genetic conditions including VHL syndrome.",
      approximateRange: "$75,000 - $150,000",
      deadline: "June 30, 2025",
      isValid: true,
      url: "https://example.com/rare-disease-grant"
    },
    {
      text: "Patient Support Program Development Grant - Funding for nonprofits to establish or expand support services for VHL patients and families.",
      approximateRange: "$25,000 - $50,000",
      deadline: "September 1, 2025",
      isValid: true,
      url: "https://example.com/patient-support-grant"
    }
   ];


const USE_SAMPLE_DATA = false;

export default function Dashboard() {
  const router = useRouter();
  const [loading, setLoading] = useState(false);
  const [results, setResults] = useState([]);
  const [ngoData, setNgoData] = useState({
    name: '',
    description: '',
    disease: '',
    mission: '',
    location: ''
  });

  useEffect(() => {
    const userData = localStorage.getItem('user');
    if (!userData) {
      router.push('/login');
      return;
    }
    const user = JSON.parse(userData);
    setNgoData({
      name: user.organizationName,
      description: user.description,
      disease: user.disease,
      mission: user.mission,
      location: user.location
    });
  }, [router]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    
    if (USE_SAMPLE_DATA) {
      setTimeout(() => {
        setResults(SAMPLE_GRANTS);
        setLoading(false);
      }, 1500); // Simulate API delay
      return;
    }

    try {
      const context = `NGO Name: ${ngoData.name}. Focus Disease: ${ngoData.disease}. 
        Mission: ${ngoData.mission}. Description: ${ngoData.description}. Location: ${ngoData.location}`;
      
      const userData = JSON.parse(localStorage.getItem('user'));
      localStorage.setItem('user', JSON.stringify({
        ...userData,
        organizationName: ngoData.name,
        description: ngoData.description,
        disease: ngoData.disease,
        mission: ngoData.mission,
        location: ngoData.location
      }));

      const promptResponse = await fetch('http://127.0.0.1:8003/plan', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: context })
      });
      const { prompts } = await promptResponse.json();
      
      const searchPromises = prompts.slice(0, 5).map(prompt =>
        fetch('http://127.0.0.1:8001/search', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ message: prompt })
        }).then(res => res.json())
      );
      
      const searchResults = await Promise.all(searchPromises);
      
      const validationPromises = searchResults.flatMap(result =>
        (result.urls || []).slice(0, 5).map(async url => {
          const response = await fetch('http://127.0.0.1:8002/validate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ link: url, context })
          });
          const data = await response.json();
          return { ...data, url }; // Include the URL in the result
        })
      );
      
      const validatedResults = await Promise.all(validationPromises);
      setResults(validatedResults.filter(r => r.isValid));
    } catch (error) {
      console.error('Error:', error);
    }
    setLoading(false);
  };

  const handleLogout = () => {
    localStorage.removeItem('user');
    router.push('/login');
  };

  return (
    <div className="min-h-screen bg-gradient-to-b from-white to-blue-50 p-8">
    <div className="max-w-6xl mx-auto">
      <nav className="flex justify-between items-center mb-12">
        <div className="flex items-center gap-3">
          <Heart className="text-blue-600 h-8 w-8" />
          <h1 className="text-3xl font-bold">Grant<span className="text-blue-600">Hope</span></h1>
        </div>
        <Button variant="outline" onClick={handleLogout}>Logout</Button>
      </nav>
   
      <div className="grid lg:grid-cols-3 gap-8">
        <Card className="lg:col-span-1 h-fit sticky top-8">
          <CardHeader>
            <div className="flex items-center gap-2">
              <Building2 className="text-blue-600 h-5 w-5" />
              <CardTitle>Organization Profile</CardTitle>
            </div>
          </CardHeader>
          <CardContent>
            <form onSubmit={handleSubmit} className="space-y-4">
              <Input
                placeholder="NGO Name"
                className="h-12"
                value={ngoData.name}
                onChange={(e) => setNgoData({...ngoData, name: e.target.value})}
              />
              <Input
                placeholder="Disease Focus"
                className="h-12"
                value={ngoData.disease}
                onChange={(e) => setNgoData({...ngoData, disease: e.target.value})}
              />
              <Textarea
                placeholder="NGO Mission"
                className="min-h-[100px]"
                value={ngoData.mission}
    
[truncated — 3346 more characters]
```

### search_agent.py

```python

from uagents import Agent, Context, Model

import requests
from tavily import TavilyClient
from dotenv import load_dotenv
import os
from fastapi.middleware.cors import CORSMiddleware
load_dotenv()

tavily_api_key=os.getenv("TAVILY_API_KEY")

def tavily_search(query, API_KEY):
    """Perform a search using the Tavily Search API and return results."""
    tavily_client = TavilyClient(api_key=API_KEY)
    return tavily_client.search(query)

class Request(Model):
    message: str


class Response(Model):
    urls:list


agent = Agent(
    name="SearcherAgent",
    seed="tavily-searcher",
    port=8001,
    endpoint="http://localhost:8001/search",
)


@agent.on_event("startup")
async def startup(ctx: Context):
    ctx.logger.info(f"Starting up {agent.name}")
    ctx.logger.info(f"With address: {agent.address}")
    ctx.logger.info(f"And wallet address: {agent.wallet.address()}")


# @agent.on_query(model=TestRequest, replies={Response})
# async def query_handler(ctx: Context, sender: str, _query: TestRequest):
#     ctx.logger.info("Query received")
#     try:
      
#         reponse_text = [x['url'] for x in tavily_search(_query.message,tavily_api_key)['results']]
      
#         await ctx.send(sender, Response(urls=reponse_text))
#     except Exception as e:
  
#         await ctx.send(sender, Response(urls=["fail"]))
# agent.run()
@agent.on_rest_post("/search",Request, Response)
async def query_handler(ctx= Context, req= Request):
    ctx.logger.info("Query received")
    try:
        reponse_text = [x['url'] for x in tavily_search(req.message,tavily_api_key)['results']]
        return Response(urls=reponse_text)
    except Exception:
        return Response(urls=["fail"])
@agent.on_rest_post("/get_content",Request, Response)
async def query_handler(ctx= Context, req= Request):
    ctx.logger.info("Query received")
    try:
        reponse_text = [x['content'] for x in tavily_search(req.message,tavily_api_key)['results']]
        return Response(urls=reponse_text)
    except Exception:
        return Response(urls=["fail"])
agent.run()
```

### prompt_plan_agent.py

```python


from uagents import Agent, Context, Model

import requests


# Importing necessary libraries
from uagents import Agent, Context
from uagents.setup import fund_agent_if_low
from uagents import Model
import google.generativeai as genai
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
import os
import re
import ast
load_dotenv()

gemini_api_key=os.getenv("GEMINI_API_KEY")



class Request(Model):
    message: str


class Response(Model):
    prompts:list



def find_and_eval_lists(text):
    pattern = r'\[([^\[\]]*)\]'
    matches = re.findall(pattern, text)
    return [ast.literal_eval(f'[{match}]') for match in matches]
agent = Agent(
    name="PromptPlannerAgent",
    seed="gemini-planner",
    port=8003,
    endpoint="http://localhost:8003/plan",
)
genai.configure(api_key=gemini_api_key) 
model = genai.GenerativeModel('gemini-1.5-flash')
@agent.on_event("startup")
async def startup(ctx: Context):
    ctx.logger.info(f"Starting up {agent.name}")
    ctx.logger.info(f"With address: {agent.address}")
    ctx.logger.info(f"And wallet address: {agent.wallet.address()}")


# @agent.on_query(model=TestRequest, replies={Response})
# async def query_handler(ctx: Context, sender: str, _query: TestRequest):
#     ctx.logger.info("Query received")
#     try:
#         response_text = model.generate_content(f"Create a list (RETURN ONLY A PYTHON LIST) of possible prompts to search for this grants for an NGO with this description {_query.message}: ")
        
#         await ctx.send(sender, Response(prompts=find_and_eval_lists(response_text)[0]))
#     except Exception:
#         await ctx.send(sender, Response(prompts=["fail"]))
# agent.run()
@agent.on_rest_post("/plan",Request, Response)
async def query_handler(ctx: Context, req: Request):
    ctx.logger.info("Query received")
    try:
        
        response_text = model.generate_content(f"Create a list (RETURN ONLY A PYTHON LIST) of possible prompts to search for this grants for an NGO with this description, make some relate closely, make some a bit broad {req.message}: ").text
        return Response(prompts=find_and_eval_lists(response_text)[0])
    except Exception as e:
        return Response(prompts=["fail"])
agent.run()
```

### validate_webpage_agent.py

```python


from uagents import Agent, Context, Model

import requests


# Importing necessary libraries
from uagents import Agent, Context
from uagents.setup import fund_agent_if_low
from uagents import Model
import google.generativeai as genai
from dotenv import load_dotenv
import os
import re
import ast
from fastapi.middleware.cors import CORSMiddleware
from bs4 import BeautifulSoup
load_dotenv()
import re
import ast

def find_and_eval_dicts(text):
    pattern = r'\{([^\{\}]*(?:\{[^\{\}]*\}[^\{\}]*)*)\}'
    matches = re.findall(pattern, text, re.DOTALL)  # re.DOTALL makes . match newlines
    return [ast.literal_eval(f'{{{match}}}') for match in matches]

gemini_api_key=os.getenv("GEMINI_API_KEY")
def get_webpage_content(link):
    resp=requests.get(link) 
      
    answer= ''
    if resp.status_code==200: 
    
        soup=BeautifulSoup(resp.text,'html.parser')    
        l=soup.findAll("div")
       
        for i in l: 
            answer+=i.text
        return answer
    else: 
        return 'error'


class Request(Model):
    link: str
    context:str


class Response(Model):
    text:str
    isValid:bool
    approximateRange:str
    deadline:str





agent = Agent(
    name="WebPageEvalAgent",
    seed="web-agent",
    port=8002,
    endpoint="http://localhost:8002/plan",
)

genai.configure(api_key=gemini_api_key) 
model = genai.GenerativeModel('gemini-1.5-flash')

@agent.on_event("startup")
async def startup(ctx: Context):
    ctx.logger.info(f"Starting up {agent.name}")
    ctx.logger.info(f"With address: {agent.address}")
    ctx.logger.info(f"And wallet address: {agent.wallet.address()}")


# @agent.on_query(model=TestRequest, replies={Response})
# async def query_handler(ctx: Context, sender: str, _query: TestRequest):
#     ctx.logger.info("Query received")
#     try:
#         response_text = model.generate_content(f"Create a list (RETURN ONLY A PYTHON LIST) of possible prompts to search for this grants for an NGO with this description {_query.message}: ")
        
#         await ctx.send(sender, Response(prompts=find_and_eval_lists(response_text)[0]))
#     except Exception:
#         await ctx.send(sender, Response(prompts=["fail"]))
# agent.run()
@agent.on_rest_post("/validate",Request, Response)
async def query_handler(ctx: Context, req: Request):
    ctx.logger.info("Query received")
    try:
        webcontext = get_webpage_content(req.link)
 
        response_text = model.generate_content(f"RETURN YOUR ANSWER AS A DICTIONARY {{'is_applicable':(boolean: (TRUE/FALSE)), 'reasoning':why you think that way,'approximateRange':what the range is (if you don't know take an estimate),'deadline':try to get the deadline for the next open cycle and say instead like Opens xyz as opposed to Closes xyz}}. Does this grant: {webcontext} \n sound applicable for my specific NGO usecase: {req.context}").text
        
        response_dict= find_and_eval_dicts(response_text)[0]

        return Response(text=response_dict["reasoning"],isValid=response_dict["is_applicable"],approximateRange=response_dict["approximateRange"],deadline=response_dict["deadline"])
    except Exception as e:
      
        return Response(text="fail",isValid=False,approximateRange='',deadline='')
agent.run()
```

[11 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]