BeyondJawad commited on
Commit
a3d17e4
·
verified ·
1 Parent(s): a2c609c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -219
app.py CHANGED
@@ -1,226 +1,65 @@
1
- # app.py
2
- import os
3
- import time
4
- import json
5
- import requests
6
- import pandas as pd
7
  import gradio as gr
8
- from html import escape
9
-
10
- # ---------- Configuration / Environment ----------
11
- HF_API_TOKEN = os.getenv("HF_API_TOKEN") # set this in Space Secrets
12
- MODEL_ID = os.getenv("MODEL_ID", "meta-llama/Meta-Llama-3-8B-Instruct")
13
- JOB_SHEET_CSV = os.getenv("JOB_SHEET_CSV", "") # optional: published Google Sheet CSV URL
14
-
15
- HF_API_URL = f"https://api-inference.huggingface.co/models/{MODEL_ID}"
16
- HEADERS = {"Authorization": f"Bearer {HF_API_TOKEN}"} if HF_API_TOKEN else {}
17
-
18
- # ---------- Lightweight local fallback knowledge (prevents 'no answer') ----------
19
- FALLBACK_KB = {
20
- "gravity_en": "Gravity is a force that pulls objects toward each other. On Earth it makes things fall down and keeps the Moon orbiting Earth. In simple terms: mass attracts mass.",
21
- "gravity_ur": "کشش ثقل ایک ایسی قوت ہے جو چیزوں کو ایک دوسرے کی طرف کھینچتی ہے۔ زمین پر یہی وجہ ہے کہ چیزیں نیچے گرتی ہیں اور چاند زمین کے گرد گھومتا ہے۔",
22
- "hello_en": "Hello! I'm StudyMate AI — ask me about subjects, jobs, or study tips. Try: 'Explain gravity in Urdu' or 'Job ideas for graphic designer'.",
23
- "hello_ur": "سلام! میں StudyMate AI ہوں — مجھ سے مضامین، نوکریاں، یا مطالعے کے مشورے پوچھیں۔"
24
- }
25
-
26
- # ---------- Helper utilities ----------
27
- def detect_arabic_script(text: str) -> bool:
28
- """Rudimentary detection for Urdu/Sindhi/Arabic script presence."""
29
- for ch in text:
30
- if '\u0600' <= ch <= '\u06FF' or '\u0750' <= ch <= '\u077F':
31
- return True
32
- return False
33
-
34
- def call_hf_inference(prompt: str, max_tokens: int = 300, retries: int = 2, wait: float = 1.0):
35
- """Call Hugging Face Inference API with retries and graceful error handling."""
36
- if not HF_API_TOKEN:
37
- return {"error": "No HF token configured."}
38
-
39
- payload = {
40
- "inputs": prompt,
41
- "parameters": {"max_new_tokens": max_tokens, "temperature": 0.3, "do_sample": True},
42
- }
43
- last_err = None
44
- for attempt in range(retries + 1):
45
  try:
