OppaAI commited on
Commit
ded9789
·
verified ·
1 Parent(s): be9d87d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -112
app.py CHANGED
@@ -6,140 +6,126 @@ from datetime import datetime
6
 
7
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
8
  logger = logging.getLogger(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- mcp = FastMCP("Jobicy Canada Remote Jobs Agent")
11
 
12
  @mcp.tool(name="search_jobs")
13
- def search_jobs_tool(query: str = "", location: str = "", limit: int = 20) -> dict:
 
 
 
 
 
 
14
  """
15
- Search for remote jobs using the Jobicy API.
16
 
17
  Args:
18
- query (str): Optional job title or keyword to filter results.
19
- location (str): Optional country or location (e.g., "canada", "united states").
20
- Use "Anywhere" or leave blank for global search.
 
21
  limit (int): Maximum number of job results to return (1–50).
22
 
23
  Returns:
24
- dict: A dictionary with either a list of jobs (under "jobs") or an "error" message.
25
  """
26
  base_url = "https://jobicy.com/api/v2/remote-jobs"
27
- params = {}
28
-
29
- if query.strip():
30
- params["tag"] = query.strip()
31
- if location.strip() and location.strip().lower() != "anywhere":
32
- params["geo"] = location.strip().lower()
33
-
34
- headers = {
35
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
36
- "AppleWebKit/537.36 (KHTML, like Gecko) "
37
- "Chrome/126.0.0.0 Safari/537.36"
38
- }
39
-
40
  try:
41
- logger.info(f"Requesting Jobicy API with params: {params}")
42
- response = requests.get(base_url, params=params, headers=headers, timeout=10)
43
- response.raise_for_status()
44
- data = response.json()
45
-
46
- jobs_raw = data.get("jobs", [])
47
-
48
- jobs = []
49
- for job in jobs_raw:
50
- salary_min = job.get("annualSalaryMin")
51
- salary_max = job.get("annualSalaryMax")
52
- currency = job.get("salaryCurrency", "")
53
- if salary_min and salary_min != 0 and salary_max and salary_max != 0:
54
- salary_str = f"{salary_min} - {salary_max} {currency}".strip()
55
- else:
56
- salary_str = "Not specified"
57
-
58
- jobs.append({
59
- "title": job.get("jobTitle", "No Title"),
60
- "company": job.get("companyName", "Unknown Company"),
61
- "location": job.get("jobGeo", "Remote"),
62
- "url": job.get("url", "#"),
63
- "pubDate": job.get("pubDate", "Unknown Date"),
64
- "salary": salary_str
65
- })
66
-
67
- def parse_date(job):
68
- try:
69
- return datetime.fromisoformat(job["pubDate"].replace("Z", "+00:00"))
70
- except Exception:
71
- return datetime.min
72
-
73
- jobs.sort(key=parse_date, reverse=True)
74
- jobs = jobs[:limit]
75
-
76
- logger.info(f"Found {len(jobs)} jobs from Jobicy after sorting.")
77
- return {"jobs": jobs}
78
-
79
- except requests.exceptions.HTTPError as http_err:
80
- logger.error(f"HTTP error: {http_err}")
81
- return {"error": f"HTTP error occurred: {http_err}"}
82
- except requests.exceptions.Timeout:
83
- logger.error("Request timed out.")
84
- return {"error": "Request timed out. Please try again later."}
85
- except requests.exceptions.RequestException as req_err:
86
- logger.error(f"Request error: {req_err}")
87
- return {"error": f"Request error: {req_err}"}
88
  except Exception as e:
89
- logger.error(f"Unexpected error: {str(e)}")
90
- return {"error": f"Unexpected error: {str(e)}"}
91
-
92
- def search_jobs_ui(query="", location="", limit=10):
93
- limit = int(max(1, min(limit, 50)))
94
- result = search_jobs_tool(query, location, limit)
95
-
96
- if "error" in result:
97
- return f"❌ Error: {result['error']}"
98
-
99
- jobs = result.get("jobs", [])
100
- if not jobs:
101
- return "No jobs found for your search. Try different keywords or location."
102
-
103
- output = "# Jobicy Remote Job Search Results\n\n"
104
- for i, job in enumerate(jobs, 1):
105
- try:
106
- posted_date = job['pubDate'][:10]
107
- except Exception:
108
- posted_date = job['pubDate']
109
-
110
- output += (
111
- f"### {i}. {job['title']}\n\n"
112
- f"Company: {job['company']}\n\n"
113
- f"Location: {job['location']}\n\n"
114
- f"Salary: {job['salary']}\n\n"
115
- f"Posted On: {posted_date}\n\n"
116
- f"[Apply Here]({job['url']})\n\n"
117
- "---\n\n"
 
 
 
 
 
 
 
 
 
 
 
118
  )
119
- return output
120
-
121
- country_choices = [
122
- "Anywhere",
123
- "argentina", "australia", "austria", "bangladesh", "belgium", "brazil", "canada", "chile", "china",
124
- "colombia", "czech republic", "denmark", "egypt", "estonia", "finland", "france", "germany", "ghana",
125
- "greece", "hong kong", "hungary", "iceland", "india", "indonesia", "ireland", "israel", "italy", "japan",
126
- "kenya", "latvia", "lithuania", "malaysia", "mexico", "netherlands", "new zealand", "nigeria", "norway",
127
- "pakistan", "philippines", "poland", "portugal", "romania", "russia", "saudi arabia", "serbia", "singapore",
128
- "slovakia", "slovenia", "south africa", "south korea", "spain", "sweden", "switzerland", "taiwan", "thailand",
129
- "turkey", "ukraine", "united arab emirates", "united kingdom", "united states", "vietnam"
130
- ]
131
 
132
 
 
133
  app = gr.Interface(
134
  fn=search_jobs_ui,
135
  inputs=[
136
- gr.Textbox(label="Job Title / Keyword (optional)", placeholder="e.g., Software Engineer"),
137
- gr.Dropdown(label="Country (optional)", choices=country_choices, value="Anywhere", interactive=True),
138
- gr.Slider(minimum=1, maximum=200, value=20, step=1, label="Number of Results"),
 
 
139
  ],
140
  outputs=gr.Markdown(),
141
  title="Jobicy Remote Job Search",
142
- description="Search remote jobs by optional job title and country, sorted by most recent postings.",
143
  theme="huggingface"
144
  )
145
 
 
6
 
7
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
8
  logger = logging.getLogger(__name__)
9
+ mcp = FastMCP("Jobicy Remote Jobs Agent")
10
+
11
+ # ✅ Jobicy 官方欄位選項
12
+ JOB_CATEGORIES = [
13
+ "", "Business & Management", "Content & Editorial", "Customer Service",
14
+ "Creative & Design", "DevOps & SysAdmin", "Software Engineering",
15
+ "Finance & Accounting", "HR & Recruiting", "Product & Operations",
16
+ "Programming", "Sales & Marketing"
17
+ ]
18
+
19
+ JOB_TYPES = [
20
+ "", "Remote Full Time Jobs", "Remote Part Time Jobs", "Remote Contract Jobs"
21
+ ]
22
+
23
+ COMPANY_INDUSTRIES = [
24
+ "", "Technology", "Finance & Accounting", "Health, Wellness & Fitness",
25
+ "Internet & Online Media"
26
+ ]
27
+
28
+ COUNTRY_CHOICES = [
29
+ "Anywhere", "usa", "canada", "uk", "europe", "latam", "apac"
30
+ ]
31
 
 
32
 
33
  @mcp.tool(name="search_jobs")
34
+ def search_jobs_tool(
35
+ category: str = "",
36
+ job_type: str = "",
37
+ industry: str = "",
38
+ country: str = "",
39
+ limit: int = 20
40
+ ) -> dict:
41
  """
42
+ Search for remote jobs from the Jobicy API using optional filters.
43
 
44
  Args:
45
+ category (str): Job Category (e.g., "Software Engineering").
46
+ job_type (str): Job Type (e.g., "Remote Full Time Jobs").
47
+ industry (str): Company Industry (e.g., "Technology").
48
+ country (str): Country or region (e.g., "canada", "uk", or "Anywhere").
49
  limit (int): Maximum number of job results to return (1–50).
50
 
51
  Returns:
52
+ dict: A dictionary containing a list of jobs under the "jobs" key or an error message under "error".
53
  """
54
  base_url = "https://jobicy.com/api/v2/remote-jobs"
55
+ params = {"count": max(1, min(limit, 50))}
56
+
57
+ if category:
58
+ params["industry"] = category.lower().replace(" & ", "-").replace(" ", "-")
59
+ if job_type:
60
+ params["jobType"] = job_type.lower().replace(" ", "-")
61
+ if industry:
62
+ params["companyIndustry"] = industry.lower().replace(" & ", "-").replace(" ", "-")
63
+ if country and country.lower() != "anywhere":
64
+ params["geo"] = country.lower()
65
+
66
+ logger.info(f"Requesting Jobicy with params: {params}")
 
67
  try:
68
+ resp = requests.get(base_url, params=params, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
69
+ resp.raise_for_status()
70
+ jobs_raw = resp.json().get("jobs", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  except Exception as e:
72
+ return {"error": f"Fetch error: {e}"}
73
+
74
+ def format_job(j):
75
+ posted = j.get("pubDate", "")[:10]
76
+ sal_min = j.get("annualSalaryMin")
77
+ sal_max = j.get("annualSalaryMax")
78
+ cur = j.get("salaryCurrency", "")
79
+ salary = f"{sal_min}–{sal_max} {cur}".strip() if sal_min or sal_max else "Not specified"
80
+ return {
81
+ "title": j.get("jobTitle", "No Title"),
82
+ "company": j.get("companyName", ""),
83
+ "location": j.get("jobGeo", ""),
84
+ "url": j.get("url", "#"),
85
+ "pubDate": posted,
86
+ "salary": salary
87
+ }
88
+
89
+ sorted_jobs = sorted(jobs_raw, key=lambda x: x.get("pubDate", ""), reverse=True)[:params["count"]]
90
+ return {"jobs": [format_job(j) for j in sorted_jobs]}
91
+
92
+
93
+ def search_jobs_ui(category, job_type, industry, country, limit):
94
+ """
95
+ Format and display search results as Markdown for the Gradio interface.
96
+ """
97
+ res = search_jobs_tool(category, job_type, industry, country, limit)
98
+ if "error" in res:
99
+ return f" {res['error']}"
100
+ if not res["jobs"]:
101
+ return "No jobs found."
102
+
103
+ md = "# Remote Jobs Results\n\n"
104
+ for i, j in enumerate(res["jobs"], 1):
105
+ md += (
106
+ f"### {i}. {j['title']}\n\n"
107
+ f"**Company:** {j['company']}\n"
108
+ f"**Location:** {j['location']}\n"
109
+ f"**Salary:** {j['salary']}\n"
110
+ f"**Posted On:** {j['pubDate']}\n"
111
+ f"[Apply Here]({j['url']})\n\n---\n\n"
112
  )
113
+ return md
 
 
 
 
 
 
 
 
 
 
 
114
 
115
 
116
+ # 🎨 Gradio UI Components
117
  app = gr.Interface(
118
  fn=search_jobs_ui,
119
  inputs=[
120
+ gr.Dropdown(label="Job Category (optional)", choices=JOB_CATEGORIES, value=""),
121
+ gr.Dropdown(label="Job Type (optional)", choices=JOB_TYPES, value=""),
122
+ gr.Dropdown(label="Company Industry (optional)", choices=COMPANY_INDUSTRIES, value=""),
123
+ gr.Dropdown(label="Country / Region (optional)", choices=COUNTRY_CHOICES, value="Anywhere"),
124
+ gr.Slider(minimum=1, maximum=50, value=20, step=1, label="Number of Results"),
125
  ],
126
  outputs=gr.Markdown(),
127
  title="Jobicy Remote Job Search",
128
+ description="Search remote jobs using filters from Jobicy: job category, job type, company industry, and region.",
129
  theme="huggingface"
130
  )
131