yuvarajareddy001 commited on
Commit
28f56d3
·
verified ·
1 Parent(s): fd8e718

Upload 6 files

Browse files
Files changed (6) hide show
  1. app.py +69 -0
  2. post_generator.py +92 -0
  3. preprocess.py +85 -0
  4. processed_posts.json +100 -0
  5. raw_posts.json +42 -0
  6. requirements.txt +7 -0
app.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from post_generator import *
3
+
4
+ # Options for length and language
5
+ length_options = ["Short", "Medium", "Long"]
6
+ language_options = ["English", "Hinglish"]
7
+
8
+ # Main app layout
9
+ def main():
10
+ st.title("🕵️ LinkedIn Content Generator")
11
+ st.markdown(
12
+ """<style>
13
+ .css-1oe6wy4, .css-1y4p8pa {
14
+ background-color: #f8f9fa;
15
+ border-radius: 15px;
16
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
17
+ }
18
+ .css-1v0mbdj {
19
+ background-color: #ffffff;
20
+ padding: 20px;
21
+ border-radius: 10px;
22
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
23
+ }
24
+ </style>
25
+ """,
26
+ unsafe_allow_html=True,
27
+ )
28
+
29
+ st.markdown("**The code replicates the writing style of the selected author and generates posts accordingly. You can customize the raw data to match any author of your choice.**")
30
+ st.markdown("**Using llama-3.3-70b-versatile model with groc API and Prompt Engineering**")
31
+
32
+ st.markdown("---")
33
+
34
+ # Organize UI into sections
35
+ st.header("Choose Post Parameters")
36
+
37
+ fs = FewShotPosts()
38
+ tags = fs.get_tags()
39
+
40
+ # Create three columns for inputs
41
+ col1, col2, col3 = st.columns([1.5, 1, 1])
42
+ with col1:
43
+ selected_tag = st.selectbox("Select a Topic", options=tags)
44
+
45
+ with col2:
46
+ selected_length = st.selectbox("Select Length", options=length_options)
47
+
48
+ with col3:
49
+ selected_language = st.selectbox("Select Language", options=language_options)
50
+
51
+ st.markdown("---")
52
+
53
+ st.header("Generate Post")
54
+
55
+ # Generate Button and Display
56
+ if st.button("Generate"):
57
+ post = generate_post(selected_length, selected_language, selected_tag)
58
+
59
+ # Display the generated post
60
+ st.write(post)
61
+ else:
62
+ st.write("Click the button above to generate a LinkedIn post.")
63
+
64
+ st.markdown("---")
65
+
66
+
67
+ # Run the app
68
+ if __name__ == "__main__":
69
+ main()
post_generator.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_groq import ChatGroq
2
+ import os
3
+ from dotenv import load_dotenv
4
+ import pandas as pd
5
+ import json
6
+
7
+
8
+ class FewShotPosts:
9
+ def __init__(self, file_path="processed_posts.json"):
10
+ self.df = None
11
+ self.unique_tags = None
12
+ self.load_posts(file_path)
13
+
14
+ def load_posts(self, file_path):
15
+ with open(file_path, encoding="utf-8") as f:
16
+ posts = json.load(f)
17
+ self.df = pd.json_normalize(posts)
18
+ self.df['length'] = self.df['line_count'].apply(self.categorize_length)
19
+ # collect unique tags
20
+ all_tags = self.df['tags'].apply(lambda x: x).sum()
21
+ self.unique_tags = list(set(all_tags))
22
+
23
+ def get_filtered_posts(self, length, language, tag):
24
+ df_filtered = self.df[
25
+ (self.df['tags'].apply(lambda tags: tag in tags)) & # Tags contain 'Influencer'
26
+ (self.df['language'] == language) & # Language is 'English'
27
+ (self.df['length'] == length) # Line count is less than 5
28
+ ]
29
+ return df_filtered.to_dict(orient='records')
30
+
31
+ def categorize_length(self, line_count):
32
+ if line_count < 5:
33
+ return "Short"
34
+ elif 5 <= line_count <= 10:
35
+ return "Medium"
36
+ else:
37
+ return "Long"
38
+
39
+ def get_tags(self):
40
+ return self.unique_tags
41
+
42
+
43
+ load_dotenv()
44
+ llm = ChatGroq(groq_api_key=os.getenv("GROQ_API_KEY"), model_name="llama-3.3-70b-versatile")
45
+ few_shot = FewShotPosts()
46
+
47
+ def get_length_str(length):
48
+ if length == "Short":
49
+ return "1 to 5 lines"
50
+ if length == "Medium":
51
+ return "6 to 10 lines"
52
+ if length == "Long":
53
+ return "11 to 15 lines"
54
+
55
+
56
+ def generate_post(length, language, tag):
57
+ prompt = get_prompt(length, language, tag)
58
+ response = llm.invoke(prompt)
59
+ return response.content
60
+
61
+
62
+ def get_prompt(length, language, tag):
63
+ length_str = get_length_str(length)
64
+
65
+ prompt = f'''
66
+ Generate a LinkedIn post using the below information. No preamble.
67
+
68
+ 1) Topic: {tag}
69
+ 2) Length: {length_str}
70
+ 3) Language: {language}
71
+ If Language is Hinglish then it means it is a mix of Hindi and English.
72
+ The script for the generated post should always be English.
73
+ '''
74
+ # prompt = prompt.format(post_topic=tag, post_length=length_str, post_language=language)
75
+
76
+ examples = few_shot.get_filtered_posts(length, language, tag)
77
+
78
+ if len(examples) > 0:
79
+ prompt += "4) Use the writing style as per the following examples."
80
+
81
+ for i, post in enumerate(examples):
82
+ post_text = post['text']
83
+ prompt += f'\n\n Example {i+1}: \n\n {post_text}'
84
+
85
+ if i == 1: # Use max two samples
86
+ break
87
+
88
+ return prompt
89
+
90
+
91
+ if __name__ == "__main__":
92
+ print(generate_post("Medium", "English", "Mental Health"))
preprocess.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from llm_helper import llm
3
+ from langchain_core.prompts import PromptTemplate
4
+ from langchain_core.output_parsers import JsonOutputParser
5
+ from langchain_core.exceptions import OutputParserException
6
+
7
+
8
+ def process_posts(raw_file_path, processed_file_path=None):
9
+ with open(raw_file_path, encoding='utf-8') as file:
10
+ posts = json.load(file)
11
+ enriched_posts = []
12
+ for post in posts:
13
+ metadata = extract_metadata(post['text'])
14
+ post_with_metadata = post | metadata
15
+ enriched_posts.append(post_with_metadata)
16
+
17
+ unified_tags = get_unified_tags(enriched_posts)
18
+ for post in enriched_posts:
19
+ current_tags = post['tags']
20
+ new_tags = {unified_tags[tag] for tag in current_tags}
21
+ post['tags'] = list(new_tags)
22
+
23
+ with open(processed_file_path, encoding='utf-8', mode="w") as outfile:
24
+ json.dump(enriched_posts, outfile, indent=4)
25
+
26
+
27
+ def extract_metadata(post):
28
+ template = '''
29
+ You are given a LinkedIn post. You need to extract number of lines, language of the post and tags.
30
+ 1. Return a valid JSON. No preamble.
31
+ 2. JSON object should have exactly three keys: line_count, language and tags.
32
+ 3. tags is an array of text tags. Extract maximum two tags.
33
+ 4. Language should be English or Hinglish (Hinglish means hindi + english)
34
+
35
+ Here is the actual post on which you need to perform this task:
36
+ {post}
37
+ '''
38
+
39
+ pt = PromptTemplate.from_template(template)
40
+ chain = pt | llm
41
+ response = chain.invoke(input={"post": post})
42
+
43
+ try:
44
+ json_parser = JsonOutputParser()
45
+ res = json_parser.parse(response.content)
46
+ except OutputParserException:
47
+ raise OutputParserException("Context too big. Unable to parse jobs.")
48
+ return res
49
+
50
+
51
+ def get_unified_tags(posts_with_metadata):
52
+ unique_tags = set()
53
+ # Loop through each post and extract the tags
54
+ for post in posts_with_metadata:
55
+ unique_tags.update(post['tags']) # Add the tags to the set
56
+
57
+ unique_tags_list = ','.join(unique_tags)
58
+
59
+ template = '''I will give you a list of tags. You need to unify tags with the following requirements,
60
+ 1. Tags are unified and merged to create a shorter list.
61
+ Example 1: "Jobseekers", "Job Hunting" can be all merged into a single tag "Job Search".
62
+ Example 2: "Motivation", "Inspiration", "Drive" can be mapped to "Motivation"
63
+ Example 3: "Personal Growth", "Personal Development", "Self Improvement" can be mapped to "Self Improvement"
64
+ Example 4: "Scam Alert", "Job Scam" etc. can be mapped to "Scams"
65
+ 2. Each tag should be follow title case convention. example: "Motivation", "Job Search"
66
+ 3. Output should be a JSON object, No preamble
67
+ 3. Output should have mapping of original tag and the unified tag.
68
+ For example: {{"Jobseekers": "Job Search", "Job Hunting": "Job Search", "Motivation": "Motivation}}
69
+
70
+ Here is the list of tags:
71
+ {tags}
72
+ '''
73
+ pt = PromptTemplate.from_template(template)
74
+ chain = pt | llm
75
+ response = chain.invoke(input={"tags": str(unique_tags_list)})
76
+ try:
77
+ json_parser = JsonOutputParser()
78
+ res = json_parser.parse(response.content)
79
+ except OutputParserException:
80
+ raise OutputParserException("Context too big. Unable to parse jobs.")
81
+ return res
82
+
83
+
84
+ if __name__ == "__main__":
85
+ process_posts("raw_posts.json", "processed_posts.json")
processed_posts.json ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "text": "Just saw a LinkedIn Influencer with 'Organic Growth' written in the profile with 65K+ followers claiming that he can help you in growing your platform, copying the posts from other influencers.",
4
+ "engagement": 90,
5
+ "line_count": 1,
6
+ "language": "English",
7
+ "tags": [
8
+ "Influencer",
9
+ "Organic Growth"
10
+ ]
11
+ },
12
+ {
13
+ "text": "Jobseekers, this one\u2019s for you.\n Every application, every interview, every follow-up\u2026 the pressure is immense.\n And I know what you're thinking: Am I not good enough? \n But let me tell you, this isn\u2019t about you or your skills. It\u2019s about a broken system where 60% of applicants never hear back. \n Your mental health is not worth sacrificing for a system that doesn\u2019t acknowledge your worth. \n Please remember, taking care of yourself is the real priority. \n Your dream job will come, but for now, breathe. \ud83c\udf3b",
14
+ "engagement": 347,
15
+ "line_count": 7,
16
+ "language": "English",
17
+ "tags": [
18
+ "Job Search",
19
+ "Mental Health"
20
+ ]
21
+ },
22
+ {
23
+ "text": "Looking for jobs on LinkedIn is like online dating: Full of promises, but in the end, you\u2019re just left ghosted.",
24
+ "engagement": 109,
25
+ "line_count": 1,
26
+ "language": "English",
27
+ "tags": [
28
+ "Job Search",
29
+ "Online Dating"
30
+ ]
31
+ },
32
+ {
33
+ "text": "LinkedIn scams be like: 'Congratulations, you've been selected for a role you didn\u2019t even apply for!' \n The catch? Pay Rs. 50,000 for the honor.",
34
+ "engagement": 115,
35
+ "line_count": 2,
36
+ "language": "English",
37
+ "tags": [
38
+ "Scams"
39
+ ]
40
+ },
41
+ {
42
+ "text": "sapne dekhna achi baat hai,\nlekin job ka sapna dekh ke 'interested' likhna,\nyeh toh achi baat nahi hai na?",
43
+ "engagement": 545,
44
+ "line_count": 3,
45
+ "language": "Hinglish",
46
+ "tags": [
47
+ "Job Search",
48
+ "Sapne"
49
+ ]
50
+ },
51
+ {
52
+ "text": "Next time when I'll be reading some LinkedIn Influencer's story, I am starting from the last line.\nIf there's a link attached to it, it's most probably a fake one.\nSaves me time!",
53
+ "engagement": 188,
54
+ "line_count": 3,
55
+ "language": "English",
56
+ "tags": [
57
+ "Productivity",
58
+ "Time Management"
59
+ ]
60
+ },
61
+ {
62
+ "text": "Every time I poured my heart into 5-6 rounds of interviews and faced rejection, it felt like a punch in the gut. The sleepless nights, the endless preparation, all for nothing.\n\nBut looking back, I realize it wasn\u2019t nothing. It was the Universe\u2019s way of saying, \u201cNot this one, something better is on the way.\u201d\n\nEvery single time, I\u2019ve been shown why that rejection happened.\n\nDoors I thought I wanted to walk through were shut, only to have the right ones swing open.\n\nThe kind that aligned with my growth, my values, and my happiness.\n\nAt first, it stung. It hurt deeply. But now, when things don\u2019t go as planned, I don\u2019t panic.\n\nI don\u2019t question my worth. I sit back, breathe, and trust. The Universe knows.\n\nI know there's another plan waiting. Something bigger, better, and just for me.\n\nTo anyone feeling the weight of rejection: trust that the closed doors are protecting you from something you can\u2019t see right now.\n\nYour path is being cleared for something even more beautiful.",
63
+ "engagement": 206,
64
+ "line_count": 16,
65
+ "language": "English",
66
+ "tags": [
67
+ "Motivation"
68
+ ]
69
+ },
70
+ {
71
+ "text": "To everyone who's still looking for a job...\n\nI see you. I feel you. \ud83d\udc94\n\nEvery rejection email feels like a punch in the gut, and every 'We'll get back to you' sounds more like 'You'll never hear from us.'\n\nBut I want you to know, you're not alone in this. \ud83c\udf38\n\nAccording to a study, 80% of jobseekers struggle with anxiety and self-doubt during their search. It's normal to feel lost, but it's not the end.\n\nTake breaks, breathe, and remember, this doesn't define you. Your worth is not tied to an offer letter. \ud83d\udca5\n\nYour mental health matters more than any job.",
72
+ "engagement": 899,
73
+ "line_count": 9,
74
+ "language": "English",
75
+ "tags": [
76
+ "Job Search",
77
+ "Mental Health"
78
+ ]
79
+ },
80
+ {
81
+ "text": "Sometimes, we forget that a company\u2019s brand name doesn\u2019t define someone\u2019s talent. It\u2019s easy to get caught up in the 'big company = big talent' mindset, but that's not always the case.\n\nI\u2019ve had the privilege of working with people from smaller companies (lesser known) who blow me away with their skills and dedication. They don\u2019t need a fancy title or a famous brand behind them to prove their worth.\n\nI've seen the other side too\u2014people in top-tier companies feeling lost, overwhelmed, or stuck, even though the world sees them as 'successful'.\n\nLet\u2019s stop attaching someone\u2019s value to the company they work for. Freshers especially need to hear this\u2014skills are what matter, not the size of the company behind them.\n\nAt the end of the day, happiness and growth don\u2019t come from a brand name, they come from doing what you love and constantly improving your craft.",
82
+ "engagement": 166,
83
+ "line_count": 11,
84
+ "language": "English",
85
+ "tags": [
86
+ "Self Improvement",
87
+ "Career Advice"
88
+ ]
89
+ },
90
+ {
91
+ "text": "So when I left a toxic work environment, I told my manager a simple thing and felt so good \ud83d\ude2f\n\nI just said-\n\n'Hope your son gets a manager like you.\nI hope that the manager behaves the same way as you did with me.\nThank you.'\n\nNow tell me 1 thing-\n\nShe always said that she was a great manager.\nWhy will she get offended?\n\nI just told her that I wish her son would get a manager like she was.\n\nIf you felt bad, then that means you were a bad manager and now you know it. \ud83e\b80\n\nIf you feel good, then take it as a blessing for your son and you'll really want someone to treat your son/daughter in the same way.\n\nShe cannot be even angry with me else it'll prove that she was not a 'great' manager.\n\nMuskan - 1\nManager - 0\n\nMuskan -> Aura +100000000\n\n(Fictional message unfortunately :(\n)\n\nHope you all become the people that your sons/daughters will like to work under \ud83d\ude4f\n\nThere are a lot of bad people/things, bring a small change and break the chain :)",
92
+ "engagement": 1111,
93
+ "line_count": 19,
94
+ "language": "English",
95
+ "tags": [
96
+ "Motivation",
97
+ "Leadership"
98
+ ]
99
+ }
100
+ ]
raw_posts.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "text": "Just saw a LinkedIn Influencer with 'Organic Growth' written in the profile with 65K+ followers claiming that he can help you in growing your platform, copying the posts from other influencers.",
4
+ "engagement": 90
5
+ },
6
+ {
7
+ "text": "Jobseekers, this one’s for you.\n Every application, every interview, every follow-up… the pressure is immense.\n And I know what you're thinking: Am I not good enough? \n But let me tell you, this isn’t about you or your skills. It’s about a broken system where 60% of applicants never hear back. \n Your mental health is not worth sacrificing for a system that doesn’t acknowledge your worth. \n Please remember, taking care of yourself is the real priority. \n Your dream job will come, but for now, breathe. 🌻",
8
+ "engagement": 347
9
+ },
10
+ {
11
+ "text": "Looking for jobs on LinkedIn is like online dating: Full of promises, but in the end, you’re just left ghosted.",
12
+ "engagement": 109
13
+ },
14
+ {
15
+ "text": "LinkedIn scams be like: 'Congratulations, you've been selected for a role you didn’t even apply for!' \n The catch? Pay Rs. 50,000 for the honor.",
16
+ "engagement": 115
17
+ },
18
+ {
19
+ "text": "sapne dekhna achi baat hai,\nlekin job ka sapna dekh ke 'interested' likhna,\nyeh toh achi baat nahi hai na?",
20
+ "engagement": 545
21
+ },
22
+ {
23
+ "text": "Next time when I'll be reading some LinkedIn Influencer's story, I am starting from the last line.\nIf there's a link attached to it, it's most probably a fake one.\nSaves me time!",
24
+ "engagement": 188
25
+ },
26
+ {
27
+ "text": "Every time I poured my heart into 5-6 rounds of interviews and faced rejection, it felt like a punch in the gut. The sleepless nights, the endless preparation, all for nothing.\n\nBut looking back, I realize it wasn’t nothing. It was the Universe’s way of saying, “Not this one, something better is on the way.”\n\nEvery single time, I’ve been shown why that rejection happened.\n\nDoors I thought I wanted to walk through were shut, only to have the right ones swing open.\n\nThe kind that aligned with my growth, my values, and my happiness.\n\nAt first, it stung. It hurt deeply. But now, when things don’t go as planned, I don’t panic.\n\nI don’t question my worth. I sit back, breathe, and trust. The Universe knows.\n\nI know there's another plan waiting. Something bigger, better, and just for me.\n\nTo anyone feeling the weight of rejection: trust that the closed doors are protecting you from something you can’t see right now.\n\nYour path is being cleared for something even more beautiful.",
28
+ "engagement": 206
29
+ },
30
+ {
31
+ "text": "To everyone who's still looking for a job...\n\nI see you. I feel you. \ud83d\udc94\n\nEvery rejection email feels like a punch in the gut, and every 'We'll get back to you' sounds more like 'You'll never hear from us.'\n\nBut I want you to know, you're not alone in this. \ud83c\udf38\n\nAccording to a study, 80% of jobseekers struggle with anxiety and self-doubt during their search. It's normal to feel lost, but it's not the end.\n\nTake breaks, breathe, and remember, this doesn't define you. Your worth is not tied to an offer letter. \ud83d\udca5\n\nYour mental health matters more than any job.",
32
+ "engagement": 899
33
+ },
34
+ {
35
+ "text": "Sometimes, we forget that a company’s brand name doesn’t define someone’s talent. It’s easy to get caught up in the 'big company = big talent' mindset, but that's not always the case.\n\nI’ve had the privilege of working with people from smaller companies (lesser known) who blow me away with their skills and dedication. They don’t need a fancy title or a famous brand behind them to prove their worth.\n\nI've seen the other side too—people in top-tier companies feeling lost, overwhelmed, or stuck, even though the world sees them as 'successful'.\n\nLet’s stop attaching someone’s value to the company they work for. Freshers especially need to hear this—skills are what matter, not the size of the company behind them.\n\nAt the end of the day, happiness and growth don’t come from a brand name, they come from doing what you love and constantly improving your craft.",
36
+ "engagement": 166
37
+ },
38
+ {
39
+ "text": "So when I left a toxic work environment, I told my manager a simple thing and felt so good \ud83d\ude2f\n\nI just said-\n\n'Hope your son gets a manager like you.\nI hope that the manager behaves the same way as you did with me.\nThank you.'\n\nNow tell me 1 thing-\n\nShe always said that she was a great manager.\nWhy will she get offended?\n\nI just told her that I wish her son would get a manager like she was.\n\nIf you felt bad, then that means you were a bad manager and now you know it. \ud83e\b80\n\nIf you feel good, then take it as a blessing for your son and you'll really want someone to treat your son/daughter in the same way.\n\nShe cannot be even angry with me else it'll prove that she was not a 'great' manager.\n\nMuskan - 1\nManager - 0\n\nMuskan -> Aura +100000000\n\n(Fictional message unfortunately :(\n)\n\nHope you all become the people that your sons/daughters will like to work under \ud83d\ude4f\n\nThere are a lot of bad people/things, bring a small change and break the chain :)",
40
+ "engagement": 1111
41
+ }
42
+ ]
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ streamlit==1.35.0
2
+ langchain==0.2.14
3
+ langchain-core==0.2.39
4
+ langchain-community==0.2.12
5
+ langchain_groq==0.1.9
6
+ pandas==2.0.2
7
+ python-dotenv==1.0.1