Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from fastmcp import FastMCP | |
| import logging | |
| import requests | |
| from datetime import datetime | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") | |
| logger = logging.getLogger(__name__) | |
| mcp = FastMCP("Jobicy Remote Jobs Agent") | |
| COMPANY_INDUSTRIES = [ | |
| ("", ""), | |
| ("Business Development", "business"), | |
| ("Content & Editorial", "copywriting"), | |
| ("Creative & Design", "design-multimedia"), | |
| ("Customer Success", "supporting"), | |
| ("Data Science & Analytics", "data-science"), | |
| ("DevOps & Infrastructure", "admin"), | |
| ("Finance & Accounting", "accounting-finance"), | |
| ("HR & Recruiting", "hr"), | |
| ("Legal & Compliance", "legal"), | |
| ("Marketing & Sales", "marketing"), | |
| ("Product & Operations", "management"), | |
| ("Programming", "dev"), | |
| ("Sales", "seller"), | |
| ("SEO", "seo"), | |
| ("Social Media Marketing", "smm"), | |
| ("Software Engineering", "engineering"), | |
| ("Technical Support", "technical-support"), | |
| ("Web & App Design", "web-app-design"), | |
| ] | |
| COUNTRY_CHOICES = [ | |
| "", "USA", "Canada", "UK", "China", "APAC", "EMEA", "LATAM", "Argentina", "Australia", "Austria", "Belgium", "Brazil", "Bulgaria", | |
| "Costa Rica", "Croatia", "Cyprus", "Czechia", "Denmark", "Estonia", "Europe", | |
| "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Israel", "Italy", "Japan", | |
| "Latvia", "Lithuania", "Mexico", "Netherlands", "New Zealand", "Norway", "Philippines", "Poland", | |
| "Portugal", "Romania", "Singapore", "Slovakia", "Slovenia", "South Korea", "Spain", "Sweden", | |
| "Switzerland", "Thailand", "Türkiye", "UAE", "Vietnam" | |
| ] | |
| def search_jobs_tool(industry: str = "", country: str = "", keyword: str = "", limit: int = 20) -> dict: | |
| """ | |
| Search remote jobs from the Jobicy API with optional filters. | |
| Parameters: | |
| industry (str): Company industry to filter jobs by (e.g., 'business', 'design-multimedia'). | |
| Leave empty to include all industries. | |
| country (str): Region or country to filter jobs by (e.g., 'usa', 'canada'). | |
| Leave empty or 'anywhere' to include all locations. | |
| keyword (str): Keyword or tag to search for in job listings (e.g., 'python', 'data'). | |
| Leave empty for no keyword filtering. | |
| limit (int): Number of job results to return, between 1 and 50. Defaults to 20. | |
| Returns: | |
| dict: A dictionary with a "jobs" key containing a list of job dictionaries with: | |
| - title: Job title | |
| - company: Company name | |
| - location: Job location | |
| - url: Application or job posting URL | |
| - pubDate: Date posted (YYYY-MM-DD) | |
| - salary: Salary range or 'Not specified' | |
| Or an "error" key with an error message if the fetch fails. | |
| """ | |
| base = "https://jobicy.com/api/v2/remote-jobs" | |
| params = {"count": max(1, min(limit, 50))} | |
| if industry: | |
| params["industry"] = industry.lower().replace(" & ", "-").replace(" ", "-") | |
| if country and country.lower() != "anywhere": | |
| params["geo"] = country.lower() | |
| if keyword: | |
| params["tag"] = keyword.strip() | |
| logger.info(f"Requesting Jobicy API with params: {params}") | |
| session = requests.Session() | |
| session.headers.update({ | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0 Safari/537.36", | |
| "Accept": "application/json, text/javascript, */*; q=0.01", | |
| "Referer": "https://jobicy.com/remote-jobs", | |
| "Origin": "https://jobicy.com", | |
| }) | |
| try: | |
| resp = session.get(base, params=params, timeout=10) | |
| resp.raise_for_status() | |
| jobs_raw = resp.json().get("jobs", []) | |
| except Exception as e: | |
| return {"error": f"Fetch error: {e}"} | |
| def fmt(j): | |
| posted = j.get("pubDate", "")[:10] | |
| sal_min = j.get("annualSalaryMin") | |
| sal_max = j.get("annualSalaryMax") | |
| cur = j.get("salaryCurrency", "") | |
| salary = f"{sal_min}–{sal_max} {cur}".strip() if sal_min or sal_max else "Not specified" | |
| return { | |
| "title": j.get("jobTitle", "No Title"), | |
| "company": j.get("companyName", ""), | |
| "location": j.get("jobGeo", ""), | |
| "url": j.get("url", "#"), | |
| "pubDate": posted, | |
| "salary": salary | |
| } | |
| sorted_jobs = sorted(jobs_raw, key=lambda x: x.get("pubDate", ""), reverse=True)[:params["count"]] | |
| return {"jobs": [fmt(j) for j in sorted_jobs]} | |
| def search_jobs_ui(industry, country, keyword, limit): | |
| """ | |
| Search remote jobs from the Jobicy API with optional filters. | |
| Parameters: | |
| industry (str): Company industry to filter jobs by (e.g., 'business', 'design-multimedia'). | |
| Leave empty to include all industries. | |
| country (str): Region or country to filter jobs by (e.g., 'usa', 'canada'). | |
| Leave empty or 'anywhere' to include all locations. | |
| keyword (str): Keyword or tag to search for in job listings (e.g., 'python', 'data'). | |
| Leave empty for no keyword filtering. | |
| limit (int): Number of job results to return, between 1 and 50. Defaults to 20. | |
| Returns: | |
| dict: A dictionary with a "jobs" key containing a list of job dictionaries with: | |
| - title: Job title | |
| - company: Company name | |
| - location: Job location | |
| - url: Application or job posting URL | |
| - pubDate: Date posted (YYYY-MM-DD) | |
| - salary: Salary range or 'Not specified' | |
| Or an "error" key with an error message if the fetch fails. | |
| """ | |
| res = search_jobs_tool(industry=industry, country=country, keyword=keyword, limit=limit) | |
| if "error" in res: | |
| return f"❌ {res['error']}" | |
| if not res["jobs"]: | |
| return "No jobs found with these filters." | |
| md = "# Remote Jobs Results\n\n" | |
| for i, j in enumerate(res["jobs"], 1): | |
| search_query = f"{j['title']} {j['company']}" | |
| search_url = f"https://www.google.com/search?q={requests.utils.quote(search_query)}" | |
| md += ( | |
| f"### {i}. {j['title']}\n\n" | |
| f"**Company:** {j['company']}\n\n" | |
| f"**Location:** {j['location']}\n\n" | |
| f"**Salary:** {j['salary']}\n\n" | |
| f"**Posted On:** {j['pubDate']}\n\n" | |
| f"🔗 [Apply / More Info]({j['url']}) \n" | |
| f"🔍 [Google Search]({search_url})\n\n" | |
| "---\n\n" | |
| ) | |
| return md | |
| app = gr.Interface( | |
| fn=search_jobs_ui, | |
| inputs=[ | |
| gr.Dropdown(label="Company Industry (optional) (Empty = All)", choices=COMPANY_INDUSTRIES, value=""), | |
| gr.Dropdown(label="Country / Region (optional) (Empty = Anywhere)", choices=COUNTRY_CHOICES, value=""), | |
| gr.Textbox(label="Keyword / Tag (optional)", placeholder="e.g., python, data, web"), | |
| gr.Slider(minimum=1, maximum=50, value=20, step=1, label="Number of Results"), | |
| ], | |
| outputs=gr.Markdown(), | |
| title="Jobicy Remote Job Search", | |
| description="""""Search remote jobs by industry, region, and keyword. Results sorted by most recent. | |
| The Apply Now in the Jobicy is only for Paid accounts. | |
| If you don't have one, just click the Google Search to search the job link in Google and apply there.""", | |
| theme="huggingface" | |
| ) | |
| if __name__ == "__main__": | |
| app.launch(mcp_server=True) | |