diff --git a/.gitignore b/.gitignore index b6b32d43..39a52973 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ __pycache__ *.pywpz *.pywpzp + lib/workspace/alwrity_web_research/* lib/workspace/alwrity_web_research_cache/* web_research_report* diff --git a/alwrity.py b/alwrity.py index 9650cd27..0cb6f5a6 100644 --- a/alwrity.py +++ b/alwrity.py @@ -201,4 +201,4 @@ def setup_environment_paths(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/lib/utils/alwrity_sidebar.py b/lib/utils/alwrity_sidebar.py new file mode 100644 index 00000000..8665bead --- /dev/null +++ b/lib/utils/alwrity_sidebar.py @@ -0,0 +1,244 @@ +import streamlit as st +import logging + +from .config_manager import save_config + +# Configure logging +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(), # Output to console + #logging.FileHandler('alwrity.log') # Output to file + ] +) +logger = logging.getLogger(__name__) + +# Sidebar configuration +def sidebar_configuration(): + """Configure the sidebar with all necessary options.""" + try: + # Configure sidebar styling + st.sidebar.markdown(""" + + """, unsafe_allow_html=True) + + logger.info("Initializing sidebar configuration") + st.sidebar.title("🛠️ Personalization & Settings 🏗️") + + with st.sidebar.expander("**👷 Content Personalization**"): + logger.debug("Setting up content personalization options") + blog_length = st.text_input("**Content Length (words)**", value="2000", + help="Approximate word count for blogs. Note: Actual length may vary based on GPT provider and max token count.") + + blog_tone_options = ["Casual", "Professional", "How-to", "Beginner", "Research", "Programming", "Social Media", "Customize"] + blog_tone = st.selectbox("**Content Tone**", + options=blog_tone_options, + help="Select the desired tone for the blog content.") + logger.debug(f"Selected blog tone: {blog_tone}") + + if blog_tone == "Customize": + custom_tone = st.text_input("Enter the tone of your content", help="Specify the tone of your content.") + if custom_tone: + blog_tone = custom_tone + logger.debug(f"Custom tone set to: {custom_tone}") + else: + logger.warning("Custom tone not specified") + st.warning("Please specify the tone of your content.") + + blog_demographic_options = ["Professional", "Gen-Z", "Tech-savvy", "Student", "Digital Marketing", "Customize"] + + blog_demographic = st.selectbox("**Target Audience**", + options=blog_demographic_options, + help="Select the primary audience for the blog content.") + if blog_demographic == "Customize": + custom_demographic = st.text_input("Enter your target audience", + help="Specify your target audience.", + placeholder="Eg. Domain expert, Content creator, Financial expert etc..") + if custom_demographic: + blog_demographic = custom_demographic + else: + st.warning("Please specify your target audience.") + + blog_type = st.selectbox("**Content Type**", + options=["Informational", "Commercial", "Company", "News", "Finance", "Competitor", "Programming", "Scholar"], + help="Select the category that best describes the blog content.") + + blog_language = st.selectbox("**Content Language**", + options=["English", "Spanish", "German", "Chinese", "Arabic", "Nepali", "Hindi", "Hindustani", "Customize"], + help="Select the language in which the blog will be written.") + if blog_language == "Customize": + custom_lang = st.text_input("Enter the language of your choice", help="Specify the content language.") + if custom_lang: + blog_language = custom_lang + else: + st.warning("Please specify the language of your content.") + + blog_output_format = st.selectbox("**Content Output Format**", + options=["markdown", "HTML", "plaintext"], + help="Select the format for the blog output.") + + with st.sidebar.expander("**🩻 Images Personalization**"): + image_generation_model = st.selectbox("**Image Generation Model**", + options=["stable-diffusion", "dalle2", "dalle3"], + help="Select the model to generate images for the blog.") + number_of_blog_images = st.number_input("**Number of Blog Images**", value=1, help="Specify the number of images to include in the blog.") + + with st.sidebar.expander("**🤖 LLM Personalization**"): + gpt_provider = st.selectbox("**GPT Provider**", + options=["google", "openai", "minstral"], + help="Select the provider for the GPT model.") + model = st.text_input("**Model**", value="gemini-1.5-flash-latest", help="Specify the model version to use from the selected provider.") + temperature = st.slider( + "Temperature", + min_value=0.1, + max_value=1.0, + value=0.7, + step=0.1, + format="%.1f", + help="""Temperature controls the 'creativity' or randomness of the text generated by GPT. + Greater determinism with higher values indicating more randomness.""" + ) + + top_p = st.slider( + "Top-p", + min_value=0.0, + max_value=1.0, + value=0.9, + step=0.1, + format="%.1f", + help="Top-p sampling controls the level of diversity in the generated text." + ) + + # Selectbox for max tokens + max_tokens_options = [500, 1000, 2000, 4000, 16000, 32000, 64000] + max_tokens = st.selectbox( + "Max Tokens", + options=max_tokens_options, + index=max_tokens_options.index(4000), + help="Max tokens determine the maximum length of the output sequence generated by a model." + ) + n = st.number_input("N", + value=1, + min_value=1, + max_value=10, + help="Defines the number of words or characters grouped together in a sequence when analyzing text.") + frequency_penalty = st.slider( + "Frequency Penalty", + min_value=0.0, + max_value=2.0, + value=1.0, + step=0.1, + format="%.1f", + help="Influences word selection during text generation, promoting diversity with higher values." + ) + + presence_penalty = st.slider( + "Presence Penalty", + min_value=0.0, + max_value=2.0, + value=1.0, + step=0.1, + format="%.1f", + help="Encourages the use of diverse words by discouraging repetition." + ) + + with st.sidebar.expander("**🕵️ Search Engine Personalization**"): + geographic_location = st.selectbox("**Geographic Location**", + options=["us", "in", "fr", "cn"], + help="Select the geographic location for tailoring search results.") + search_language = st.selectbox("**Search Language**", + options=["en", "zn-cn", "de", "hi"], + help="Select the language for the search results.") + number_of_results = st.number_input("**Number of Results**", + value=10, + max_value=20, + min_value=1, + help="Specify the number of search results to retrieve.") + time_range = st.selectbox("**Time Range**", + options=["anytime", "past day", "past week", "past month", "past year"], + help="Select the time range for filtering search results.") + include_domains = st.text_input("**Include Domains**", value="", + help="List specific domains to include in search results. Leave blank to include all domains.") + similar_url = st.text_input("**Similar URL**", value="", help="Provide a URL to find similar results. Leave blank if not needed.") + + # Storing collected inputs in a dictionary + config = { + "Blog Content Characteristics": { + "Blog Length": blog_length, + "Blog Tone": blog_tone, + "Blog Demographic": blog_demographic, + "Blog Type": blog_type, + "Blog Language": blog_language, + "Blog Output Format": blog_output_format + }, + "Blog Images Details": { + "Image Generation Model": image_generation_model, + "Number of Blog Images": number_of_blog_images + }, + "LLM Options": { + "GPT Provider": gpt_provider, + "Model": model, + "Temperature": temperature, + "Top-p": top_p, + "Max Tokens": max_tokens, + "N": n, + "Frequency Penalty": frequency_penalty, + "Presence Penalty": presence_penalty + }, + "Search Engine Parameters": { + "Geographic Location": geographic_location, + "Search Language": search_language, + "Number of Results": number_of_results, + "Time Range": time_range, + "Include Domains": include_domains, + "Similar URL": similar_url + } + } + + # Writing the configuration to a file whenever a change is made + save_config(config) + except Exception as e: + logger.error(f"Error configuring sidebar: {str(e)}") + st.error(f"Error configuring sidebar: {str(e)}") \ No newline at end of file diff --git a/pages/ai_research_setup_page.py b/pages/ai_research_setup_page.py new file mode 100644 index 00000000..5d094cd4 --- /dev/null +++ b/pages/ai_research_setup_page.py @@ -0,0 +1,50 @@ +"""Page for AI Research Setup redirection.""" + +import streamlit as st +from loguru import logger +import sys +import os + +# Configure logger +logger.remove() # Remove default handler +logger.add( + "logs/ai_research_setup_page.log", + rotation="500 MB", + retention="10 days", + level="DEBUG", + format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", + backtrace=True, + diagnose=True +) +logger.add( + sys.stdout, + level="INFO", + format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}" +) + +# Set page config +st.set_page_config( + layout="wide", + initial_sidebar_state="collapsed", + menu_items={ + 'Get Help': None, + 'Report a bug': None, + 'About': None + } +) + +def render_ai_research_setup_page(): + """Render the AI Research Setup page.""" + try: + logger.info("Starting AI Research Setup page") + + # Import and render the AI Research Setup component + from lib.utils.api_key_manager.components.ai_research_setup import render_ai_research_setup + render_ai_research_setup() + + except Exception as e: + logger.error(f"Error in render_ai_research_setup_page: {str(e)}") + st.error(f"An error occurred: {str(e)}") + +if __name__ == "__main__": + render_ai_research_setup_page() \ No newline at end of file diff --git a/pages/personalization_setup.py b/pages/personalization_setup.py new file mode 100644 index 00000000..d35e9930 --- /dev/null +++ b/pages/personalization_setup.py @@ -0,0 +1,84 @@ +import streamlit as st +import os +import json +from pathlib import Path + +st.set_page_config( + page_title="Personalization Setup", + page_icon="⚙️", + layout="wide" +) + +st.title("Personalization Setup") + +# Initialize session state for active tab if not exists +if 'active_tab' not in st.session_state: + st.session_state.active_tab = "Writing Preferences" + +# Create tabs for different sections +tab1, tab2 = st.tabs(["Writing Preferences", "AI Configuration"]) + +with tab1: + st.write(""" + This section allows you to customize your AI writing experience. + Configure your preferences and settings here. + """) + + # Add your personalization options here + st.subheader("Writing Style Preferences") + tone = st.selectbox( + "Select your preferred writing tone", + ["Professional", "Casual", "Academic", "Creative"] + ) + + st.subheader("Content Preferences") + content_type = st.multiselect( + "Select your preferred content types", + ["Blog Posts", "Articles", "Social Media", "Technical Writing", "Creative Writing"] + ) + + if st.button("Save Preferences"): + st.success("Your preferences have been saved!") + +with tab2: + st.subheader("AI Configuration Settings") + + # Create a form for AI configuration + with st.form("ai_config_form"): + # API Keys + st.text_input("OpenAI API Key", type="password", key="openai_key") + st.text_input("Google API Key", type="password", key="google_key") + st.text_input("SerpAPI Key", type="password", key="serpapi_key") + + # Model Selection + st.selectbox("Select Model", ["gpt-3.5-turbo", "gpt-4"], key="model") + + # Temperature + st.slider("Temperature", 0.0, 2.0, 0.7, 0.1, key="temperature") + + # Max Tokens + st.number_input("Max Tokens", 100, 4000, 2000, 100, key="max_tokens") + + # Submit button + submitted = st.form_submit_button("Save Configuration") + + if submitted: + # Create config directory if it doesn't exist + config_dir = Path("config") + config_dir.mkdir(exist_ok=True) + + # Save configuration + config = { + "openai_key": st.session_state.openai_key, + "google_key": st.session_state.google_key, + "serpapi_key": st.session_state.serpapi_key, + "model": st.session_state.model, + "temperature": st.session_state.temperature, + "max_tokens": st.session_state.max_tokens + } + + config_file = config_dir / "test_config.json" + with open(config_file, "w") as f: + json.dump(config, f, indent=4) + + st.success("Configuration saved successfully!") \ No newline at end of file diff --git a/pages/style_utils.py b/pages/style_utils.py new file mode 100644 index 00000000..cf0345cc --- /dev/null +++ b/pages/style_utils.py @@ -0,0 +1,352 @@ +"""CSS styles and utilities for ALwrity pages.""" + +def get_base_styles() -> str: + """ + Get the base CSS styles for ALwrity. + + Returns: + str: CSS styles as a string + """ + return """ + + """ + +def get_glassmorphic_styles() -> str: + """ + Get the glassmorphic CSS styles for ALwrity. + + Returns: + str: CSS styles as a string + """ + return """ + + """ + +def get_glass_container(content: str) -> str: + """Wrap content in a glass container.""" + return f""" +
+ {content} +
+ """ + +def get_info_section(content: str) -> str: + """Wrap content in an info section.""" + return f""" +
+ {content} +
+ """ + +def get_example_box(content: str) -> str: + """Wrap content in an example box.""" + return f""" +
+ {content} +
+ """ + +def get_analysis_section(title: str, content: str) -> str: + """Create an analysis section with title and content.""" + return f""" +
+

{title}

+ {content} +
+ """ + +def get_style_guide_html() -> str: + """ + Get the style guide HTML content. + + Returns: + str: HTML content for the style guide section + """ + return """ + ### How ALwrity Discovers Your Style + + **AI-Powered Style Analysis** + + ALwrity AI analyzes your existing content to understand your unique writing style and preferences. This helps us generate content that matches your voice perfectly. + + **Step 1: Content Analysis** + + We'll analyze your website content or written samples to understand: + + - Writing tone and voice + - Vocabulary and language style + - Content structure and formatting + - Target audience and engagement style + + **Step 2: Style Recommendations** + + Based on the analysis, we'll provide: + + - Personalized writing guidelines + - Content structure templates + - Tone and voice recommendations + - Audience engagement strategies + + **Step 3: Content Generation** + + Finally, we'll use these insights to: + + - Generate content that matches your style + - Maintain consistency across all content + - Optimize for your target audience + - Ensure brand voice alignment + """ + +def get_test_config_styles() -> str: + """ + Get all CSS styles for test configuration settings page. + + Returns: + str: Combined CSS styles as a string + """ + return f"{get_base_styles()}{get_glassmorphic_styles()}" \ No newline at end of file diff --git a/pages/test_config_settings.py b/pages/test_config_settings.py new file mode 100644 index 00000000..1ab9a2c8 --- /dev/null +++ b/pages/test_config_settings.py @@ -0,0 +1,310 @@ +"""Test configuration settings page for ALwrity.""" + +import streamlit as st +from loguru import logger +import asyncio +from lib.web_crawlers.async_web_crawler import AsyncWebCrawlerService +from pages.style_utils import ( + get_test_config_styles, + get_glass_container, + get_info_section, + get_example_box, + get_analysis_section, + get_style_guide_html +) +import sys +from lib.personalization.style_analyzer import StyleAnalyzer + +# Set page config - must be the first Streamlit command +st.set_page_config( + layout="wide", + initial_sidebar_state="collapsed", + menu_items={ + 'Get Help': None, + 'Report a bug': None, + 'About': None + } +) + +import yaml +from pathlib import Path +import os +from loguru import logger +from lib.utils.read_main_config_params import get_personalization_settings +from lib.web_crawlers.crawl4ai_web_crawler import analyze_style + +# Configure logger +logger.remove() # Remove default handler +logger.add( + "logs/test_config_settings.log", + rotation="500 MB", + retention="10 days", + level="DEBUG", + format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", + backtrace=True, + diagnose=True +) +logger.add( + sys.stdout, + level="INFO", + format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {message}" +) + +# Apply CSS styles +st.markdown(get_test_config_styles(), unsafe_allow_html=True) + +def load_website_url(): + """Load website URL from config file.""" + try: + logger.debug("Loading website URL from config file") + config_path = Path(os.environ["ALWRITY_CONFIG"]) + config = yaml.safe_load(config_path.read_text()) + url = config.get('website', {}).get('url', '') + logger.info(f"Loaded website URL: {url}") + return url + except Exception as e: + logger.error(f"Error loading website URL: {str(e)}", exc_info=True) + return '' + +def display_style_analysis(analysis_results: dict): + """Display the style analysis results in a structured format.""" + try: + # Writing Style Section + st.markdown("### 🎨 Writing Style Analysis") + writing_style = analysis_results.get("writing_style", {}) + writing_style_content = f""" + + """ + st.markdown(get_analysis_section("Writing Style", writing_style_content), unsafe_allow_html=True) + + # Content Characteristics Section + content_chars = analysis_results.get("content_characteristics", {}) + content_chars_content = f""" + + """ + st.markdown(get_analysis_section("Content Characteristics", content_chars_content), unsafe_allow_html=True) + + # Target Audience Section + target_audience = analysis_results.get("target_audience", {}) + target_audience_content = f""" + + """ + st.markdown(get_analysis_section("Target Audience", target_audience_content), unsafe_allow_html=True) + + # Content Type Section + content_type = analysis_results.get("content_type", {}) + content_type_content = f""" + + """ + st.markdown(get_analysis_section("Content Type", content_type_content), unsafe_allow_html=True) + + # Recommended Settings Section + recommended = analysis_results.get("recommended_settings", {}) + recommended_content = f""" + + """ + st.markdown(get_analysis_section("Recommended Settings", recommended_content), unsafe_allow_html=True) + + except Exception as e: + logger.error(f"Error displaying style analysis: {str(e)}") + st.error(f"Error displaying analysis results: {str(e)}") + +def render_test_config_settings(): + """Render the test configuration settings page.""" + try: + logger.info("Starting to render test configuration settings") + + # Add back button at the top + col1, col2 = st.columns([1, 3]) + with col1: + if st.button("← Back to Personalization Setup"): + logger.info("User clicked back to personalization setup") + # Set session state for navigation + st.session_state.current_step = 4 + st.session_state.next_step = "personalization_setup" + # Navigate back to personalization setup + st.switch_page("pages/personalization_setup.py") + + # Title and description + st.title("🎨 Find Your Style with ALwrity") + st.markdown(get_glass_container( + "

Enter a website URL or provide content samples to analyze your writing style and get personalized recommendations.

" + ), unsafe_allow_html=True) + + # Create two columns for the layout + col1, col2 = st.columns([2, 1]) + + with col1: + # Website URL input + st.markdown("### Website URL") + url = st.text_input( + "Enter your website URL", + placeholder="https://example.com", + help="Provide your website URL to analyze your content style. Leave empty if you want to provide written samples instead." + ) + logger.debug(f"Website URL input value: {url}") + + # Alternative: Written samples + if not url: + st.markdown("### Written Samples") + st.markdown(get_info_section(""" +

No website URL? No problem! You can provide written samples of your content instead.

+

Share your best articles, blog posts, or any content that represents your writing style.

+ """), unsafe_allow_html=True) + samples = st.text_area( + "Paste your content samples here", + help="Paste 2-3 samples of your best content. This helps ALwrity understand your writing style." + ) + logger.debug(f"Sample text length: {len(samples) if samples else 0}") + + st.markdown('', unsafe_allow_html=True) + + # ALwrity Style button + st.markdown("
", unsafe_allow_html=True) + if st.button("🎨 ALwrity Style", use_container_width=True): + if url: + with st.status("Starting style analysis...", expanded=True) as status: + try: + logger.info(f"Starting style analysis for URL: {url}") + + # Step 1: Initialize crawler + status.update(label="Step 1/4: Initializing web crawler...", state="running") + crawler_service = AsyncWebCrawlerService() + + # Step 2: Crawl website + status.update(label="Step 2/4: Crawling website content...", state="running") + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + result = loop.run_until_complete(crawler_service.crawl_website(url)) + loop.close() + + if result.get('success', False): + content = result.get('content', {}) + + # Step 3: Initialize style analyzer + status.update(label="Step 3/4: Analyzing content style...", state="running") + style_analyzer = StyleAnalyzer() + + # Step 4: Perform style analysis + status.update(label="Step 4/4: Generating style recommendations...", state="running") + style_analysis = style_analyzer.analyze_content_style(content) + + if style_analysis.get('error'): + status.update(label="Analysis failed", state="error") + st.error(f"Style analysis failed: {style_analysis['error']}") + else: + status.update(label="Analysis complete!", state="complete") + # Display style analysis results + display_style_analysis(style_analysis) + + # Display original content in tabs + tab1, tab2, tab3 = st.tabs(["Content", "Metadata", "Links"]) + + with tab1: + st.markdown("### Main Content") + st.markdown(content.get('main_content', 'No content found')) + + with tab2: + st.markdown("### Metadata") + st.markdown(f""" + **Title:** {content.get('title', 'No title found')} + + **Description:** {content.get('description', 'No description found')} + + **Meta Tags:** + {content.get('meta_tags', {})} + """) + + with tab3: + st.markdown("### Links") + for link in content.get('links', []): + st.markdown(f"- [{link.get('text', '')}]({link.get('href', '')})") + + else: + status.update(label="Crawling failed", state="error") + st.error(f"Failed to analyze website: {result.get('error', 'Unknown error')}") + + except Exception as e: + logger.error(f"Error during style analysis: {str(e)}") + st.error(f"Analysis failed: {str(e)}") + elif samples: + with st.spinner("Analyzing content samples..."): + try: + # TODO: Implement sample text analysis + st.info("Sample text analysis coming soon!") + except Exception as e: + logger.error(f"Error analyzing samples: {str(e)}") + st.error(f"Analysis failed: {str(e)}") + else: + st.warning("Please provide either a website URL or content samples") + + with col2: + st.markdown(""" + ### How ALwrity Discovers Your Style + + **AI-Powered Style Analysis** + + ALwrity AI analyzes your existing content to understand your unique writing style and preferences. This helps us generate content that matches your voice perfectly. + + **Step 1: Content Analysis** + + We'll analyze your website content or written samples to understand: + + - Writing tone and voice + - Vocabulary and language style + - Content structure and formatting + - Target audience and engagement style + + **Step 2: Style Recommendations** + + Based on the analysis, we'll provide: + + - Personalized writing guidelines + - Content structure templates + - Tone and voice recommendations + - Audience engagement strategies + + **Step 3: Content Generation** + + Finally, we'll use these insights to: + + - Generate content that matches your style + - Maintain consistency across all content + - Optimize for your target audience + - Ensure brand voice alignment + """) + + except Exception as e: + logger.error(f"Error in render_test_config_settings: {str(e)}") + st.error(f"An error occurred: {str(e)}") + +if __name__ == "__main__": + logger.info("Starting test config settings page") + render_test_config_settings() + logger.info("Test config settings page rendered successfully") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 881f53b4..e2dbc0e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ beautifulsoup4==4.12.2 aiohttp>=3.11.11 openai>=1.3.7 PyPDF2>=3.0.1 -google-genai==1.9.0 +google-genai>=1.9.0 anthropic>=0.18.1 tenacity>=8.2.3 tabulate>=0.9.0