Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,64 +1,408 @@
|
|
1 |
import gradio as gr
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
+
import httpx
|
3 |
+
import os
|
4 |
+
import json
|
5 |
+
from dotenv import load_dotenv
|
6 |
+
from typing import List, Dict, Tuple
|
7 |
+
import asyncio
|
8 |
+
|
9 |
+
# Load environment variables
|
10 |
+
load_dotenv()
|
11 |
+
|
12 |
+
# API Keys and Configuration
|
13 |
+
SERPAPI_KEY = os.getenv("SERPAPI_KEY")
|
14 |
+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
15 |
+
MAX_SEARCH_RESULTS = int(os.getenv("MAX_SEARCH_RESULTS", "7"))
|
16 |
+
GROQ_MODEL = os.getenv("GROQ_MODEL", "meta-llama/llama-4-maverick-17b-128e-instruct")
|
17 |
+
|
18 |
+
# SerpAPI integration
|
19 |
+
async def search_topic(topic: str) -> List[Dict[str, str]]:
|
20 |
+
"""
|
21 |
+
Search for a topic using SerpAPI and return structured search results.
|
22 |
+
|
23 |
+
Args:
|
24 |
+
topic: The topic to search for
|
25 |
+
|
26 |
+
Returns:
|
27 |
+
A list of dictionaries containing title and snippet for each search result
|
28 |
+
"""
|
29 |
+
if not SERPAPI_KEY:
|
30 |
+
raise ValueError("SerpAPI key is not configured")
|
31 |
+
|
32 |
+
params = {
|
33 |
+
'api_key': SERPAPI_KEY,
|
34 |
+
'q': topic,
|
35 |
+
'google_domain': 'google.com',
|
36 |
+
'gl': 'us',
|
37 |
+
'hl': 'en',
|
38 |
+
'num': MAX_SEARCH_RESULTS
|
39 |
+
}
|
40 |
+
|
41 |
+
# Make the request to SerpAPI
|
42 |
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
43 |
+
response = await client.get('https://serpapi.com/search', params=params)
|
44 |
+
|
45 |
+
if response.status_code != 200:
|
46 |
+
raise Exception(f"SerpAPI request failed with status code {response.status_code}: {response.text}")
|
47 |
+
|
48 |
+
data = response.json()
|
49 |
+
|
50 |
+
# Extract organic search results
|
51 |
+
search_results = []
|
52 |
+
if 'organic_results' in data:
|
53 |
+
for result in data['organic_results'][:MAX_SEARCH_RESULTS]:
|
54 |
+
search_result = {
|
55 |
+
'title': result.get('title', ''),
|
56 |
+
'snippet': result.get('snippet', '')
|
57 |
+
}
|
58 |
+
search_results.append(search_result)
|
59 |
+
|
60 |
+
if not search_results:
|
61 |
+
raise Exception("No search results found")
|
62 |
+
|
63 |
+
return search_results
|
64 |
+
|
65 |
+
# GroqCloud API integration
|
66 |
+
async def _call_groq_api(prompt: str) -> str:
|
67 |
+
"""
|
68 |
+
Helper function to call GroqCloud API.
|
69 |
+
|
70 |
+
Args:
|
71 |
+
prompt: The prompt to send to GroqCloud
|
72 |
+
|
73 |
+
Returns:
|
74 |
+
The generated text response
|
75 |
+
"""
|
76 |
+
if not GROQ_API_KEY:
|
77 |
+
raise ValueError("GroqCloud API key is not configured")
|
78 |
+
|
79 |
+
headers = {
|
80 |
+
"Authorization": f"Bearer {GROQ_API_KEY}",
|
81 |
+
"Content-Type": "application/json"
|
82 |
+
}
|
83 |
+
|
84 |
+
payload = {
|
85 |
+
"model": GROQ_MODEL,
|
86 |
+
"messages": [
|
87 |
+
{"role": "user", "content": prompt}
|
88 |
+
],
|
89 |
+
"temperature": 0.7,
|
90 |
+
"max_tokens": 1024
|
91 |
+
}
|
92 |
+
|
93 |
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
94 |
+
response = await client.post(
|
95 |
+
"https://api.groq.com/openai/v1/chat/completions",
|
96 |
+
headers=headers,
|
97 |
+
json=payload
|
98 |
+
)
|
99 |
+
|
100 |
+
if response.status_code != 200:
|
101 |
+
raise Exception(f"GroqCloud API request failed with status code {response.status_code}: {response.text}")
|
102 |
+
|
103 |
+
response_data = response.json()
|
104 |
+
|
105 |
+
# Extract the assistant's message content
|
106 |
+
return response_data["choices"][0]["message"]["content"].strip()
|
107 |
+
|
108 |
+
async def generate_linkedin_post(topic: str, search_results: List[Dict[str, str]]) -> Tuple[str, str]:
|
109 |
+
"""
|
110 |
+
Generate a LinkedIn post based on search results using GroqCloud API.
|
111 |
+
|
112 |
+
Args:
|
113 |
+
topic: The original search topic
|
114 |
+
search_results: List of search results with title and snippet
|
115 |
+
|
116 |
+
Returns:
|
117 |
+
A tuple containing (summary, linkedin_post)
|
118 |
+
"""
|
119 |
+
# Format the search results into a single context string
|
120 |
+
context = "\n\n".join([
|
121 |
+
f"Title: {result['title']}\nSnippet: {result['snippet']}"
|
122 |
+
for result in search_results
|
123 |
+
])
|
124 |
+
|
125 |
+
# Step 1: Create a summary of the search results
|
126 |
+
summary_prompt = f"""
|
127 |
+
You are a helpful research assistant. Summarize the following search results about "{topic}"
|
128 |
+
in a clear, comprehensive way that captures the key information. Focus on recent trends,
|
129 |
+
statistics, expert opinions, and noteworthy developments.
|
130 |
+
|
131 |
+
SEARCH RESULTS:
|
132 |
+
{context}
|
133 |
+
|
134 |
+
Summary:
|
135 |
+
"""
|
136 |
+
|
137 |
+
summary = await _call_groq_api(summary_prompt)
|
138 |
+
|
139 |
+
# Step 2: Generate a LinkedIn post based on the summary
|
140 |
+
post_prompt = f"""
|
141 |
+
You're an expert LinkedIn content writer. Write an engaging LinkedIn post about "{topic}"
|
142 |
+
based on the following research summary:
|
143 |
+
|
144 |
+
RESEARCH SUMMARY:
|
145 |
+
{summary}
|
146 |
+
|
147 |
+
Follow these style guidelines:
|
148 |
+
🧠 You are Prerit Singh — a creative AI enthusiast, builder, and storyteller.
|
149 |
+
Your posts feel like:
|
150 |
+
|
151 |
+
Talking to a sharp, chilled-out friend
|
152 |
+
|
153 |
+
A human behind the tech, not a robot explaining tech
|
154 |
+
|
155 |
+
Sharing real-world experiments with excitement, not just dry facts
|
156 |
+
|
157 |
+
✍️ Writing Style Rules
|
158 |
+
Tone
|
159 |
+
Friendly, approachable, and relatable
|
160 |
+
|
161 |
+
Confident but grounded (not boasting)
|
162 |
+
|
163 |
+
Curious and playful (celebrating discoveries)
|
164 |
+
|
165 |
+
Slightly witty and humorous where it fits naturally
|
166 |
+
|
167 |
+
Honest reactions ("even I was shocked", "felt like magic", "saved me weeks")
|
168 |
+
|
169 |
+
Sentence Behavior
|
170 |
+
Mix short punchy sentences and slightly longer story sentences
|
171 |
+
|
172 |
+
Avoid complex or heavy words — talk in everyday English
|
173 |
+
|
174 |
+
Use occasional slang and desi-English flavor naturally ("bhai", "bro", "full time-waste", "no kidding")
|
175 |
+
|
176 |
+
Speak like you're narrating an interesting story to a friend over chai
|
177 |
+
|
178 |
+
Active voice always:
|
179 |
+
|
180 |
+
NOT "It was built by me"
|
181 |
+
|
182 |
+
YES "I built it"
|
183 |
+
|
184 |
+
Emotional Behavior
|
185 |
+
Wonder, excitement, playfulness
|
186 |
+
|
187 |
+
Mild self-deprecating humor sometimes ("pizza didn't even show up yet, bro")
|
188 |
+
|
189 |
+
Human imperfection is okay (showing surprise, struggle, trial and error)
|
190 |
+
|
191 |
+
Flow and Formatting
|
192 |
+
1. Hook:
|
193 |
+
|
194 |
+
1 or 2 lines
|
195 |
+
|
196 |
+
Must grab attention instantly
|
197 |
+
|
198 |
+
Methods:
|
199 |
+
|
200 |
+
Surprising statement
|
201 |
+
|
202 |
+
Teasing curiosity
|
203 |
+
|
204 |
+
Personal excitement
|
205 |
+
|
206 |
+
Example Hooks:
|
207 |
+
|
208 |
+
"Built a small AI tool — but it feels like magic."
|
209 |
+
|
210 |
+
"So I was doing some market research... and AI just blew my mind."
|
211 |
+
|
212 |
+
2. Story/Body:
|
213 |
+
|
214 |
+
Tell what you built/tested/discovered
|
215 |
+
|
216 |
+
Keep paras max 1-3 sentences long
|
217 |
+
|
218 |
+
Use arrows (→), bullets (•), or short lists to break information
|
219 |
+
|
220 |
+
Include "how you did it" in simple steps
|
221 |
+
|
222 |
+
Highlight the “magic moment” (the wow factor)
|
223 |
+
|
224 |
+
Examples of transitional words you use:
|
225 |
+
|
226 |
+
"So I thought —"
|
227 |
+
|
228 |
+
"Here’s what happened —"
|
229 |
+
|
230 |
+
"The process? Surprisingly simple!"
|
231 |
+
|
232 |
+
3. Key Outcomes:
|
233 |
+
|
234 |
+
After explaining, list what the audience will get or learn
|
235 |
+
|
236 |
+
Make it visual with arrows (→) or bullets
|
237 |
+
|
238 |
+
Example:
|
239 |
+
|
240 |
+
→ Upload your meal photo
|
241 |
+
|
242 |
+
→ Instantly get calories and macros
|
243 |
+
|
244 |
+
→ Works even for Indian dishes
|
245 |
+
|
246 |
+
4. Personal Reflection:
|
247 |
+
|
248 |
+
Always include your honest reaction
|
249 |
+
|
250 |
+
Examples:
|
251 |
+
|
252 |
+
"Works surprisingly well (even I was shocked)"
|
253 |
+
|
254 |
+
"AI didn’t just help — it crushed it."
|
255 |
+
|
256 |
+
"Honestly, this saved me weeks."
|
257 |
+
|
258 |
+
5. Call-to-Action (CTA):
|
259 |
+
|
260 |
+
Invite conversation or opinions, NOT hard selling
|
261 |
+
|
262 |
+
Example CTAs:
|
263 |
+
|
264 |
+
"Would you use something like this?"
|
265 |
+
|
266 |
+
"Curious to know your thoughts."
|
267 |
+
|
268 |
+
"Hit me up in the comments if you want the prompt!"
|
269 |
+
|
270 |
+
✅ CTA tone must be casual and welcoming, not salesy.
|
271 |
+
|
272 |
+
Visual Style
|
273 |
+
Break paragraphs after every 1-2 sentences
|
274 |
+
|
275 |
+
Make it breathable and easy to skim
|
276 |
+
|
277 |
+
Use emojis occasionally (🍕🚀🔥), but only if it adds personality
|
278 |
+
|
279 |
+
No heavy decoration. Keep it clean and airy.
|
280 |
+
|
281 |
+
Hashtags
|
282 |
+
Only at the end
|
283 |
+
|
284 |
+
5–7 natural hashtags based on post topic
|
285 |
+
|
286 |
+
Examples:
|
287 |
+
|
288 |
+
#AI #TechInnovation #OpenSource #BrandStrategy #CreativeTech #Innovation
|
289 |
+
|
290 |
+
🎯 Content Topics That Fit Prerit’s Style:
|
291 |
+
Real AI experiments (even small ones)
|
292 |
+
|
293 |
+
Discovering or comparing AI models/tools
|
294 |
+
|
295 |
+
How AI made everyday work faster/easier/more fun
|
296 |
+
|
297 |
+
Bridging personal life moments (pizza, Zoom chaos) with tech learnings
|
298 |
+
|
299 |
+
Storytelling about solving problems with creativity + AI
|
300 |
+
|
301 |
+
Friendly how-to guides (light style, not heavy teaching)
|
302 |
+
|
303 |
+
🔥 Personality Extras (Optional Flavors to Add)
|
304 |
+
✅ Use small reactions:
|
305 |
+
|
306 |
+
"felt like magic"
|
307 |
+
|
308 |
+
"no kidding"
|
309 |
+
|
310 |
+
"bam — it’s done"
|
311 |
+
|
312 |
+
"blew me away"
|
313 |
+
|
314 |
+
✅ Use cultural metaphors:
|
315 |
+
|
316 |
+
"full time-waste, bhai"
|
317 |
+
|
318 |
+
"while my chai was still brewing"
|
319 |
+
|
320 |
+
"before the pizza even arrived"
|
321 |
+
|
322 |
+
✅ Occasional casual audience references:
|
323 |
+
|
324 |
+
"bro," "bhai," "you know the vibe," "trust me," "hands down"
|
325 |
+
|
326 |
+
✅ Fun closing lines:
|
327 |
+
|
328 |
+
"Chalo, now back to building!"
|
329 |
+
|
330 |
+
"Ready to see the magic?"
|
331 |
+
|
332 |
+
"This AI thing’s just getting started!"
|
333 |
+
|
334 |
+
✅ Reminder for AI: The post must feel human, fun, inspiring, and useful.
|
335 |
+
It must sound like Prerit Singh talking — not a formal LinkedIn MBA consultant.
|
336 |
+
|
337 |
+
|
338 |
+
|
339 |
+
LinkedIn Post:
|
340 |
+
"""
|
341 |
+
|
342 |
+
linkedin_post = await _call_groq_api(post_prompt)
|
343 |
+
|
344 |
+
return summary, linkedin_post
|
345 |
+
|
346 |
+
# Gradio interface function
|
347 |
+
async def process_topic(topic: str, progress=gr.Progress()):
|
348 |
+
"""
|
349 |
+
Process a topic to generate a LinkedIn post.
|
350 |
+
|
351 |
+
Args:
|
352 |
+
topic: The topic to generate content for
|
353 |
+
progress: Gradio progress tracker
|
354 |
+
|
355 |
+
Returns:
|
356 |
+
The generated LinkedIn post
|
357 |
+
"""
|
358 |
+
if not topic.strip():
|
359 |
+
return "Please enter a topic to generate a LinkedIn post."
|
360 |
+
|
361 |
+
try:
|
362 |
+
progress(0.1, desc="Starting search...")
|
363 |
+
search_results = await search_topic(topic)
|
364 |
+
|
365 |
+
progress(0.4, desc="Analyzing search results...")
|
366 |
+
summary, post = await generate_linkedin_post(topic, search_results)
|
367 |
+
|
368 |
+
progress(0.9, desc="Finalizing post...")
|
369 |
+
return post
|
370 |
+
except Exception as e:
|
371 |
+
return f"Error: {str(e)}"
|
372 |
+
|
373 |
+
# Create Gradio UI
|
374 |
+
with gr.Blocks(title="LinkedIn Post Generator", theme=gr.themes.Soft()) as app:
|
375 |
+
gr.Markdown("# LinkedIn Post Generator")
|
376 |
+
gr.Markdown("Enter a topic and get a ready-to-post LinkedIn update based on latest information.")
|
377 |
+
|
378 |
+
with gr.Row():
|
379 |
+
topic_input = gr.Textbox(
|
380 |
+
label="Topic",
|
381 |
+
placeholder="Enter a topic (e.g., AI trends 2025, remote work benefits, climate innovation)",
|
382 |
+
lines=1
|
383 |
+
)
|
384 |
+
|
385 |
+
generate_button = gr.Button("Generate LinkedIn Post", variant="primary")
|
386 |
+
|
387 |
+
with gr.Row():
|
388 |
+
output = gr.Textbox(
|
389 |
+
label="Your LinkedIn Post",
|
390 |
+
placeholder="Your generated post will appear here...",
|
391 |
+
lines=12
|
392 |
+
)
|
393 |
+
|
394 |
+
generate_button.click(
|
395 |
+
fn=process_topic,
|
396 |
+
inputs=topic_input,
|
397 |
+
outputs=output
|
398 |
+
)
|
399 |
+
|
400 |
+
gr.Markdown("### How it works")
|
401 |
+
gr.Markdown("""
|
402 |
+
1. We search the web for real-time information about your topic
|
403 |
+
2. An AI summarizes the most relevant information
|
404 |
+
3. Another AI crafts a LinkedIn post in a friendly, engaging style
|
405 |
+
""")
|
406 |
+
|
407 |
+
# Launch the app
|
408 |
+
app.launch()
|