SynthGen / app.py
ReallyFloppyPenguin's picture
Update app.py
b4174f8 verified
import gradio as gr
import json
import tempfile
import os
import re # For parsing conversation
from typing import Union, Optional, Dict, Tuple # Import Dict and Tuple
# Import the actual functions from synthgen
from synthgen import (
generate_synthetic_text,
generate_prompts,
generate_synthetic_conversation,
generate_corpus_content # Import the new function
)
# We no longer need to import api_key here or check it directly in app.py
# --- Helper Functions for JSON Generation ---
# Use Union for Python < 3.10 compatibility
def create_json_file(data: object, base_filename: str) -> Union[str, None]:
"""Creates a temporary JSON file and returns its path."""
try:
# Create a temporary file with a .json extension
with tempfile.NamedTemporaryFile(mode='w', suffix=".json", delete=False, encoding='utf-8') as temp_file:
json.dump(data, temp_file, indent=4, ensure_ascii=False)
return temp_file.name # Return the path to the temporary file
except Exception as e:
print(f"Error creating JSON file {base_filename}: {e}")
return None
# Add the missing function definition
def create_text_file(data: str, base_filename: str) -> Union[str, None]:
"""Creates a temporary text file and returns its path."""
try:
# Ensure filename ends with .txt
if not base_filename.lower().endswith(".txt"):
base_filename += ".txt" # Append if missing for clarity, though suffix handles it
# Create a temporary file with a .txt extension
with tempfile.NamedTemporaryFile(mode='w', suffix=".txt", delete=False, encoding='utf-8') as temp_file:
temp_file.write(data)
return temp_file.name # Return the path to the temporary file
except Exception as e:
print(f"Error creating text file {base_filename}: {e}")
return None
def parse_conversation_string(text: str) -> list[dict]:
"""Parses a multi-line conversation string into a list of message dictionaries."""
messages = []
# Regex to capture "User:" or "Assistant:" at the start of a line, followed by content
pattern = re.compile(r"^(User|Assistant):\s*(.*)$", re.IGNORECASE | re.MULTILINE)
matches = pattern.finditer(text)
for match in matches:
role = match.group(1).lower()
content = match.group(2).strip()
messages.append({"role": role, "content": content})
# If parsing fails or format is unexpected, return raw text in a single message?
# Or return empty list? Let's return what we found.
if not messages and text: # If regex found nothing but text exists
print(f"Warning: Could not parse conversation structure for: '{text[:100]}...'")
# Fallback: return the whole text as a single assistant message? Or user?
# Let's return a generic system message indicating the raw content
# return [{"role": "system", "content": f"Unparsed conversation text: {text}"}]
# Or maybe just return empty, TBD based on preference
pass # Return empty list if parsing fails for now
return messages
# Wrapper for text generation (remains largely the same, but error handling is improved in synthgen)
def run_generation(prompt: str, model: str, num_samples: int) -> str:
"""
Wrapper function for Gradio interface to generate multiple text samples.
Relies on generate_synthetic_text for API calls and error handling.
"""
if not prompt:
return "Error: Please enter a prompt."
if num_samples <= 0:
return "Error: Number of samples must be positive."
output = f"Generating {num_samples} samples using model '{model}'...\n"
output += "="*20 + "\n\n"
# generate_synthetic_text now handles API errors internally
for i in range(num_samples):
# The function returns the text or an error string starting with "Error:"
generated_text = generate_synthetic_text(prompt, model)
output += f"--- Sample {i+1} ---\n"
output += generated_text + "\n\n" # Append result directly
output += "="*20 + "\nGeneration complete (check results above for errors)."
return output
# Removed the placeholder backend functions (generate_prompts_backend, generate_single_conversation)
# Modified function to handle multiple conversation prompts using the real backend
def run_conversation_generation(system_prompts_text: str, model: str, num_turns: int) -> str:
"""
Wrapper function for Gradio interface to generate multiple conversations
based on a list of prompts, calling generate_synthetic_conversation.
"""
if not system_prompts_text:
return "Error: Please enter or generate at least one system prompt/topic."
if num_turns <= 0:
return "Error: Number of turns must be positive."
prompts = [p.strip() for p in system_prompts_text.strip().split('\n') if p.strip()]
if not prompts:
return "Error: No valid prompts found in the input."
output = f"Generating {len(prompts)} conversations ({num_turns} turns each) using model '{model}'...\n"
output += "="*40 + "\n\n"
for i, prompt in enumerate(prompts):
# Call the actual function from synthgen.py
# It handles API calls and returns the conversation or an error string.
conversation_text = generate_synthetic_conversation(prompt, model, num_turns)
# We don't need a try-except here because the function itself returns error strings
# The title is now included within the returned string from the function
output += f"--- Conversation {i+1}/{len(prompts)} ---\n"
output += conversation_text + "\n\n" # Append result directly
output += "="*40 + "\nGeneration complete (check results above for errors)."
return output
# Helper function for the Gradio UI to generate prompts using the real backend
def generate_prompts_ui(
num_prompts: int,
model: str,
temperature: float, # Add settings
top_p: float,
max_tokens: int
) -> str:
"""UI Wrapper to call the generate_prompts backend and format for Textbox."""
# Handle optional settings
temp_val = temperature if temperature > 0 else None
top_p_val = top_p if 0 < top_p <= 1 else None
# Use a specific max_tokens for prompt generation or pass from UI? Let's pass from UI
max_tokens_val = max_tokens if max_tokens > 0 else 200 # Set a default if UI value is 0
if not model:
return "Error: Please select a model for prompt generation."
if num_prompts <= 0:
return "Error: Number of prompts to generate must be positive."
if num_prompts > 50:
return "Error: Cannot generate more than 50 prompts at a time."
print(f"Generating prompts with settings: Temp={temp_val}, Top-P={top_p_val}, MaxTokens={max_tokens_val}") # Debug print
try:
# Call the actual function from synthgen.py, passing settings
prompts_list = generate_prompts(
num_prompts,
model,
temperature=temp_val,
top_p=top_p_val,
max_tokens=max_tokens_val
)
return "\n".join(prompts_list)
except ValueError as e:
# Catch errors raised by generate_prompts (e.g., API errors, parsing errors)
return f"Error generating prompts: {e}"
except Exception as e:
# Catch any other unexpected errors
print(f"Unexpected error in generate_prompts_ui: {e}")
return f"An unexpected error occurred: {e}"
# --- Modified Generation Wrappers ---
# Wrapper for text generation + JSON preparation - RETURNS TUPLE
def run_generation_and_prepare_json(
prompt: str,
model: str,
num_samples: int,
temperature: float,
top_p: float,
max_tokens: int
) -> Tuple[gr.update, gr.update]: # Return type hint (optional)
"""Generates text samples and prepares a JSON file for download."""
# Handle optional settings
temp_val = temperature if temperature > 0 else None
top_p_val = top_p if 0 < top_p <= 1 else None
max_tokens_val = max_tokens if max_tokens > 0 else None
# Handle errors by returning updates for both outputs in a tuple
if not prompt:
return (gr.update(value="Error: Please enter a prompt."), gr.update(value=None))
if num_samples <= 0:
return (gr.update(value="Error: Number of samples must be positive."), gr.update(value=None))
output_str = f"Generating {num_samples} samples using model '{model}'...\n"
output_str += f"(Settings: Temp={temp_val}, Top-P={top_p_val}, MaxTokens={max_tokens_val})\n"
output_str += "="*20 + "\n\n"
results_list = []
for i in range(num_samples):
generated_text = generate_synthetic_text(
prompt, model, temperature=temp_val, top_p=top_p_val, max_tokens=max_tokens_val
)
output_str += f"--- Sample {i+1} ---\n"
output_str += generated_text + "\n\n"
if not generated_text.startswith("Error:"):
results_list.append(generated_text)
output_str += "="*20 + "\nGeneration complete (check results above for errors)."
json_filepath = create_json_file(results_list, "text_samples.json")
# Return tuple of updates in the order of outputs list
return (gr.update(value=output_str), gr.update(value=json_filepath))
# Wrapper for conversation generation + JSON preparation - RETURNS TUPLE
def run_conversation_generation_and_prepare_json(
system_prompts_text: str,
model: str,
num_turns: int,
temperature: float,
top_p: float,
max_tokens: int
) -> Tuple[gr.update, gr.update]: # Return type hint (optional)
"""Generates conversations and prepares a JSON file for download."""
temp_val = temperature if temperature > 0 else None
top_p_val = top_p if 0 < top_p <= 1 else None
max_tokens_val = max_tokens if max_tokens > 0 else None
# Handle errors by returning updates for both outputs in a tuple
if not system_prompts_text:
return (gr.update(value="Error: Please enter or generate at least one system prompt/topic."), gr.update(value=None))
if num_turns <= 0:
return (gr.update(value="Error: Number of turns must be positive."), gr.update(value=None))
prompts = [p.strip() for p in system_prompts_text.strip().split('\n') if p.strip()]
if not prompts:
return (gr.update(value="Error: No valid prompts found in the input."), gr.update(value=None))
output_str = f"Generating {len(prompts)} conversations ({num_turns} turns each) using model '{model}'...\n"
output_str += f"(Settings: Temp={temp_val}, Top-P={top_p_val}, MaxTokens={max_tokens_val})\n"
output_str += "="*40 + "\n\n"
results_list_structured = []
for i, prompt in enumerate(prompts):
conversation_text = generate_synthetic_conversation(
prompt, model, num_turns, temperature=temp_val, top_p=top_p_val, max_tokens=max_tokens_val
)
output_str += f"--- Conversation {i+1}/{len(prompts)} ---\n"
output_str += conversation_text + "\n\n"
# --- Parsing Logic ---
core_conversation_text = conversation_text
if conversation_text.startswith("Error:"): core_conversation_text = None
elif "\n\n" in conversation_text:
parts = conversation_text.split("\n\n", 1)
core_conversation_text = parts[1] if len(parts) > 1 else conversation_text
if core_conversation_text:
messages = parse_conversation_string(core_conversation_text)
if messages: results_list_structured.append({"prompt": prompt, "messages": messages})
else: results_list_structured.append({"prompt": prompt, "error": "Failed to parse structure.", "raw_text": core_conversation_text})
elif conversation_text.startswith("Error:"): results_list_structured.append({"prompt": prompt, "error": conversation_text})
else: results_list_structured.append({"prompt": prompt, "error": "Could not extract content.", "raw_text": conversation_text})
# --- End Parsing Logic ---
output_str += "="*40 + "\nGeneration complete (check results above for errors)."
json_filepath = create_json_file(results_list_structured, "conversations.json")
# Return tuple of updates in the order of outputs list
return (gr.update(value=output_str), gr.update(value=json_filepath))
# Define content_type_labels globally for use in UI and wrapper functions
content_type_labels = {
"Corpus Snippets": "# Snippets",
"Short Story": "Approx Words",
"Article": "Approx Words"
}
content_type_defaults = {
"Corpus Snippets": 5,
"Short Story": 1000, # Match new backend default
"Article": 1500 # Match new backend default
}
# Wrapper for Corpus/Content Generation
def run_corpus_generation_and_prepare_file(
topic: str,
content_type: str,
length_param: int,
model: str,
temperature: float,
top_p: float,
max_tokens: int
) -> Tuple[gr.update, gr.update]:
"""Generates corpus/story/article content and prepares a file for download."""
temp_val = temperature if temperature > 0 else None
top_p_val = top_p if 0 < top_p <= 1 else None
max_tokens_val = max_tokens if max_tokens > 0 else None
# Use the global dictionary for error messages
label_for_error = content_type_labels.get(content_type, 'Length Param')
if not topic: return (gr.update(value="Error: Please enter a topic."), gr.update(value=None))
if not content_type: return (gr.update(value="Error: Please select a content type."), gr.update(value=None))
if length_param <= 0: return (gr.update(value=f"Error: Please enter a positive value for '{label_for_error}'."), gr.update(value=None))
print(f"Generating {content_type} about '{topic}'...")
output_str = f"Generating {content_type} about '{topic}' using model '{model}'...\n"
output_str += f"(Settings: Temp={temp_val}, Top-P={top_p_val}, MaxTokens={max_tokens_val})\n" + "="*40 + "\n\n"
generated_content = generate_corpus_content(
topic=topic, content_type=content_type, length_param=length_param, model=model,
temperature=temp_val, top_p=top_p_val, max_tokens=max_tokens_val
)
output_str += generated_content
file_path = None
if not generated_content.startswith("Error:"):
core_content = generated_content
if "\n\n" in generated_content: parts = generated_content.split("\n\n", 1); core_content = parts[1] if len(parts) > 1 else generated_content
if content_type == "Corpus Snippets":
snippets = [s.strip() for s in core_content.split('---') if s.strip()]
if not snippets: snippets = [s.strip() for s in core_content.split('\n\n') if s.strip()]
corpus_data = {"topic": topic, "snippets": snippets}
file_path = create_json_file(corpus_data, f"{topic}_corpus.json")
else:
file_path = create_text_file(core_content, f"{topic}_{content_type.replace(' ','_')}.txt")
return (gr.update(value=output_str), gr.update(value=file_path))
# NEW function to update the length parameter label and default value
def update_length_param_ui(content_type: str) -> gr.update:
"""Updates the label and default value of the length parameter input."""
new_label = content_type_labels.get(content_type, "Length Param")
new_value = content_type_defaults.get(content_type, 5) # Default to 5 if type unknown
return gr.update(label=new_label, value=new_value)
# --- Generation Wrappers ---
# ... (generate_prompts_ui, run_generation_and_prepare_json, run_conversation_generation_and_prepare_json remain the same) ...
# NEW UI Wrapper for generating TOPICS
def generate_topics_ui(
num_topics: int,
model: str,
temperature: float,
top_p: float,
max_tokens: int
) -> str:
"""UI Wrapper to generate diverse topics using the AI."""
temp_val = temperature if temperature > 0 else None
top_p_val = top_p if 0 < top_p <= 1 else None
max_tokens_val = max_tokens if max_tokens > 0 else 150 # Limit token for topic list
if not model:
return "Error: Please select a model for topic generation."
if num_topics <= 0:
return "Error: Number of topics to generate must be positive."
if num_topics > 50: # Keep limit reasonable
return "Error: Cannot generate more than 50 topics at a time."
print(f"Generating {num_topics} topics with settings: Temp={temp_val}, Top-P={top_p_val}, MaxTokens={max_tokens_val}")
# Instruction focused on generating topics
instruction = (
f"Generate exactly {num_topics} diverse and interesting topics suitable for generating content like articles, stories, or corpus snippets. "
f"Each topic should be concise (a few words to a short phrase). "
f"Present each topic on a new line, with no other introductory or concluding text or numbering."
f"\n\nExamples:\n"
f"The future of renewable energy\n"
f"The history of the Silk Road\n"
f"The impact of social media on mental health"
)
system_msg = "You are an expert topic generator. Follow the user's instructions precisely."
try:
# Use the core text generation function
generated_text = generate_synthetic_text(
instruction,
model,
system_message=system_msg,
temperature=temp_val,
top_p=top_p_val,
max_tokens=max_tokens_val
)
if generated_text.startswith("Error:"):
raise ValueError(generated_text) # Propagate error
# Split into lines and clean up
topics_list = [t.strip() for t in generated_text.strip().split('\n') if t.strip()]
if not topics_list:
print(f"Warning: Failed to parse topics from generated text. Raw text:\n{generated_text}")
raise ValueError("AI failed to generate topics in the expected format.")
# Return newline-separated string for the Textbox
return "\n".join(topics_list[:num_topics]) # Truncate if needed
except ValueError as e:
return f"Error generating topics: {e}"
except Exception as e:
print(f"Unexpected error in generate_topics_ui: {e}")
return f"An unexpected error occurred: {e}"
# Modified Wrapper for Bulk Corpus/Content Generation
def run_bulk_content_generation_and_prepare_json(
topics_text: str, # Renamed from topic
content_type: str,
length_param: int,
model: str,
temperature: float,
top_p: float,
max_tokens: int
) -> Tuple[gr.update, gr.update]:
"""Generates content for multiple topics and prepares a JSON file."""
temp_val = temperature if temperature > 0 else None
top_p_val = top_p if 0 < top_p <= 1 else None
max_tokens_val = max_tokens if max_tokens > 0 else None
# --- Input Validation ---
if not topics_text:
return (gr.update(value="Error: Please enter or generate at least one topic."), gr.update(value=None))
if not content_type:
return (gr.update(value="Error: Please select a content type."), gr.update(value=None))
topics = [t.strip() for t in topics_text.strip().split('\n') if t.strip()]
if not topics:
return (gr.update(value="Error: No valid topics found in the input."), gr.update(value=None))
label_for_error = content_type_labels.get(content_type, 'Length Param')
if length_param <= 0:
return (gr.update(value=f"Error: Please enter a positive value for '{label_for_error}'."), gr.update(value=None))
# --- End Validation ---
output_str = f"Generating {content_type} for {len(topics)} topics using model '{model}'...\n"
output_str += f"(Settings: Temp={temp_val}, Top-P={top_p_val}, MaxTokens={max_tokens_val})\n" + "="*40 + "\n\n"
bulk_results = [] # Store results for JSON
# --- Loop through topics ---
for i, topic in enumerate(topics):
print(f"Generating {content_type} for topic {i+1}/{len(topics)}: '{topic}'...")
output_str += f"--- Topic {i+1}/{len(topics)}: '{topic}' ---\n"
generated_content_full = generate_corpus_content( # Returns string including title/error
topic=topic, content_type=content_type, length_param=length_param, model=model,
temperature=temp_val, top_p=top_p_val, max_tokens=max_tokens_val
)
output_str += generated_content_full + "\n\n" # Add full result to textbox
# --- Prepare structured result for JSON ---
result_entry = {"topic": topic, "content_type": content_type}
if generated_content_full.startswith("Error:"):
result_entry["status"] = "error"
result_entry["error_message"] = generated_content_full
result_entry["content"] = None
else:
result_entry["status"] = "success"
result_entry["error_message"] = None
# Extract core content (remove potential title added by backend)
core_content = generated_content_full
if "\n\n" in generated_content_full:
parts = generated_content_full.split("\n\n", 1)
core_content = parts[1] if len(parts) > 1 else generated_content_full
if content_type == "Corpus Snippets":
snippets = [s.strip() for s in core_content.split('---') if s.strip()]
if not snippets: snippets = [s.strip() for s in core_content.split('\n\n') if s.strip()]
result_entry["content"] = snippets # Store list for corpus
else:
result_entry["content"] = core_content # Store string for story/article
bulk_results.append(result_entry)
# --- End JSON preparation ---
# --- Finalize ---
output_str += "="*40 + f"\nBulk generation complete for {len(topics)} topics."
json_filepath = create_json_file(bulk_results, f"{content_type.replace(' ','_')}_bulk_results.json")
return (gr.update(value=output_str), gr.update(value=json_filepath))
# --- Gradio Interface Definition ---
with gr.Blocks() as demo:
gr.Markdown("# Synthetic Data Generator using OpenRouter")
gr.Markdown(
"Generate synthetic text samples, conversations, or other content using various models"
)
# Removed the api_key_loaded check and warning Markdown
# Define model choices (can be shared or specific per tab)
# Consider fetching these dynamically from OpenRouter if possible in the future
model_choices = [
"deepseek/deepseek-chat-v3-0324:free", # Example free model
"meta-llama/llama-3.3-70b-instruct:free",
"deepseek/deepseek-r1:free",
"google/gemini-2.5-pro-exp-03-25:free",
"qwen/qwen-2.5-72b-instruct:free",
"featherless/qwerky-72b:free",
"google/gemma-3-27b-it:free",
"mistralai/mistral-small-24b-instruct-2501:free",
"deepseek/deepseek-r1-distill-llama-70b:free",
"sophosympatheia/rogue-rose-103b-v0.2:free",
"nvidia/llama-3.1-nemotron-70b-instruct:free",
"microsoft/phi-3-medium-128k-instruct:free",
"undi95/toppy-m-7b:free",
"huggingfaceh4/zephyr-7b-beta:free",
"openrouter/quasar-alpha"
# Add more model IDs as needed
]
default_model = model_choices[0] if model_choices else None
# --- Shared Model Settings ---
# Use an Accordion for less clutter
with gr.Accordion("Model Settings (Optional)", open=False):
# Set reasonable ranges and defaults. Use 0 for Max Tokens/Top-P to signify 'None'/API default.
temperature_slider = gr.Slider(minimum=0.0, maximum=2.0, value=0.7, step=0.1, label="Temperature", info="Controls randomness. Higher values are more creative, lower are more deterministic. 0 means use API default.")
top_p_slider = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.05, label="Top-P (Nucleus Sampling)", info="Considers only tokens with cumulative probability mass >= top_p. 0 means use API default.")
max_tokens_slider = gr.Number(value=0, minimum=0, maximum=8192, step=64, label="Max Tokens", info="Maximum number of tokens to generate in the completion. 0 means use API default.")
with gr.Tabs():
with gr.TabItem("Text Generation"):
with gr.Row():
prompt_input_text = gr.Textbox(label="Prompt", placeholder="Enter your prompt here (e.g., Generate a short product description for a sci-fi gadget)", lines=3)
with gr.Row():
model_input_text = gr.Dropdown(
label="OpenRouter Model ID",
choices=model_choices,
value=default_model
)
num_samples_input_text = gr.Number(label="Number of Samples", value=3, minimum=1, maximum=20, step=1)
generate_button_text = gr.Button("Generate Text Samples")
output_text = gr.Textbox(label="Generated Samples", lines=15, show_copy_button=True)
# Add File component for download
download_file_text = gr.File(label="Download Samples as JSON")
generate_button_text.click(
fn=run_generation_and_prepare_json,
inputs=[
prompt_input_text, model_input_text, num_samples_input_text,
temperature_slider, top_p_slider, max_tokens_slider # Add settings inputs
],
outputs=[output_text, download_file_text]
)
with gr.TabItem("Conversation Generation"):
gr.Markdown("Enter one system prompt/topic per line below, or use the 'Generate Prompts' button.")
with gr.Row():
# Textbox for multiple prompts
prompt_input_conv = gr.Textbox(
label="Prompts (one per line)",
lines=5, # Make it multi-line
placeholder="Enter prompts here, one per line...\ne.g., Act as a pirate discussing treasure maps.\nDiscuss the future of space travel."
)
with gr.Row():
# Input for number of prompts to generate
num_prompts_input_conv = gr.Number(label="Number of Prompts to Generate", value=5, minimum=1, maximum=20, step=1) # Keep max reasonable
# Button to trigger AI prompt generation
generate_prompts_button = gr.Button("Generate Prompts using AI")
with gr.Row():
# Model selection for conversation generation AND prompt generation
model_input_conv = gr.Dropdown(
label="OpenRouter Model ID (for generation)",
choices=model_choices,
value=default_model
)
with gr.Row():
# Input for number of turns per conversation
num_turns_input_conv = gr.Number(label="Number of Turns per Conversation (approx)", value=5, minimum=1, maximum=20, step=1) # Keep max reasonable
# Button to generate the conversations based on the prompts in the Textbox
generate_conversations_button = gr.Button("Generate Conversations")
output_conv = gr.Textbox(label="Generated Conversations", lines=15, show_copy_button=True)
# Add File component for download
download_file_conv = gr.File(label="Download Conversations as JSON")
# Connect the "Generate Prompts" button to the UI wrapper
generate_prompts_button.click(
fn=generate_prompts_ui, # Use the wrapper that calls the real function
inputs=[
num_prompts_input_conv, model_input_conv,
temperature_slider, top_p_slider, max_tokens_slider # Add settings inputs
],
outputs=prompt_input_conv
)
# Connect the "Generate Conversations" button to the real function wrapper
generate_conversations_button.click(
fn=run_conversation_generation_and_prepare_json, # Use the wrapper that calls the real function
inputs=[
prompt_input_conv, model_input_conv, num_turns_input_conv,
temperature_slider, top_p_slider, max_tokens_slider # Add settings inputs
],
outputs=[output_conv, download_file_conv] # Output to both Textbox and File
)
# --- Content Generation Tab (Modified for Bulk) ---
with gr.TabItem("Bulk Content Generation"):
output_content = gr.Textbox(label="Generated Content (Log)", lines=15, show_copy_button=True)
# Output is now always JSON
download_file_content = gr.File(label="Download Results as JSON")
gr.Markdown("Enter one topic per line below, or use the 'Generate Topics' button.")
with gr.Row():
# Changed to multi-line Textbox
topic_input_content = gr.Textbox(
label="Topics (one per line)",
lines=5,
placeholder="Enter topics here, one per line...\ne.g., The future of renewable energy\nThe history of the Silk Road"
)
# --- Topic Generation ---
with gr.Accordion("Topic Generation Options", open=False):
with gr.Row():
num_topics_input = gr.Number(label="# Topics to Generate", value=5, minimum=1, maximum=50, step=1)
# Use shared model selector below and settings
generate_topics_button = gr.Button("Generate Topics using AI")
# --- Generation Settings ---
with gr.Row():
content_type_choices = list(content_type_labels.keys())
content_type_input = gr.Dropdown(
label="Content Type", choices=content_type_choices, value=content_type_choices[0]
)
default_length_label = content_type_labels[content_type_choices[0]]
default_length_value = content_type_defaults[content_type_choices[0]]
length_param_input = gr.Number(
label=default_length_label, value=default_length_value, minimum=1, step=1
)
with gr.Row():
model_input_content = gr.Dropdown(label="Model", choices=model_choices, value=default_model)
# Button to trigger bulk generation
generate_content_button = gr.Button("Generate Bulk Content")
# --- Event Listeners ---
# Listener to update length param UI
content_type_input.change(
fn=update_length_param_ui,
inputs=content_type_input,
outputs=length_param_input
)
# Listener for topic generation button
generate_topics_button.click(
fn=generate_topics_ui,
inputs=[ # Pass necessary inputs for topic generation
num_topics_input, model_input_content, # Use this tab's model selector
temperature_slider, top_p_slider, max_tokens_slider
],
outputs=topic_input_content # Output generated topics to the textbox
)
# Listener for main generation button
generate_content_button.click(
fn=run_bulk_content_generation_and_prepare_json, # Use the new bulk wrapper
inputs=[
topic_input_content, content_type_input, length_param_input,
model_input_content,
temperature_slider, top_p_slider, max_tokens_slider
],
outputs=[output_content, download_file_content]
)
# Launch the Gradio app
if __name__ == "__main__":
print("Launching Gradio App...")
print("Make sure the OPENROUTER_API_KEY environment variable is set.")
# Use share=True for temporary public link if running locally and need to test
demo.launch() # share=True