diff --git a/.gitignore b/.gitignore index b6b32d43..d168362f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,64 @@ __pycache__ *.pywpz *.pywpzp +.swp +.swo +.swn +.swnw +.swnwp +.swnwpz +.swnwpzp + +*.log +*.log.* +*.log.*.* +*.log.*.*.* +*.log.*.*.*.* +*.log.*.*.*.*.* + +.venv +*.cpython* +*.cpython-312.pyc + +*venv +*.venv +*.venv* +*.venv_* +*.venv_*_* +*.venv_*_*_* +*.venv_*_*_*_* + +*venv +venv_new* +venv_* + +AI-Writer_cursor_workspace.code-workspace +*.code-workspace +.cursorignore +lib/ai_writers/__pycache__/ai_agents_crew_writer.cpython-312.pyc +*cpython* +*.cpython* +.DS_Store +.vscode +*.pyc +.env +.env.local +.env.development.local +.env.test.local +pycache +__pycache__ +*.pyc +*.pyo +*.pyd +*.pyw +*.pyz +*.pywz +*.pyzw +*.pyzp +*.pywp +*.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 a2907f62..39d8d700 100644 --- a/alwrity.py +++ b/alwrity.py @@ -65,7 +65,7 @@ st.set_page_config( page_title="AI Writer - Content Generation Platform", page_icon="βοΈ", layout="wide", - initial_sidebar_state="expanded", # Changed from collapsed to expanded + initial_sidebar_state="collapsed", # Start with collapsed sidebar menu_items={ 'Get Help': None, 'Report a bug': None, @@ -73,32 +73,33 @@ st.set_page_config( } ) -# Load and apply custom CSS -with open('lib/workspace/alwrity_ui_styling.css', 'r') as f: - css = f.read() - -st.markdown(f""" +# Add CSS to hide sidebar during setup +st.markdown(""" """, unsafe_allow_html=True) +import os +import json +import base64 +import logging +import logging +from datetime import datetime +import os +import json +import base64 +import logging +from datetime import datetime + # Configure logging logging.basicConfig( level=logging.DEBUG, @@ -110,11 +111,37 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) +from lib.utils.config_manager import save_config from lib.utils.ui_setup import setup_ui +from lib.utils.alwrity_sidebar import sidebar_configuration from lib.utils.api_key_manager.api_key_manager import APIKeyManager, render from lib.utils.api_key_manager.validation import check_all_api_keys from dotenv import load_dotenv -from lib.utils.ui_setup import setup_ui, setup_alwrity_ui +from lib.utils.content_generators import ai_writers, content_planning_tools, blog_from_keyword, story_input_section, essay_writer, ai_news_writer, ai_finance_ta_writer, write_ai_prod_desc, do_web_research, competitor_analysis +from lib.utils.seo_tools import ai_seo_tools +from lib.utils.ui_setup import setup_ui, setup_tabs +from lib.utils.alwrity_utils import ai_agents_team, ai_social_writer +from lib.utils.file_processor import load_image, read_prompts, write_prompts +from lib.utils.voice_processing import record_voice + +def process_folder_for_rag(folder_path): + """Placeholder for the process_folder_for_rag function.""" + logger.info(f"Processing folder for RAG: {folder_path}") + st.write(f"This is a placeholder for processing the folder: {folder_path}") + + +def save_config(config): + """ + Saves the provided configuration dictionary to a JSON file specified by the environment variable. + """ + try: + logger.debug(f"Saving configuration to {os.getenv('ALWRITY_CONFIG')}") + with open(os.getenv("ALWRITY_CONFIG"), "w") as config_file: + json.dump(config, config_file, indent=4) + logger.info("Configuration saved successfully") + except Exception as e: + logger.error(f"Error saving configuration: {str(e)}", exc_info=True) + st.error(f"An error occurred while saving the configuration: {e}") def main(): @@ -133,110 +160,36 @@ def main(): # Check API keys and show setup if needed if not check_all_api_keys(api_key_manager): logger.info("API keys not verified") - # Add CSS to hide sidebar during setup - st.markdown(""" - - """, unsafe_allow_html=True) render(api_key_manager) return else: logger.info("All API keys verified") - # Remove the CSS that hides the sidebar and ensure it's expanded + # Remove the CSS that hides the sidebar st.markdown(""" - """, unsafe_allow_html=True) - # Set session state to ensure sidebar stays expanded - if 'sidebar_expanded' not in st.session_state: - st.session_state.sidebar_expanded = True - - # Force sidebar state - st.sidebar.markdown(""" - - """, unsafe_allow_html=True) - - setup_alwrity_ui() + setup_environment_paths() + sidebar_configuration() + setup_tabs() def setup_environment_paths(): @@ -255,5 +208,52 @@ def setup_environment_paths(): raise +# Functions for the main options +def ai_writers(): + options = [ + "AI Blog Writer", + "Story Writer", + "Essay writer", + "Write News reports", + "Write Financial TA report", + "AI Product Description Writer", + "AI Copywriter", + "Quit" + ] + choice = st.selectbox("**πSelect a content creation type:**", options, index=0, format_func=lambda x: f"π {x}") + + if choice == "AI Blog Writer": + blog_from_keyword() + elif choice == "Story Writer": + story_input_section() + elif choice == "Essay writer": + essay_writer() + elif choice == "Write News reports": + ai_news_writer() + elif choice == "Write Financial TA report": + ai_finance_ta_writer() + elif choice == "AI Product Description Writer": + write_ai_prod_desc() + elif choice == "Quit": + st.subheader("Exiting, Getting Lost. But.... I have nowhere to go π₯Ήπ₯Ή") + + + +def alwrity_brain(): + st.title("π§ Alwrity Brain, Better than yours!") + st.write("Choose a folder to write content on. Alwrity will do RAG on these documents. The documents can of any type, pdf, pptx, docs, txt, cs etc. Video files and Audio files are also permitted.") + + folder_path = st.text_input("**Enter folder path:**") + if st.button("**Process Folder**"): + if folder_path: + try: + process_folder_for_rag(folder_path) + st.success("Folder processed successfully!") + except Exception as e: + st.error(f"Error processing folder: {e}") + else: + st.warning("Please enter a valid folder path.") + + if __name__ == "__main__": main() 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/lib/utils/api_key_manager/components/personalization_setup.py b/lib/utils/api_key_manager/components/personalization_setup.py index cd61fb15..f4f773c4 100644 --- a/lib/utils/api_key_manager/components/personalization_setup.py +++ b/lib/utils/api_key_manager/components/personalization_setup.py @@ -8,13 +8,11 @@ from typing import Dict, Any from ..manager import APIKeyManager from ....web_crawlers.async_web_crawler import AsyncWebCrawlerService from ....personalization.style_analyzer import StyleAnalyzer -from lib.utils.style_utils import ( - get_test_config_styles, +from pages.style_utils import ( + get_analysis_section, get_glass_container, get_info_section, - get_example_box, - get_analysis_section, - get_style_guide_html + get_example_box ) from .base import render_navigation_buttons from .alwrity_integrations import render_alwrity_integrations @@ -620,7 +618,7 @@ def render_personalization_setup(api_key_manager: APIKeyManager) -> Dict[str, An st.warning("Please provide either a website URL or content samples") with col2: - st.markdown(get_glass_container(""" + st.markdown(""" ### How ALwrity Discovers Your Style **AI-Powered Style Analysis** @@ -653,15 +651,10 @@ def render_personalization_setup(api_key_manager: APIKeyManager) -> Dict[str, An - Maintain consistency across all content - Optimize for your target audience - Ensure brand voice alignment - """)) + """) # API Configuration Form - st.markdown(get_glass_container(""" - ### API Configuration - - Configure your API settings for optimal content generation. - """)) - + st.markdown("### API Configuration") with st.form("ai_config_form"): # API Keys st.text_input("OpenAI API Key", type="password", key="openai_key") diff --git a/lib/utils/content_generators.py b/lib/utils/content_generators.py index e1c4a0a8..5ec2fcde 100644 --- a/lib/utils/content_generators.py +++ b/lib/utils/content_generators.py @@ -7,6 +7,7 @@ from lib.alwrity_ui.keyword_web_researcher import do_web_research from lib.ai_writers.ai_story_writer.story_writer import story_input_section from lib.ai_writers.ai_product_description_writer import write_ai_prod_desc #from lib.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_content_planner +from lib.utils.seo_tools import ai_seo_tools def ai_writers(): @@ -100,6 +101,6 @@ def content_planning_tools(): if st.button("**Ideate Content Calendar**"): if plan_keywords: #ai_agents_content_planner(plan_keywords) - st.header("Coming Soon.") + st.header("COming Soon.") else: st.error("Come on, really, Enter some keywords to plan on..") diff --git a/lib/utils/ui_setup.py b/lib/utils/ui_setup.py index e2b6d623..fb54c9fd 100644 --- a/lib/utils/ui_setup.py +++ b/lib/utils/ui_setup.py @@ -4,7 +4,6 @@ from lib.utils.file_processor import load_image from lib.utils.content_generators import content_planning_tools, ai_writers from lib.utils.alwrity_utils import ai_social_writer from lib.utils.seo_tools import ai_seo_tools -from lib.utils.settings_page import render_settings_page def setup_ui(): @@ -68,73 +67,40 @@ def setup_ui(): border-radius: 8px; color: white; } - - /* Sidebar navigation styling */ - .sidebar-nav { - padding: 1rem 0; - } - - .nav-button { - width: 100%; - text-align: left; - padding: 0.5rem 1rem; - background: transparent; - border: none; - color: #2c3e50; - font-weight: 500; - cursor: pointer; - transition: all 0.3s ease; - margin: 0.2rem 0; - border-radius: 4px; - } - - .nav-button:hover { - background: rgba(0,0,0,0.05); - padding-left: 1.5rem; - } - - .nav-button.active { - background: #1565C0; - color: white; - } """, unsafe_allow_html=True) + image_base64 = load_image("lib/workspace/alwrity_logo.png") + st.markdown(f""" +
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 0d144f47..4760f08b 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.0.0 +google-generativeai<0.9.0,>=0.8.0 anthropic>=0.18.1 tenacity>=8.2.3 tabulate>=0.9.0 @@ -31,8 +31,7 @@ prompt_toolkit>=3.0.43 html2image>=2.0.5 lxml[html_clean]>=5.3.0 lxml_html_clean>=0.4.1 -streamlit>=1.44.0 -Authlib>=1.3.2 +streamlit>=1.29.0 yfinance>=0.2.36 pandas_ta>=0.3.14b0 firecrawl-py>=1.14.1