Merge branch 'alwrity_keyword_research'

This commit is contained in:
ajaysi
2025-04-06 12:41:18 +05:30
25 changed files with 4050 additions and 868 deletions

66
.gitignore vendored
View File

@@ -21,6 +21,72 @@ __pycache__
*.pywpz
*.pywpzp
lib/workspace/alwrity_web_research/*
lib/workspace/alwrity_web_research_cache/*
web_research_report*
.swp
.swo
.swn
.swnw
.swnwp
.swnwpz
.swnwpzp
*.log
*.log.*
*.log.*.*
*.log.*.*.*
*.log.*.*.*.*
*.log.*.*.*.*.*
.venv
*.cpython*
*.cpython-312.pyc
*venv
*.venv
*.venv*
*.venv_*
*.venv_*_*
*.venv_*_*_*
*.venv_*_*_*_*
*venv
venv_new*
venv_*
AI-Writer_cursor_workspace.code-workspace
*.code-workspace
.cursorignore
lib/ai_writers/__pycache__/ai_agents_crew_writer.cpython-312.pyc
*cpython*
*.cpython*
.DS_Store
.vscode
*.pyc
.env
.env.local
.env.development.local
.env.test.local
pycache
__pycache__
*.pyc
*.pyo
*.pyd
*.pyw
*.pyz
*.pywz
*.pyzw
*.pyzp
*.pywp
*.pywpz
*.pywpzp
lib/workspace/alwrity_web_research/*
lib/workspace/alwrity_web_research_cache/*
web_research_report*
.swp
.swo
.swn

View File

@@ -1,11 +1,22 @@
import streamlit as st
import os
import json
import base64
import logging
from datetime import datetime
<<<<<<< HEAD
import os
import json
import base64
import logging
from datetime import datetime
# Set page config - must be the first Streamlit command
st.set_page_config(
page_title="AI Writer - Content Generation Platform",
page_icon="✍️",
layout="wide",
initial_sidebar_state="collapsed", # Start with collapsed sidebar
initial_sidebar_state="expanded", # Changed from collapsed to expanded
menu_items={
'Get Help': None,
'Report a bug': None,
@@ -13,21 +24,102 @@ st.set_page_config(
}
)
# Add CSS to hide sidebar during setup
st.markdown("""
# Load and apply custom CSS
with open('lib/workspace/alwrity_ui_styling.css', 'r') as f:
css = f.read()
st.markdown(f"""
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
.stDeployButton {display:none;}
/* Hide sidebar during setup */
[data-testid="stSidebar"] {
/* Hide Streamlit header elements */
header {{
visibility: hidden !important;
width: 0px !important;
position: fixed !important;
}
height: 0px !important;
}}
/* Hide Deploy button */
.stDeployButton {{
display: none !important;
}}
/* Adjust top padding since we removed the header */
.main .block-container {{
padding-top: 1rem !important;
}}
{css}
</style>
""", unsafe_allow_html=True)
import os
import json
import base64
import logging
import logging
from datetime import datetime
import os
import json
import base64
import logging
from datetime import datetime
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
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__)
=======
>>>>>>> b48a3b1 (Google Search Grounded results, Content Calendar Ideator, Competitor Analysis, and Keyword Researcher)
# Set page config - must be the first Streamlit command
st.set_page_config(
page_title="AI Writer - Content Generation Platform",
page_icon="✍️",
layout="wide",
initial_sidebar_state="expanded", # Changed from collapsed to expanded
menu_items={
'Get Help': None,
'Report a bug': None,
'About': None
}
)
# Load and apply custom CSS
with open('lib/workspace/alwrity_ui_styling.css', 'r') as f:
css = f.read()
st.markdown(f"""
<style>
/* Hide Streamlit header elements */
header {{
visibility: hidden !important;
height: 0px !important;
}}
/* Hide Deploy button */
.stDeployButton {{
display: none !important;
}}
/* Adjust top padding since we removed the header */
.main .block-container {{
padding-top: 1rem !important;
}}
{css}
</style>
""", unsafe_allow_html=True)
import os
import json
import base64
import logging
import logging
from datetime import datetime
import os
import json
import base64
@@ -45,37 +137,11 @@ logging.basicConfig(
)
logger = logging.getLogger(__name__)
from lib.utils.config_manager import save_config
from lib.utils.ui_setup import setup_ui
from lib.utils.alwrity_sidebar import sidebar_configuration
from lib.utils.api_key_manager.api_key_manager import APIKeyManager, render
from lib.utils.api_key_manager.validation import check_all_api_keys
from dotenv import load_dotenv
from lib.utils.content_generators import ai_writers, content_planning_tools, blog_from_keyword, story_input_section, essay_writer, ai_news_writer, ai_finance_ta_writer, write_ai_prod_desc, do_web_research, competitor_analysis
from lib.utils.seo_tools import ai_seo_tools
from lib.utils.ui_setup import setup_ui, setup_tabs
from lib.utils.alwrity_utils import ai_agents_team, ai_social_writer
from lib.utils.file_processor import load_image, read_prompts, write_prompts
from lib.utils.voice_processing import record_voice
def process_folder_for_rag(folder_path):
"""Placeholder for the process_folder_for_rag function."""
logger.info(f"Processing folder for RAG: {folder_path}")
st.write(f"This is a placeholder for processing the folder: {folder_path}")
def save_config(config):
"""
Saves the provided configuration dictionary to a JSON file specified by the environment variable.
"""
try:
logger.debug(f"Saving configuration to {os.getenv('ALWRITY_CONFIG')}")
with open(os.getenv("ALWRITY_CONFIG"), "w") as config_file:
json.dump(config, config_file, indent=4)
logger.info("Configuration saved successfully")
except Exception as e:
logger.error(f"Error saving configuration: {str(e)}", exc_info=True)
st.error(f"An error occurred while saving the configuration: {e}")
from lib.utils.ui_setup import setup_ui, setup_alwrity_ui
def main():
@@ -94,36 +160,110 @@ def main():
# Check API keys and show setup if needed
if not check_all_api_keys(api_key_manager):
logger.info("API keys not verified")
# Add CSS to hide sidebar during setup
st.markdown("""
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
.stDeployButton {display:none;}
/* Hide sidebar during setup */
[data-testid="stSidebar"] {
visibility: hidden !important;
width: 0px !important;
position: fixed !important;
}
</style>
""", unsafe_allow_html=True)
render(api_key_manager)
return
else:
logger.info("All API keys verified")
# Remove the CSS that hides the sidebar
# Remove the CSS that hides the sidebar and ensure it's expanded
st.markdown("""
<style>
#MainMenu {visibility: visible;}
footer {visibility: visible;}
.stDeployButton {display:block;}
/* Sidebar styling */
[data-testid="stSidebar"] {
visibility: visible !important;
width: 250px !important;
position: relative !important;
transition: width 0.3s ease-in-out;
}
/* Expanded state */
[data-testid="stSidebar"][aria-expanded="true"] {
width: 250px !important;
width: 288px !important;
margin-left: 0 !important;
}
/* Collapsed state */
[data-testid="stSidebar"][aria-expanded="false"] {
width: 250px !important;
width: 0 !important;
margin-left: 0 !important;
}
/* Main content area adjustments */
.main .block-container {
padding-left: 2rem;
padding-left: 2rem !important;
padding-right: 2rem !important;
max-width: none;
}
/* Ensure content reflows when sidebar is collapsed */
@media (max-width: 768px) {
.main .block-container {
padding-left: 1rem !important;
padding-right: 1rem !important;
}
}
</style>
<script>
// Force sidebar to be expanded initially
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.querySelector('[data-testid="stSidebar"]');
if (sidebar) {
sidebar.setAttribute('aria-expanded', 'true');
sidebar.style.transition = 'width 0.3s ease-in-out';
// Handle sidebar content
const sidebarContent = sidebar.querySelector('.css-1d391kg');
if (sidebarContent) {
sidebarContent.style.width = sidebar.getAttribute('aria-expanded') === 'true' ? '288px' : '0px';
sidebarContent.style.display = 'block';
sidebarContent.style.transition = 'width 0.3s ease-in-out';
}
// Add event listener for sidebar toggle
const toggleButton = document.querySelector('button[kind="header"]');
if (toggleButton) {
toggleButton.addEventListener('click', function() {
const isExpanded = sidebar.getAttribute('aria-expanded') === 'true';
if (sidebarContent) {
sidebarContent.style.width = isExpanded ? '0px' : '288px';
}
});
}
}
});
</script>
""", unsafe_allow_html=True)
# Set session state to ensure sidebar stays expanded
if 'sidebar_expanded' not in st.session_state:
st.session_state.sidebar_expanded = True
# Force sidebar state
st.sidebar.markdown("""
<style>
[data-testid="stSidebar"] {
width: 288px !important;
}
</style>
""", unsafe_allow_html=True)
setup_environment_paths()
sidebar_configuration()
setup_tabs()
setup_alwrity_ui()
def setup_environment_paths():
@@ -142,52 +282,5 @@ def setup_environment_paths():
raise
# Functions for the main options
def ai_writers():
options = [
"AI Blog Writer",
"Story Writer",
"Essay writer",
"Write News reports",
"Write Financial TA report",
"AI Product Description Writer",
"AI Copywriter",
"Quit"
]
choice = st.selectbox("**👇Select a content creation type:**", options, index=0, format_func=lambda x: f"📝 {x}")
if choice == "AI Blog Writer":
blog_from_keyword()
elif choice == "Story Writer":
story_input_section()
elif choice == "Essay writer":
essay_writer()
elif choice == "Write News reports":
ai_news_writer()
elif choice == "Write Financial TA report":
ai_finance_ta_writer()
elif choice == "AI Product Description Writer":
write_ai_prod_desc()
elif choice == "Quit":
st.subheader("Exiting, Getting Lost. But.... I have nowhere to go 🥹🥹")
def alwrity_brain():
st.title("🧠 Alwrity Brain, Better than yours!")
st.write("Choose a folder to write content on. Alwrity will do RAG on these documents. The documents can of any type, pdf, pptx, docs, txt, cs etc. Video files and Audio files are also permitted.")
folder_path = st.text_input("**Enter folder path:**")
if st.button("**Process Folder**"):
if folder_path:
try:
process_folder_for_rag(folder_path)
st.success("Folder processed successfully!")
except Exception as e:
st.error(f"Error processing folder: {e}")
else:
st.warning("Please enter a valid folder path.")
if __name__ == "__main__":
main()
main()

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

@@ -105,124 +105,55 @@ def plot_interest_by_region(kw_list):
def get_related_queries_and_save_csv(keywords, hl='en-US', tz=360, cat=0, timeframe='today 12-m'):
"""
Get related queries for the given search keywords and save the result to a CSV file.
Args:
search_keywords (list): List of search keywords.
hl (str): Language parameter, default is 'en-US'.
tz (int): Timezone parameter, default is 360.
cat (int): Category parameter, default is 0.
timeframe (str): Timeframe parameter, default is 'today 12-m'.
Returns:
pd.DataFrame: DataFrame containing related queries.
"""
try:
# Build model
pytrends = TrendReq(hl=hl, tz=tz)
pytrends.build_payload(kw_list=keywords, cat=cat, timeframe=timeframe)
# Get related queries
data = pytrends.related_queries()
# Extract data from the result
top_queries = list(data.values())[0]['top']
rising_queries = list(data.values())[0]['rising']
top_rising_queries = top_queries + rising_queries
# Convert lists to DataFrames
df_top_queries = pd.DataFrame(top_queries)
df_rising_queries = pd.DataFrame(rising_queries) # Added this line
# Rename columns to avoid duplicates
df_top_queries.columns = ['Top query', 'value']
df_rising_queries.columns = ['Rising query', 'value']
# Save to CSV
all_queries_df = pd.concat([df_top_queries, df_rising_queries], axis=1)
#all_queries_df.to_csv('related_queries.csv', index=False)
# Display additional information
console = Console()
# Display additional information with emojis and bold formatting
print("\n📢❗🚨 ")
print("\n\033[1m🔝 Top\033[0m: The most popular search queries. Scoring is on a relative scale where a value of 100 is the most commonly searched query, 50 is a query searched half as often, and a value of 0 is a query searched for less than 1% as often as the most popular query.\n")
print("\n\033[1m🚀 Rising\033[0m: Queries with the biggest increase in search frequency since the last time period. Results marked 'Breakout' had a tremendous increase, probably because these queries are new and had few (if any) prior searches.\n")
# Display the DataFrame using tabulate
table = tabulate(all_queries_df, headers='keys', tablefmt='fancy_grid')
print(table)
# Save the combined table to a file
try:
save_in_file(table)
except Exception as save_results_err:
logger.error(f"Failed to save search results: {save_results_err}")
return top_rising_queries
except Exception as e:
print(f"get_related_queries_and_save_csv: ERROR: An error occurred: {e}")
def get_related_topics_and_save_csv(search_keywords):
"""
Get related topics for the given search keywords and save the result to a CSV file.
Args:
search_keywords (list): List of search keywords.
Returns:
pd.DataFrame: DataFrame containing related topics.
"""
search_keywords = [f"{search_keywords}"]
try:
# Build model
pytrends = TrendReq(hl='en-US', tz=360)
pytrends.build_payload(kw_list=search_keywords, timeframe='today 12-m')
# Build payload
# FIXME: Remove hardcoding.
pytrends.build_payload(search_keywords, cat=0, timeframe='today 12-m')
# Get related topics
try:
data = pytrends.related_topics()
except Exception as err:
logger.error(f"Failed to get pytrends realted topics: {err}")
return None
# Extract data from the result
top_topics = list(data.values())[0]['top']
rising_topics = list(data.values())[0]['rising']
# Get related topics - this returns a dictionary
topics_data = pytrends.related_topics()
# Convert lists to DataFrames
df_top_topics = pd.DataFrame(top_topics)
df_rising_topics = pd.DataFrame(rising_topics)
# FIXME:Exclude specified columns
columns_to_exclude = ['hasData', 'value', 'topic_mid', 'link']
df_top_topics = df_top_topics.drop(columns=columns_to_exclude, errors='ignore')
df_rising_topics = df_rising_topics.drop(columns=columns_to_exclude, errors='ignore')
# Rename columns to avoid duplicates and provide meaningful names
df_top_topics.columns = ['Top- ' + col if col != 'topic_title' else col for col in df_top_topics.columns]
df_rising_topics.columns = ['Rising- ' + col if col != 'topic_title' else col for col in df_rising_topics.columns]
all_topics_df = pd.concat([df_top_topics, df_rising_topics], axis=1)
print(f"\n\n 📢❗🚨 Rising and Trending Keywords for {search_keywords}\n")
print("\033[1m🔝 Top\033[0m: The most popular search topics.")
print("\033[1m🚀 Rising\033[0m: Topics experiencing a significant increase in search frequency since the last time period. Topics marked :pile_of_poop:'Breakout' had a tremendous surge, likely because they are new and had few prior searches.")
# Display the DataFrame using tabulate
pd.set_option('display.max_rows', all_topics_df.shape[0]+1)
print(all_topics_df.head(10))
table = tabulate(all_topics_df, headers='keys', tablefmt='fancy_grid')
try:
save_in_file(table)
except Exception as save_results_err:
logger.error(f"Failed to save search results: {save_results_err}")
return all_topics_df
# Extract data for the first keyword
if topics_data and search_keywords[0] in topics_data:
keyword_data = topics_data[search_keywords[0]]
# Create two separate dataframes for top and rising
top_df = keyword_data.get('top', pd.DataFrame())
rising_df = keyword_data.get('rising', pd.DataFrame())
return {
'top': top_df[['topic_title', 'value']] if not top_df.empty else pd.DataFrame(),
'rising': rising_df[['topic_title', 'value']] if not rising_df.empty else pd.DataFrame()
}
except Exception as e:
logger.error(f"ERROR: An error occurred in related topics: {e}")
return pd.DataFrame()
logger.error(f"Error in related topics: {e}")
return {'top': pd.DataFrame(), 'rising': pd.DataFrame()}
def get_related_queries_and_save_csv(search_keywords):
search_keywords = [f"{search_keywords}"]
try:
pytrends = TrendReq(hl='en-US', tz=360)
pytrends.build_payload(kw_list=search_keywords, timeframe='today 12-m')
# Get related queries - this returns a dictionary
queries_data = pytrends.related_queries()
# Extract data for the first keyword
if queries_data and search_keywords[0] in queries_data:
keyword_data = queries_data[search_keywords[0]]
# Create two separate dataframes for top and rising
top_df = keyword_data.get('top', pd.DataFrame())
rising_df = keyword_data.get('rising', pd.DataFrame())
return {
'top': top_df if not top_df.empty else pd.DataFrame(),
'rising': rising_df if not rising_df.empty else pd.DataFrame()
}
except Exception as e:
logger.error(f"Error in related queries: {e}")
return {'top': pd.DataFrame(), 'rising': pd.DataFrame()}
def get_source(url):
@@ -507,22 +438,17 @@ def do_google_trends_analysis(search_term):
else:
all_the_keywords.append(suggestions_df['Keywords'].tolist())
all_the_keywords = ','.join([', '.join(filter(None, map(str, sublist))) for sublist in all_the_keywords])
# Generate a random sleep time between 2 and 3 seconds
time.sleep(random.uniform(2, 3))
#
# # FIXME: Get result from vision GPT. Fetch and visualize Google Trends data
# #trends_data = fetch_google_trends_interest_overtime("llamaindex")
#
# # FIXME: Plot Interest Over time.
# result_df = plot_interest_by_region(search_term)
#
# Display additional information
try:
result_df = get_related_topics_and_save_csv(search_term)
logger.info(f"Related topics:: result_df: {result_df}")
# Extract 'Top' topic_title
if result_df:
top_topic_title = result_df['topic_title'].values.tolist()
top_topic_title = result_df['top']['topic_title'].values.tolist()
# Join each sublist into one string separated by comma
#top_topic_title = [','.join(filter(None, map(str, sublist))) for sublist in top_topic_title]
top_topic_title = ','.join([', '.join(filter(None, map(str, sublist))) for sublist in top_topic_title])
@@ -551,3 +477,24 @@ def do_google_trends_analysis(search_term):
return(all_the_keywords)
except Exception as e:
logger.error(f"Error in Google Trends Analysis: {e}")
def get_trending_searches(country='united_states'):
"""Get trending searches for a specific country."""
try:
pytrends = TrendReq(hl='en-US', tz=360)
trending_searches = pytrends.trending_searches(pn=country)
return trending_searches
except Exception as e:
logger.error(f"Error getting trending searches: {e}")
return pd.DataFrame()
def get_realtime_trends(country='US'):
"""Get realtime trending searches for a specific country."""
try:
pytrends = TrendReq(hl='en-US', tz=360)
realtime_trends = pytrends.realtime_trending_searches(pn=country)
return realtime_trends
except Exception as e:
logger.error(f"Error getting realtime trends: {e}")
return pd.DataFrame()

View File

@@ -22,14 +22,27 @@
import os
import json
import time
from pathlib import Path
import sys
from datetime import datetime
import streamlit as st
import pandas as pd
import random
import numpy as np
from lib.alwrity_ui.display_google_serp_results import (
process_research_results,
process_search_results,
display_research_results
)
from lib.alwrity_ui.google_trends_ui import display_google_trends_data, process_trends_data
from .tavily_ai_search import get_tavilyai_results
from .metaphor_basic_neural_web_search import metaphor_find_similar, metaphor_search_articles
from .metaphor_basic_neural_web_search import metaphor_search_articles, streamlit_display_metaphor_results
from .google_serp_search import google_search
from .google_trends_researcher import do_google_trends_analysis
#from .google_gemini_web_researcher import do_gemini_web_research
from loguru import logger
# Configure logger
@@ -40,68 +53,689 @@ logger.add(sys.stdout,
)
def gpt_web_researcher(search_keywords):
""" Keyword based web researcher, basic, neural and Semantic search."""
def gpt_web_researcher(search_keywords, search_mode, **kwargs):
"""Keyword based web researcher with progress tracking."""
logger.info(f"Starting web research - Keywords: {search_keywords}, Mode: {search_mode}")
logger.debug(f"Additional parameters: {kwargs}")
try:
google_search_result = do_google_serp_search(search_keywords)
tavily_search_result = do_tavily_ai_search(search_keywords)
metaphor_search_result = do_metaphor_ai_research(search_keywords)
gtrends_search_result = do_google_pytrends_analysis(search_keywords)
# get_rag_results(search_query)
print(f"\n\nReview the analysis in this file at: {os.environ.get('SEARCH_SAVE_FILE')}\n")
# Reset session state variables for this research operation
if 'metaphor_results_displayed' in st.session_state:
del st.session_state.metaphor_results_displayed
# Initialize result container
research_results = None
# Create status containers
status_container = st.empty()
progress_bar = st.progress(0)
def update_progress(message, progress=None, level="info"):
if progress is not None:
progress_bar.progress(progress)
if level == "error":
status_container.error(f"🚫 {message}")
elif level == "warning":
status_container.warning(f"⚠️ {message}")
else:
status_container.info(f"🔄 {message}")
logger.debug(f"Progress update [{level}]: {message}")
if search_mode == "google":
logger.info("Starting Google research pipeline")
try:
# First try Google SERP
update_progress("Initiating SERP search...", progress=10)
serp_results = do_google_serp_search(search_keywords, **kwargs)
if serp_results and serp_results.get('organic'):
logger.info("SERP search successful")
update_progress("SERP search completed", progress=40)
research_results = serp_results
else:
logger.warning("SERP search returned no results, falling back to Gemini")
update_progress("No SERP results, trying Gemini...", progress=45)
# Keep it commented. Fallback to Gemini
#try:
# gemini_results = do_gemini_web_research(search_keywords)
# if gemini_results:
# logger.info("Gemini research successful")
# update_progress("Gemini research completed", progress=80)
# research_results = {
# 'source': 'gemini',
# 'results': gemini_results
# }
#except Exception as gemini_err:
# logger.error(f"Gemini research failed: {gemini_err}")
# update_progress("Gemini research failed", level="warning")
if research_results:
update_progress("Processing final results...", progress=90)
processed_results = process_research_results(research_results)
if processed_results:
update_progress("Research completed!", progress=100, level="success")
display_research_results(processed_results)
return processed_results
else:
error_msg = "Failed to process research results"
logger.warning(error_msg)
update_progress(error_msg, level="warning")
return None
else:
error_msg = "No results from either SERP or Gemini"
logger.warning(error_msg)
update_progress(error_msg, level="warning")
return None
except Exception as search_err:
error_msg = f"Research pipeline failed: {str(search_err)}"
logger.error(error_msg, exc_info=True)
update_progress(error_msg, level="error")
raise
elif search_mode == "ai":
logger.info("Starting AI research pipeline")
try:
# Do Tavily AI Search
update_progress("Initiating Tavily AI search...", progress=10)
# Extract relevant parameters for Tavily search
include_domains = kwargs.pop('include_domains', None)
search_depth = kwargs.pop('search_depth', 'advanced')
# Pass the parameters to get_tavilyai_results
t_results = get_tavilyai_results(
keywords=search_keywords,
max_results=kwargs.get('num_results', 10),
include_domains=include_domains,
search_depth=search_depth,
**kwargs
)
# Do Metaphor AI Search
update_progress("Initiating Metaphor AI search...", progress=50)
metaphor_results, metaphor_titles = do_metaphor_ai_research(search_keywords)
if metaphor_results is None:
update_progress("Metaphor AI search failed, continuing with Tavily results only...", level="warning")
else:
update_progress("Metaphor AI search completed successfully", progress=75)
# Add debug logging to check the structure of metaphor_results
logger.debug(f"Metaphor results structure: {type(metaphor_results)}")
if isinstance(metaphor_results, dict):
logger.debug(f"Metaphor results keys: {metaphor_results.keys()}")
if 'data' in metaphor_results:
logger.debug(f"Metaphor data keys: {metaphor_results['data'].keys()}")
if 'results' in metaphor_results['data']:
logger.debug(f"Number of results: {len(metaphor_results['data']['results'])}")
# Display Metaphor results only if not already displayed
if 'metaphor_results_displayed' not in st.session_state:
st.session_state.metaphor_results_displayed = True
# Make sure to pass the correct parameters to streamlit_display_metaphor_results
streamlit_display_metaphor_results(metaphor_results, search_keywords)
# Add Google Trends Analysis
update_progress("Initiating Google Trends analysis...", progress=80)
try:
# Add an informative message about Google Trends
with st.expander(" About Google Trends Analysis", expanded=False):
st.markdown("""
**What is Google Trends Analysis?**
Google Trends Analysis provides insights into how often a particular search-term is entered relative to the total search-volume across various regions of the world, and in various languages.
**What data will be shown?**
- **Related Keywords**: Terms that are frequently searched together with your keyword
- **Interest Over Time**: How interest in your keyword has changed over the past 12 months
- **Regional Interest**: Where in the world your keyword is most popular
- **Related Queries**: What people search for before and after searching for your keyword
- **Related Topics**: Topics that are closely related to your keyword
**How to use this data:**
- Identify trending topics in your industry
- Understand seasonal patterns in search behavior
- Discover related keywords for content planning
- Target content to specific regions with high interest
""")
trends_results = do_google_pytrends_analysis(search_keywords)
if trends_results:
update_progress("Google Trends analysis completed successfully", progress=90)
# Store trends results in the research_results
if metaphor_results:
metaphor_results['trends_data'] = trends_results
else:
# If metaphor_results is None, create a new container for results
metaphor_results = {'trends_data': trends_results}
# Display Google Trends data using the new UI module
display_google_trends_data(trends_results, search_keywords)
else:
update_progress("Google Trends analysis returned no results", level="warning")
except Exception as trends_err:
logger.error(f"Google Trends analysis failed: {trends_err}")
update_progress("Google Trends analysis failed", level="warning")
st.error(f"Error in Google Trends analysis: {str(trends_err)}")
# Return the combined results
update_progress("Research completed!", progress=100, level="success")
return metaphor_results or t_results
except Exception as ai_err:
error_msg = f"AI research pipeline failed: {str(ai_err)}"
logger.error(error_msg, exc_info=True)
update_progress(error_msg, level="error")
raise
else:
error_msg = f"Unsupported search mode: {search_mode}"
logger.error(error_msg)
update_progress(error_msg, level="error")
raise ValueError(error_msg)
except Exception as err:
logger.error(f"Failed in gpt_web_researcher: {err}")
error_msg = f"Failed in gpt_web_researcher: {str(err)}"
logger.error(error_msg, exc_info=True)
if 'update_progress' in locals():
update_progress(error_msg, level="error")
raise
def do_google_serp_search(search_keywords):
""" COmmon function to do google SERP analysis and return results. """
# FIXME: Add a return filter to either return full json, titles, PAA, relatedsearches etc.
def do_google_serp_search(search_keywords, status_container, update_progress, **kwargs):
"""Perform Google SERP analysis with sidebar progress tracking."""
logger.info("="*50)
logger.info("Starting Google SERP Search")
logger.info("="*50)
try:
logger.info(f"Doing Google search for: {search_keywords}\n")
# Validate parameters
update_progress("Validating search parameters")
status_container.info("📝 Validating parameters...")
if not search_keywords or not isinstance(search_keywords, str):
logger.error(f"Invalid search keywords: {search_keywords}")
raise ValueError("Search keywords must be a non-empty string")
# Update search initiation
update_progress(f"Initiating search for: '{search_keywords}'")
status_container.info("🌐 Querying search API...")
logger.info(f"Search params: {kwargs}")
# Execute search
g_results = google_search(search_keywords)
if g_results:
# Log success
update_progress("Search completed successfully", "success")
# Update statistics
stats = f"""Found:
- {len(g_results.get('organic', []))} organic results
- {len(g_results.get('peopleAlsoAsk', []))} related questions
- {len(g_results.get('relatedSearches', []))} related searches"""
update_progress(stats)
# Process results
update_progress("Processing search results")
status_container.info("⚡ Processing results...")
processed_results = process_search_results(g_results)
# Extract titles
update_progress("Extracting information")
g_titles = extract_info(g_results, 'titles')
return(g_results, g_titles)
# Final success
update_progress("Analysis completed successfully", "success")
status_container.success("✨ Research completed!")
# Clear main status after delay
time.sleep(1)
status_container.empty()
return {
'results': g_results,
'titles': g_titles,
'summary': processed_results,
'stats': {
'organic_count': len(g_results.get('organic', [])),
'questions_count': len(g_results.get('peopleAlsoAsk', [])),
'related_count': len(g_results.get('relatedSearches', []))
}
}
else:
update_progress("No results found", "warning")
status_container.warning("⚠️ No results found")
return None
except Exception as err:
logger.error(f"Failed to do Google SERP research: {err}")
return None
# Not failing, as tavily would do same and then GPT-V to search.
error_msg = f"Search failed: {str(err)}"
update_progress(error_msg, "error")
logger.error(error_msg)
logger.debug("Stack trace:", exc_info=True)
raise
finally:
logger.info("="*50)
logger.info("Google SERP Search function completed")
logger.info("="*50)
def do_tavily_ai_search(search_keywords, max_results=10):
def do_tavily_ai_search(search_keywords, max_results=10, **kwargs):
""" Common function to do Tavily AI web research."""
try:
# FIXME: Include the follow-up questions as blog FAQs.
logger.info(f"Doing Tavily AI search for: {search_keywords}")
t_results = get_tavilyai_results(search_keywords, max_results)
t_titles = tavily_extract_information(t_results, 'titles')
t_answer = tavily_extract_information(t_results, 'answer')
return(t_results, t_titles, t_answer)
# Prepare Tavily search parameters
tavily_params = {
'max_results': max_results,
'search_depth': 'advanced' if kwargs.get('search_depth', 3) > 2 else 'basic',
'time_range': kwargs.get('time_range', 'year'),
'include_domains': kwargs.get('include_domains', [""]) if kwargs.get('include_domains') else [""]
}
# Pass the parameters to get_tavilyai_results
t_results = get_tavilyai_results(
keywords=search_keywords,
**tavily_params
)
if t_results:
t_titles = tavily_extract_information(t_results, 'titles')
t_answer = tavily_extract_information(t_results, 'answer')
return(t_results, t_titles, t_answer)
else:
logger.warning("No results returned from Tavily AI search")
return None, None, None
except Exception as err:
logger.error(f"Failed to do Tavily AI Search: {err}")
return None, None, None
def do_metaphor_ai_research(search_keywords):
""" """
"""
Perform Metaphor AI research and return results with titles.
Args:
search_keywords (str): Keywords to search for
Returns:
tuple: (response_articles, titles) or (None, None) if search fails
"""
try:
logger.info(f"Start Semantic/Neural web search with Metahpor: {search_keywords}")
logger.info(f"Start Semantic/Neural web search with Metaphor: {search_keywords}")
response_articles = metaphor_search_articles(search_keywords)
m_titles = metaphor_extract_titles_or_text(response_articles, return_titles=True)
return(response_articles, m_titles)
if response_articles and 'data' in response_articles:
m_titles = [result.get('title', '') for result in response_articles['data'].get('results', [])]
return response_articles, m_titles
else:
logger.warning("No valid results from Metaphor search")
return None, None
except Exception as err:
logger.error(f"Failed to do Metaphor search: {err}")
return None, None
def do_google_pytrends_analysis(search_keywords):
""" """
def do_google_pytrends_analysis(keywords):
"""
Perform Google Trends analysis for the given keywords.
Args:
keywords (str): The search keywords to analyze
Returns:
dict: A dictionary containing formatted Google Trends data with the following keys:
- related_keywords: List of related keywords
- interest_over_time: DataFrame with date and interest columns
- regional_interest: DataFrame with country_code, country, and interest columns
- related_queries: DataFrame with query and value columns
- related_topics: DataFrame with topic and value columns
"""
logger.info(f"Performing Google Trends analysis for keywords: {keywords}")
# Create a progress container for Streamlit
progress_container = st.empty()
progress_bar = st.progress(0)
def update_progress(message, progress=None, level="info"):
"""Helper function to update progress in Streamlit UI"""
if progress is not None:
progress_bar.progress(progress)
if level == "error":
progress_container.error(f"🚫 {message}")
elif level == "warning":
progress_container.warning(f"⚠️ {message}")
else:
progress_container.info(f"🔄 {message}")
logger.debug(f"Progress update [{level}]: {message}")
try:
logger.info(f"Do Google Trends analysis for given keywords: {search_keywords}")
return(do_google_trends_analysis(search_keywords))
except Exception as err:
logger.error(f"Failed to do google trends analysis: {err}")
# Initialize the formatted data dictionary
formatted_data = {
'related_keywords': [],
'interest_over_time': pd.DataFrame(),
'regional_interest': pd.DataFrame(),
'related_queries': pd.DataFrame(),
'related_topics': pd.DataFrame()
}
# Get raw trends data from google_trends_researcher
update_progress("Fetching Google Trends data...", progress=10)
raw_trends_data = do_google_trends_analysis(keywords)
if not raw_trends_data:
logger.warning("No Google Trends data returned")
update_progress("No Google Trends data returned", level="warning", progress=20)
return formatted_data
# Process related keywords from the raw data
update_progress("Processing related keywords...", progress=30)
if isinstance(raw_trends_data, list):
formatted_data['related_keywords'] = raw_trends_data
elif isinstance(raw_trends_data, dict):
if 'keywords' in raw_trends_data:
formatted_data['related_keywords'] = raw_trends_data['keywords']
if 'interest_over_time' in raw_trends_data:
formatted_data['interest_over_time'] = raw_trends_data['interest_over_time']
if 'regional_interest' in raw_trends_data:
formatted_data['regional_interest'] = raw_trends_data['regional_interest']
if 'related_queries' in raw_trends_data:
formatted_data['related_queries'] = raw_trends_data['related_queries']
if 'related_topics' in raw_trends_data:
formatted_data['related_topics'] = raw_trends_data['related_topics']
# If we have keywords but missing other data, try to fetch them using pytrends directly
if formatted_data['related_keywords'] and (
formatted_data['interest_over_time'].empty or
formatted_data['regional_interest'].empty or
formatted_data['related_queries'].empty or
formatted_data['related_topics'].empty
):
try:
update_progress("Fetching additional data from Google Trends API...", progress=40)
from pytrends.request import TrendReq
pytrends = TrendReq(hl='en-US', tz=360)
# Build payload with the main keyword
update_progress("Building search payload...", progress=45)
pytrends.build_payload([keywords], timeframe='today 12-m', geo='')
# Get interest over time if missing
if formatted_data['interest_over_time'].empty:
try:
update_progress("Fetching interest over time data...", progress=50)
interest_df = pytrends.interest_over_time()
if not interest_df.empty:
formatted_data['interest_over_time'] = interest_df.reset_index()
update_progress(f"Successfully fetched interest over time data with {len(formatted_data['interest_over_time'])} data points", progress=55)
else:
update_progress("No interest over time data available", level="warning", progress=55)
except Exception as e:
logger.error(f"Error fetching interest over time: {e}")
update_progress(f"Error fetching interest over time: {str(e)}", level="warning", progress=55)
# Get regional interest if missing
if formatted_data['regional_interest'].empty:
try:
update_progress("Fetching regional interest data...", progress=60)
regional_df = pytrends.interest_by_region()
if not regional_df.empty:
formatted_data['regional_interest'] = regional_df.reset_index()
update_progress(f"Successfully fetched regional interest data for {len(formatted_data['regional_interest'])} regions", progress=65)
else:
update_progress("No regional interest data available", level="warning", progress=65)
except Exception as e:
logger.error(f"Error fetching regional interest: {e}")
update_progress(f"Error fetching regional interest: {str(e)}", level="warning", progress=65)
# Get related queries if missing
if formatted_data['related_queries'].empty:
try:
update_progress("Fetching related queries data...", progress=70)
# Get related queries data
related_queries = pytrends.related_queries()
# Create empty DataFrame as fallback
formatted_data['related_queries'] = pd.DataFrame(columns=['query', 'value'])
# Simple direct approach to avoid list index errors
if related_queries and isinstance(related_queries, dict):
# Check if our keyword exists in the results
if keywords in related_queries:
keyword_data = related_queries[keywords]
# Process top queries if available
if 'top' in keyword_data and keyword_data['top'] is not None:
try:
update_progress("Processing top related queries...", progress=75)
# Convert to DataFrame if it's not already
if isinstance(keyword_data['top'], pd.DataFrame):
top_df = keyword_data['top']
else:
# Try to convert to DataFrame
top_df = pd.DataFrame(keyword_data['top'])
# Ensure it has the right columns
if not top_df.empty:
# Rename columns if needed
if 'query' in top_df.columns:
# Already has the right column name
pass
elif len(top_df.columns) > 0:
# Use first column as query
top_df = top_df.rename(columns={top_df.columns[0]: 'query'})
# Add to our results
formatted_data['related_queries'] = top_df
update_progress(f"Successfully processed {len(top_df)} top related queries", progress=80)
except Exception as e:
logger.warning(f"Error processing top queries: {e}")
update_progress(f"Error processing top queries: {str(e)}", level="warning", progress=80)
# Process rising queries if available
if 'rising' in keyword_data and keyword_data['rising'] is not None:
try:
update_progress("Processing rising related queries...", progress=85)
# Convert to DataFrame if it's not already
if isinstance(keyword_data['rising'], pd.DataFrame):
rising_df = keyword_data['rising']
else:
# Try to convert to DataFrame
rising_df = pd.DataFrame(keyword_data['rising'])
# Ensure it has the right columns
if not rising_df.empty:
# Rename columns if needed
if 'query' in rising_df.columns:
# Already has the right column name
pass
elif len(rising_df.columns) > 0:
# Use first column as query
rising_df = rising_df.rename(columns={rising_df.columns[0]: 'query'})
# Combine with existing data if we have any
if not formatted_data['related_queries'].empty:
formatted_data['related_queries'] = pd.concat([formatted_data['related_queries'], rising_df])
update_progress(f"Successfully processed {len(rising_df)} rising related queries", progress=90)
else:
formatted_data['related_queries'] = rising_df
update_progress(f"Successfully processed {len(rising_df)} rising related queries", progress=90)
except Exception as e:
logger.warning(f"Error processing rising queries: {e}")
update_progress(f"Error processing rising queries: {str(e)}", level="warning", progress=90)
except Exception as e:
logger.error(f"Error fetching related queries: {e}")
update_progress(f"Error fetching related queries: {str(e)}", level="warning", progress=90)
# Ensure we have an empty DataFrame with the right columns
formatted_data['related_queries'] = pd.DataFrame(columns=['query', 'value'])
# Get related topics if missing
if formatted_data['related_topics'].empty:
try:
update_progress("Fetching related topics data...", progress=95)
# Get related topics data
related_topics = pytrends.related_topics()
# Create empty DataFrame as fallback
formatted_data['related_topics'] = pd.DataFrame(columns=['topic', 'value'])
# Simple direct approach to avoid list index errors
if related_topics and isinstance(related_topics, dict):
# Check if our keyword exists in the results
if keywords in related_topics:
keyword_data = related_topics[keywords]
# Process top topics if available
if 'top' in keyword_data and keyword_data['top'] is not None:
try:
update_progress("Processing top related topics...", progress=97)
# Convert to DataFrame if it's not already
if isinstance(keyword_data['top'], pd.DataFrame):
top_df = keyword_data['top']
else:
# Try to convert to DataFrame
top_df = pd.DataFrame(keyword_data['top'])
# Ensure it has the right columns
if not top_df.empty:
# Rename columns if needed
if 'topic_title' in top_df.columns:
top_df = top_df.rename(columns={'topic_title': 'topic'})
elif len(top_df.columns) > 0 and 'topic' not in top_df.columns:
# Use first column as topic
top_df = top_df.rename(columns={top_df.columns[0]: 'topic'})
# Add to our results
formatted_data['related_topics'] = top_df
update_progress(f"Successfully processed {len(top_df)} top related topics", progress=98)
except Exception as e:
logger.warning(f"Error processing top topics: {e}")
update_progress(f"Error processing top topics: {str(e)}", level="warning", progress=98)
# Process rising topics if available
if 'rising' in keyword_data and keyword_data['rising'] is not None:
try:
update_progress("Processing rising related topics...", progress=99)
# Convert to DataFrame if it's not already
if isinstance(keyword_data['rising'], pd.DataFrame):
rising_df = keyword_data['rising']
else:
# Try to convert to DataFrame
rising_df = pd.DataFrame(keyword_data['rising'])
# Ensure it has the right columns
if not rising_df.empty:
# Rename columns if needed
if 'topic_title' in rising_df.columns:
rising_df = rising_df.rename(columns={'topic_title': 'topic'})
elif len(rising_df.columns) > 0 and 'topic' not in rising_df.columns:
# Use first column as topic
rising_df = rising_df.rename(columns={rising_df.columns[0]: 'topic'})
# Combine with existing data if we have any
if not formatted_data['related_topics'].empty:
formatted_data['related_topics'] = pd.concat([formatted_data['related_topics'], rising_df])
update_progress(f"Successfully processed {len(rising_df)} rising related topics", progress=100)
else:
formatted_data['related_topics'] = rising_df
update_progress(f"Successfully processed {len(rising_df)} rising related topics", progress=100)
except Exception as e:
logger.warning(f"Error processing rising topics: {e}")
update_progress(f"Error processing rising topics: {str(e)}", level="warning", progress=100)
except Exception as e:
logger.error(f"Error fetching related topics: {e}")
update_progress(f"Error fetching related topics: {str(e)}", level="warning", progress=100)
# Ensure we have an empty DataFrame with the right columns
formatted_data['related_topics'] = pd.DataFrame(columns=['topic', 'value'])
except Exception as e:
logger.error(f"Error fetching additional trends data: {e}")
update_progress(f"Error fetching additional trends data: {str(e)}", level="warning", progress=100)
# Ensure all DataFrames have the correct column names for the UI
update_progress("Finalizing data formatting...", progress=100)
if not formatted_data['interest_over_time'].empty:
if 'date' not in formatted_data['interest_over_time'].columns:
formatted_data['interest_over_time'] = formatted_data['interest_over_time'].reset_index()
if 'interest' not in formatted_data['interest_over_time'].columns and keywords in formatted_data['interest_over_time'].columns:
formatted_data['interest_over_time'] = formatted_data['interest_over_time'].rename(columns={keywords: 'interest'})
if not formatted_data['regional_interest'].empty:
if 'country_code' not in formatted_data['regional_interest'].columns and 'geoName' in formatted_data['regional_interest'].columns:
formatted_data['regional_interest'] = formatted_data['regional_interest'].rename(columns={'geoName': 'country_code'})
if 'interest' not in formatted_data['regional_interest'].columns and keywords in formatted_data['regional_interest'].columns:
formatted_data['regional_interest'] = formatted_data['regional_interest'].rename(columns={keywords: 'interest'})
if not formatted_data['related_queries'].empty:
# Handle different column names that might be present in the related queries DataFrame
if 'query' not in formatted_data['related_queries'].columns:
if 'Top query' in formatted_data['related_queries'].columns:
formatted_data['related_queries'] = formatted_data['related_queries'].rename(columns={'Top query': 'query'})
elif 'Rising query' in formatted_data['related_queries'].columns:
formatted_data['related_queries'] = formatted_data['related_queries'].rename(columns={'Rising query': 'query'})
elif 'query' not in formatted_data['related_queries'].columns and len(formatted_data['related_queries'].columns) > 0:
# If we have a DataFrame but no 'query' column, use the first column as 'query'
first_col = formatted_data['related_queries'].columns[0]
formatted_data['related_queries'] = formatted_data['related_queries'].rename(columns={first_col: 'query'})
if 'value' not in formatted_data['related_queries'].columns and len(formatted_data['related_queries'].columns) > 1:
# If we have a second column, use it as 'value'
second_col = formatted_data['related_queries'].columns[1]
formatted_data['related_queries'] = formatted_data['related_queries'].rename(columns={second_col: 'value'})
elif 'value' not in formatted_data['related_queries'].columns:
# If no 'value' column exists, add one with default values
formatted_data['related_queries']['value'] = 0
if not formatted_data['related_topics'].empty:
# Handle different column names that might be present in the related topics DataFrame
if 'topic' not in formatted_data['related_topics'].columns:
if 'topic_title' in formatted_data['related_topics'].columns:
formatted_data['related_topics'] = formatted_data['related_topics'].rename(columns={'topic_title': 'topic'})
elif 'topic' not in formatted_data['related_topics'].columns and len(formatted_data['related_topics'].columns) > 0:
# If we have a DataFrame but no 'topic' column, use the first column as 'topic'
first_col = formatted_data['related_topics'].columns[0]
formatted_data['related_topics'] = formatted_data['related_topics'].rename(columns={first_col: 'topic'})
if 'value' not in formatted_data['related_topics'].columns and len(formatted_data['related_topics'].columns) > 1:
# If we have a second column, use it as 'value'
second_col = formatted_data['related_topics'].columns[1]
formatted_data['related_topics'] = formatted_data['related_topics'].rename(columns={second_col: 'value'})
elif 'value' not in formatted_data['related_topics'].columns:
# If no 'value' column exists, add one with default values
formatted_data['related_topics']['value'] = 0
# Clear the progress container after completion
progress_container.empty()
progress_bar.empty()
return formatted_data
except Exception as e:
logger.error(f"Error in Google Trends analysis: {e}")
update_progress(f"Error in Google Trends analysis: {str(e)}", level="error", progress=100)
# Clear the progress container after error
progress_container.empty()
progress_bar.empty()
return {
'related_keywords': [],
'interest_over_time': pd.DataFrame(),
'regional_interest': pd.DataFrame(),
'related_queries': pd.DataFrame(),
'related_topics': pd.DataFrame()
}
def metaphor_extract_titles_or_text(json_data, return_titles=True):
@@ -163,4 +797,4 @@ def tavily_extract_information(json_data, keyword):
elif keyword == 'follow-query':
return json_data['follow_up_questions']
else:
return f"Invalid keyword: {keyword}"
return f"Invalid keyword: {keyword}"

View File

@@ -116,55 +116,331 @@ def metaphor_find_similar(similar_url):
return search_response
def metaphor_search_articles(query):
def calculate_date_range(time_range: str) -> tuple:
"""
Search for articles using the Metaphor API.
Calculate start and end dates based on time range selection.
Args:
time_range (str): One of 'past_day', 'past_week', 'past_month', 'past_year', 'anytime'
Returns:
tuple: (start_date, end_date) in ISO format with milliseconds
"""
now = datetime.utcnow()
end_date = now.strftime('%Y-%m-%dT%H:%M:%S.999Z')
if time_range == 'past_day':
start_date = (now - timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S.000Z')
elif time_range == 'past_week':
start_date = (now - timedelta(weeks=1)).strftime('%Y-%m-%dT%H:%M:%S.000Z')
elif time_range == 'past_month':
start_date = (now - timedelta(days=30)).strftime('%Y-%m-%dT%H:%M:%S.000Z')
elif time_range == 'past_year':
start_date = (now - timedelta(days=365)).strftime('%Y-%m-%dT%H:%M:%S.000Z')
else: # anytime
start_date = None
end_date = None
return start_date, end_date
def metaphor_search_articles(query, search_options: dict = None):
"""
Search for articles using the Metaphor/Exa API.
Args:
query (str): The search query.
num_results (int): Number of results to retrieve.
use_autoprompt (bool): Whether to use autoprompt.
include_domains (list): List of domains to include.
time_range (str): Time range for published articles ("day", "week", "month", "year", "anytime").
search_options (dict): Search configuration options including:
- num_results (int): Number of results to retrieve
- use_autoprompt (bool): Whether to use autoprompt
- include_domains (list): List of domains to include
- time_range (str): One of 'past_day', 'past_week', 'past_month', 'past_year', 'anytime'
- exclude_domains (list): List of domains to exclude
Returns:
MetaphorResponse: The response from the Metaphor API.
dict: Search results and metadata
"""
metaphor = get_metaphor_client()
exa = get_metaphor_client()
try:
include_domains, start_published_date, num_results, similar_url = cfg_search_param('exa')
logger.info(f"Metaphor web search with Date: {start_published_date} and Query: {query}")
# Initialize default search options
if search_options is None:
search_options = {}
# Get config parameters or use defaults
try:
search_response = metaphor.search_and_contents(
query,
include_domains=include_domains,
use_autoprompt=True,
start_published_date=start_published_date,
num_results=num_results
)
except Exception as err:
logger.error(f"Failed in metaphor.search_and_contents: {err}")
# From each webpage, get a summary of the web page.
contents_response = search_response.results
# FIXME: Need to summarize for smaller input context window.
# for content in tqdm(contents_response, desc="Reading Web URL content:", unit="content"):
# summarized_content = summarize_web_content(content.text, "gemini")
# content.text = summarized_content
print_search_result(contents_response)
include_domains, _, num_results, _ = cfg_search_param('exa')
except Exception as cfg_err:
logger.warning(f"Failed to load config parameters: {cfg_err}. Using defaults.")
include_domains = None
num_results = 10
# Calculate date range based on time_range option
time_range = search_options.get('time_range', 'anytime')
start_published_date, end_published_date = calculate_date_range(time_range)
# Prepare search parameters
search_params = {
'num_results': search_options.get('num_results', num_results),
'summary': True, # Always get summaries
'include_domains': search_options.get('include_domains', include_domains),
'use_autoprompt': search_options.get('use_autoprompt', True),
}
# Add date parameters only if they are not None
if start_published_date:
search_params['start_published_date'] = start_published_date
if end_published_date:
search_params['end_published_date'] = end_published_date
logger.info(f"Exa web search with params: {search_params} and Query: {query}")
# Execute search
search_response = exa.search_and_contents(
query,
**search_params
)
if not search_response or not hasattr(search_response, 'results'):
logger.warning("No results returned from Exa search")
return None
# Get cost information safely
try:
cost_dollars = {
'total': float(search_response.cost_dollars['total']),
} if hasattr(search_response, 'cost_dollars') else None
except Exception as cost_err:
logger.warning(f"Error processing cost information: {cost_err}")
cost_dollars = None
# Format response to match expected structure
formatted_response = {
"data": {
"requestId": getattr(search_response, 'request_id', None),
"resolvedSearchType": "neural",
"results": [
{
"id": result.url,
"title": result.title,
"url": result.url,
"publishedDate": result.published_date if hasattr(result, 'published_date') else None,
"author": getattr(result, 'author', None),
"score": getattr(result, 'score', 0),
"summary": result.summary if hasattr(result, 'summary') else None,
"text": result.text if hasattr(result, 'text') else None,
"image": getattr(result, 'image', None),
"favicon": getattr(result, 'favicon', None)
}
for result in search_response.results
],
"costDollars": cost_dollars
}
}
# Get AI-generated answer from Metaphor
try:
exa_answer = get_exa_answer(query)
if exa_answer:
formatted_response.update(exa_answer)
except Exception as exa_err:
logger.warning(f"Error getting Exa answer: {exa_err}")
# Get AI-generated answer from Tavily
try:
# Import the function directly from the module
import importlib
tavily_module = importlib.import_module('lib.ai_web_researcher.tavily_ai_search')
if hasattr(tavily_module, 'do_tavily_ai_search'):
tavily_response = tavily_module.do_tavily_ai_search(query)
if tavily_response and 'answer' in tavily_response:
formatted_response.update({
"tavily_answer": tavily_response.get("answer"),
"tavily_citations": tavily_response.get("citations", []),
"tavily_cost_dollars": tavily_response.get("costDollars", {"total": 0})
})
else:
logger.warning("do_tavily_ai_search function not found in tavily_ai_search module")
except Exception as tavily_err:
logger.warning(f"Error getting Tavily answer: {tavily_err}")
# Return the formatted response without displaying it
# The display will be handled by gpt_web_researcher
return formatted_response
if similar_url:
logger.info(f"Doing similar/semantic search for URL: {similar_url}")
metaphor_find_similar(similar_url)
return contents_response
except Exception as e:
logger.error(f"Error in Metaphor searching articles: {e}")
raise
logger.error(f"Error in Exa searching articles: {e}")
return None
def streamlit_display_metaphor_results(metaphor_response, search_keywords=None):
"""Display Metaphor search results in Streamlit."""
if not metaphor_response:
st.error("No search results found.")
return
# Add debug logging
logger.debug(f"Displaying Metaphor results. Type: {type(metaphor_response)}")
if isinstance(metaphor_response, dict):
logger.debug(f"Metaphor response keys: {metaphor_response.keys()}")
# Initialize session state variables if they don't exist
if 'search_insights' not in st.session_state:
st.session_state.search_insights = None
if 'metaphor_response' not in st.session_state:
st.session_state.metaphor_response = None
if 'insights_generated' not in st.session_state:
st.session_state.insights_generated = False
# Store the current response in session state
st.session_state.metaphor_response = metaphor_response
# Display search results
st.subheader("🔍 Search Results")
# Calculate metrics - handle different data structures
results = []
if isinstance(metaphor_response, dict):
if 'data' in metaphor_response and 'results' in metaphor_response['data']:
results = metaphor_response['data']['results']
elif 'results' in metaphor_response:
results = metaphor_response['results']
total_results = len(results)
avg_relevance = sum(r.get('score', 0) for r in results) / total_results if total_results > 0 else 0
# Display metrics
col1, col2 = st.columns(2)
with col1:
st.metric("Total Results", total_results)
with col2:
st.metric("Average Relevance Score", f"{avg_relevance:.2f}")
# Display AI-generated answers if available
if 'tavily_answer' in metaphor_response or 'metaphor_answer' in metaphor_response:
st.subheader("🤖 AI-Generated Answers")
if 'tavily_answer' in metaphor_response:
st.markdown("**Tavily AI Answer:**")
st.write(metaphor_response['tavily_answer'])
if 'metaphor_answer' in metaphor_response:
st.markdown("**Metaphor AI Answer:**")
st.write(metaphor_response['metaphor_answer'])
# Get Search Insights button
if st.button("Generate Search Insights", key="metaphor_generate_insights_button"):
st.session_state.insights_generated = True
st.rerun()
# Display insights if they exist in session state
if st.session_state.search_insights:
st.subheader("🔍 Search Insights")
st.write(st.session_state.search_insights)
# Display search results in a data editor
st.subheader("📊 Detailed Results")
# Prepare data for display
results_data = []
for result in results:
result_data = {
'Title': result.get('title', ''),
'URL': result.get('url', ''),
'Snippet': result.get('summary', ''),
'Relevance Score': result.get('score', 0),
'Published Date': result.get('publishedDate', '')
}
results_data.append(result_data)
# Create DataFrame
df = pd.DataFrame(results_data)
# Display the DataFrame if it's not empty
if not df.empty:
# Configure columns
st.dataframe(
df,
column_config={
"Title": st.column_config.TextColumn(
"Title",
help="Title of the search result",
width="large",
),
"URL": st.column_config.LinkColumn(
"URL",
help="Link to the search result",
width="medium",
display_text="Visit Article",
),
"Snippet": st.column_config.TextColumn(
"Snippet",
help="Summary of the search result",
width="large",
),
"Relevance Score": st.column_config.NumberColumn(
"Relevance Score",
help="Relevance score of the search result",
format="%.2f",
width="small",
),
"Published Date": st.column_config.DateColumn(
"Published Date",
help="Publication date of the search result",
width="medium",
),
},
hide_index=True,
)
# Add popover for snippets
st.markdown("""
<style>
.snippet-popover {
position: relative;
display: inline-block;
}
.snippet-popover .snippet-content {
visibility: hidden;
width: 300px;
background-color: #f9f9f9;
color: #333;
text-align: left;
border-radius: 6px;
padding: 10px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -150px;
opacity: 0;
transition: opacity 0.3s;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.snippet-popover:hover .snippet-content {
visibility: visible;
opacity: 1;
}
</style>
""", unsafe_allow_html=True)
# Display snippets with popover
st.subheader("📝 Snippets")
for i, result in enumerate(results):
snippet = result.get('summary', '')
if snippet:
st.markdown(f"""
<div class="snippet-popover">
<strong>{result.get('title', '')}</strong>
<div class="snippet-content">
{snippet}
</div>
</div>
""", unsafe_allow_html=True)
else:
st.info("No detailed results available.")
# Add a collapsible section for the raw JSON data
with st.expander("Research Results (JSON)", expanded=False):
st.json(metaphor_response)
def metaphor_news_summarizer(news_keywords):
@@ -240,3 +516,56 @@ def metaphor_scholar_search(query, include_domains=None, time_range="anytime"):
return response
except Exception as e:
logger.error(f"Error in searching papers: {e}")
def get_exa_answer(query: str, system_prompt: str = None) -> dict:
"""
Get an AI-generated answer for a query using Exa's answer endpoint.
Args:
query (str): The search query to get an answer for
system_prompt (str, optional): Custom system prompt for the LLM. If None, uses default prompt.
Returns:
dict: Response containing answer, citations, and cost information
{
"answer": str,
"citations": list[dict],
"costDollars": dict
}
"""
exa = get_metaphor_client()
try:
# Use default system prompt if none provided
if system_prompt is None:
system_prompt = (
"I am doing research to write factual content. "
"Help me find answers for content generation task. "
"Provide detailed, well-structured answers with clear citations."
)
logger.info(f"Getting Exa answer for query: {query}")
logger.debug(f"Using system prompt: {system_prompt}")
# Make API call to get answer with system_prompt parameter
result = exa.answer(
query,
model="exa",
text=True # Include full text in citations
)
if not result or not result.get('answer'):
logger.warning("No answer received from Exa")
return None
# Format response to match expected structure
response = {
"answer": result.get('answer'),
"citations": result.get('citations', []),
"costDollars": result.get('costDollars', {"total": 0})
}
return response
except Exception as e:
logger.error(f"Error getting Exa answer: {e}")
return None

View File

@@ -49,17 +49,9 @@ from tenacity import retry, stop_after_attempt, wait_random_exponential
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
def get_tavilyai_results(keywords, max_results=5):
def get_tavilyai_results(keywords, max_results=5, include_domains=None, search_depth="advanced", **kwargs):
"""
Get Tavily AI search results based on specified keywords and options.
Args:
keywords (str): Keywords for Tavily AI search.
include_urls (str): Comma-separated URLs to include in the search.
search_depth (str, optional): Search depth option (default is "advanced").
Returns:
dict: Tavily AI search results.
"""
# Run Tavily search
logger.info(f"Running Tavily search on: {keywords}")
@@ -74,56 +66,100 @@ def get_tavilyai_results(keywords, max_results=5):
client = TavilyClient(api_key=api_key)
except Exception as err:
logger.error(f"Failed to create Tavily client. Check TAVILY_API_KEY: {err}")
# Read search config params from the file.
try:
include_urls = cfg_search_param('tavily')
except Exception as err:
logger.error(f"Failed to read search params from main_config: {err}")
raise
try:
if include_urls:
tavily_search_result = client.search(keywords,
search_depth="advanced",
include_answer=True,
max_results=max_results,
include_domains=include_urls)
else:
tavily_search_result = client.search(keywords,
search_depth = "advanced",
include_answer=True,
max_results=max_results)
# Create search parameters exactly matching Tavily's API format
tavily_search_result = client.search(
query=keywords,
search_depth="advanced",
time_range="year",
include_answer="advanced",
include_domains=[""] if not include_domains else include_domains,
max_results=max_results
)
if tavily_search_result:
print_result_table(tavily_search_result)
streamlit_display_results(tavily_search_result)
return tavily_search_result
return None
print_result_table(tavily_search_result)
streamlit_display_results(tavily_search_result)
return(tavily_search_result)
except Exception as err:
logger.error(f"Failed to do Tavily Research: {err}")
raise
def streamlit_display_results(output_data):
"""Display Tavily AI search results in Streamlit UI."""
"""Display Tavily AI search results in Streamlit UI with enhanced visualization."""
# Prepare data for display
table_data = []
# Display the 'answer' in Streamlit with enhanced styling
answer = output_data.get("answer", "No answer available")
st.markdown("### 🤖 AI-Generated Answer")
st.markdown(f"""
<div style="background-color: #f0f2f6; padding: 20px; border-radius: 10px; border-left: 5px solid #4CAF50;">
{answer}
</div>
""", unsafe_allow_html=True)
# Display follow-up questions if available
follow_up_questions = output_data.get("follow_up_questions", [])
if follow_up_questions:
st.markdown("### ❓ Follow-up Questions")
for i, question in enumerate(follow_up_questions, 1):
st.markdown(f"**{i}.** {question}")
# Prepare data for display with dataeditor
st.markdown("### 📊 Search Results")
# Create a DataFrame for the results
import pandas as pd
results_data = []
for item in output_data.get("results", []):
title = item.get("title", "")
snippet = item.get("content", "")
link = item.get("url", "")
table_data.append([title, snippet, link])
results_data.append({
"Title": title,
"Content": snippet,
"Link": link
})
if results_data:
df = pd.DataFrame(results_data)
# Display the data editor
st.data_editor(
df,
column_config={
"Title": st.column_config.TextColumn(
"Title",
help="Article title",
width="medium",
),
"Content": st.column_config.TextColumn(
"Content",
help="Click the button below to view full content",
width="large",
),
"Link": st.column_config.LinkColumn(
"Link",
help="Click to visit the website",
width="small",
display_text="Visit Site"
),
},
hide_index=True,
use_container_width=True,
)
# Display the table in Streamlit
st.table(table_data)
# Display the 'answer' in Streamlit
answer = output_data.get("answer", "No answer available")
st.write(f"**The answer to your search query:** {answer}")
# Display follow-up questions if available
follow_up_questions = output_data.get("follow_up_questions", [])
if follow_up_questions:
st.write(f"**Follow-up questions for the query:** {output_data.get('query')}")
st.write(", ".join(follow_up_questions))
# Add popovers for full content display
for item in output_data.get("results", []):
with st.popover(f"View content: {item.get('title', '')[:50]}..."):
st.markdown(item.get("content", ""))
else:
st.info("No results found for your search query.")
def print_result_table(output_data):

View File

@@ -0,0 +1,277 @@
import streamlit as st
import logging
from datetime import datetime
from typing import Dict, Optional, Any
# Configure module logger
logger = logging.getLogger(__name__)
def display_research_results(results: Dict[str, Any]) -> None:
"""
Display research results in a structured format with tabs.
Args:
results (dict): Processed research results containing summary and data
"""
if not results:
st.warning("No results to display")
return
# Create tabs for different result sections
tabs = st.tabs(["📊 Summary", "🔍 Results", "📈 Statistics"])
with tabs[0]:
display_summary_section(results)
with tabs[1]:
if results['source'] == 'gemini':
display_gemini_results(results)
else:
display_serp_results(results)
with tabs[2]:
display_statistics(results)
def process_research_results(results: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Process and format research results."""
logger.info("Processing research results")
try:
if not results:
return None
processed = {
'timestamp': str(datetime.now()),
'source': results.get('source', 'unknown'),
'summary': {},
'data': {}
}
if results.get('source') == 'gemini':
processed.update(process_gemini_results(results))
else:
processed.update(process_serp_results(results))
logger.info("Results processing completed")
return processed
except Exception as err:
logger.error(f"Failed to process results: {err}", exc_info=True)
return None
def process_search_results(search_results: Dict[str, Any], search_type: str = "general") -> Optional[Dict[str, Any]]:
"""Process search results and prepare for display."""
logger.info(f"Processing {search_type} search results")
try:
if not search_results:
return None
processed = {
'organic': process_organic_results(search_results.get('organic', [])),
'peopleAlsoAsk': process_paa_results(search_results.get('peopleAlsoAsk', [])),
'relatedSearches': process_related_searches(search_results.get('relatedSearches', [])),
'metadata': {
'timestamp': str(datetime.now()),
'type': search_type
}
}
return processed
except Exception as err:
logger.error(f"Error processing search results: {err}", exc_info=True)
return None
# Helper functions for result processing
def process_organic_results(results):
"""Process organic search results."""
return [{
'title': result.get('title', 'No Title'),
'link': result.get('link', '#'),
'snippet': result.get('snippet', 'No snippet available'),
'position': result.get('position', 'N/A')
} for result in results]
def process_paa_results(results):
"""Process People Also Ask results."""
return [{
'question': result.get('title', ''),
'answer': result.get('snippet', 'No answer available'),
'link': result.get('link', '#')
} for result in results]
def process_related_searches(results):
"""Process related searches."""
return [query.get('query', '') for query in results]
def process_gemini_results(results: Dict[str, Any]) -> Dict[str, Any]:
"""
Process Gemini API research results.
Args:
results (dict): Raw Gemini research results
Returns:
dict: Processed results with summary and data
"""
gemini_data = results.get('results', {})
return {
'summary': {
'main_findings': gemini_data.get('main_response', ''),
'sources': gemini_data.get('grounding_data', []),
'processing_time': gemini_data.get('metadata', {}).get('timestamp'),
'total_sources': len(gemini_data.get('grounding_data', [])),
'model': gemini_data.get('metadata', {}).get('model', 'unknown')
},
'data': gemini_data
}
def process_serp_results(results: Dict[str, Any]) -> Dict[str, Any]:
"""
Process SERP search results.
Args:
results (dict): Raw SERP results
Returns:
dict: Processed results with summary and data
"""
organic_results = results.get('organic', [])
paa_results = results.get('peopleAlsoAsk', [])
related_searches = results.get('relatedSearches', [])
return {
'summary': {
'total_results': len(organic_results),
'sources': [result.get('link') for result in organic_results],
'titles': [result.get('title') for result in organic_results],
'total_questions': len(paa_results),
'total_related': len(related_searches)
},
'data': {
'organic': process_organic_results(organic_results),
'peopleAlsoAsk': process_paa_results(paa_results),
'relatedSearches': process_related_searches(related_searches)
}
}
# Display helper functions
def display_summary_section(results):
"""Display summary section of results."""
st.markdown("### 📋 Research Summary")
st.markdown(f"""
- **Source**: {results['source'].title()}
- **Time**: {results['timestamp']}
- **Total Sources**: {len(results.get('summary', {}).get('sources', []))}
""")
def display_gemini_results(results):
"""Display Gemini-specific results."""
st.markdown("### 🤖 Gemini Research Findings")
st.write(results['summary']['main_findings'])
with st.expander("🌐 Sources and References", expanded=False):
st.write(results['data'].get('grounding_data', 'No sources available'))
def display_serp_results(results):
"""Display SERP-specific results."""
st.markdown("### 🔍 Search Results")
for result in results['data'].get('organic', []):
with st.expander(f"📄 {result['title']}", expanded=False):
st.markdown(f"""
**Rank:** {result['position']}
**Link:** [{result['link']}]({result['link']})
**Snippet:**
{result['snippet']}
""")
def display_statistics(results: Dict[str, Any]) -> None:
"""
Display statistical information about search results.
Args:
results (dict): Processed research results
"""
st.markdown("### 📈 Research Statistics")
# Source-specific metrics
if results['source'] == 'gemini':
col1, col2 = st.columns(2)
with col1:
st.metric(
"Sources Analyzed",
results.get('summary', {}).get('total_sources', 0)
)
with col2:
st.metric(
"Model Used",
results.get('summary', {}).get('model', 'Unknown')
)
else: # SERP results
col1, col2, col3 = st.columns(3)
with col1:
st.metric(
"Organic Results",
results.get('summary', {}).get('total_results', 0)
)
with col2:
st.metric(
"Related Questions",
results.get('summary', {}).get('total_questions', 0)
)
with col3:
st.metric(
"Related Searches",
results.get('summary', {}).get('total_related', 0)
)
# Common metrics
st.markdown("#### 🕒 Timing Information")
st.info(f"Research completed at: {results['timestamp']}")
# Display data quality metrics
st.markdown("#### 📊 Data Quality")
quality_metrics = calculate_quality_metrics(results)
col1, col2 = st.columns(2)
with col1:
st.progress(quality_metrics['completeness'])
st.caption("Data Completeness")
with col2:
st.progress(quality_metrics['relevance'])
st.caption("Estimated Relevance")
def calculate_quality_metrics(results: Dict[str, Any]) -> Dict[str, float]:
"""
Calculate quality metrics for the research results.
Args:
results (dict): Processed research results
Returns:
dict: Quality metrics including completeness and relevance scores
"""
try:
if results['source'] == 'gemini':
completeness = 1.0 if results['summary']['main_findings'] else 0.0
relevance = 0.8 if results['summary']['sources'] else 0.4
else:
organic_results = results.get('summary', {}).get('total_results', 0)
completeness = min(organic_results / 10, 1.0) # Normalize to 0-1
has_paa = bool(results.get('summary', {}).get('total_questions', 0))
has_related = bool(results.get('summary', {}).get('total_related', 0))
relevance = (0.6 + (0.2 if has_paa else 0) + (0.2 if has_related else 0))
return {
'completeness': completeness,
'relevance': relevance
}
except Exception as err:
logger.error(f"Error calculating quality metrics: {err}")
return {'completeness': 0.0, 'relevance': 0.0}

View File

@@ -0,0 +1,458 @@
"""
Module for displaying Google Trends data in the Streamlit UI.
This module provides functions for visualizing Google Trends data, including:
- Interest over time
- Regional interest
- Related queries
- Related topics
"""
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import logging
# Set up logging
logger = logging.getLogger(__name__)
def display_google_trends_data(trends_data, search_keyword):
"""
Display Google Trends data in a structured format with tabs for different sections.
Args:
trends_data (dict): Dictionary containing Google Trends data
search_keyword (str): The search keyword used for the analysis
"""
if not trends_data:
st.warning("No Google Trends data available for this search.")
return
st.subheader(f"Google Trends Analysis for '{search_keyword}'")
# Add an informative message about Google Trends
with st.expander(" About Google Trends Data", expanded=False):
st.markdown("""
**What is Google Trends?**
Google Trends is a public web facility that shows how often a particular search-term is entered relative to the total search-volume across various regions of the world, and in various languages.
**What data is shown here?**
- **Related Keywords**: Terms that are frequently searched together with your keyword
- **Interest Over Time**: How interest in your keyword has changed over the past 12 months
- **Regional Interest**: Where in the world your keyword is most popular
- **Related Queries**: What people search for before and after searching for your keyword
- **Related Topics**: Topics that are closely related to your keyword
**How to interpret the data:**
- Interest values range from 0 to 100, where 100 is the peak popularity for the term
- A value of 50 means the term is half as popular as the peak
- A value of 0 means there was not enough data for this term
""")
# Create tabs for different sections
tab1, tab2, tab3, tab4, tab5 = st.tabs([
"Related Keywords",
"Interest Over Time",
"Regional Interest",
"Related Queries",
"Related Topics"
])
with tab1:
display_keywords_section(trends_data.get('related_keywords', []))
with tab2:
display_interest_over_time(trends_data.get('interest_over_time', pd.DataFrame()))
with tab3:
display_regional_interest(trends_data.get('regional_interest', pd.DataFrame()))
with tab4:
display_related_queries(trends_data.get('related_queries', pd.DataFrame()))
with tab5:
display_related_topics(trends_data.get('related_topics', pd.DataFrame()))
# Add a footer with data source information
st.markdown("---")
st.caption("Data source: Google Trends | Last updated: " + pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S"))
def display_keywords_section(keywords):
"""Display related keywords from Google Trends in a table format."""
if not keywords:
st.info("No related keywords data available.")
return
st.subheader("Related Keywords")
st.write("Keywords related to your search:")
# Add explanation about related keywords
with st.expander(" About Related Keywords", expanded=False):
st.markdown("""
**What are Related Keywords?**
Related keywords are terms that are frequently searched together with your main keyword.
These keywords can help you understand what topics are associated with your search term
and can be valuable for content planning and SEO strategies.
**How to use this data:**
- Use these keywords to expand your content strategy
- Identify gaps in your content that you could fill
- Understand what your audience is interested in
- Improve your SEO by incorporating these terms naturally in your content
""")
# Create a DataFrame for better display
df = pd.DataFrame(keywords, columns=['Keyword'])
st.dataframe(df, use_container_width=True)
# Add a note about the number of keywords
st.caption(f"Found {len(keywords)} related keywords")
def display_interest_over_time(interest_df):
"""Display a chart showing interest over time for a given search keyword."""
if interest_df.empty:
st.info("No interest over time data available.")
return
st.subheader("Interest Over Time")
# Add explanation about interest over time
with st.expander(" About Interest Over Time", expanded=False):
st.markdown("""
**What is Interest Over Time?**
Interest Over Time shows how interest in your search term has changed over the past 12 months.
The data is normalized and presented on a scale from 0 to 100, where 100 is the peak popularity
for the term, 50 means the term is half as popular, and 0 means there was not enough data.
**How to interpret this chart:**
- Look for peaks and valleys to identify trends
- Compare with seasonal patterns or events
- Identify if interest is growing, declining, or stable
- Use this data to time your content releases for maximum impact
""")
try:
# Ensure we have the required columns
if 'date' not in interest_df.columns:
st.error("Interest over time data is missing the 'date' column.")
return
if 'interest' not in interest_df.columns:
st.error("Interest over time data is missing the 'interest' column.")
return
# Create the chart
fig = px.line(
interest_df,
x='date',
y='interest',
title='Interest Over Time',
labels={'date': 'Date', 'interest': 'Interest'},
line_shape='spline'
)
fig.update_layout(
xaxis_title="Date",
yaxis_title="Interest",
hovermode='x unified'
)
st.plotly_chart(fig, use_container_width=True)
# Add summary statistics
if not interest_df.empty:
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Average Interest", f"{interest_df['interest'].mean():.1f}")
with col2:
st.metric("Peak Interest", f"{interest_df['interest'].max():.1f}")
with col3:
st.metric("Lowest Interest", f"{interest_df['interest'].min():.1f}")
except Exception as e:
st.error(f"Error displaying interest over time chart: {str(e)}")
logger.error(f"Error in display_interest_over_time: {e}")
def display_regional_interest(regional_df):
"""Display a chart showing interest by region for the search keyword."""
if regional_df.empty:
st.info("No regional interest data available.")
return
st.subheader("Regional Interest")
# Add explanation about regional interest
with st.expander(" About Regional Interest", expanded=False):
st.markdown("""
**What is Regional Interest?**
Regional Interest shows how interest in your search term varies across different countries.
The data is normalized and presented on a scale from 0 to 100, where 100 is the peak popularity
for the term in that region, 50 means the term is half as popular, and 0 means there was not enough data.
**How to interpret this map:**
- Darker colors indicate higher interest in that region
- Lighter colors indicate lower interest
- Hover over a country to see the exact interest value
- Use this data to target your content to specific regions
""")
try:
# Ensure we have the required columns
if 'country_code' not in regional_df.columns:
st.error("Regional interest data is missing the 'country_code' column.")
return
if 'interest' not in regional_df.columns:
st.error("Regional interest data is missing the 'interest' column.")
return
# Create the choropleth map
fig = go.Figure(data=go.Choropleth(
locations=regional_df['country_code'],
z=regional_df['interest'],
text=regional_df['country_code'], # This will show in the hover text
colorscale='Viridis',
colorbar_title="Interest Level",
zmin=0,
zmax=100,
marker_line_color='darkgray',
marker_line_width=0.5,
showscale=True,
colorbar=dict(
title="Interest Level",
tickformat=".0f",
tickmode="linear",
tick0=0,
dtick=20
)
))
# Update the layout for better visualization
fig.update_layout(
title=dict(
text='Regional Interest Distribution',
x=0.5,
xanchor='center'
),
geo=dict(
showframe=False,
showcoastlines=True,
projection_type='equirectangular',
showland=True,
landcolor='lightgray',
showocean=True,
oceancolor='aliceblue',
showcountries=True,
countrycolor='darkgray'
),
width=800,
height=500,
margin=dict(l=0, r=0, t=30, b=0)
)
# Display the map
st.plotly_chart(fig, use_container_width=True)
# Display top 5 countries with highest interest
if not regional_df.empty:
st.subheader("Top Regions by Interest")
top_regions = regional_df.sort_values('interest', ascending=False).head(5)
# Create a more visually appealing bar chart for top regions
fig_bar = go.Figure(data=[
go.Bar(
x=top_regions['country_code'],
y=top_regions['interest'],
text=top_regions['interest'].round(1),
textposition='auto',
marker_color='rgb(55, 83, 109)'
)
])
fig_bar.update_layout(
title='Top 5 Regions by Interest Level',
xaxis_title='Region',
yaxis_title='Interest Level',
yaxis_range=[0, 100],
showlegend=False
)
st.plotly_chart(fig_bar, use_container_width=True)
except Exception as e:
st.error(f"Error displaying regional interest chart: {str(e)}")
logger.error(f"Error in display_regional_interest: {e}")
def display_related_queries(queries_df):
"""Display related queries in a structured format."""
if queries_df.empty:
st.info("No related queries data available.")
return
st.subheader("Related Queries")
# Add explanation about related queries
with st.expander(" About Related Queries", expanded=False):
st.markdown("""
**What are Related Queries?**
Related Queries show what people search for before and after searching for your keyword.
These queries can help you understand the search intent and context around your keyword.
**How to interpret this data:**
- The 'value' column shows the relative interest compared to your main keyword
- Higher values indicate stronger association with your keyword
- Use these queries to expand your content strategy
- Identify what questions your audience is trying to answer
""")
try:
# Ensure we have the required columns
if 'query' not in queries_df.columns:
st.error("Related queries data is missing the 'query' column.")
return
if 'value' not in queries_df.columns:
st.error("Related queries data is missing the 'value' column.")
return
# Sort by value in descending order
queries_df = queries_df.sort_values('value', ascending=False)
# Display as a table
st.dataframe(queries_df, use_container_width=True)
# Add a note about the number of queries
st.caption(f"Found {len(queries_df)} related queries")
except Exception as e:
st.error(f"Error displaying related queries: {str(e)}")
logger.error(f"Error in display_related_queries: {e}")
def display_trending_searches(trending_df):
"""Display trending searches in the UI."""
if trending_df.empty:
st.info("No trending searches data available.")
return
st.subheader("📊 Trending Searches")
# Display as numbered list with emojis
for idx, search in enumerate(trending_df[0].head(10), 1):
st.write(f"{idx}. 🔍 {search}")
def display_realtime_trends(trends_df):
"""Display realtime trending searches in the UI."""
if trends_df.empty:
st.info("No realtime trends data available.")
return
st.subheader("⚡ Realtime Trends")
# Create tabs for different categories
if not trends_df.empty:
# Display top 5 trends with their titles and articles
for _, row in trends_df.head(5).iterrows():
with st.expander(f"🔥 {row.get('title', 'Trending Topic')}"):
st.write(f"**Traffic:** {row.get('traffic', 'N/A')}")
if 'articles' in row:
st.write("📰 Related Articles:")
for article in row['articles'][:3]: # Show top 3 articles
st.write(f"- {article['title']}")
def display_related_topics(topics_df):
"""Display related topics in a structured format."""
if topics_df.empty:
st.info("No related topics data available.")
return
st.subheader("Related Topics")
# Add explanation about related topics
with st.expander(" About Related Topics", expanded=False):
st.markdown("""
**What are Related Topics?**
Related Topics show broader topics that are associated with your search term.
These topics can help you understand the broader context and themes related to your keyword.
**How to interpret this data:**
- The 'value' column shows the relative interest compared to your main keyword
- Higher values indicate stronger association with your keyword
- Use these topics to understand the broader context of your keyword
- Identify themes that might be relevant to your content strategy
""")
try:
# Ensure we have the required columns
if 'topic' not in topics_df.columns:
st.error("Related topics data is missing the 'topic' column.")
return
if 'value' not in topics_df.columns:
st.error("Related topics data is missing the 'value' column.")
return
# Sort by value in descending order
topics_df = topics_df.sort_values('value', ascending=False)
# Display as a table
st.dataframe(topics_df, use_container_width=True)
# Add a note about the number of topics
st.caption(f"Found {len(topics_df)} related topics")
except Exception as e:
st.error(f"Error displaying related topics: {str(e)}")
logger.error(f"Error in display_related_topics: {e}")
def process_trends_data(trends_data):
"""
Process and format Google Trends data for display.
Args:
trends_data (dict): Raw Google Trends data
Returns:
dict: Formatted data ready for display
"""
if not trends_data:
return {}
processed_data = {}
# Process related keywords
if 'related_keywords' in trends_data:
processed_data['related_keywords'] = trends_data['related_keywords']
# Process interest over time
if 'interest_over_time' in trends_data and not trends_data['interest_over_time'].empty:
processed_data['interest_over_time'] = trends_data['interest_over_time']
# Process regional interest
if 'regional_interest' in trends_data and not trends_data['regional_interest'].empty:
processed_data['regional_interest'] = trends_data['regional_interest']
# Process related queries
if 'related_queries' in trends_data and not trends_data['related_queries'].empty:
processed_data['related_queries'] = trends_data['related_queries']
# Process related topics
if 'related_topics' in trends_data and not trends_data['related_topics'].empty:
processed_data['related_topics'] = trends_data['related_topics']
return processed_data

View File

@@ -0,0 +1,529 @@
import os
import time
import logging
import streamlit as st
from datetime import datetime
from lib.ai_web_researcher.gpt_online_researcher import gpt_web_researcher
from lib.utils.read_main_config_params import read_return_config_section
# Configure module-level logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# Create console handler if it doesn't exist
if not logger.handlers:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
def reload_env_variables():
"""Reload environment variables from .env file."""
try:
from dotenv import load_dotenv
load_dotenv(override=True)
return True
except Exception as e:
logger.error(f"Failed to reload environment variables: {str(e)}")
return False
def save_api_key_to_env(key_name, key_value):
"""Save API key to .env file."""
try:
env_path = os.path.join(os.getcwd(), '.env')
# Read existing .env content
existing_content = {}
if os.path.exists(env_path):
with open(env_path, 'r') as f:
for line in f:
if '=' in line:
key, value = line.strip().split('=', 1)
existing_content[key] = value
# Update or add new key
existing_content[key_name] = key_value
# Write back to .env
with open(env_path, 'w') as f:
for key, value in existing_content.items():
f.write(f"{key}={value}\n")
# Update environment variable and reload all env vars
os.environ[key_name] = key_value
if reload_env_variables():
return True
return False
except Exception as e:
logger.error(f"Failed to save API key to .env: {str(e)}")
return False
def validate_api_keys():
"""Validate required API keys and return their status."""
logger.info("Validating API keys")
# Get API keys
api_keys = {
'SERPER_API_KEY': os.getenv('SERPER_API_KEY'),
'METAPHOR_API_KEY': os.getenv('METAPHOR_API_KEY'),
'TAVILY_API_KEY': os.getenv('TAVILY_API_KEY'),
'FIRECRAWL_API_KEY': os.getenv('FIRECRAWL_API_KEY')
}
# Test SERPER_API_KEY validity
if api_keys['SERPER_API_KEY']:
try:
# Make a test request
import requests
test_url = "https://google.serper.dev/search"
headers = {
'X-API-KEY': api_keys['SERPER_API_KEY'],
'Content-Type': 'application/json'
}
test_payload = {"q": "test", "gl": "us", "hl": "en", "num": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
api_keys['SERPER_API_KEY_VALID'] = response.status_code == 200
if not api_keys['SERPER_API_KEY_VALID']:
logger.error(f"SERPER_API_KEY validation failed: {response.status_code} - {response.text}")
except Exception as e:
logger.error(f"Error validating SERPER_API_KEY: {str(e)}")
api_keys['SERPER_API_KEY_VALID'] = False
else:
api_keys['SERPER_API_KEY_VALID'] = False
return api_keys
def do_web_research():
"""Main function to perform web research based on user input."""
# Reset session state variables for this research operation
if 'metaphor_results_displayed' in st.session_state:
del st.session_state.metaphor_results_displayed
logger.info("Starting do_web_research function")
try:
# Get API keys without validation
api_keys = {
'SERPER_API_KEY': os.getenv('SERPER_API_KEY'),
'METAPHOR_API_KEY': os.getenv('METAPHOR_API_KEY'),
'TAVILY_API_KEY': os.getenv('TAVILY_API_KEY'),
'FIRECRAWL_API_KEY': os.getenv('FIRECRAWL_API_KEY')
}
if not api_keys['SERPER_API_KEY']:
st.error("""
🚫 SERPER_API_KEY is missing. Please configure your API key.
""")
with st.popover("⚙️ Configure API Keys"):
st.markdown("""
### API Key Configuration
Enter your API keys below to enable research features.
""")
# SERPER API Key
serper_col1, serper_col2 = st.columns([3, 1])
with serper_col1:
serper_key = st.text_input(
"Serper API Key",
type="password",
placeholder="Enter your Serper API key",
help="Get your key at https://serper.dev"
)
test_key = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving")
with serper_col2:
if st.button("Save Serper", use_container_width=True):
if serper_key:
if test_key:
# Test the API key
try:
import requests
test_url = "https://google.serper.dev/search"
headers = {
'X-API-KEY': serper_key,
'Content-Type': 'application/json'
}
test_payload = {"q": "test", "gl": "us", "hl": "en", "num": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
if response.status_code == 200:
if save_api_key_to_env('SERPER_API_KEY', serper_key):
st.success("✅ Serper API key validated and saved!")
st.rerun()
else:
st.error("Failed to save API key")
else:
st.error(f"API key validation failed: {response.status_code} - {response.text}")
except Exception as e:
st.error(f"Error validating API key: {str(e)}")
else:
# Skip validation and save directly
if save_api_key_to_env('SERPER_API_KEY', serper_key):
st.success("✅ Serper API key saved!")
time.sleep(0.5) # Small delay to ensure the key is saved
st.rerun()
else:
st.error("Failed to save API key")
# METAPHOR API Key
if not api_keys.get('METAPHOR_API_KEY'):
metaphor_col1, metaphor_col2 = st.columns([3, 1])
with metaphor_col1:
metaphor_key = st.text_input(
"Metaphor API Key",
type="password",
placeholder="Enter your Metaphor API key",
help="Get your key at https://metaphor.systems"
)
test_metaphor = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving")
with metaphor_col2:
if st.button("Save Metaphor", use_container_width=True):
if metaphor_key:
if test_metaphor:
# Test the API key
try:
import requests
test_url = "https://api.metaphor.systems/v1/search"
headers = {
'Authorization': f'Bearer {metaphor_key}',
'Content-Type': 'application/json'
}
test_payload = {"query": "test", "numResults": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
if response.status_code == 200:
if save_api_key_to_env('METAPHOR_API_KEY', metaphor_key):
st.success("✅ Metaphor API key validated and saved!")
st.rerun()
else:
st.error("Failed to save API key")
else:
st.error(f"API key validation failed: {response.status_code} - {response.text}")
except Exception as e:
st.error(f"Error validating API key: {str(e)}")
else:
# Skip validation and save directly
if save_api_key_to_env('METAPHOR_API_KEY', metaphor_key):
st.success("✅ Metaphor API key saved!")
st.rerun()
else:
st.error("Failed to save API key")
# TAVILY API Key
if not api_keys.get('TAVILY_API_KEY'):
tavily_col1, tavily_col2 = st.columns([3, 1])
with tavily_col1:
tavily_key = st.text_input(
"Tavily API Key",
type="password",
placeholder="Enter your Tavily API key",
help="Get your key at https://tavily.com"
)
test_tavily = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving")
with tavily_col2:
if st.button("Save Tavily", use_container_width=True):
if tavily_key:
if test_tavily:
# Test the API key
try:
import requests
test_url = "https://api.tavily.com/v1/search"
headers = {
'Authorization': f'Bearer {tavily_key}',
'Content-Type': 'application/json'
}
test_payload = {"query": "test", "max_results": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
if response.status_code == 200:
if save_api_key_to_env('TAVILY_API_KEY', tavily_key):
st.success("✅ Tavily API key validated and saved!")
st.rerun()
else:
st.error("Failed to save API key")
else:
st.error(f"API key validation failed: {response.status_code} - {response.text}")
except Exception as e:
st.error(f"Error validating API key: {str(e)}")
else:
# Skip validation and save directly
if save_api_key_to_env('TAVILY_API_KEY', tavily_key):
st.success("✅ Tavily API key saved!")
st.rerun()
else:
st.error("Failed to save API key")
# FIRECRAWL API Key
if not api_keys.get('FIRECRAWL_API_KEY'):
firecrawl_col1, firecrawl_col2 = st.columns([3, 1])
with firecrawl_col1:
firecrawl_key = st.text_input(
"Firecrawl API Key",
type="password",
placeholder="Enter your Firecrawl API key",
help="Get your key at https://firecrawl.co"
)
test_firecrawl = st.checkbox("Test API key before saving", value=False, help="Validate the API key before saving")
with firecrawl_col2:
if st.button("Save Firecrawl", use_container_width=True):
if firecrawl_key:
if test_firecrawl:
# Test the API key
try:
import requests
test_url = "https://api.firecrawl.co/v1/search"
headers = {
'Authorization': f'Bearer {firecrawl_key}',
'Content-Type': 'application/json'
}
test_payload = {"query": "test", "limit": 1}
response = requests.post(test_url, headers=headers, json=test_payload)
if response.status_code == 200:
if save_api_key_to_env('FIRECRAWL_API_KEY', firecrawl_key):
st.success("✅ Firecrawl API key validated and saved!")
st.rerun()
else:
st.error("Failed to save API key")
else:
st.error(f"API key validation failed: {response.status_code} - {response.text}")
except Exception as e:
st.error(f"Error validating API key: {str(e)}")
else:
# Skip validation and save directly
if save_api_key_to_env('FIRECRAWL_API_KEY', firecrawl_key):
st.success("✅ Firecrawl API key saved!")
st.rerun()
else:
st.error("Failed to save API key")
st.markdown("""
---
### Need Help?
1. Click the links above to get your API keys
2. Enter the keys in the fields above
3. Click Save to store them securely
4. The app will refresh automatically
""")
return
# Initialize session state for research options
if "research_options" not in st.session_state:
st.session_state.research_options = {
"primary_keywords": "",
"related_keywords": "",
"target_audience": ["General"],
"content_type": ["Blog Posts"],
"search_depth": 3,
"geo_location": "us",
"search_language": "en",
"num_results": 10,
"time_range": "past month",
"include_domains": "",
"similar_url": "",
"search_mode": "google" # Default search mode
}
# Define the research options dialog function
@st.dialog("🔍 Research Options", width="large")
def show_research_options():
tab1, tab2, tab3 = st.tabs(["Basic", "Advanced", "Technical"])
with tab1:
st.session_state.research_options["related_keywords"] = st.text_input(
"Related Keywords",
value=st.session_state.research_options["related_keywords"],
placeholder="Enter related terms...",
help="Additional keywords to provide context and expand research"
)
st.session_state.research_options["target_audience"] = st.multiselect(
"Target Audience",
["General", "Technical", "Business", "Academic", "Youth", "Senior"],
default=st.session_state.research_options["target_audience"],
help="Select your target audience to focus research"
)
st.session_state.research_options["content_type"] = st.multiselect(
"Content Type",
["Blog Posts", "Articles", "Social Media", "Whitepapers", "Tutorials", "Videos"],
default=st.session_state.research_options["content_type"],
help="Select content types to tailor research results"
)
st.session_state.research_options["search_depth"] = st.slider(
"Search Depth",
min_value=1,
max_value=5,
value=st.session_state.research_options["search_depth"],
help="Higher depth means more comprehensive but slower research"
)
with tab2:
col1, col2 = st.columns(2)
with col1:
st.session_state.research_options["geo_location"] = st.selectbox(
"Geographic Location",
options=["us", "in", "uk", "fr", "de", "jp", "custom"],
index=["us", "in", "uk", "fr", "de", "jp"].index(st.session_state.research_options["geo_location"]),
help="Target specific geographic region for research"
)
st.session_state.research_options["num_results"] = st.number_input(
"Number of Results",
min_value=1,
max_value=100,
value=st.session_state.research_options["num_results"],
help="Number of results to analyze"
)
with col2:
st.session_state.research_options["search_language"] = st.selectbox(
"Search Language",
options=["en", "hi", "fr", "de", "es", "custom"],
index=["en", "hi", "fr", "de", "es"].index(st.session_state.research_options["search_language"]),
help="Primary language for search results"
)
st.session_state.research_options["time_range"] = st.selectbox(
"Time Range",
options=["past day", "past week", "past month", "past year", "anytime"],
index=["past day", "past week", "past month", "past year", "anytime"].index(st.session_state.research_options["time_range"]),
help="Time period for research results"
)
with tab3:
st.session_state.research_options["include_domains"] = st.text_input(
"Include Domains",
value=st.session_state.research_options["include_domains"],
placeholder="example.com, another.com",
help="Specific domains to include in research"
)
st.session_state.research_options["similar_url"] = st.text_input(
"Similar URL",
value=st.session_state.research_options["similar_url"],
placeholder="https://example.com/page",
help="Find content similar to this URL"
)
# Research method selection
st.markdown("### Select Research Method")
search_options = [
("google", "🔍 Google Search", "Traditional web research with AI analysis", bool(api_keys['SERPER_API_KEY'])),
("ai", "🤖 AI Search", "Neural search with semantic analysis", bool(api_keys['METAPHOR_API_KEY'] and api_keys['TAVILY_API_KEY'])),
("deep", "🔬 Deep Search (Beta)", "Advanced deep web analysis", bool(all(api_keys.values())))
]
enabled_options = [opt[1] for opt in search_options if opt[3]]
if enabled_options:
selected_option = st.radio(
"Search Method",
options=enabled_options,
horizontal=True,
help="Choose your preferred research method"
)
# Map the selected option to the search_mode value
for mode, label, _, _ in search_options:
if label == selected_option:
st.session_state.research_options["search_mode"] = mode
break
else:
st.warning("No search methods available. Please configure API keys.")
col1, col2 = st.columns([1, 1])
with col1:
if st.button("Apply", use_container_width=True, type="primary"):
st.session_state.show_options_dialog = False
st.rerun()
with col2:
if st.button("Cancel", use_container_width=True):
st.session_state.show_options_dialog = False
st.rerun()
# Main interface
st.title("ALwrity Web Researcher")
# Primary search area with help popover
with st.popover(" Keyword Research Tips"):
st.markdown("""
### How to Get Better Results
1. **Primary Keywords**: Your main topic or focus
2. **Related Keywords**: Supporting terms that add context
3. **Search Depth**: Higher depth = more comprehensive but slower
4. **Target Audience**: Affects content recommendations
5. **Content Type**: Influences research focus
6. **Search Mode**: Choose between traditional web research(Google), AI-powered search(Tavily and Metaphor) and Deep Researcher
""")
col1, col2 = st.columns([3, 1])
with col1:
st.session_state.research_options["primary_keywords"] = st.text_input(
"Primary Keywords",
value=st.session_state.research_options["primary_keywords"],
placeholder="Enter main keywords for research...",
help="Enter your main topic or focus keywords"
)
with col2:
if st.button("Research Options", use_container_width=True):
show_research_options()
# Execute search button
if st.button("🔍 Start Research", type="primary", use_container_width=True):
if not st.session_state.research_options["primary_keywords"]:
st.warning("⚠️ Please enter primary keywords for research")
return
try:
# Create compact progress display
progress_container = st.container()
with progress_container:
status_col, progress_col = st.columns([3, 1])
with status_col:
status_display = st.empty()
status_display.info("🚀 Initializing research...")
with progress_col:
progress_bar = st.progress(0)
# Execute search with all parameters
web_research_result = gpt_web_researcher(
search_keywords=st.session_state.research_options["primary_keywords"],
search_mode=st.session_state.research_options["search_mode"],
related_keywords=st.session_state.research_options["related_keywords"],
target_audience=st.session_state.research_options["target_audience"],
content_type=st.session_state.research_options["content_type"],
search_depth=st.session_state.research_options["search_depth"],
geo_location=st.session_state.research_options["geo_location"],
search_language=st.session_state.research_options["search_language"],
num_results=st.session_state.research_options["num_results"],
time_range=st.session_state.research_options["time_range"],
include_domains=st.session_state.research_options["include_domains"],
similar_url=st.session_state.research_options["similar_url"]
)
if web_research_result:
status_display.success("✨ Research completed!")
# Display results in an organized way
with st.expander("📊 Research Results", expanded=False):
st.write(web_research_result)
else:
st.warning("No results found for your search")
except Exception as e:
error_msg = f"Research failed: {str(e)}"
logger.error(error_msg, exc_info=True)
st.error(f"🚫 Research failed: {error_msg}")
except Exception as e:
logger.error(f"Unexpected error in web research: {e}", exc_info=True)
st.error("🚫 An unexpected error occurred. Please try again.")

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

@@ -457,26 +457,6 @@ def competitor_analysis():
st.error("Please enter a valid URL.")
def do_web_research():
""" Input keywords and do web research and present a report."""
st.title("Web Research Assistant")
st.write("Enter keywords for web research. The keywords should be at least three words long.")
search_keywords = st.text_input("Search Keywords", placeholder="Enter keywords for web research...")
if st.button("Start Web Research"):
if search_keywords and len(search_keywords.split()) >= 3:
try:
st.info(f"Starting web research on given keywords: {search_keywords}")
with st.spinner("Performing web research..."):
web_research_result = gpt_web_researcher(search_keywords)
st.success("Web research completed successfully!")
st.write(web_research_result)
except Exception as err:
st.error(f"ERROR: Failed to do web research: {err}")
else:
st.warning("Search keywords should be at least three words long. Please try again.")
def ai_finance_ta_writer():
st.markdown("<div class='sub-header'>AI Financial Technical Analysis Writer</div>", unsafe_allow_html=True)

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

View File

@@ -1,12 +1,12 @@
import streamlit as st
from lib.utils.alwrity_utils import (
blog_from_keyword, ai_agents_team, essay_writer, ai_news_writer,
ai_finance_ta_writer, ai_social_writer, do_web_research, competitor_analysis
ai_finance_ta_writer, competitor_analysis
)
from lib.alwrity_ui.keyword_web_researcher import do_web_research
from lib.ai_writers.ai_story_writer.story_writer import story_input_section
from lib.ai_writers.ai_product_description_writer import write_ai_prod_desc
#from lib.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_content_planner
from lib.utils.seo_tools import ai_seo_tools
def ai_writers():
@@ -39,30 +39,67 @@ def ai_writers():
def content_planning_tools():
st.markdown("""**Alwrity content Ideation & Planning** : Provide few keywords to do comprehensive web research.
Provide few keywords to get Google, Neural, pytrends analysis. Know keywords, blog titles to target.
Generate months long content calendar around given keywords.""")
# Add custom CSS for compact layout
st.markdown("""
<style>
/* Reduce top padding of main container */
.main .block-container {
padding-top: 0rem !important;
padding-bottom: 1rem !important;
}
/* Reduce spacing between elements */
.stTabs {
margin-top: 0.5rem !important;
}
/* Make markdown text more compact */
.element-container {
margin-bottom: 0.5rem !important;
}
/* Adjust subheader margins */
.stMarkdown h3 {
margin-top: 0 !important;
margin-bottom: 0.5rem !important;
}
</style>
""", unsafe_allow_html=True)
options = [
"Keywords Researcher",
"Competitor Analysis",
"Content Calender Ideator"
]
choice = st.radio("Select a content planning tool:", options, index=0, format_func=lambda x: f"🔍 {x}")
# Make description more compact using a smaller font
st.markdown("""
<div style='font-size: 0.9em; margin-bottom: 0.5rem;'>
<strong>Alwrity content Ideation & Planning</strong>: Provide few keywords to do comprehensive web research.
Provide few keywords to get Google, Neural, pytrends analysis. Know keywords, blog titles to target.
Generate months long content calendar around given keywords.
</div>
""", unsafe_allow_html=True)
if choice == "Keywords Researcher":
# Create tabs with reduced spacing
tab_keywords, tab_competitor, tab_calendar = st.tabs([
"🔍 Keywords Researcher",
"📊 Competitor Analysis",
"📅 Content Calendar Ideator"
])
# Keywords Researcher tab
with tab_keywords:
do_web_research()
elif choice == "Competitor Analysis":
# Competitor Analysis tab
with tab_competitor:
competitor_analysis()
elif choice == "Content Calender Ideator":
# Content Calendar Ideator tab
with tab_calendar:
plan_keywords = st.text_input(
"**Enter Your main Keywords to get 2 months content calendar:**",
placeholder="Enter 2-3 main keywords to generate AI content calendar with keyword researched blog titles",
help="The keywords are the ones where you would want to generate 50-60 blogs/articles on."
)
if st.button("**Ideate Content Calender**"):
if st.button("**Ideate Content Calendar**"):
if plan_keywords:
#ai_agents_content_planner(plan_keywords)
st.header("COming Soon.")
st.header("Coming Soon.")
else:
st.error("Come on, really, Enter some keywords to plan on..")

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

View File

@@ -267,79 +267,147 @@ def get_glassmorphic_styles() -> str:
</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:
"""Wrap content in a glass container."""
"""Returns HTML for a glass-morphism container."""
return f"""
<div class="glass-container">
<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:
"""Wrap content in an info section."""
"""Returns HTML for an info section."""
return f"""
<div class="info-section">
<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:
"""Wrap content in an example box."""
"""Returns HTML for an example box."""
return f"""
<div class="example-box">
<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:
"""Create an analysis section with title and content."""
"""Returns HTML for an analysis section."""
return f"""
<div class="analysis-section">
<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:
"""
Get the style guide HTML content.
Returns:
str: HTML content for the style guide section
"""
"""Returns HTML 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
<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:

View File

@@ -148,8 +148,8 @@ def render_test_config_settings():
# 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")
# 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")

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;
}

View File

@@ -1,50 +0,0 @@
"""Page for AI Research Setup redirection."""
import streamlit as st
from loguru import logger
import sys
import os
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/ai_research_setup_page.log",
rotation="500 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
backtrace=True,
diagnose=True
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Set page config
st.set_page_config(
layout="wide",
initial_sidebar_state="collapsed",
menu_items={
'Get Help': None,
'Report a bug': None,
'About': None
}
)
def render_ai_research_setup_page():
"""Render the AI Research Setup page."""
try:
logger.info("Starting AI Research Setup page")
# Import and render the AI Research Setup component
from lib.utils.api_key_manager.components.ai_research_setup import render_ai_research_setup
render_ai_research_setup()
except Exception as e:
logger.error(f"Error in render_ai_research_setup_page: {str(e)}")
st.error(f"An error occurred: {str(e)}")
if __name__ == "__main__":
render_ai_research_setup_page()

View File

@@ -1,84 +0,0 @@
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!")

View File

@@ -6,7 +6,7 @@ beautifulsoup4==4.12.2
aiohttp>=3.11.11
openai>=1.3.7
PyPDF2>=3.0.1
google-generativeai<0.9.0,>=0.8.0
google-genai>=1.0.0
anthropic>=0.18.1
tenacity>=8.2.3
tabulate>=0.9.0
@@ -31,7 +31,8 @@ prompt_toolkit>=3.0.43
html2image>=2.0.5
lxml[html_clean]>=5.3.0
lxml_html_clean>=0.4.1
streamlit>=1.29.0
streamlit>=1.44.0
Authlib>=1.3.2
yfinance>=0.2.36
pandas_ta>=0.3.14b0
firecrawl-py>=1.14.1