Merge branch 'main' into new_alwrity
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,6 +21,7 @@ __pycache__
|
||||
*.pywpz
|
||||
*.pywpzp
|
||||
|
||||
|
||||
lib/workspace/alwrity_web_research/*
|
||||
lib/workspace/alwrity_web_research_cache/*
|
||||
web_research_report*
|
||||
|
||||
@@ -201,4 +201,4 @@ def setup_environment_paths():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
244
lib/utils/alwrity_sidebar.py
Normal file
244
lib/utils/alwrity_sidebar.py
Normal 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)}")
|
||||
50
pages/ai_research_setup_page.py
Normal file
50
pages/ai_research_setup_page.py
Normal 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()
|
||||
84
pages/personalization_setup.py
Normal file
84
pages/personalization_setup.py
Normal 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
352
pages/style_utils.py
Normal 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()}"
|
||||
310
pages/test_config_settings.py
Normal file
310
pages/test_config_settings.py
Normal 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")
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user