Spaces:
Running
Running
| import os | |
| import requests | |
| import random | |
| import threading | |
| import time | |
| import schedule | |
| import tweepy | |
| import gradio as gr | |
| # 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): | |
| response = requests.post(API_URL, headers=headers, json=payload) | |
| return response.json() | |
| # 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." | |
| response = query({ | |
| "messages": [{"role": "user", "content": prompt}], | |
| "model": "deepseek-ai/DeepSeek-V3.2:novita", | |
| "max_tokens": 200, | |
| "temperature": 0.8 | |
| }) | |
| post_content = response["choices"][0]["message"]["content"].strip() | |
| 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 = [] | |
| scheduler_running = False | |
| scheduler_thread = None | |
| # 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 | |
| if post_queue: | |
| content = post_queue.pop(0) # FIFO: first in, first out | |
| result = post_to_x(content) | |
| print(f"[Scheduler] {result}\nContent: {content}") | |
| return result, "\n\n".join(post_queue) if post_queue else "Queue is empty." | |
| else: | |
| print("[Scheduler] Queue is empty.") | |
| 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 | |
| if not scheduler_running: | |
| scheduler_running = True | |
| scheduler_thread = threading.Thread(target=run_scheduler, daemon=True) | |
| scheduler_thread.start() | |
| return "Scheduler started! Posts every 2 hours from the queue." | |
| return "Scheduler is already running." | |
| # Stop scheduler | |
| def stop_scheduler(): | |
| global scheduler_running | |
| scheduler_running = False | |
| return "Scheduler stopped." | |
| # Clear queue function | |
| def clear_queue(): | |
| global post_queue | |
| post_queue.clear() | |
| return "Queue cleared.", "Queue is empty." | |
| # Gradio functions | |
| def generate_new_post(topic): | |
| content = generate_post(topic) | |
| return content, gr.update(visible=True) | |
| def add_to_queue(content): | |
| global post_queue | |
| stripped = content.strip() | |
| if stripped and stripped not in post_queue: | |
| post_queue.append(stripped) | |
| return ( | |
| "", # Clear the current post box | |
| gr.update(visible=False), # Hide Add button | |
| "\n\n".join(post_queue) if post_queue else "Queue is empty." | |
| ) | |
| # 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, value="Random", label="Topic") | |
| generate_btn = gr.Button("Generate New Post") | |
| current_post = gr.Textbox(label="Generated Post (Review before adding)", lines=6, interactive=False) | |
| 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="Queue is empty.", | |
| lines=12, | |
| interactive=False | |
| ) | |
| with gr.Row(): | |
| start_btn = gr.Button("Start Scheduler") | |
| stop_btn = gr.Button("Stop Scheduler") | |
| clear_queue_btn = gr.Button("Clear Queue") | |
| status_box = gr.Textbox(label="Status", value="Ready", interactive=False) | |
| # Event bindings | |
| generate_btn.click( | |
| generate_new_post, | |
| inputs=topic_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] | |
| ) | |
| demo.launch() |