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
4 changes: 4 additions & 0 deletions clawedin-openclawjobfindagent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# HrFlow.ai credentials (get from hackathon organizers)
HRFLOW_SECRET_KEY=your-hrflow-secret-key
HRFLOW_API_KEY=your-api-key-here
HRFLOW_SOURCE_KEY=your-source-key-here
152 changes: 152 additions & 0 deletions clawedin-openclawjobfindagent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# ClawedIn — AI Job Finding Agent

> Upload your CV. Let AI find your next job.

ClawedIn is an AI-powered job matching agent built for the HrFlow.ai Hackathon. It parses a candidate's CV using HrFlow.ai, generates relevant job roles using OpenClaw (local LLM), searches real job listings via Brave Search, and ranks them by profile match.

---

## What It Does

1. **CV Parsing** — Upload a PDF resume. HrFlow.ai extracts skills, experience, and profile data.
2. **Job Role Generation** — OpenClaw LLM analyses extracted skills and suggests 10 relevant job titles.
3. **Job Search** — Brave Search API finds real job listings from LinkedIn, Indeed, Welcome to the Jungle, APEC, Glassdoor, and Talent.io.
4. **AI Ranking** — OpenClaw ranks the listings by match quality and explains why each job fits the candidate.
5. **Results Dashboard** — Candidates see ranked jobs with match scores, reasons, and direct apply links.

---

## Architecture
```
Streamlit UI (port 8501)
↓ Upload CV
FastAPI Skill (port 8001)
↓ HrFlow Parsing API → extract skills
↓ Brave Search API → find real job listings
↓ OpenClaw LLM → rank jobs by relevance
↓ return ranked results
OpenClaw Gateway (port 18789)
↓ qwen3:1.7b via Ollama (local LLM)
```

---

## HrFlow.ai APIs Used

| API | Purpose |
|-----|---------|
| `profile/parsing/add_file` | Parse uploaded CV PDF and extract skills, experience, profile data |
| `job/searching` | Search indexed job listings |
| `job/scoring` | Score jobs against candidate profile |

---

## Prerequisites

