Google Search Grounded results, Content Calendar Ideator, Competitor Analysis, and Keyword Researcher

This commit is contained in:
ajaysi
2025-04-02 22:41:25 +05:30
parent 9d27d8469c
commit bf2b1f596f
16 changed files with 1408 additions and 530 deletions

View File

@@ -0,0 +1,155 @@
import os
import streamlit as st
from google import genai
from google.genai.types import Tool, GenerateContentConfig, GoogleSearch
# Set page config
st.set_page_config(
page_title="Gemini Grounding Search",
page_icon="🔍",
layout="wide"
)
# Custom CSS for styling
st.markdown("""
<style>
.container {
align-items: center;
border-radius: 8px;
display: flex;
font-family: Google Sans, Roboto, sans-serif;
font-size: 14px;
line-height: 20px;
padding: 8px 12px;
background-color: #fafafa;
box-shadow: 0 0 0 1px #0000000f;
margin-top: 20px;
}
.chip {
display: inline-block;
border: solid 1px;
border-radius: 16px;
min-width: 14px;
padding: 5px 16px;
text-align: center;
user-select: none;
margin: 0 8px;
background-color: #ffffff;
border-color: #d2d2d2;
color: #5e5e5e;
text-decoration: none;
}
.chip:hover {
background-color: #f2f2f2;
}
.carousel {
overflow: auto;
scrollbar-width: none;
white-space: nowrap;
margin-right: -12px;
display: flex;
align-items: center;
}
.headline {
display: flex;
margin-right: 4px;
align-items: center;
}
.gradient-container {
position: relative;
}
.gradient {
position: absolute;
transform: translate(3px, -9px);
height: 36px;
width: 9px;
background: linear-gradient(90deg, #fafafa 15%, #fafafa00 100%);
}
.result-text {
font-size: 16px;
line-height: 1.6;
color: #202124;
margin: 20px 0;
white-space: pre-wrap;
}
@media (prefers-color-scheme: dark) {
.container {
background-color: #1f1f1f;
box-shadow: 0 0 0 1px #ffffff26;
}
.headline-label {
color: #fff;
}
.chip {
background-color: #2c2c2c;
border-color: #3c4043;
color: #fff;
}
.chip:hover {
background-color: #353536;
}
.gradient {
background: linear-gradient(90deg, #1f1f1f 15%, #1f1f1f00 100%);
}
.result-text {
color: #e8eaed;
}
}
</style>
""", unsafe_allow_html=True)
# Title
st.title("Gemini Grounding Search")
# Initialize Gemini client
if 'GEMINI_API_KEY' not in os.environ:
api_key = st.text_input("Enter your Gemini API Key:", type="password")
if api_key:
os.environ['GEMINI_API_KEY'] = api_key
# Search input
search_query = st.text_input("Enter your search query:", "When is the next total solar eclipse in the United States?")
if st.button("Search"):
if 'GEMINI_API_KEY' not in os.environ:
st.error("Please enter your Gemini API Key first!")
else:
try:
client = genai.Client(api_key=os.environ['GEMINI_API_KEY'])
model_id = "gemini-2.0-flash"
google_search_tool = Tool(
google_search = GoogleSearch()
)
with st.spinner("Searching..."):
response = client.models.generate_content(
model=model_id,
contents=search_query,
config=GenerateContentConfig(
tools=[google_search_tool],
response_modalities=["TEXT"],
)
)
# Display search results header
st.header("Search Results")
# Display the response text
if response.candidates[0].content.parts:
st.markdown('<div class="result-text">' +
response.candidates[0].content.parts[0].text.replace('\n', '<br>') +
'</div>',
unsafe_allow_html=True)
# Display the grounding metadata
if hasattr(response.candidates[0], 'grounding_metadata') and \
hasattr(response.candidates[0].grounding_metadata, 'search_entry_point') and \
hasattr(response.candidates[0].grounding_metadata.search_entry_point, 'rendered_content'):
st.header("Related Searches")
rendered_content = response.candidates[0].grounding_metadata.search_entry_point.rendered_content
st.markdown(rendered_content, unsafe_allow_html=True)
except Exception as e:
st.error(f"An error occurred: {str(e)}")