46
- r = requests.post(HF_API_URL, headers=HEADERS, json=payload, timeout=30)
47
- r.raise_for_status()
48
- out = r.json()
49
- # Inference API returns list or dict depending on model
50
- if isinstance(out, list) and "generated_text" in out[0]:
51
- return {"text": out[0]["generated_text"]}
52
- if isinstance(out, dict) and "generated_text" in out:
53
- return {"text": out["generated_text"]}
54
- # some models return other structures
55
- if isinstance(out, dict) and "error" in out:
56
- last_err = out.get("error")
57
- else:
58
- # try to extract text heuristically
59
- txt = json.dumps(out)[:2000]
60
- return {"text": txt}
61
- except Exception as e:
62
- last_err = str(e)
63
- time.sleep(wait)
64
- return {"error": f"Inference failed after retries. Last error: {last_err}"}
65
-
66
- def safe_answer(user_text: str, mode: str):
67
- """Main orchestrator: returns string to show user (tries inference, then fallback)."""
68
- is_arabic = detect_arabic_script(user_text)
69
- language_hint = "Urdu" if is_arabic else "English"
70
- system_intro = (
71
- f"You are StudyMate AI — a polite, highly helpful tutor and local helper. "
72
- f"Answer the user's query clearly and concisely in {language_hint}. "
73
- "If user asks for local resources or jobs, give practical steps and links if known. "
74
- "Keep answers short (<= 250 words) and include 'Confidence: high/medium/low' at the end."
75
- )
76
-
77
- # Build the prompt
78
- prompt = f"{system_intro}\n\nUser: {user_text}\n\nAssistant:"
79
-
80
- # Try model
81
- model_resp = call_hf_inference(prompt)
82
- if model_resp.get("text"):
83
- out = model_resp["text"]
84
- # Clean up common repeats of 'Assistant:' from returned text
85
- return out.strip()
86
- else:
87
- # fallback: try to map a few common Qs to local KB so user isn't disappointed
88
- if "gravity" in user_text.lower() or "کشش" in user_text:
89
- return FALLBACK_KB["gravity_ur"] if is_arabic else FALLBACK_KB["gravity_en"] \
90
- + "\n\nConfidence: high\nNext step: Ask for an example or practice question."
91
- if any(greet in user_text.lower() for greet in ["hi", "hello", "salam", "سلام"]):
92
- return FALLBACK_KB["hello_ur"] if is_arabic else FALLBACK_KB["hello_en"]
93
- # final fallback message
94
- err = model_resp.get("error", "Model unavailable.")
95
- return (
96
- f"Sorry — I couldn't generate a full answer right now. "
97
- f"I can try a short summary: {FALLBACK_KB.get('hello_ur') if is_arabic else FALLBACK_KB.get('hello_en')}\n\n"
98
- f"Error: {escape(err)}\n\nPlease try again or rephrase. Confidence: low."
99
- )
100
-
101
- # ---------- Job finder using published Google Sheet CSV ----------
102
- def fetch_jobs_from_csv(csv_url: str):
103
- """Return a pandas DataFrame parsed from a public CSV URL or empty DataFrame on failure."""
104
- if not csv_url:
105
- return pd.DataFrame()
106
- try:
107
- df = pd.read_csv(csv_url)
108
- return df
109
- except Exception as e:
110
- return pd.DataFrame()
111
-
112
- def find_jobs_by_skill(skill_query: str, csv_url: str, limit: int = 6):
113
- """Very simple skill matching on job listings CSV (case-insensitive match on text columns)."""
114
- df = fetch_jobs_from_csv(csv_url)
115
- if df.empty:
116
- return []
117
- skill = skill_query.lower()
118
- matches = []
119
- # Search across all text columns for the skill word
120
- text_cols = [c for c in df.columns if df[c].dtype == object]
121
- for _, row in df.sample(min(len(df), 200)).iterrows():
122
- row_text = " ".join(str(row[c]) for c in text_cols).lower()
123
- if skill in row_text:
124
- # Build a short job card
125
- matches.append({
126
- "title": str(row.get("Job Title", row.get("title", "Job"))),
127
- "company": str(row.get("Company", row.get("company", ""))),
128
- "location": str(row.get("Location", row.get("location", ""))),
129
- "link": str(row.get("Apply Link", row.get("link", ""))),
130
- "snippet": (str(row.get("Description", ""))[:180] + "...") if row.get("Description") else ""
131
- })
132
- if len(matches) >= limit:
133
- break
134
- return matches
135
-
136
- # ---------- Gradio UI with Comet-like theme ----------
137
- COMET_CSS = r"""
138
- /* Comet-like dark glass theme */
139
- body { background: radial-gradient(circle at 15% 20%, #0b1630 0%, #071028 20%, #000814 100%), url('https://images.unsplash.com/photo-1506744038136-46273834b3fb?q=80&w=2000&auto=format&fit=crop'); background-size: cover; color: #e6eef8; }
140
- .gradio-container { background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02)); border-radius: 18px; padding: 22px; backdrop-filter: blur(12px) saturate(140%); box-shadow: 0 10px 40px rgba(2,6,23,0.6); }
141
- #title { color: #eaf4ff; font-weight:700; }
142
- h2 { color: #dff0ff; }
143
- .gr-box { border-radius: 12px!important; }
144
- input, textarea { background: rgba(255,255,255,0.03) !important; color: #e6eef8 !important; border: 1px solid rgba(255,255,255,0.06) !important; }
145
- button { background: linear-gradient(90deg,#6ad1ff,#7b6bff) !important; color: #041028 !important; border: none !important; box-shadow: 0 6px 18px rgba(75,60,255,0.14); }
146
- footer { display:none !important; }
147
-
148
- /* comet streak */
149
- #comet {
150
- position: absolute;
151
- right: -20%;
152
- top: -10%;
153
- width: 60%;
154
- height: 400px;
155
- background: linear-gradient(120deg, rgba(255,255,255,0.03), rgba(120,80,255,0.1));
156
- transform: rotate(-25deg);
157
- filter: blur(30px);
158
- pointer-events: none;
159
- border-radius: 40%;
160
- opacity: 0.6;
161
  }
162
  """
