|
import streamlit as st |
|
import urllib.request |
|
import json |
|
import requests |
|
import os |
|
from langchain_core.messages import HumanMessage, SystemMessage |
|
from langchain_openai import ChatOpenAI |
|
|
|
|
|
os.environ["OPENAI_API_KEY"] = "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" |
|
|
|
def fetch_ai_news(num_articles=10): |
|
""" |
|
Fetches top AI news using the GNews API and returns a list of articles with their details. |
|
""" |
|
api_key = "4e21d7361fdd189f4243c9df6725fb60" |
|
url = f"https://gnews.io/api/v4/search?q=artificial+intelligence&lang=en&country=us&max={num_articles}&sortby=publishedAt&apikey={api_key}" |
|
try: |
|
with urllib.request.urlopen(url) as response: |
|
data = json.loads(response.read().decode("utf-8")) |
|
return data.get("articles", []) |
|
except Exception as e: |
|
st.error(f"An error occurred while fetching AI news: {e}") |
|
return [] |
|
|
|
def scrape_content(url: str) -> str: |
|
""" |
|
Scrapes the content of a given link using Jina AI. |
|
""" |
|
try: |
|
response = requests.get("https://r.jina.ai/" + url) |
|
if response.status_code == 200: |
|
return response.text |
|
else: |
|
st.error(f"Failed to fetch the content from {url}. Status code: {response.status_code}") |
|
return "" |
|
except Exception as e: |
|
st.error(f"An error occurred while scraping {url}: {e}") |
|
return "" |
|
|
|
def generate_blog(content: str, title: str, instructions: str = "") -> str: |
|
""" |
|
Generates a professional-quality blog based on the scraped content and user instructions. |
|
""" |
|
system_prompt = """You are a professional blog writer specializing in creating detailed, insightful, and engaging blogs about AI and technology. |
|
Follow these guidelines to create high-quality content: |
|
|
|
1. Structure: |
|
- Create an engaging title that captures attention |
|
- Write a compelling introduction that sets the context |
|
- Develop a well-organized main body with detailed analysis |
|
- Include practical implications and real-world applications |
|
- Conclude with key points and future outlook |
|
|
|
2. Style: |
|
- Maintain a professional yet conversational tone |
|
- Use clear paragraphs and transitions |
|
- Include relevant examples and case studies |
|
- Balance technical detail with accessibility |
|
- Incorporate industry insights and expert perspectives |
|
|
|
3. Technical Accuracy: |
|
- Verify technical concepts and terminology |
|
- Explain complex ideas clearly |
|
- Provide context for technical innovations |
|
- Include relevant statistics and data points |
|
- Reference key developments and trends |
|
|
|
4. Engagement: |
|
- Use engaging subheadings |
|
- Include relevant quotes when appropriate |
|
- Ask thought-provoking questions |
|
- Provide actionable insights |
|
- Create a narrative flow |
|
|
|
5. SEO Optimization: |
|
- Use relevant keywords naturally |
|
- Create scannable content with proper formatting |
|
- Include meta description suggestions |
|
- Structure headings hierarchically |
|
- Optimize for readability |
|
|
|
Ensure the content is: |
|
- Well-researched and accurate |
|
- Engaging and valuable to readers |
|
- Properly structured and formatted |
|
- Technical yet accessible |
|
- Original and insightful""" |
|
|
|
if instructions: |
|
system_prompt += f"\n\nAdditional instructions: {instructions}" |
|
|
|
chat = ChatOpenAI(model="gpt-4o-mini") |
|
messages = [ |
|
SystemMessage(content=system_prompt), |
|
HumanMessage(content=f"Content: {content}\n\nGenerate a blog with title: {title}") |
|
] |
|
response = chat(messages) |
|
return response.content |
|
|
|
def modify_blog(blog_content: str, modification_instructions: str) -> str: |
|
""" |
|
Modifies an existing blog based on user instructions while maintaining quality and coherence. |
|
""" |
|
system_prompt = """ Modifies an existing blog based on user instructions while maintaining quality and coherence. |
|
Makes changes visibly apparent to the user. |
|
""" |
|
system_prompt = """You are an expert blog editor specializing in AI and technology content. Your task is to modify the provided blog according to the user's instructions while maintaining its professional quality. |
|
|
|
Follow these two key guidelines: |
|
1. Content Modification: |
|
- Apply requested changes precisely while preserving core message |
|
- Maintain professional quality, technical accuracy, and readability |
|
- Ensure smooth transitions and logical flow throughout the modified content |
|
- Make the requested changes regardless of the type (shortening, expanding, changing tone, etc.) |
|
|
|
2. Visibility of Changes: |
|
- Add a "## MODIFICATIONS SUMMARY" section at the very end of the blog |
|
- List 2-4 bullet points that clearly explain what major changes were made |
|
- Be specific about what was modified (e.g., "Shortened introduction by 40%", "Added new section on ethics") |
|
|
|
Return the complete modified blog with the summary section at the end.""" |
|
|
|
chat = ChatOpenAI(model="gpt-4o-mini") |
|
messages = [ |
|
SystemMessage(content=system_prompt), |
|
HumanMessage(content=f"Original blog:\n\n{blog_content}\n\nPlease modify the blog according to these instructions:\n{modification_instructions}") |
|
] |
|
response = chat(messages) |
|
return response.content |
|
|
|
def generate_newspaper(articles: list) -> str: |
|
""" |
|
Generates a comprehensive multi-page newspaper format with in-depth analysis of AI news. |
|
""" |
|
system_prompt = """You are an experienced newspaper editor-in-chief specializing in AI and technology. Create a comprehensive, multi-page newspaper that deeply analyzes the provided articles. |
|
|
|
NEWSPAPER STRUCTURE: |
|
|
|
FRONT PAGE: |
|
1. Main Headline & Lead Story |
|
- Compelling primary headline |
|
- Engaging subheadline |
|
- In-depth analysis of key story |
|
- Related infographic descriptions |
|
- Expert quotes and insights |
|
|
|
TECHNOLOGY SECTION: |
|
2. Technical Deep Dives |
|
- Detailed technical analysis |
|
- Architecture and implementation details |
|
- Expert technical perspectives |
|
- Innovation challenges and solutions |
|
- Future technical implications |
|
|
|
BUSINESS & INDUSTRY: |
|
3. Market Impact Analysis |
|
- Industry trends and patterns |
|
- Market predictions |
|
- Investment implications |
|
- Startup ecosystem insights |
|
- Corporate strategy analysis |
|
|
|
SOCIETY & ETHICS: |
|
4. Societal Implications |
|
- Workforce impact analysis |
|
- Ethical considerations |
|
- Policy and regulation updates |
|
- Privacy and security concerns |
|
- Social responsibility discussion |
|
|
|
INNOVATION SPOTLIGHT: |
|
5. Research & Development |
|
- Latest breakthroughs |
|
- Academic perspectives |
|
- Emerging technologies |
|
- Innovation roadmaps |
|
- Future predictions |
|
|
|
EDITORIAL & OPINION: |
|
6. Expert Commentary |
|
- Industry leader perspectives |
|
- Academic viewpoints |
|
- Policy maker opinions |
|
- Public discourse analysis |
|
- Strategic recommendations |
|
|
|
FORMAT GUIDELINES: |
|
- Use newspaper-style formatting |
|
- Include clear section breaks |
|
- Add relevant subheadings |
|
- Maintain consistent style |
|
- Use proper citations |
|
|
|
WRITING STYLE: |
|
- Professional and authoritative |
|
- Technically accurate |
|
- Balanced and objective |
|
- Engaging and insightful |
|
- Forward-thinking |
|
|
|
Create a cohesive narrative that connects related stories and provides comprehensive coverage of the AI landscape.""" |
|
|
|
chat = ChatOpenAI(model="gpt-4o-mini") |
|
messages = [ |
|
SystemMessage(content=system_prompt), |
|
HumanMessage(content=f"Articles to include:\n\n{json.dumps(articles, indent=2)}\n\nGenerate a comprehensive newspaper analyzing these articles.") |
|
] |
|
response = chat(messages) |
|
return response.content |
|
|
|
|
|
st.set_page_config(page_title="AI News Hub", page_icon="π°", layout="wide") |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.main { |
|
padding: 2rem; |
|
} |
|
.stButton>button { |
|
width: 100%; |
|
border-radius: 5px; |
|
height: 3em; |
|
background-color: #4CAF50; |
|
color: white; |
|
} |
|
.stTextInput>div>div>input { |
|
border-radius: 5px; |
|
} |
|
.stSelectbox>div>div>input { |
|
border-radius: 5px; |
|
} |
|
.news-card { |
|
padding: 1rem; |
|
border-radius: 10px; |
|
border: 1px solid #e0e0e0; |
|
margin: 1rem 0; |
|
} |
|
.tab-content { |
|
padding: 1rem; |
|
} |
|
.card { |
|
padding: 1.5rem; |
|
border-radius: 10px; |
|
border: 1px solid #e0e0e0; |
|
margin-bottom: 1.5rem; |
|
background-color: #f9f9f9; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.title("π€ AI News Hub") |
|
st.markdown("---") |
|
|
|
|
|
if "articles" not in st.session_state: |
|
st.session_state.articles = [] |
|
if "custom_links" not in st.session_state: |
|
st.session_state.custom_links = [] |
|
if "generated_blogs" not in st.session_state: |
|
st.session_state.generated_blogs = {} |
|
if "newspaper" not in st.session_state: |
|
st.session_state.newspaper = "" |
|
|
|
|
|
with st.sidebar: |
|
st.header("βοΈ Configuration") |
|
st.markdown("---") |
|
|
|
|
|
st.subheader("π News Settings") |
|
num_articles = st.slider( |
|
"Number of articles to fetch:", |
|
min_value=0, |
|
max_value=10, |
|
value=5, |
|
step=5, |
|
help="Select how many AI news articles to fetch" |
|
) |
|
|
|
|
|
st.subheader("π Add Custom Link") |
|
with st.form("custom_link_form"): |
|
custom_url = st.text_input("URL:", placeholder="https://example.com") |
|
custom_title = st.text_input("Title:", placeholder="Article title") |
|
custom_source = st.text_input("Source:", placeholder="Website name") |
|
submitted = st.form_submit_button("Add Link") |
|
|
|
if submitted and custom_url and custom_title: |
|
st.session_state.custom_links.append({ |
|
'url': custom_url, |
|
'title': custom_title, |
|
'source': {'name': custom_source or 'Custom Source'}, |
|
'publishedAt': 'Custom', |
|
'description': 'User-added article' |
|
}) |
|
st.success("β
Link added successfully!") |
|
|
|
|
|
news_tab, blog_tab, newspaper_tab = st.tabs([ |
|
"π° AI Fetch News", |
|
"βοΈ Blog Generation", |
|
"π Newspaper" |
|
]) |
|
|
|
|
|
with news_tab: |
|
st.header("π Latest AI News") |
|
|
|
if st.button("π Fetch Latest AI News", help="Get fresh AI news articles"): |
|
with st.spinner("Fetching latest news..."): |
|
st.session_state.articles = fetch_ai_news(num_articles) |
|
st.success(f"π {len(st.session_state.articles)} articles fetched!") |
|
|
|
|
|
all_sources = st.session_state.articles + st.session_state.custom_links |
|
|
|
if all_sources: |
|
st.subheader("π° Available Sources") |
|
|
|
|
|
fetched_news_tab, custom_links_tab = st.tabs(["π Fetched News", "π Custom Links"]) |
|
|
|
with fetched_news_tab: |
|
if st.session_state.articles: |
|
for i, article in enumerate(st.session_state.articles): |
|
with st.container(): |
|
st.markdown(f""" |
|
<div class="news-card"> |
|
<h3>{article['title']}</h3> |
|
<p><strong>Source:</strong> {article['source']['name']}</p> |
|
<p><strong>Published:</strong> {article['publishedAt']}</p> |
|
<p>{article['description']}</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
else: |
|
st.info("No fetched news yet. Click 'Fetch Latest AI News' to get started.") |
|
|
|
with custom_links_tab: |
|
if st.session_state.custom_links: |
|
for i, link in enumerate(st.session_state.custom_links): |
|
with st.container(): |
|
st.markdown(f""" |
|
<div class="news-card"> |
|
<h3>{link['title']}</h3> |
|
<p><strong>Source:</strong> {link['source']['name']}</p> |
|
<p><strong>URL:</strong> {link['url']}</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
else: |
|
st.info("No custom links added yet. Use the sidebar to add custom links.") |
|
else: |
|
st.info("No news sources available. Fetch news or add custom links to get started.") |
|
|
|
|
|
with blog_tab: |
|
st.header("βοΈ Blog Generation") |
|
|
|
|
|
all_sources = st.session_state.articles + st.session_state.custom_links |
|
|
|
if all_sources: |
|
col1, col2 = st.columns([2, 1]) |
|
|
|
with col1: |
|
st.subheader("π Select Sources") |
|
|
|
selected_sources = st.multiselect( |
|
"Select sources for blog generation:", |
|
range(len(all_sources)), |
|
format_func=lambda x: all_sources[x]['title'] |
|
) |
|
|
|
if selected_sources: |
|
with st.container(): |
|
st.markdown('<div class="card">', unsafe_allow_html=True) |
|
st.subheader("π― Generation Options") |
|
blog_instructions = st.text_area( |
|
"Additional instructions (optional):", |
|
placeholder="E.g., 'Focus on technical aspects' or 'Make it business-oriented'", |
|
help="Provide specific instructions for blog generation" |
|
) |
|
|
|
if st.button("π Generate Blog Content"): |
|
for idx in selected_sources: |
|
source = all_sources[idx] |
|
with st.spinner(f"Processing: {source['title']}"): |
|
content = scrape_content(source['url']) |
|
if content: |
|
blog = generate_blog(content, source['title'], blog_instructions) |
|
st.session_state.generated_blogs[idx] = { |
|
'title': source['title'], |
|
'content': blog, |
|
'version': 1 |
|
} |
|
st.success(f"β
Generated: {source['title']}") |
|
st.markdown('</div>', unsafe_allow_html=True) |
|
else: |
|
st.info("Select sources from the list to generate blog content.") |
|
|
|
with col2: |
|
st.subheader("βΉοΈ Selected Sources") |
|
if selected_sources: |
|
for idx in selected_sources: |
|
source = all_sources[idx] |
|
st.markdown(f""" |
|
<div class="news-card"> |
|
<h4>{source['title']}</h4> |
|
<p><strong>Source:</strong> {source['source']['name']}</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
else: |
|
st.info("No sources selected yet.") |
|
|
|
|
|
if st.session_state.generated_blogs: |
|
st.markdown("---") |
|
st.subheader("π Generated Blog Content") |
|
|
|
for idx, blog in st.session_state.generated_blogs.items(): |
|
with st.expander(f"π {blog['title']}"): |
|
st.markdown(blog['content']) |
|
|
|
|
|
st.markdown("### βοΈ Modify Content") |
|
mod_instructions = st.text_area( |
|
"Modification instructions:", |
|
key=f"blog_mod_{idx}", |
|
help="Describe how you want to modify this blog content" |
|
) |
|
|
|
if st.button("π Apply Changes", key=f"blog_mod_btn_{idx}"): |
|
with st.spinner("Applying modifications..."): |
|
modified = modify_blog(blog['content'], mod_instructions) |
|
st.session_state.generated_blogs[idx]['content'] = modified |
|
st.session_state.generated_blogs[idx]['version'] += 1 |
|
st.success(f"β
Changes applied! (Version {st.session_state.generated_blogs[idx]['version']})") |
|
else: |
|
st.warning("β οΈ No sources available. Fetch news or add custom links first.") |
|
|
|
|
|
with newspaper_tab: |
|
st.header("π° AI Newspaper Generation") |
|
|
|
|
|
all_sources = st.session_state.articles + st.session_state.custom_links |
|
|
|
if all_sources: |
|
col1, col2 = st.columns([2, 1]) |
|
|
|
with col1: |
|
st.subheader("π Select Sources") |
|
|
|
newspaper_sources = st.multiselect( |
|
"Select sources for newspaper generation:", |
|
range(len(all_sources)), |
|
format_func=lambda x: all_sources[x]['title'], |
|
key="newspaper_sources" |
|
) |
|
|
|
if newspaper_sources: |
|
with st.container(): |
|
st.markdown('<div class="card">', unsafe_allow_html=True) |
|
st.subheader("π― Newspaper Options") |
|
newspaper_instructions = st.text_area( |
|
"Additional instructions (optional):", |
|
placeholder="E.g., 'Focus on business impact' or 'Highlight ethical considerations'", |
|
help="Provide specific instructions for newspaper generation", |
|
key="newspaper_instructions" |
|
) |
|
|
|
if st.button("π Generate Newspaper"): |
|
with st.spinner("Generating comprehensive newspaper..."): |
|
|
|
selected_articles = [] |
|
for idx in newspaper_sources: |
|
source = all_sources[idx] |
|
content = scrape_content(source['url']) |
|
if content: |
|
selected_articles.append({ |
|
'title': source['title'], |
|
'content': content, |
|
'source': source['source']['name'], |
|
'url': source['url'] |
|
}) |
|
|
|
if selected_articles: |
|
newspaper_content = generate_newspaper(selected_articles) |
|
st.session_state.newspaper = { |
|
'content': newspaper_content, |
|
'version': 1 |
|
} |
|
st.success("β
Newspaper generated!") |
|
else: |
|
st.error("β οΈ Could not retrieve content from any of the selected sources.") |
|
st.markdown('</div>', unsafe_allow_html=True) |
|
else: |
|
st.info("Select sources from the list to generate a newspaper.") |
|
|
|
with col2: |
|
st.subheader("βΉοΈ Selected Sources") |
|
if newspaper_sources: |
|
for idx in newspaper_sources: |
|
source = all_sources[idx] |
|
st.markdown(f""" |
|
<div class="news-card"> |
|
<h4>{source['title']}</h4> |
|
<p><strong>Source:</strong> {source['source']['name']}</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
else: |
|
st.info("No sources selected yet.") |
|
|
|
|
|
if st.session_state.newspaper: |
|
st.markdown("---") |
|
st.subheader("π° Generated Newspaper") |
|
|
|
with st.expander("π AI Newspaper", expanded=True): |
|
st.markdown(st.session_state.newspaper['content']) |
|
|
|
|
|
st.markdown("### βοΈ Modify Newspaper") |
|
newspaper_mod_instructions = st.text_area( |
|
"Modification instructions:", |
|
key="newspaper_mod", |
|
help="Describe how you want to modify this newspaper content" |
|
) |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
if st.button("π Apply Changes", key="newspaper_mod_btn"): |
|
with st.spinner("Applying modifications..."): |
|
modified = modify_blog(st.session_state.newspaper['content'], newspaper_mod_instructions) |
|
st.session_state.newspaper['content'] = modified |
|
st.session_state.newspaper['version'] += 1 |
|
st.success(f"β
Changes applied! (Version {st.session_state.newspaper['version']})") |
|
|
|
with col2: |
|
if st.button("πΎ Save as TXT", key="save_newspaper"): |
|
with open("ai_newspaper.txt", "w", encoding="utf-8") as f: |
|
f.write(st.session_state.newspaper['content']) |
|
st.success("β
Saved as 'ai_newspaper.txt'") |
|
|
|
with col3: |
|
if st.button("ποΈ Clear Newspaper", key="clear_newspaper"): |
|
st.session_state.newspaper = "" |
|
st.success("β
Newspaper cleared!") |
|
st.rerun() |
|
else: |
|
st.warning("β οΈ No sources available. Fetch news or add custom links first.") |