Adedoyinjames's picture
Update app.py
4ee8d23 verified
import os
import requests
import random
import threading
import time
import schedule
import tweepy
import gradio as gr
import json
# Secrets from HF Space environment
HF_TOKEN = os.environ['HF_TOKEN']
CONSUMER_KEY = os.environ['CONSUMER_KEY']
CONSUMER_SECRET = os.environ['CONSUMER_SECRET']
ACCESS_TOKEN = os.environ['ACCESS_TOKEN']
ACCESS_SECRET = os.environ['ACCESS_SECRET']
# LLM API setup
API_URL = "https://router.huggingface.co/v1/chat/completions"
headers = {
"Authorization": f"Bearer {HF_TOKEN}",
}
def query(payload):
try:
response = requests.post(API_URL, headers=headers, json=payload, timeout=120)
response.raise_for_status()
json_response = response.json()
return json_response
except requests.exceptions.HTTPError as http_err:
error_detail = f"HTTP error: {http_err} - Status: {response.status_code}"
try:
error_body = response.json()
error_detail += f" | Details: {error_body}"
except:
error_detail += f" | Raw response: {response.text[:300]}"
print(error_detail)
raise ValueError(error_detail)
except requests.exceptions.RequestException as req_err:
error_msg = f"Request failed: {req_err}"
print(error_msg)
raise ValueError(error_msg)
except ValueError as json_err:
error_msg = f"JSON decode error: {json_err} | Raw: {response.text[:300]}"
print(error_msg)
raise ValueError(error_msg)
# Topics for posts
topics = [
"GeoAI as a field and it's application",
"application of Geology with Tech for exploration",
"application of AI in geological exploration",
"Entrepreneurship"
]
# Function to generate a high-quality post using LLM
def generate_post(topic=None):
if topic is None or topic == "Random":
topic = random.choice(topics)
prompt = f"Generate a high-quality, educational, and informative X (Twitter) post under 280 characters showcasing expertise in {topic}. Make it engaging, insightful, and professional. Include relevant hashtags and hashtags shouldn't be more than 2 or not even at all in some post also avoid using - in the post so it looks natural."
payload = {
"messages": [{"role": "user", "content": prompt}],
"model": "deepseek-ai/DeepSeek-V3.2:novita",
"max_tokens": 200,
"temperature": 0.8
}
try:
response = query(payload)
print("API Response:", response)
if "choices" in response and response["choices"]:
post_content = response["choices"][0]["message"]["content"].strip()
elif "error" in response:
raise ValueError(f"API returned error: {response['error']}")
elif isinstance(response, dict) and "content" in response:
post_content = response["content"].strip()
else:
raise KeyError("Unexpected response format - no 'choices' or parsable content")
except Exception as e:
error_msg = f"Failed to generate post: {str(e)}"
print(error_msg)
post_content = f"[Generation failed: {str(e)} — please check logs or try again later.]"
if len(post_content) > 280:
post_content = post_content[:277] + "..."
return post_content
# Tweepy v2 Client setup
client = tweepy.Client(
consumer_key=CONSUMER_KEY,
consumer_secret=CONSUMER_SECRET,
access_token=ACCESS_TOKEN,
access_token_secret=ACCESS_SECRET
)
# Global queue and scheduler state
post_queue = []
posted_history = []
scheduler_running = False
scheduler_thread = None
next_post_time = None
# Persistence functions for queue
def save_queue():
with open('queue.json', 'w') as f:
json.dump(post_queue, f)
def load_queue():
global post_queue
if os.path.exists('queue.json'):
with open('queue.json', 'r') as f:
post_queue = json.load(f)
# Persistence functions for posted history
def save_posted():
with open('posted.json', 'w') as f:
json.dump(posted_history, f)
def load_posted():
global posted_history
if os.path.exists('posted.json'):
with open('posted.json', 'r') as f:
posted_history = json.load(f)
# Load at startup
load_queue()
load_posted()
# Function to post to X
def post_to_x(content):
try:
response = client.create_tweet(text=content)
tweet_id = response.data['id']
return f"Posted! https://x.com/user/status/{tweet_id}"
except Exception as e:
return f"Error: {str(e)}"
# Scheduler job — posts one item from queue
def scheduled_post_job():
global post_queue, posted_history, next_post_time
if post_queue:
content = post_queue.pop(0)
result = post_to_x(content)
save_queue()
if "Posted!" in result:
posted_history.append({
"content": content,
"result": result,
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
})
save_posted()
print(f"[Scheduler] {result}\nContent: {content}")
if post_queue:
next_post_time = time.time() + 7200
else:
next_post_time = None
return result, "\n\n".join(post_queue) if post_queue else "Queue is empty."
else:
print("[Scheduler] Queue is empty.")
next_post_time = None
return "No post scheduled — queue is empty.", "Queue is empty."
# Background scheduler loop
def run_scheduler():
global scheduler_running
schedule.every(2).hours.do(scheduled_post_job)
while scheduler_running:
schedule.run_pending()
time.sleep(30)
# Start scheduler
def start_scheduler():
global scheduler_running, scheduler_thread, next_post_time
if not scheduler_running:
scheduler_running = True
scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
scheduler_thread.start()
if post_queue:
scheduled_post_job()
if post_queue:
next_post_time = time.time() + 7200
return "Scheduler started! Posts every 2 hours from the queue."
return "Scheduler is already running."
# Stop scheduler
def stop_scheduler():
global scheduler_running, next_post_time
scheduler_running = False
next_post_time = None
return "Scheduler stopped."
# Clear queue function
def clear_queue():
global post_queue, next_post_time
post_queue.clear()
next_post_time = None
save_queue()
return "Queue cleared.", "Queue is empty."
# Refresh queue display
def refresh_queue():
return "\n\n".join(post_queue) if post_queue else "Queue is empty."
# Refresh posted history display
def refresh_posted():
if posted_history:
return "\n\n".join([f"{p['timestamp']} - {p['result']}\n{p['content']}" for p in posted_history])
else:
return "No posts yet."
# Get time until next post
def get_timer():
global next_post_time
if next_post_time is None or not scheduler_running:
return "No upcoming posts or scheduler stopped."
remaining = next_post_time - time.time()
if remaining <= 0:
return "Posting soon..."
hours, rem = divmod(remaining, 3600)
mins, secs = divmod(rem, 60)
return f"{int(hours):02d}:{int(mins):02d}:{int(secs):02d}"
# Gradio functions
def generate_new_post(topic, num_posts):
contents = [generate_post(topic) for _ in range(num_posts)]
return "\n\n---\n\n".join(contents), gr.update(visible=True)
def add_to_queue(content):
global post_queue, next_post_time
posts = [p.strip() for p in content.split("---") if p.strip()]
added = False
for p in posts:
if p and p not in post_queue:
post_queue.append(p)
added = True
if added:
save_queue()
if scheduler_running and next_post_time is None and post_queue:
next_post_time = time.time() + 7200
return (
"",
gr.update(visible=False),
"\n\n".join(post_queue) if post_queue else "Queue is empty."
)
def refresh_all():
return refresh_queue(), refresh_posted(), get_timer()
# Gradio Interface
with gr.Blocks(title="X Post Generator & Scheduler") as demo:
gr.Markdown("# AI/Tech/Startups X Post Generator & Queue Scheduler")
gr.Markdown("Generate → Review → Add to queue → Start scheduler for automated 2-hour posting.")
with gr.Row():
topic_input = gr.Dropdown(choices=["Random"] + topics, label="Topic", allow_custom_value=True)
num_posts_input = gr.Number(label="Number of Posts", value=1, minimum=1, maximum=20, step=1)
generate_btn = gr.Button("Generate New Post(s)")
current_post = gr.Textbox(label="Generated Post(s) (Review and edit before adding; separate multiples with ---)", lines=6, interactive=True)
add_btn = gr.Button("➕ Add to Queue", visible=False)
queue_display = gr.Textbox(
label="Post Queue (will be posted in order, every 2 hours)",
value=refresh_queue(),
lines=12,
interactive=False
)
posted_display = gr.Textbox(
label="Posted History",
value=refresh_posted(),
lines=12,
interactive=False
)
timer_display = gr.Textbox(label="Time Until Next Post", value=get_timer(), interactive=False)
with gr.Row():
start_btn = gr.Button("Start Scheduler")
stop_btn = gr.Button("Stop Scheduler")
clear_queue_btn = gr.Button("Clear Queue")
refresh_btn = gr.Button("Refresh Queue & History")
status_box = gr.Textbox(label="Status", value="Ready", interactive=False)
# Invisible timer to update countdown every 1 second
timer = gr.Timer(value=1, active=True)
# Event bindings
generate_btn.click(
generate_new_post,
inputs=[topic_input, num_posts_input],
outputs=[current_post, add_btn]
)
add_btn.click(
add_to_queue,
inputs=current_post,
outputs=[current_post, add_btn, queue_display]
)
start_btn.click(
start_scheduler,
outputs=status_box
)
stop_btn.click(
stop_scheduler,
outputs=status_box
)
clear_queue_btn.click(
clear_queue,
outputs=[status_box, queue_display]
)
refresh_btn.click(
refresh_all,
outputs=[queue_display, posted_display, timer_display]
)
timer.tick(
get_timer,
outputs=timer_display
)
# Automatically start scheduler on app load
demo.load(
start_scheduler,
outputs=status_box
)
demo.load(
get_timer,
outputs=timer_display
)
demo.launch()