163
 
164
- def format_jobs_md(jobs):
165
- if not jobs:
166
- return "No community job posts found. Share a job using the Google Form (link in README)."
167
- md = ""
168
- for j in jobs:
169
- md += f"**{escape(j['title'])}** — {escape(j['company'])} — {escape(j['location'])}\n\n"
170
- if j.get("snippet"):
171
- md += f"{escape(j['snippet'])}\n\n"
172
- if j.get("link"):
173
- md += f"[Apply link]({escape(j['link'])})\n\n---\n"
174
- return md
175
-
176
- with gr.Blocks(css=COMET_CSS) as demo:
177
- gr.HTML("<div id='comet'></div>")
178
- gr.Markdown("<h2 id='title'>🚀 StudyMate AI — Comet Edition</h2>"
179
- "<p>Learn, find jobs, and get local help in English, Urdu & Sindhi. Public & free.</p>")
180
- with gr.Row():
181
- with gr.Column(scale=3):
182
- mode = gr.Radio(choices=["Study Help", "Job Finder", "General Chat"], value="Study Help", label="Mode")
183
- user_input = gr.Textbox(placeholder="Ask in English or Urdu — e.g., 'کشش ثقل آسان الفاظ میں بتائیں' or 'job ideas for designer in karachi'", label="Your question", lines=3)
184
- send = gr.Button("Ask StudyMate ✨")
185
- output_chat = gr.Markdown("", label="Answer")
186
- with gr.Column(scale=1):
187
- gr.Markdown("### 🔎 Quick Actions")
188
- daily_tip_btn = gr.Button("Daily Tip")
189
- last_q_btn = gr.Button("Sample Qs")
190
- gr.Markdown("### 📢 Community Jobs")
191
- jobs_md = gr.Markdown("Jobs will appear here when you set JOB_SHEET_CSV in Space Secrets.")
192
- # Event handlers
193
- def on_ask(user_text, mode_sel):
194
- user_text = (user_text or "").strip()
195
- if not user_text:
196
- return "Please type a question first."
197
- if mode_sel == "Job Finder":
198
- # use community CSV URL first
199
- jobs = find_jobs_by_skill(user_text, JOB_SHEET_CSV)
200
- if jobs:
201
- return format_jobs_md(jobs)
202
- # else fallback: ask model for job ideas
203
- prompt = f"You are a career advisor. Suggest 6 practical job or gig ideas for someone with skill: {user_text}. Give short bullets and local ideas."
204
- ans = safe_answer(prompt, mode_sel)
205
- return ans
206
- else:
207
- ans = safe_answer(user_text, mode_sel)
208
- return ans
209
-
210
- def on_daily_tip():
211
- tip_prompt = "Give one short practical study or career tip in 1-2 sentences and include a motivation line."
212
- return safe_answer(tip_prompt, "General Chat")
213
-
214
- def on_sample_qs():
215
- return ("Try these:\n\n"
216
- "- Explain Newton's 2nd law in simple Urdu.\n"
217
- "- Give 5 flashcards about basic calculus.\n"
218
- "- Suggest 3 low-cost freelance gigs for a graphic designer in Pakistan.")
219
-
220
- send.click(on_ask, [user_input, mode], output_chat)
221
- daily_tip_btn.click(lambda: on_daily_tip(), None, output_chat)
222
- last_q_btn.click(lambda: on_sample_qs(), None, output_chat)
223
-
224
- gr.Markdown("**Notes:** This app uses a remote model via Hugging Face Inference API. Add your `HF_API_TOKEN` as a Space Secret. If the model is unavailable, StudyMate will show helpful fallback messages so users are not left hanging.")
225
 
