Made changes to Getting started with ALwrity and added lot of details on API keys

This commit is contained in:
ajaysi
2025-04-01 13:11:40 +05:30
parent ce8c253f59
commit 22d027d1c4
11 changed files with 1241 additions and 184 deletions

58
.gitignore vendored
View File

@@ -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*

View File

@@ -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("""
<style>
/* Hide Streamlit header elements */
header {{
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
.stDeployButton {display:none;}
/* Hide sidebar during setup */
[data-testid="stSidebar"] {
visibility: hidden !important;
height: 0px !important;
}}
/* Hide Deploy button */
.stDeployButton {{
display: none !important;
}}
/* Adjust top padding since we removed the header */
.main .block-container {{
padding-top: 1rem !important;
}}
{css}
width: 0px !important;
position: fixed !important;
}
</style>
""", 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("""
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
.stDeployButton {display:none;}
/* Hide sidebar during setup */
[data-testid="stSidebar"] {
visibility: hidden !important;
width: 0px !important;
position: fixed !important;
}
</style>
""", 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("""
<style>
#MainMenu {visibility: visible;}
footer {visibility: visible;}
.stDeployButton {display:block;}
/* Sidebar styling */
[data-testid="stSidebar"] {
visibility: visible !important;
width: 250px !important;
position: relative !important;
transition: width 0.3s ease-in-out;
}
/* Expanded state */
[data-testid="stSidebar"][aria-expanded="true"] {
width: 288px !important;
margin-left: 0 !important;
width: 250px !important;
}
/* Collapsed state */
[data-testid="stSidebar"][aria-expanded="false"] {
width: 0 !important;
margin-left: 0 !important;
width: 250px !important;
}
/* Main content area adjustments */
.main .block-container {
padding-left: 2rem !important;
padding-right: 2rem !important;
max-width: none;
}
/* Ensure content reflows when sidebar is collapsed */
@media (max-width: 768px) {
.main .block-container {
padding-left: 1rem !important;
padding-right: 1rem !important;
}
padding-left: 2rem;
}
</style>
<script>
// Force sidebar to be expanded initially
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.querySelector('[data-testid="stSidebar"]');
if (sidebar) {
sidebar.setAttribute('aria-expanded', 'true');
sidebar.style.transition = 'width 0.3s ease-in-out';
// Handle sidebar content
const sidebarContent = sidebar.querySelector('.css-1d391kg');
if (sidebarContent) {
sidebarContent.style.width = sidebar.getAttribute('aria-expanded') === 'true' ? '288px' : '0px';
sidebarContent.style.display = 'block';
sidebarContent.style.transition = 'width 0.3s ease-in-out';
}
// Add event listener for sidebar toggle
const toggleButton = document.querySelector('button[kind="header"]');
if (toggleButton) {
toggleButton.addEventListener('click', function() {
const isExpanded = sidebar.getAttribute('aria-expanded') === 'true';
if (sidebarContent) {
sidebarContent.style.width = isExpanded ? '0px' : '288px';
}
});
}
}
});
</script>
""", 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("""
<style>
[data-testid="stSidebar"] {
width: 288px !important;
}
</style>
""", 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()

View File

@@ -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("""
<style>
[data-testid="stSidebar"] {
min-width: 250px !important;
max-width: 250px !important;
visibility: visible !important;
position: relative !important;
transform: translateX(0) !important;
}
[data-testid="stSidebar"][aria-expanded="true"] {
min-width: 250px !important;
max-width: 250px !important;
transform: translateX(0) !important;
}
[data-testid="stSidebar"][aria-expanded="false"] {
min-width: 250px !important;
max-width: 250px !important;
transform: translateX(0) !important;
}
.stSidebar .element-container {
padding: 0.5rem;
}
.stSidebar .stMarkdown {
padding: 0.5rem;
}
.stSidebar .stSelectbox {
padding: 0.5rem;
}
.stSidebar .stTextInput {
padding: 0.5rem;
}
.stSidebar .stNumberInput {
padding: 0.5rem;
}
.stSidebar .stSlider {
padding: 0.5rem;
}
/* Ensure sidebar is visible */
section[data-testid="stSidebar"] {
visibility: visible !important;
transform: translateX(0) !important;
}
</style>
""", 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)}")

View File

@@ -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")

View File

@@ -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..")

View File

@@ -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;
}
</style>
""", unsafe_allow_html=True)
image_base64 = load_image("lib/workspace/alwrity_logo.png")
st.markdown(f"""
<div class='main-header'>
<img src='data:image/png;base64,{image_base64}' alt='Alwrity Logo' style='height: 50px; margin-right: 10px; vertical-align: middle;'>
Welcome to Alwrity!
</div>
""", unsafe_allow_html=True)
def setup_alwrity_ui():
"""Sets up the main navigation in the sidebar."""
# Initialize session state for active tab if not exists
if 'active_tab' not in st.session_state:
st.session_state.active_tab = "Content Planning"
# Define the navigation items with their icons and functions
nav_items = {
"Content Planning": ("📅", content_planning_tools),
"AI Writers": ("📝", ai_writers),
"Agents Teams": ("🤝", lambda: st.subheader("Agents Teams - Coming Soon!")),
"AI SEO Tools": ("🔍", ai_seo_tools),
"AI Social Tools": ("📱", ai_social_writer),
"Ask Alwrity": ("💬", lambda: (
st.subheader("Chat with your Data, Chat with any Data.. COMING SOON !"),
st.markdown("Create a collection by uploading files (PDF, MD, CSV, etc), or crawl a data source (Websites, more sources coming soon."),
st.markdown("One can ask/chat, summarize and do semantic search over the uploaded data")
)),
"ALwrity Settings": ("⚙️", render_settings_page)
}
def setup_tabs():
"""Sets up the main tabs in the Streamlit app."""
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs(
["📅Content Planning", " 📝🤖AI Writers", "🤝🤖Agents Teams", "🛠🔍AI SEO tools", "📱AI Social Tools", " 💬Ask Alwrity"])
with tab1:
content_planning_tools()
# Create sidebar navigation
st.sidebar.markdown("### ALwrity Options")
st.sidebar.markdown('<div class="sidebar-nav">', unsafe_allow_html=True)
with tab2:
ai_writers()
# Create navigation buttons
for name, (icon, func) in nav_items.items():
button_class = "nav-button active" if st.session_state.active_tab == name else "nav-button"
if st.sidebar.button(f"{icon} {name}", key=f"nav_{name}",
help=f"Navigate to {name}", use_container_width=True):
st.session_state.active_tab = name
with tab3:
#ai_agents_team()
st.subheader("Agents Teams")
with tab4:
ai_seo_tools()
st.sidebar.markdown('</div>', unsafe_allow_html=True)
with tab5:
ai_social_writer()
# Display content based on active tab
st.title(f"{nav_items[st.session_state.active_tab][0]} {st.session_state.active_tab}")
nav_items[st.session_state.active_tab][1]()
with tab6:
st.subheader("Chat with your Data, Chat with any Data.. COMING SOON !")
st.markdown("Create a collection by uploading files (PDF, MD, CSV, etc), or crawl a data source (Websites, more sources coming soon.")
st.markdown("One can ask/chat, summarize and do semantic search over the uploaded data")
# alwrity_chat_docqa()

View File

@@ -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="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# 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()

View File

@@ -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!")

352
pages/style_utils.py Normal file
View File

@@ -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 """
<style>
/* Hide main menu */
#MainMenu {
visibility: hidden !important;
}
/* Hide footer */
footer {
visibility: hidden !important;
}
/* Hide deploy button */
.stDeployButton {
display: none !important;
}
/* Hide sidebar in both states */
[data-testid="stSidebar"][aria-expanded="true"],
[data-testid="stSidebar"][aria-expanded="false"] {
visibility: hidden !important;
width: 0px !important;
position: fixed !important;
}
/* Hide hamburger menu */
.st-emotion-cache-1rs6os {
visibility: hidden !important;
}
/* Ensure main content takes full width */
.main .block-container {
max-width: 100% !important;
padding-top: 1rem !important;
}
</style>
"""
def get_glassmorphic_styles() -> str:
"""
Get the glassmorphic CSS styles for ALwrity.
Returns:
str: CSS styles as a string
"""
return """
<style>
.glass-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
padding: 20px;
margin: 10px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.glass-container:hover {
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.47);
}
.info-section {
background: linear-gradient(135deg, rgba(31,119,180,0.1), rgba(31,119,180,0.05));
border-radius: 12px;
padding: 16px;
margin: 8px 0;
}
.info-section h4 {
color: #1f77b4;
margin-bottom: 8px;
}
.info-section p {
margin: 4px 0;
line-height: 1.5;
}
.metric-card {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 15px;
margin: 10px 0;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.metric-value {
font-size: 24px;
font-weight: bold;
color: #00ff00;
}
.metric-label {
font-size: 14px;
color: #888;
}
.progress-bar {
height: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #00ff00, #00ccff);
transition: width 0.3s ease;
}
.stTabs [data-baseweb="tab-list"] {
gap: 2px;
}
.stTabs [data-baseweb="tab"] {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 4px;
padding: 10px 20px;
margin: 0 2px;
}
.stTabs [data-baseweb="tab"]:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.stTabs [aria-selected="true"] {
background-color: rgba(255, 255, 255, 0.3) !important;
border: 1px solid rgba(255, 255, 255, 0.4);
}
.stExpander {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
margin: 10px 0;
}
.stExpander:hover {
border-color: rgba(255, 255, 255, 0.2);
}
.stExpander .streamlit-expanderHeader {
background: rgba(255, 255, 255, 0.1);
border-radius: 8px 8px 0 0;
padding: 10px 15px;
}
.stExpander .streamlit-expanderContent {
background: rgba(255, 255, 255, 0.05);
border-radius: 0 0 8px 8px;
padding: 15px;
}
.example-box {
background: rgba(31,119,180,0.05);
border-left: 3px solid #1f77b4;
padding: 12px;
margin: 8px 0;
border-radius: 0 8px 8px 0;
box-shadow: 0 2px 4px rgba(31,119,180,0.1);
}
.example-box p {
margin: 4px 0;
font-style: italic;
}
.example-box code {
color: #00ff00;
font-family: monospace;
}
.analysis-section {
background: rgba(31,119,180,0.05);
border-radius: 12px;
padding: 16px;
margin: 8px 0;
}
.analysis-section h3 {
color: #1f77b4;
margin-bottom: 12px;
}
.analysis-section ul {
margin: 8px 0;
padding-left: 20px;
}
.analysis-section li {
margin: 4px 0;
}
.insight-card {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 15px;
margin: 10px 0;
}
.insight-card h4 {
color: #00ff00;
margin-bottom: 10px;
}
.insight-card ul {
margin: 0;
padding-left: 20px;
}
.insight-card li {
margin: 5px 0;
}
.recommendation-box {
background: rgba(0, 255, 0, 0.1);
border: 1px solid rgba(0, 255, 0, 0.2);
border-radius: 6px;
padding: 10px;
margin: 5px 0;
}
.recommendation-box h5 {
color: #00ff00;
margin-bottom: 5px;
}
.recommendation-box p {
margin: 0;
font-size: 14px;
}
.stButton>button {
background: linear-gradient(90deg, #00ff00, #00ccff);
border: none;
color: white;
padding: 10px 20px;
border-radius: 5px;
font-weight: bold;
transition: all 0.3s ease;
}
.stButton>button:hover {
background: linear-gradient(90deg, #00ccff, #00ff00);
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.stProgress .st-bo {
background-color: rgba(255, 255, 255, 0.1);
}
.stProgress .st-bo > div {
background: linear-gradient(90deg, #00ff00, #00ccff);
}
</style>
"""
def get_glass_container(content: str) -> str:
"""Wrap content in a glass container."""
return f"""
<div class="glass-container">
{content}
</div>
"""
def get_info_section(content: str) -> str:
"""Wrap content in an info section."""
return f"""
<div class="info-section">
{content}
</div>
"""
def get_example_box(content: str) -> str:
"""Wrap content in an example box."""
return f"""
<div class="example-box">
{content}
</div>
"""
def get_analysis_section(title: str, content: str) -> str:
"""Create an analysis section with title and content."""
return f"""
<div class="analysis-section">
<h3>{title}</h3>
{content}
</div>
"""
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()}"

View File

@@ -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="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# 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"""
<ul>
<li><strong>Tone:</strong> {writing_style.get("tone", "N/A")}</li>
<li><strong>Voice:</strong> {writing_style.get("voice", "N/A")}</li>
<li><strong>Complexity:</strong> {writing_style.get("complexity", "N/A")}</li>
<li><strong>Engagement Level:</strong> {writing_style.get("engagement_level", "N/A")}</li>
</ul>
"""
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"""
<ul>
<li><strong>Sentence Structure:</strong> {content_chars.get("sentence_structure", "N/A")}</li>
<li><strong>Vocabulary Level:</strong> {content_chars.get("vocabulary_level", "N/A")}</li>
<li><strong>Paragraph Organization:</strong> {content_chars.get("paragraph_organization", "N/A")}</li>
<li><strong>Content Flow:</strong> {content_chars.get("content_flow", "N/A")}</li>
</ul>
"""
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"""
<ul>
<li><strong>Demographics:</strong> {', '.join(target_audience.get("demographics", ["N/A"]))}</li>
<li><strong>Expertise Level:</strong> {target_audience.get("expertise_level", "N/A")}</li>
<li><strong>Industry Focus:</strong> {target_audience.get("industry_focus", "N/A")}</li>
<li><strong>Geographic Focus:</strong> {target_audience.get("geographic_focus", "N/A")}</li>
</ul>
"""
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"""
<ul>
<li><strong>Primary Type:</strong> {content_type.get("primary_type", "N/A")}</li>
<li><strong>Secondary Types:</strong> {', '.join(content_type.get("secondary_types", ["N/A"]))}</li>
<li><strong>Purpose:</strong> {content_type.get("purpose", "N/A")}</li>
<li><strong>Call to Action:</strong> {content_type.get("call_to_action", "N/A")}</li>
</ul>
"""
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"""
<ul>
<li><strong>Writing Tone:</strong> {recommended.get("writing_tone", "N/A")}</li>
<li><strong>Target Audience:</strong> {recommended.get("target_audience", "N/A")}</li>
<li><strong>Content Type:</strong> {recommended.get("content_type", "N/A")}</li>
<li><strong>Creativity Level:</strong> {recommended.get("creativity_level", "N/A")}</li>
<li><strong>Geographic Location:</strong> {recommended.get("geographic_location", "N/A")}</li>
</ul>
"""
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(
"<p>Enter a website URL or provide content samples to analyze your writing style and get personalized recommendations.</p>"
), 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("""
<p>No website URL? No problem! You can provide written samples of your content instead.</p>
<p>Share your best articles, blog posts, or any content that represents your writing style.</p>
"""), 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('</div>', unsafe_allow_html=True)
# ALwrity Style button
st.markdown("<div style='height: 20px'></div>", 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")

View File

@@ -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