|
|
|
""" |
|
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 |
|
|
|
|
|
logger = setup_logging() |
|
|
|
|
|
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: |
|
|
|
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)", |
|
) |
|
|
|
|
|
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: |
|
|
|
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)", |
|
) |
|
|
|
|
|
active_autocomplete = self.get_active_autocomplete() |
|
|
|
|
|
temp_autocomplete = SmartAutoComplete( |
|
active_autocomplete.settings if active_autocomplete else settings |
|
) |
|
if custom_prompts: |
|
temp_autocomplete.CONTEXT_PROMPTS = custom_prompts |
|
|
|
|
|
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: |
|
|
|
if not current_text: |
|
return suggestion |
|
|
|
|
|
words = current_text.split() |
|
if words and not current_text.endswith((".", "!", "?", "\n")): |
|
|
|
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" |
|
|
|
|
|
if not api_key.startswith("sk-"): |
|
return "โ Invalid API key format. OpenAI keys start with 'sk-'" |
|
|
|
|
|
from config.settings import AppSettings |
|
|
|
custom_settings = AppSettings() |
|
custom_settings.OPENAI_API_KEY = api_key.strip() |
|
|
|
|
|
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: |
|
|
|
test_autocomplete = ( |
|
self.custom_autocomplete if self.user_api_key else autocomplete |
|
) |
|
|
|
if api_key and api_key.strip(): |
|
|
|
from config.settings import AppSettings |
|
|
|
test_settings = AppSettings() |
|
test_settings.OPENAI_API_KEY = api_key.strip() |
|
test_autocomplete = SmartAutoComplete(test_settings) |
|
|
|
|
|
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 = """ |
|
.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: |
|
|
|
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_selector = gr.Radio( |
|
choices=[ |
|
("๐ง Email Writing", "email"), |
|
("โ๏ธ Creative Writing", "creative"), |
|
("๐ผ LinkedIn Content", "linkedin"), |
|
], |
|
value="linkedin", |
|
label="Select Context", |
|
elem_classes=["context-selector"], |
|
) |
|
|
|
|
|
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"], |
|
) |
|
|
|
|
|
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_btn = gr.Button( |
|
"๐ Get Suggestions", variant="primary", size="lg" |
|
) |
|
|
|
|
|
with gr.Accordion("โ๏ธ Settings", open=False): |
|
|
|
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_length = gr.Slider( |
|
minimum=50, |
|
maximum=500, |
|
value=150, |
|
step=10, |
|
label="Output Length (tokens)", |
|
) |
|
|
|
gr.Checkbox(label="Show debug information", value=False) |
|
|
|
|
|
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 = gr.Textbox( |
|
label="๐ Status", |
|
value="Ready to help! Start typing...", |
|
interactive=False, |
|
lines=2, |
|
) |
|
|
|
|
|
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, |
|
) |
|
|
|
|
|
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!", |
|
) |
|
|
|
|
|
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}") |
|
|
|
|
|
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 |
|
) |
|
|
|
|
|
if suggestions: |
|
copy_text = suggestions[0] if suggestions else "" |
|
copy_visible = True |
|
else: |
|
copy_text = "" |
|
copy_visible = False |
|
|
|
|
|
copy_update = gr.update(visible=copy_visible, value=copy_text) |
|
return status, copy_update |
|
|
|
|
|
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_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], |
|
) |
|
|
|
|
|
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: |
|
|
|
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...") |
|
|
|
|
|
interface = create_interface() |
|
|
|
interface.launch( |
|
server_name="0.0.0.0", |
|
server_port=7860, |
|
share=False, |
|
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() |
|
|