- Python 3.12+
- [Ollama](https://ollama.ai) with `qwen3:1.7b` model
- [OpenClaw](https://openclaw.ai) gateway
- Brave Search API key (free tier available at [brave.com/search/api](https://brave.com/search/api))
- HrFlow.ai API credentials

---

## Installation

### 1. Clone the repo
```bash
git clone https://github.com/YOUR_USERNAME/app-store-public.git
cd app-store-public/clawedin
```

### 2. Install FastAPI dependencies
```bash
cd hrflow-skill
pip install poetry
poetry install
```

### 3. Install Streamlit dependencies
```bash
cd ../Streamlit
pip install -r requirements.txt
```

### 4. Install Ollama and pull model
```bash
# Install Ollama from https://ollama.ai
ollama pull qwen3:1.7b
```

### 5. Install and configure OpenClaw
```bash
npm install -g openclaw
openclaw configure
openclaw config set plugins.entries.brave.enabled true
openclaw config set plugins.entries.brave.config.webSearch.apiKey "YOUR_BRAVE_KEY"
openclaw config set tools.web.search.provider "brave"
```

---

## Configuration

Copy `.env.example` to `.env` in the `hrflow-skill` folder:
```bash
cp hrflow-skill/.env.example hrflow-skill/.env
```

Fill in your credentials:
```env
HRFLOW_API_KEY=your_hrflow_api_key
HRFLOW_USER_EMAIL=your_hrflow_email
HRFLOW_SOURCE_KEY=your_source_key
HRFLOW_BOARD_KEY=your_board_key
BRAVE_API_KEY=your_brave_api_key
GATEWAY_TOKEN=your_openclaw_gateway_token
```

---

## Running the App

Open 3 terminals:

**Terminal 1 — OpenClaw Gateway**
```bash
openclaw gateway --port 18789
```

**Terminal 2 — FastAPI Skill**
```bash
cd hrflow-skill
poetry run uvicorn main:app --host 0.0.0.0 --port 8001 --reload
```

**Terminal 3 — Streamlit UI**
```bash
cd Streamlit
streamlit run app.py --server.address 0.0.0.0 --server.port 8501
```

Open your browser at `http://localhost:8501`

---

## Stack

| Layer | Technology |
|-------|-----------|
| Frontend | Streamlit |
| Backend | FastAPI (Python) |
| LLM | qwen3:1.7b via Ollama |
| AI Agent | OpenClaw 2026.3.24 |
| Job Search | Brave Search API |
| CV Parsing | HrFlow.ai Parsing API |

---

## Team

**Team 7 — ClawedIn**
Built at the HrFlow.ai Hackathon 2026.
89 changes: 89 additions & 0 deletions clawedin-openclawjobfindagent/Streamlit/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import streamlit as st
from core.styles import apply_design_system
from core.session import init_session
from modules import auth, upload, engine, dashboard
from modules import keywords as keywords_module
from datetime import datetime

st.set_page_config(
page_title="ClawedIn",
page_icon="🦞",
layout="wide",
initial_sidebar_state="expanded"
)
apply_design_system()
init_session()

# Sidebar
with st.sidebar:
st.markdown("""
<div style="text-align:center;padding:16px 0 8px">
<div style="font-size:40px">🤖</div>
<div style="font-size:20px;font-weight:700;color:#f1f5f9">ClawedIn</div>
<div style="font-size:12px;color:#64748b;margin-top:4px">Powered by OpenClaw + HrFlow</div>
</div>
""", unsafe_allow_html=True)

st.divider()

if st.session_state.logged_in:
name = st.session_state.user_data.get("email", "Candidate")
st.markdown(f"""
<div style="padding:8px 0">
<div style="color:#94a3b8;font-size:12px">Logged in as</div>
<div style="color:#e2e8f0;font-size:14px;font-weight:500">{name}</div>
<div style="color:#64748b;font-size:12px;margin-top:4px">{datetime.now().strftime('%d %b %Y')}</div>
</div>
""", unsafe_allow_html=True)

st.divider()

# Progress tracker
step = st.session_state.step
steps = ["upload", "keywords", "processing", "results"]
step_names = ["Upload CV", "Review Profile", "AI Search", "Results"]
current = steps.index(step) if step in steps else 0

st.markdown("<div style='color:#64748b;font-size:12px;margin-bottom:8px'>PROGRESS</div>", unsafe_allow_html=True)
for i, (s, n) in enumerate(zip(steps, step_names)):
if i < current:
icon = "✅"
color = "#16a34a"
elif i == current:
icon = "▶️"
color = "#2563eb"
else:
icon = "○"
color = "#475569"
st.markdown(f"<div style='color:{color};font-size:13px;padding:3px 0'>{icon} {n}</div>", unsafe_allow_html=True)

st.divider()

if st.button("🔄 New Search", use_container_width=True):
st.session_state.step = "upload"
st.session_state.results = []
st.session_state.parsed_cv = None
st.session_state.uploaded_file = None
st.session_state.job_roles = []
st.rerun()

if st.button("Logout", use_container_width=True):
st.session_state.logged_in = False
st.session_state.step = "upload"
st.rerun()
else:
st.markdown("<div style='color:#64748b;font-size:13px'>Log in to activate your AI agent.</div>", unsafe_allow_html=True)

# Router
if not st.session_state.logged_in:
auth.render()
else:
step = st.session_state.step
if step == "upload":
upload.render()
elif step == "keywords":
keywords_module.render()
elif step == "processing":
engine.render()
elif step == "results":
dashboard.render()
31 changes: 31 additions & 0 deletions clawedin-openclawjobfindagent/Streamlit/assets/log.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions clawedin-openclawjobfindagent/Streamlit/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
33 changes: 33 additions & 0 deletions clawedin-openclawjobfindagent/Streamlit/core/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import requests

FASTAPI_URL = "http://localhost:8001"
GATEWAY_URL = "http://localhost:18789"
GATEWAY_TOKEN = "ea28e9b03545f00a79e3d3c857e18fb0df7e83dc8f211b75"


def health_check():
status = {}
try:
requests.get(f"{FASTAPI_URL}/health", timeout=2)
status["fastapi"] = True
except:
status["fastapi"] = False
try:
requests.get(GATEWAY_URL, timeout=2)
status["openclaw"] = True
except:
status["openclaw"] = False
return status


def parse_and_score(file_bytes, filename, location="Paris"):
try:
resp = requests.post(
f"{FASTAPI_URL}/full-pipeline",
files={"file": (filename, file_bytes, "application/pdf")},
params={"location": location},
timeout=180
)
return resp.json()
except Exception as e:
return {"error": str(e), "profile": {}, "results": []}
Loading
Loading