226
  demo.launch()
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ import os
3
+ from huggingface_hub import InferenceClient
4
+
5
+ # 🔒 Load token and set fallback models
6
+ HF_TOKEN = os.getenv("HF_API_TOKEN")
7
+ MODELS = [
8
+ "meta-llama/Meta-Llama-3-8B-Instruct",
9
+ "mistralai/Mistral-7B-Instruct-v0.2",
10
+ "google/gemma-7b-it"
11
+ ]
12
+
13
+ # Function to query models
14
+ def ask_studymate(prompt):
15
+ if not HF_TOKEN:
16
+ return "⚠️ Missing API token. Please set HF_API_TOKEN in your Space Variables."
17
+
18
+ for model in MODELS:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  try:
20
+ client = InferenceClient(model=model, token=HF_TOKEN)
21
+ reply = ""
22
+ for message in client.chat_completion(
23
+ messages=[{"role": "user", "content": prompt}],
24
+ max_tokens=512,
25
+ stream=True
26
+ ):
27
+ if "content" in message.delta:
28
+ reply += message.delta["content"]
29
+ if reply.strip():
30
+ return reply
31
+ except Exception:
32
+ continue
33
+ return "😔 Sorry, I couldn’t generate a complete answer right now. Please try again."
34
+
35
+ # 🌈 iOS Transparent / Comet Theme
36
+ theme_css = """
37
+ body {
38
+ background: linear-gradient(135deg, rgba(255,255,255,0.3), rgba(0,0,0,0.2));
39
+ backdrop-filter: blur(25px);
40
+ }
41
+ .gradio-container {
42
+ border-radius: 20px !important;
43
+ box-shadow: 0 8px 30px rgba(0,0,0,0.2);
44
+ background: rgba(255,255,255,0.4);
45
+ }
46
+ textarea, input, button {
47
+ border-radius: 12px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
  """
50
 
51
+ with gr.Blocks(css=theme_css, title="StudyMate AI") as demo:
52
+ gr.Markdown(
53
+ "<h1 style='text-align:center; color:#1d1d1f;'>📘 StudyMate AI</h1>"
54
+ "<p style='text-align:center; font-size:16px;'>Your AI Study Partner — Ask anything, anytime.</p>"
55
+ )
56
+ chat = gr.ChatInterface(
57
+ fn=ask_studymate,
58
+ chatbot=gr.Chatbot(height=500, bubble_full_width=False),
59
+ textbox=gr.Textbox(placeholder="Ask StudyMate anything...", label="Your Question"),
60
+ theme="glass",
61
+ retry_btn="🔁 Retry",
62
+ clear_btn="🧹 Clear",
63
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  demo.launch()