Sandipan Haldar
feat: Add OpenAI API key input option in UI
ac246b3
raw
history blame
23.7 kB
#!/usr/bin/env python3
"""
Smart Auto-Complete - Main Application
A context-aware text completion tool built with Gradio
"""
from typing import List, Tuple
import gradio as gr
from config.settings import AppSettings
from src.autocomplete import SmartAutoComplete
from src.utils import setup_logging
# Initialize logging
logger = setup_logging()
# Initialize settings and autocomplete engine
settings = AppSettings()
autocomplete = SmartAutoComplete(settings)
class AutoCompleteApp:
def __init__(self):
self.last_request_time = 0
self.current_suggestions = []
self.user_api_key = None
self.custom_autocomplete = None
def get_suggestions(
self, text: str, context: str, output_tokens: int = 150, user_context: str = ""
) -> Tuple[List[str], str]:
"""
Get auto-complete suggestions for the given text and context
Returns: (suggestions_list, status_message)
"""
try:
# Input validation
if not text or len(text.strip()) < 2:
return [], "โœ๏ธ Please enter some text to get suggestions..."
if len(text) > settings.MAX_INPUT_LENGTH:
return (
[],
f"โš ๏ธ Text too long (max {settings.MAX_INPUT_LENGTH} characters)",
)
# Get suggestions from autocomplete engine
suggestions = autocomplete.get_suggestions(
text=text,
context=context,
max_tokens=output_tokens,
user_context=user_context,
)
self.current_suggestions = suggestions
if suggestions:
status = f"โœ… Found {len(suggestions)} suggestions"
else:
status = "๐Ÿค” No suggestions available for this text"
return suggestions, status
except Exception as e:
logger.error(f"Error getting suggestions: {str(e)}")
return [], f"โŒ Error: {str(e)}"
def get_suggestions_with_custom_prompts(
self,
text: str,
context: str,
output_tokens: int = 150,
user_context: str = "",
custom_prompts: dict = None,
) -> Tuple[List[str], str]:
"""
Get auto-complete suggestions with custom prompts
Returns: (suggestions_list, status_message)
"""
try:
# Input validation
if not text or len(text.strip()) < 2:
return [], "โœ๏ธ Please enter some text to get suggestions..."
if len(text) > settings.MAX_INPUT_LENGTH:
return (
[],
f"โš ๏ธ Text too long (max {settings.MAX_INPUT_LENGTH} characters)",
)
# Use the active autocomplete instance (user's custom or default)
active_autocomplete = self.get_active_autocomplete()
# Create a temporary autocomplete instance with custom prompts
temp_autocomplete = SmartAutoComplete(
active_autocomplete.settings if active_autocomplete else settings
)
if custom_prompts:
temp_autocomplete.CONTEXT_PROMPTS = custom_prompts
# Get suggestions from autocomplete engine
suggestions = temp_autocomplete.get_suggestions(
text=text,
context=context,
max_tokens=output_tokens,
user_context=user_context,
)
self.current_suggestions = suggestions
if suggestions:
status = f"โœ… Found {len(suggestions)} suggestions"
else:
status = "๐Ÿค” No suggestions available for this text"
return suggestions, status
except Exception as e:
logger.error(f"Error getting suggestions with custom prompts: {str(e)}")
return [], f"โŒ Error: {str(e)}"
def insert_suggestion(
self, current_text: str, suggestion: str, cursor_position: int = None
) -> str:
"""Insert the selected suggestion into the current text"""
try:
# Simple append for now - in a real implementation, this would be more sophisticated
if not current_text:
return suggestion
# If text ends with incomplete sentence, replace the last part
words = current_text.split()
if words and not current_text.endswith((".", "!", "?", "\n")):
# Replace the last partial word/sentence with the suggestion
return current_text + " " + suggestion.strip()
else:
return current_text + " " + suggestion.strip()
except Exception as e:
logger.error(f"Error inserting suggestion: {str(e)}")
return current_text
def update_api_key(self, api_key: str) -> str:
"""Update the OpenAI API key and reinitialize the autocomplete engine"""
try:
if not api_key or not api_key.strip():
self.user_api_key = None
self.custom_autocomplete = None
return "๐Ÿ”„ Reverted to default configuration"
# Validate the API key format
if not api_key.startswith("sk-"):
return "โŒ Invalid API key format. OpenAI keys start with 'sk-'"
# Create a custom settings object with the user's API key
from config.settings import AppSettings
custom_settings = AppSettings()
custom_settings.OPENAI_API_KEY = api_key.strip()
# Create a new autocomplete instance with the custom settings
self.custom_autocomplete = SmartAutoComplete(custom_settings)
self.user_api_key = api_key.strip()
return "โœ… API key updated successfully! Using your personal quota."
except Exception as e:
logger.error(f"Error updating API key: {str(e)}")
return f"โŒ Error updating API key: {str(e)}"
def test_api_connection(self, api_key: str = None) -> str:
"""Test the API connection with the current or provided key"""
try:
# Use custom autocomplete if user has provided a key, otherwise use default
test_autocomplete = (
self.custom_autocomplete if self.user_api_key else autocomplete
)
if api_key and api_key.strip():
# Test with the provided key
from config.settings import AppSettings
test_settings = AppSettings()
test_settings.OPENAI_API_KEY = api_key.strip()
test_autocomplete = SmartAutoComplete(test_settings)
# Test with a simple completion
test_result = test_autocomplete.get_suggestions(
text="Hello, this is a test", context="linkedin", max_tokens=10
)
if test_result and len(test_result) > 0:
return "โœ… API connection successful!"
else:
return "โŒ API connection failed - no response received"
except Exception as e:
logger.error(f"API connection test failed: {str(e)}")
return f"โŒ API connection test failed: {str(e)}"
def get_active_autocomplete(self):
"""Get the currently active autocomplete instance"""
return self.custom_autocomplete if self.user_api_key else autocomplete
def create_interface():
"""Create and configure the Gradio interface"""
app_instance = AutoCompleteApp()
# Custom CSS for better styling
custom_css = """
.suggestion-box {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
padding: 15px;
margin: 10px 0;
color: white;
cursor: pointer;
transition: transform 0.2s;
}
.suggestion-box:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.context-selector {
margin-bottom: 20px;
}
.main-input {
border-radius: 10px;
border: 2px solid #e1e5e9;
font-size: 16px;
}
"""
with gr.Blocks(
title="๐Ÿš€ Smart Auto-Complete", theme=gr.themes.Soft(), css=custom_css
) as interface:
# Header
gr.Markdown("""
# ๐Ÿš€ Smart Auto-Complete
**Intelligent text completion powered by AI**
Choose your context, enter your text, and click submit to get AI-powered completions! โœจ
๐Ÿ’ก **Tip**: Add your own OpenAI API key in Settings to use your personal quota and avoid rate limits.
""")
with gr.Row():
with gr.Column(scale=2):
# Context selection
context_selector = gr.Radio(
choices=[
("๐Ÿ“ง Email Writing", "email"),
("โœ๏ธ Creative Writing", "creative"),
("๐Ÿ’ผ LinkedIn Content", "linkedin"),
],
value="linkedin",
label="Select Context",
elem_classes=["context-selector"],
)
# User context input
context_input = gr.Textbox(
label="๐Ÿ“ Reference Information (Optional)",
placeholder="Add any background information, previous context, or references that should inform the writing...",
lines=4,
elem_classes=["context-input"],
)
# Main text input
text_input = gr.Textbox(
label="โœ๏ธ Enter your text here...",
placeholder="Enter your text and click Submit to get suggestions!",
lines=8,
elem_classes=["main-input"],
)
# Submit button
submit_btn = gr.Button(
"๐Ÿš€ Get Suggestions", variant="primary", size="lg"
)
# Settings
with gr.Accordion("โš™๏ธ Settings", open=False):
# API Key Configuration
with gr.Group():
gr.Markdown("### ๐Ÿ”‘ API Configuration")
openai_key_input = gr.Textbox(
label="OpenAI API Key (Optional)",
placeholder="sk-... (Enter your OpenAI API key to use your own quota)",
type="password",
value="",
info="Your API key is only used for this session and not stored permanently.",
)
api_status = gr.Textbox(
label="API Status",
value="Using default configuration"
if settings.OPENAI_API_KEY
else "No API key configured",
interactive=False,
lines=1,
)
test_api_btn = gr.Button("๐Ÿงช Test API Connection", size="sm")
gr.Markdown("---")
# Output Settings
output_length = gr.Slider(
minimum=50,
maximum=500,
value=150,
step=10,
label="Output Length (tokens)",
)
gr.Checkbox(label="Show debug information", value=False)
# Context Prompt Editor
with gr.Accordion("๐Ÿ”ง Edit Context Prompts", open=False):
gr.Markdown(
"**Customize your writing style for each context type. Changes apply immediately.**"
)
with gr.Tab("๐Ÿ“ง Email Context"):
email_system_prompt = gr.Textbox(
label="System Prompt",
value="""You are an expert email writing assistant. Generate professional,
contextually appropriate email completions. Focus on:
- Professional tone and structure
- Clear, concise communication
- Appropriate greetings and closings
- Business communication best practices
IMPORTANT: Generate a completion that is approximately {max_tokens} tokens long.
Adjust your response length accordingly - shorter for fewer tokens, longer for more tokens.""",
lines=8,
placeholder="Enter the system prompt for email context...",
)
email_user_template = gr.Textbox(
label="User Message Template",
value="Complete this email text naturally and professionally with approximately {max_tokens} tokens: {text}",
lines=3,
placeholder="Enter the user message template...",
)
with gr.Tab("๐ŸŽจ Creative Context"):
creative_system_prompt = gr.Textbox(
label="System Prompt",
value="""You are a creative writing assistant. Generate engaging,
imaginative story continuations. Focus on:
- Narrative consistency and flow
- Character development
- Descriptive and engaging language
- Plot advancement
IMPORTANT: Generate a completion that is approximately {max_tokens} tokens long.
Adjust your response length accordingly - shorter for fewer tokens, longer for more tokens.""",
lines=8,
placeholder="Enter the system prompt for creative context...",
)
creative_user_template = gr.Textbox(
label="User Message Template",
value="Continue this creative writing piece naturally with approximately {max_tokens} tokens: {text}",
lines=3,
placeholder="Enter the user message template...",
)
with gr.Tab("๐Ÿ’ผ LinkedIn Context"):
linkedin_system_prompt = gr.Textbox(
label="System Prompt",
value="""You are a LinkedIn writing assistant specialized in professional networking content. Generate engaging,
professional LinkedIn-appropriate text completions. Focus on:
- Professional networking tone
- Industry-relevant language
- Engaging and authentic voice
- LinkedIn best practices (hashtags, mentions, professional insights)
- Career development and business communication
IMPORTANT: Generate a completion that is approximately {max_tokens} tokens long.
Adjust your response length accordingly - shorter for fewer tokens, longer for more tokens.""",
lines=8,
placeholder="Enter the system prompt for LinkedIn context...",
)
linkedin_user_template = gr.Textbox(
label="User Message Template",
value="Complete this LinkedIn post/content naturally and professionally with approximately {max_tokens} tokens: {text}",
lines=3,
placeholder="Enter the user message template...",
)
with gr.Column(scale=1):
# Status display
status_display = gr.Textbox(
label="๐Ÿ“Š Status",
value="Ready to help! Start typing...",
interactive=False,
lines=2,
)
# Copyable textbox for suggestions (only output)
copy_textbox = gr.Textbox(
label="๐Ÿ“‹ Generated Text (Select All and Copy with Ctrl+C/Cmd+C)",
placeholder="Generated suggestions will appear here for easy copying...",
lines=8,
max_lines=15,
interactive=True,
visible=False,
)
# Demo section
with gr.Accordion("๐ŸŽฏ Try These Examples", open=False):
gr.Examples(
examples=[
[
"Meeting scheduled for next Tuesday to discuss the quarterly budget review",
"Dear Mr. Johnson,\n\nI hope this email finds you well. I wanted to follow up on",
"email",
],
[
"Fantasy adventure story with magical creatures and brave heroes",
"Once upon a time, in a kingdom far away, there lived a",
"creative",
],
[
"Professional networking and career development",
"Excited to share my thoughts on the future of AI in our industry",
"linkedin",
],
],
inputs=[context_input, text_input, context_selector],
label="Click any example to try it out!",
)
# Event handlers
def update_api_key(api_key):
"""Handle API key updates"""
status = app_instance.update_api_key(api_key)
return status
def test_api_connection(api_key):
"""Handle API connection testing"""
status = app_instance.test_api_connection(api_key)
return status
def update_suggestions(
text,
context,
output_tokens,
user_context,
email_sys,
email_user,
creative_sys,
creative_user,
linkedin_sys,
linkedin_user,
):
"""Update suggestions based on input with custom prompts"""
logger.info(
f"Getting suggestions with context: '{user_context[:50] if user_context else 'None'}...'"
)
logger.info(f"Requested output tokens: {output_tokens}")
# Create custom prompts dictionary
custom_prompts = {
"email": {
"system_prompt": email_sys,
"user_template": email_user,
"temperature": 0.6,
},
"creative": {
"system_prompt": creative_sys,
"user_template": creative_user,
"temperature": 0.8,
},
"linkedin": {
"system_prompt": linkedin_sys,
"user_template": linkedin_user,
"temperature": 0.7,
},
}
suggestions, status = app_instance.get_suggestions_with_custom_prompts(
text, context, output_tokens, user_context, custom_prompts
)
# Update the copy textbox with the suggestion
if suggestions:
copy_text = suggestions[0] if suggestions else ""
copy_visible = True
else:
copy_text = ""
copy_visible = False
# Return the copy textbox update
copy_update = gr.update(visible=copy_visible, value=copy_text)
return status, copy_update
# API Key event handlers
openai_key_input.change(
fn=update_api_key, inputs=[openai_key_input], outputs=[api_status]
)
test_api_btn.click(
fn=test_api_connection, inputs=[openai_key_input], outputs=[api_status]
)
# Submit button handler
submit_btn.click(
fn=update_suggestions,
inputs=[
text_input,
context_selector,
output_length,
context_input,
email_system_prompt,
email_user_template,
creative_system_prompt,
creative_user_template,
linkedin_system_prompt,
linkedin_user_template,
],
outputs=[status_display, copy_textbox],
)
# Footer
gr.Markdown("""
---
### ๐ŸŽฎ How to Use:
1. **Add your API key** (optional) - Enter your OpenAI API key in Settings to use your own quota
2. **Select your context** (Email, Creative, or LinkedIn)
3. **Add context information** (optional) - background info, references, or previous context
4. **Enter your text** in the main text area
5. **Adjust output length** (50-500 tokens) in settings
6. **Customize prompts** (optional) - edit AI prompts in "Edit Context Prompts" section
7. **Click "Get Suggestions"** to generate completions
8. **Copy from the generated text box** (Select All + Ctrl+C/Cmd+C)
### ๐ŸŒŸ Pro Tips:
- **API Key**: Add your own OpenAI API key to use your personal quota and avoid rate limits
- **Context Window**: Add background info, previous conversations, or references to improve suggestions
- **Email**: Try starting with "Dear..." or "I hope..." + add meeting context
- **Creative**: Start with "Once upon a time..." + add story background
- **LinkedIn**: Perfect for professional posts, career updates, industry insights + add professional context
- **Output Length**: Adjust the token slider for longer or shorter completions
- **Custom Prompts**: Edit the AI prompts to customize behavior for your specific needs
### ๐Ÿ”ง Built With:
- **Gradio** for the beautiful interface
- **OpenAI GPT** for intelligent completions
- **Python** for robust backend processing
---
<div style='text-align: center; color: #666;'>
Made with โค๏ธ for writers, developers, and creators everywhere
</div>
""")
return interface
def main():
"""Main function to run the application"""
try:
# Check API configuration - now optional since users can provide their own keys
if not settings.validate_api_keys():
logger.warning(
"No default API keys found. Users can provide their own keys in the UI."
)
print("โš ๏ธ No default API keys configured.")
print("Users can enter their own OpenAI API key in the Settings section.")
else:
logger.info("Default API keys found and validated.")
logger.info("Starting Smart Auto-Complete application...")
# Create and launch interface
interface = create_interface()
interface.launch(
server_name="0.0.0.0",
server_port=7860,
share=False, # Set to True for public sharing
show_error=True,
)
except Exception as e:
logger.error(f"Failed to start application: {str(e)}")
print(f"โŒ Error starting application: {str(e)}")
if __name__ == "__main__":
main()