View File

@@ -1,244 +0,0 @@
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,11 +8,13 @@ from typing import Dict, Any
from ..manager import APIKeyManager
from ....web_crawlers.async_web_crawler import AsyncWebCrawlerService
from ....personalization.style_analyzer import StyleAnalyzer
from pages.style_utils import (
get_analysis_section,
from lib.utils.style_utils import (
get_test_config_styles,
get_glass_container,
get_info_section,
get_example_box
get_example_box,
get_analysis_section,
get_style_guide_html
)
from .base import render_navigation_buttons
from .alwrity_integrations import render_alwrity_integrations
@@ -618,7 +620,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("""
st.markdown(get_glass_container("""
### How ALwrity Discovers Your Style
**AI-Powered Style Analysis**
@@ -651,10 +653,15 @@ 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("### API Configuration")
st.markdown(get_glass_container("""
### API Configuration
Configure your API settings for optimal content generation.
"""))
with st.form("ai_config_form"):
# API Keys
st.text_input("OpenAI API Key", type="password", key="openai_key")

438
lib/utils/settings_page.py Normal file
View File

@@ -0,0 +1,438 @@
import streamlit as st
from loguru import logger
import asyncio
from lib.web_crawlers.async_web_crawler import AsyncWebCrawlerService
from lib.personalization.style_analyzer import StyleAnalyzer
import sys
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/settings_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>"
)
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(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(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(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(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(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_settings_page():
"""Renders the settings page with all configuration options in tabs"""
st.title("🛠️ Settings & Configuration")
# Create tabs for different settings categories
tabs = st.tabs([
"👷 Content",
"🩻 Images",
"🤖 LLM",
"🕵️ Search",
"🎨 AI Personalization"
])
# Content Settings Tab
with tabs[0]:
st.header("Content Personalization")
blog_length = st.text_input(
"**Content Length (words)**",
value="2000",
key="settings_blog_length",
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,
key="settings_blog_tone",
help="Select the desired tone for the blog content."
)
if blog_tone == "Customize":
custom_tone = st.text_input(
"Enter the tone of your content",
key="settings_custom_tone",
help="Specify the tone of your content."
)
if custom_tone:
blog_tone = custom_tone
else:
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,
key="settings_blog_demographic",
help="Select the primary audience for the blog content."
)
blog_type = st.selectbox(
"**Content Type**",
options=["Informational", "Commercial", "Company", "News", "Finance", "Competitor", "Programming", "Scholar"],
key="settings_blog_type",
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"],
key="settings_blog_language",
help="Select the language in which the blog will be written."
)
blog_output_format = st.selectbox(
"**Content Output Format**",
options=["markdown", "HTML", "plaintext"],
key="settings_blog_output_format",
help="Select the format for the blog output."
)
# Images Settings Tab
with tabs[1]:
st.header("Images Personalization")
image_generation_model = st.selectbox(
"**Image Generation Model**",
options=["stable-diffusion", "dalle2", "dalle3"],
key="settings_image_model",
help="Select the model to generate images for the blog."
)
number_of_blog_images = st.number_input(
"**Number of Blog Images**",
value=1,
min_value=1,
max_value=10,
key="settings_number_of_images",
help="Specify the number of images to include in the blog."
)
# LLM Settings Tab
with tabs[2]:
st.header("LLM Personalization")
gpt_provider = st.selectbox(
"**GPT Provider**",
options=["google", "openai", "minstral"],
key="settings_gpt_provider",
help="Select the provider for the GPT model."
)
model = st.text_input(
"**Model**",
value="gemini-1.5-flash-latest",
key="settings_model",
help="Specify the model version to use from the selected provider."
)
col1, col2 = st.columns(2)
with col1:
temperature = st.slider(
"Temperature",
min_value=0.1,
max_value=1.0,
value=0.7,
step=0.1,
key="settings_temperature",
help="Controls the creativity level of the generated text."
)
max_tokens = st.selectbox(
"Max Tokens",
options=[500, 1000, 2000, 4000, 16000, 32000, 64000],
index=3,
key="settings_max_tokens",
help="Maximum length of the output sequence."
)
with col2:
top_p = st.slider(
"Top-p",
min_value=0.0,
max_value=1.0,
value=0.9,
step=0.1,
key="settings_top_p",
help="Controls diversity in text generation."
)
frequency_penalty = st.slider(
"Frequency Penalty",
min_value=0.0,
max_value=2.0,
value=1.0,
step=0.1,
key="settings_frequency_penalty",
help="Reduces word repetition in output."
)
# Search Settings Tab
with tabs[3]:
st.header("Search Engine Personalization")
geographic_location = st.selectbox(
"**Geographic Location**",
options=["us", "in", "fr", "cn"],
key="settings_geographic_location",
help="Select the geographic location for tailoring search results."
)
search_language = st.selectbox(
"**Search Language**",
options=["en", "zn-cn", "de", "hi"],
key="settings_search_language",
help="Select the language for the search results."
)
number_of_results = st.number_input(
"**Number of Results**",
value=10,
min_value=1,
max_value=20,
key="settings_number_of_results",
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"],
key="settings_time_range",
help="Select the time range for filtering search results."
)
include_domains = st.text_input(
"**Include Domains**",
value="",
key="settings_include_domains",
help="List specific domains to include in search results (comma-separated)."
)
similar_url = st.text_input(
"**Similar URL**",
value="",
key="settings_similar_url",
help="Provide a URL to find similar results."
)
# AI Personalization Tab
with tabs[4]:
st.header("🎨 AI Style Analysis")
st.markdown("""
<div style='background-color: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 10px; margin-bottom: 20px;'>
<p>Enter a website URL or provide content samples to analyze your writing style and get personalized recommendations.</p>
</div>
""", 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",
key="settings_website_url",
help="Provide your website URL to analyze your content style. Leave empty if you want to provide written samples instead."
)
# Alternative: Written samples
if not url:
st.markdown("### Written Samples")
st.markdown("""
<div style='background-color: rgba(255, 255, 255, 0.1); padding: 20px; border-radius: 10px; margin-bottom: 20px;'>
<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>
</div>
""", unsafe_allow_html=True)
samples = st.text_area(
"Paste your content samples here",
key="settings_content_samples",
help="Paste 2-3 samples of your best content. This helps ALwrity understand your writing style."
)
# ALwrity Style button
st.markdown("<div style='height: 20px'></div>", unsafe_allow_html=True)
if st.button("🎨 Analyze Style", use_container_width=True, key="settings_analyze_style"):
if url:
with st.status("Starting style analysis...", expanded=True) as status:
try:
# 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("Failed to crawl the website. Please check the URL and try again.")
except Exception as e:
status.update(label="Analysis failed", state="error")
st.error(f"An error occurred during analysis: {str(e)}")
elif samples:
with st.status("Starting style analysis...", expanded=True) as status:
try:
# Initialize style analyzer
status.update(label="Analyzing content style...", state="running")
style_analyzer = StyleAnalyzer()
# Perform style analysis
style_analysis = style_analyzer.analyze_content_style({"main_content": samples})
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)
except Exception as e:
status.update(label="Analysis failed", state="error")
st.error(f"An error occurred during analysis: {str(e)}")
else:
st.warning("Please provide either a website URL or content samples to analyze.")
# Save Settings Button
if st.button("💾 Save Settings", type="primary", use_container_width=True, key="settings_save_button"):
# Save all settings to session state
st.session_state.update({
'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,
'image_generation_model': image_generation_model,
'number_of_blog_images': number_of_blog_images,
'gpt_provider': gpt_provider,
'model': model,
'temperature': temperature,
'top_p': top_p,
'max_tokens': max_tokens,
'frequency_penalty': frequency_penalty,
'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
})
st.success("✅ Settings saved successfully!")

420
lib/utils/style_utils.py Normal file
View File

@@ -0,0 +1,420 @@
"""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_test_config_styles():
"""Returns CSS styles for the test configuration page."""
return """
<style>
.stApp {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.stButton > button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 8px;
font-weight: 500;
transition: all 0.3s ease;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.stTextInput > div > div > input {
border-radius: 8px;
border: 1px solid rgba(0,0,0,0.1);
padding: 0.5rem 1rem;
}
.stSelectbox > div > div > select {
border-radius: 8px;
border: 1px solid rgba(0,0,0,0.1);
padding: 0.5rem 1rem;
}
.stTextArea > div > div > textarea {
border-radius: 8px;
border: 1px solid rgba(0,0,0,0.1);
padding: 0.5rem 1rem;
}
.stMarkdown {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
h1, h2, h3 {
color: #2c3e50;
font-weight: 600;
}
.stSuccess {
background: linear-gradient(135deg, #43c6ac 0%, #191654 100%);
padding: 1rem;
border-radius: 8px;
color: white;
}
.stError {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%);
padding: 1rem;
border-radius: 8px;
color: white;
}
.stWarning {
background: linear-gradient(135deg, #ffd700 0%, #ffa500 100%);
padding: 1rem;
border-radius: 8px;
color: #2c3e50;
}
</style>
"""
def get_glass_container(content: str) -> str:
"""Returns HTML for a glass-morphism container."""
return f"""
<div style='
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
'>
{content}
</div>
"""
def get_info_section(content: str) -> str:
"""Returns HTML for an info section."""
return f"""
<div style='
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
'>
{content}
</div>
"""
def get_example_box(content: str) -> str:
"""Returns HTML for an example box."""
return f"""
<div style='
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
border-left: 4px solid #667eea;
'>
{content}
</div>
"""
def get_analysis_section(title: str, content: str) -> str:
"""Returns HTML for an analysis section."""
return f"""
<div style='
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
'>
<h3>{title}</h3>
{content}
</div>
"""
def get_style_guide_html() -> str:
"""Returns HTML for the style guide section."""
return """
<div style='
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
'>
<h3>Style Guide</h3>
<p>This section will contain your style guide and brand guidelines.</p>
</div>
"""
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 the main page where personalization setup is rendered
st.switch_page("alwrity.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

@@ -4,6 +4,7 @@ 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():
@@ -67,40 +68,73 @@ 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"
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()
# 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)
}
with tab2:
ai_writers()
# Create sidebar navigation
st.sidebar.markdown("### ALwrity Options")
st.sidebar.markdown('<div class="sidebar-nav">', unsafe_allow_html=True)
with tab3:
#ai_agents_team()
st.subheader("Agents Teams")
with tab4:
ai_seo_tools()
# 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 tab5:
ai_social_writer()
st.sidebar.markdown('</div>', unsafe_allow_html=True)
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()
# 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]()

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -13,13 +13,13 @@ body {
/* Main header styling */
.main-header {
font-size: 2.5em;
font-size: 2em;
font-weight: bold;
color: #1565C0;
margin-bottom: 20px;
margin-bottom: 2px;
text-align: center;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
padding-top: 10px;
padding-top: 1px;
}
/* Sub-header styling */
@@ -32,49 +32,355 @@ body {
text-align: center;
}
/* Navigation tabs styling */
.stTabs [role="tab"] {
font-size: 18px;
font-weight: bold;
color: white;
background: #1565C0;
padding: 6px 10px;
margin: 5px;
/* Enhanced Tab styling with dark red gradients */
.stTabs {
margin-top: 0.5rem;
background: white;
padding: 0.5rem;
border-radius: 2px;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05);
}
.stTabs [data-baseweb="tab-list"] {
gap: 8px;
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
padding: 8px;
border-radius: 8px;
border: 2px solid #ddd;
transition: background 0.3s ease, padding 0.3s ease;
}
.stTabs [role="tab"]:hover {
background: #1976D2;
.stTabs [data-baseweb="tab"] {
height: auto;
padding: 12px 20px;
color: #E2E8F0;
border-radius: 8px;
font-weight: 600;
font-size: 15px;
background: linear-gradient(135deg, #4A5568, #2D3748);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
letter-spacing: 0.3px;
background: white;
border-radius: 6px;
padding: 8px 16px;
font-weight: 600;
color: #475569;
transition: all 0.2s ease;
}
.stTabs [role="tab"][aria-selected="true"] {
background: #E3F2FD;
color: #333;
border: 2px solid #1565C0;
font-weight: bold;
.stTabs [data-baseweb="tab"]:hover {
color: #FFFFFF;
background: linear-gradient(135deg, #822727, #991B1B);
border-color: rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
background: #f1f5f9;
color: #1e293b;
}
/* Sidebar header styling */
.sidebar-header {
font-size: 1.5em;
font-weight: bold;
color: #333;
margin-bottom: 20px;
.stTabs [data-baseweb="tab"][aria-selected="true"] {
color: #FFFFFF;
background: linear-gradient(135deg, #3182CE, #2C5282);
border-color: #DC2626;
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);
position: relative;
background: linear-gradient(135deg, #3182ce, #2c5282);
color: white;
}
/* Sidebar option styling */
.sidebar-option {
margin-bottom: 10px;
font-size: 1.5em;
color: #1565C0;
.stTabs [data-baseweb="tab"][aria-selected="true"]::after {
content: '';
position: absolute;
bottom: -2px;
left: 10%;
width: 80%;
height: 2px;
background: linear-gradient(90deg, transparent, #FFFFFF, transparent);
border-radius: 2px;
}
.stTabs [data-baseweb="tab-panel"] {
padding: 20px;
background: linear-gradient(135deg, #FFFFFF, #F8FAFC);
border-radius: 12px;
margin-top: 10px;
border: 1px solid rgba(226, 232, 240, 0.8);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
/* Enhanced tab content for better readability */
.stTabs [data-baseweb="tab-panel"] p {
color: #1A202C;
line-height: 1.7;
font-size: 15px;
}
.stTabs [data-baseweb="tab-panel"] ul {
margin-top: 1rem;
margin-bottom: 1rem;
padding-left: 1.5rem;
}
.stTabs [data-baseweb="tab-panel"] li {
color: #2D3748;
margin-bottom: 0.5rem;
line-height: 1.6;
}
/* Tab content headings */
.stTabs [data-baseweb="tab-panel"] strong {
color: #1A202C;
font-weight: 600;
font-size: 16px;
}
/* Success/Warning messages in tabs */
.stTabs [data-baseweb="tab-panel"] .stSuccess,
.stTabs [data-baseweb="tab-panel"] .stWarning {
margin-top: 1rem;
margin-bottom: 1rem;
border-radius: 8px;
}
/* Main Application Tabs */
.tab-container {
background: linear-gradient(135deg, #1A202C, #2D3748);
border-radius: 16px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
.tab-content {
background: linear-gradient(135deg, #FFFFFF, #F8FAFC);
border-radius: 12px;
padding: 25px;
margin-top: 15px;
border: 1px solid rgba(226, 232, 240, 0.8);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.05);
}
/* Tab Content Typography */
.tab-content h1, .tab-content h2, .tab-content h3 {
color: #2D3748;
margin-bottom: 1rem;
font-weight: 600;
}
.tab-content p {
color: #4A5568;
line-height: 1.8;
margin-bottom: 1rem;
}
/* Custom Scrollbar for Tab Content */
.tab-content::-webkit-scrollbar {
width: 8px;
}
.tab-content::-webkit-scrollbar-track {
background: #F7FAFC;
border-radius: 4px;
}
.tab-content::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #CBD5E0, #A0AEC0);
border-radius: 4px;
}
.tab-content::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #A0AEC0, #718096);
}
/* Enhanced Tab Indicators */
.stTabs [data-baseweb="tab"][aria-selected="true"]::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
z-index: -1;
animation: tabPulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes tabPulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
/* Text Inputs */
.stTextInput > div {
background: #FFFFFF;
}
.stTextInput > div > div > input {
background: #F7FAFC;
border: 2px solid #E2E8F0;
border-radius: 10px;
padding: 12px 16px;
font-size: 15px;
color: #2D3748;
transition: all 0.3s ease;
}
.stTextInput > div > div > input:hover {
border-color: #CBD5E0;
background: #EDF2F7;
}
.stTextInput > div > div > input:focus {
border-color: #C53030;
box-shadow: 0 0 0 3px rgba(197, 48, 48, 0.2);
background: #FFFFFF;
}
/* Sidebar container styling - subtle modern gradient */
[data-testid="stSidebar"] {
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
color: #334155;
padding: 20px;
border-right: 1px solid rgba(148, 163, 184, 0.2);
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
transition: width 0.3s ease-in-out !important;
}
/* Collapsed sidebar styling */
[data-testid="stSidebar"][aria-expanded="false"] {
margin-left: -21rem;
}
/* Sidebar title styling - improved contrast */
[data-testid="stSidebar"] h1, [data-testid="stSidebar"] h2, [data-testid="stSidebar"] h3 {
color: #1e293b;
font-weight: 600;
margin-bottom: 1.5rem;
letter-spacing: 0.02em;
border-bottom: 2px solid #e2e8f0;
padding-bottom: 0.75rem;
}
/* Sidebar expander styling - modern and subtle */
[data-testid="stSidebar"] .st-expander {
background: linear-gradient(135deg, #ffffff, #f8fafc);
border: 1px solid #e2e8f0;
border-radius: 8px;
margin-bottom: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
overflow: hidden;
}
[data-testid="stSidebar"] .st-expander > div:first-child {
color: #334155;
font-weight: 600;
padding: 0.875rem 1rem;
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
border-bottom: 1px solid #e2e8f0;
}
/* Radio button styling - improved visibility */
[data-testid="stSidebar"] .stRadio > div {
display: flex;
flex-direction: column;
gap: 0.625rem;
}
[data-testid="stSidebar"] .stRadio > div > label {
background: #ffffff;
color: #334155;
padding: 0.75rem 1rem;
border-radius: 6px;
font-weight: 500;
border: 1px solid #e2e8f0;
transition: all 0.2s ease;
}
[data-testid="stSidebar"] .stRadio > div > label:hover {
background: linear-gradient(135deg, #f1f5f9, #e2e8f0);
transform: translateY(-1px);
border-color: #cbd5e1;
}
[data-testid="stSidebar"] .stRadio > div > label[data-selected="true"] {
background: linear-gradient(135deg, #0ea5e9, #0284c7);
color: #ffffff;
border-color: #0284c7;
box-shadow: 0 2px 4px rgba(2, 132, 199, 0.2);
}
/* Input and select styling - improved contrast */
[data-testid="stSidebar"] input, [data-testid="stSidebar"] select {
background: #ffffff;
color: #334155;
border: 1px solid #e2e8f0;
border-radius: 6px;
padding: 0.75rem;
font-size: 0.875rem;
margin-bottom: 0.75rem;
transition: all 0.2s ease;
}
[data-testid="stSidebar"] input:focus, [data-testid="stSidebar"] select:focus {
border-color: #0ea5e9;
box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.1);
outline: none;
}
/* Button styling - modern and subtle */
[data-testid="stSidebar"] button {
background: linear-gradient(135deg, #0ea5e9, #0284c7);
color: #ffffff;
border: none;
border-radius: 6px;
padding: 0.75rem 1rem;
font-weight: 500;
cursor: pointer;
transition: color 0.3s ease;
transition: all 0.2s ease;
}
.sidebar-option:hover {
color: #1976D2;
[data-testid="stSidebar"] button:hover {
background: linear-gradient(135deg, #0284c7, #0369a1);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(2, 132, 199, 0.2);
}
/* Settings button styling */
[data-testid="stSidebar"] .stButton > button {
background: linear-gradient(135deg, #3182CE, #2C5282);
color: white;
border: none;
padding: 0.75rem 1rem;
font-weight: 600;
border-radius: 8px;
transition: all 0.3s ease;
width: 100%;
margin-bottom: 1rem;
}
[data-testid="stSidebar"] .stButton > button:hover {
background: linear-gradient(135deg, #2C5282, #1A365D);
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* Scrollbar styling - subtle and modern */
[data-testid="stSidebar"]::-webkit-scrollbar {
width: 8px;
}
[data-testid="stSidebar"]::-webkit-scrollbar-track {
background: #f8fafc;
}
[data-testid="stSidebar"]::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
border: 2px solid #f8fafc;
}
[data-testid="stSidebar"]::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* Content section styling */
@@ -86,7 +392,6 @@ body {
background-color: #ffffff;
}
/* Custom button styling */
div.stButton > button:first-child {
background: #1565C0;
@@ -203,3 +508,169 @@ select option {
padding: 10px;
}
/* Content Planning Tools Styling */
.content-header {
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
padding: 1rem;
border-radius: 2px;
margin-bottom: 2rem;
border: 1px solid rgba(148, 163, 184, 0.2);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.content-header h2 {
color: #1e293b;
font-size: 1rem;
font-weight: 300;
margin-bottom: 0.5rem;
}
.content-header .subtitle {
color: #475569;
font-size: 1.1rem;
line-height: 1;
}
.tool-section {
background: white;
padding: 1rem;
border-radius: 2px;
margin-bottom: 1.5rem;
border: 1px solid #e2e8f0;
}
.tool-section h3 {
color: #1e293b;
font-size: 1.4rem;
font-weight: 600;
margin-bottom: 0.75rem;
}
.tool-section p {
color: #475569;
font-size: 1rem;
line-height: 1.5;
}
/* Button styling */
.stButton > button {
background: linear-gradient(135deg, #3182ce, #2c5282);
color: white;
border: none;
padding: 0.75rem 1.5rem;
font-weight: 600;
border-radius: 8px;
transition: all 0.3s ease;
}
.stButton > button:hover {
background: linear-gradient(135deg, #2c5282, #1a365d);
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* Search option containers styling */
.search-option-container {
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
text-align: center;
height: 100%;
transition: all 0.3s ease;
}
.search-option-container:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.search-option-container h4 {
color: #1e293b;
margin-bottom: 0.5rem;
}
.search-option-container p {
color: #64748b;
margin: 0;
}
/* Button styling for search options */
.stButton > button {
background: linear-gradient(135deg, #3182ce, #2c5282);
color: white;
border: none;
padding: 0.5rem 1rem;
font-weight: 600;
border-radius: 6px;
transition: all 0.3s ease;
}
.stButton > button:disabled {
background: linear-gradient(135deg, #94a3b8, #64748b);
cursor: not-allowed;
}
.stButton > button:not(:disabled):hover {
background: linear-gradient(135deg, #2c5282, #1e3a8a);
transform: translateY(-1px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* Search options styling */
.search-option {
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: 8px;
padding: 1rem;
height: 100%;
transition: all 0.3s ease;
text-align: center;
}
.search-option:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.search-option h4 {
color: #1e293b;
margin-bottom: 0.5rem;
font-weight: 600;
}
.search-option p {
color: #64748b;
font-size: 0.9em;
margin-bottom: 1rem;
}
.search-option.active {
border: 2px solid #3182ce;
background: linear-gradient(135deg, #ebf8ff, #e6fffa);
}
/* Add these to your existing search-option styles */
.search-option.disabled {
background: linear-gradient(135deg, #f1f5f9, #e2e8f0);
opacity: 0.8;
cursor: not-allowed;
border: 1px solid #cbd5e1;
}
.search-option .api-missing {
display: inline-block;
background: #fee2e2;
color: #dc2626;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.8em;
margin-top: 0.5rem;
}
.search-option.disabled h4,
.search-option.disabled p {
color: #64748b;
}