WIP - Streamlit UI, firecrawl - V0.5

This commit is contained in:
ajaysi
2024-06-13 17:43:57 +05:30
parent 8dfcb1f536
commit 128b6f3878
6 changed files with 196 additions and 40 deletions

View File

@@ -307,6 +307,7 @@ def main():
Welcome to Alwrity! Welcome to Alwrity!
</div> </div>
""", unsafe_allow_html=True) """, unsafe_allow_html=True)
# Export the paths and file names. Dont want alwrity to be chatty and prompt for inputs. # Export the paths and file names. Dont want alwrity to be chatty and prompt for inputs.
os.environ["SEARCH_SAVE_FILE"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_web_research", os.environ["SEARCH_SAVE_FILE"] = os.path.join(os.getcwd(), "lib", "workspace", "alwrity_web_research",
f"web_research_report_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}") f"web_research_report_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}")
@@ -375,7 +376,7 @@ def write_blog():
"AI Copywriter", "AI Copywriter",
"Quit" "Quit"
] ]
choice = st.selectbox("**Select a content creation type:**", options, index=0, format_func=lambda x: f"📝 {x}") choice = st.selectbox("**👇Select a content creation type:**", options, index=0, format_func=lambda x: f"📝 {x}")
if choice == "AI Blog Writer": if choice == "AI Blog Writer":
blog_from_keyword() blog_from_keyword()

View File

@@ -0,0 +1,112 @@
import sys
import os
from textwrap import dedent
from PIL import Image
import json
from pathlib import Path
from datetime import datetime
import streamlit as st
from dotenv import load_dotenv
load_dotenv(Path('../../.env'))
from loguru import logger
logger.remove()
logger.add(sys.stdout,
colorize=True,
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}"
)
from ..ai_web_researcher.firecrawl_web_crawler import scrape_url
from ..blog_metadata.get_blog_metadata import blog_metadata
from ..blog_postprocessing.save_blog_to_file import save_blog_to_file
from ..gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
from ..gpt_providers.text_generation.main_text_generation import llm_text_gen
import google.generativeai as genai
def blog_from_image(prompt, uploaded_img):
"""
This function will take a blog Topic to first generate sections for it
and then generate content for each section.
"""
# Use to store the blog in a string, to save in a *.md file.
blog_markdown_str = None
logger.info(f"Researching and Writing Blog on {uploaded_img} and {prompt}")
# FIXME: Implement support for Openai.
if not os.getenv("GEMINI_API_KEY"):
st.error("Only Gemini supported, Open Issue ticket on github for Openai, others.")
st.stop()
with st.status("Started Writing from Image..", expanded=True) as status:
st.empty()
status.update(label=f"Researching and Writing Blog on given Image")
try:
blog_markdown_str = write_blog_from_image(prompt, uploaded_img)
except Exception as err:
st.error(f"Failed to write blog from Image - Error: {err}")
logger.error(f"Failed to write blog from image: {err}")
st.stop()
status.update(label="Successfully wrote blog from image.", expanded=False, state="complete")
try:
status.update(label="🙎 Generating - Title, Meta Description, Tags, Categories for the content.")
blog_title, blog_meta_desc, blog_tags, blog_categories = blog_metadata(blog_markdown_str)
except Exception as err:
st.error(f"Failed to get blog metadata: {err}")
try:
status.update(label="🙎 Generating Image for the new blog.")
generated_image_filepath = generate_image(f"{blog_title} + ' ' + {blog_meta_desc}")
except Exception as err:
st.warning(f"Failed in Image generation: {err}")
saved_blog_to_file = save_blog_to_file(blog_markdown_str, blog_title, blog_meta_desc,
blog_tags, blog_categories, generated_image_filepath)
status.update(label=f"Saved the content in this file: {saved_blog_to_file}")
logger.info(f"\n\n --------- Finished writing Blog -------------- \n")
st.image(generated_image_filepath, caption=blog_title)
st.markdown(f"{blog_markdown_str}")
status.update(label=f"Finished, Review & Use your Original Content Below: {saved_blog_to_file}", state="complete")
# Clean up the temporary file after processing (optional)
os.remove(uploaded_img)
def write_blog_from_image(prompt, uploaded_img):
"""Combine the given online research and GPT blog content"""
try:
config_path = Path(os.environ["ALWRITY_CONFIG"])
with open(config_path, 'r', encoding='utf-8') as file:
config = json.load(file)
except Exception as err:
logger.error(f"Error: Failed to read values from config: {err}")
exit(1)
blog_characteristics = config['Blog Content Characteristics']
if not prompt:
prompt = f"""
As expert Creative Content writer, analyse the given image carefully.
I want you to write a detailed {blog_characteristics['Blog Type']} blog post including 5 FAQs.
Below are the guidelines to follow:
1). You must respond in {blog_characteristics['Blog Language']} language.
2). Tone and Brand Alignment: Adjust your tone, voice, personality for {blog_characteristics['Blog Tone']} audience.
3). Make sure your response content length is of {blog_characteristics['Blog Length']} words.
"""
logger.info("Generating blog and FAQs from Google web search results.")
try:
#response = llm_text_gen(prompt)
genai.configure(api_key=os.getenv('GEMINI_API_KEY'))
version = 'models/gemini-1.5-flash'
model = genai.GenerativeModel(version)
model_info = genai.get_model(version)
print(f'{version} - input limit: {model_info.input_token_limit}, output limit: {model_info.output_token_limit}')
response = model.generate_content([prompt, Image.open(uploaded_img)])
return response.text
except Exception as err:
logger.error(f"Exit: Failed to get response from LLM: {err}")
exit(1)

View File

@@ -71,19 +71,10 @@ def blog_from_url(weburl):
saved_blog_to_file = save_blog_to_file(blog_markdown_str, blog_title, blog_meta_desc, saved_blog_to_file = save_blog_to_file(blog_markdown_str, blog_title, blog_meta_desc,
blog_tags, blog_categories, generated_image_filepath) blog_tags, blog_categories, generated_image_filepath)
status.update(label=f"Saved the content in this file: {saved_blog_to_file}") status.update(label=f"Saved the content in this file: {saved_blog_to_file}")
blog_frontmatter = dedent(f"""
\n---------------------------------------------------------------------
title: {blog_title}\n
categories: [{blog_categories}]\n
tags: [{blog_tags}]\n
Meta description: {blog_meta_desc.replace(":", "-")}\n
---------------------------------------------------------------------\n
""")
logger.info(f"\n\n --------- Finished writing Blog for : {weburl} -------------- \n") logger.info(f"\n\n --------- Finished writing Blog for : {weburl} -------------- \n")
st.markdown(f"{blog_frontmatter}")
st.image(generated_image_filepath) st.image(generated_image_filepath)
st.markdown(f"{blog_markdown_str}") st.markdown(f"{blog_markdown_str}")
status.update(label=f"Finished, Review & Use your Original Content Below: {saved_blog_to_file}") status.update(label=f"Finished, Review & Use your Original Content Below: {saved_blog_to_file}", state="complete")
def write_blog_from_weburl(scraped_website): def write_blog_from_weburl(scraped_website):

View File

@@ -29,7 +29,7 @@ def gemini_text_response(prompt, temperature, top_p, n, max_tokens):
except Exception as err: except Exception as err:
logger.error(f"Failed to configure Gemini: {err}") logger.error(f"Failed to configure Gemini: {err}")
logger.info(f"Temp: {temperature}, MaxTokens: {max_tokens}, TopP: {top_p}, N: {n}") logger.info(f"Temp: {temperature}, MaxTokens: {max_tokens}, TopP: {top_p}, N: {n}")
# Set up the model # Set up AI model config
generation_config = { generation_config = {
"temperature": temperature, "temperature": temperature,
"top_p": top_p, "top_p": top_p,

View File

@@ -1,6 +1,7 @@
import os import os
import re import re
import streamlit as st import streamlit as st
import tempfile
from pathlib import Path from pathlib import Path
import configparser import configparser
from datetime import datetime from datetime import datetime
@@ -21,6 +22,7 @@ from lib.ai_writers.twitter_ai_writer import tweet_writer
from lib.ai_writers.insta_ai_writer import insta_writer from lib.ai_writers.insta_ai_writer import insta_writer
from lib.ai_writers.youtube_ai_writer import write_yt_title, write_yt_description, write_yt_script from lib.ai_writers.youtube_ai_writer import write_yt_title, write_yt_description, write_yt_script
from lib.ai_writers.web_url_ai_writer import blog_from_url from lib.ai_writers.web_url_ai_writer import blog_from_url
from lib.ai_writers.image_ai_writer import blog_from_image
from lib.ai_writers.ai_story_writer import ai_story_generator from lib.ai_writers.ai_story_writer import ai_story_generator
from lib.ai_writers.ai_essay_writer import ai_essay_generator from lib.ai_writers.ai_essay_writer import ai_essay_generator
from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
@@ -61,11 +63,15 @@ def process_input(input_text, uploaded_file):
st.write("PDF file uploaded. Add your PDF processing logic here.") st.write("PDF file uploaded. Add your PDF processing logic here.")
elif uploaded_file.type in ["application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/msword"]: elif uploaded_file.type in ["application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/msword"]:
st.write("Word document uploaded. Add your DOCX processing logic here.") st.write("Word document uploaded. Add your DOCX processing logic here.")
elif uploaded_file.type.startswith("image/"): elif uploaded_file.type.startswith("image/"):
st.image(uploaded_file) st.image(uploaded_file)
return("image_file")
elif uploaded_file.type.startswith("audio/"): elif uploaded_file.type.startswith("audio/"):
st.audio(uploaded_file) st.audio(uploaded_file)
return("audio_file") return("audio_file")
elif uploaded_file.type.startswith("video/"): elif uploaded_file.type.startswith("video/"):
st.video(uploaded_file) st.video(uploaded_file)
@@ -75,7 +81,7 @@ def blog_from_keyword():
st.title("Blog Content Writer") st.title("Blog Content Writer")
col1, col2 = st.columns([2, 1.5]) col1, col2 = st.columns([2, 1.5])
with col1: with col1:
user_input = st.text_area('**Enter Keywords/Title/YouTube Link/Web URLs**', user_input = st.text_area('**👇Enter Keywords/Title/YouTube Link/Web URLs**',
help='Provide keywords, titles, YouTube links, or web URLs to generate content.', help='Provide keywords, titles, YouTube links, or web URLs to generate content.',
placeholder="""Write Blog From: placeholder="""Write Blog From:
- Keywords/Blog Title: Provide keywords to web research & write blog. - Keywords/Blog Title: Provide keywords to web research & write blog.
@@ -84,11 +90,19 @@ def blog_from_keyword():
- Web URLs: Provide web URL to write similar blog on.""") - Web URLs: Provide web URL to write similar blog on.""")
with col2: with col2:
uploaded_file = st.file_uploader("**Attach files (Audio, Video, Image, Document)**", uploaded_file = st.file_uploader("**👇Attach files (Audio, Video, Image, Document)**",
type=["txt", "pdf", "docx", "jpg", "jpeg", "png", "mp3", "wav", "mp4", "mkv", "avi"], type=["txt", "pdf", "docx", "jpg", "jpeg", "png", "mp3", "wav", "mp4", "mkv", "avi"],
help='Attach files such as audio, video, images, or documents.') help='Attach files such as audio, video, images, or documents.')
content_type = st.radio("Select content type:", ["Normal-length content", "Long-form content", "Experimental - AI Agents team"]) temp_file_path = None
if uploaded_file is not None:
# Save the uploaded file to a temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix=uploaded_file.name) as temp_file:
temp_file.write(uploaded_file.read())
temp_file_path = temp_file.name
content_type = st.radio("**👇Select content type:**", ["Normal-length content", "Long-form content", "Experimental - AI Agents team"])
if st.button("Write Blog"): if st.button("Write Blog"):
# Clear the previous results from the screen # Clear the previous results from the screen
st.empty() st.empty()
@@ -126,6 +140,8 @@ def blog_from_keyword():
generate_audio_blog(user_input) generate_audio_blog(user_input)
elif 'web_url' in input_type: elif 'web_url' in input_type:
blog_from_url(user_input) blog_from_url(user_input)
elif 'image_file' in input_type:
blog_from_image(user_input, temp_file_path)
def ai_agents_team(): def ai_agents_team():

View File

@@ -3,14 +3,12 @@ body {
background: #f0f4f8; background: #f0f4f8;
background-image: linear-gradient(to bottom right, #d0e1f9, #e1ebf9); background-image: linear-gradient(to bottom right, #d0e1f9, #e1ebf9);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0; /* Remove any default margin */ margin: 0;
padding: 0; /* Remove any default padding */ padding: 0;
} }
.block-container { .block-container {
padding-top: 2.5rem; padding: 2.5rem 5rem 3rem 5rem;
padding-bottom: 3rem;
padding-left: 5rem;
padding-right: 5rem;
} }
/* Main header styling */ /* Main header styling */
@@ -21,7 +19,7 @@ body {
margin-bottom: 20px; margin-bottom: 20px;
text-align: center; text-align: center;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
padding-top: 10px; /* Add padding to the top to move it up */ padding-top: 10px;
} }
/* Sub-header styling */ /* Sub-header styling */
@@ -36,11 +34,11 @@ body {
/* Navigation tabs styling */ /* Navigation tabs styling */
.stTabs [role="tab"] { .stTabs [role="tab"] {
font-size: 3.75em; /* Increased font size */ font-size: 1.25em;
font-weight: bold; font-weight: bold;
color: white; color: white;
background: #1565C0; background: #1565C0;
padding: 16px 24px; /* Increased padding */ padding: 16px 24px;
margin: 5px; margin: 5px;
border-radius: 8px; border-radius: 8px;
border: 2px solid #ddd; border: 2px solid #ddd;
@@ -57,11 +55,9 @@ body {
border: 2px solid #1565C0; border: 2px solid #1565C0;
} }
/* Sidebar header styling */ /* Sidebar header styling */
.sidebar-header { .sidebar-header {
font-size: 2.2em; font-size: 1.5em;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
margin-bottom: 20px; margin-bottom: 20px;
@@ -70,8 +66,14 @@ body {
/* Sidebar option styling */ /* Sidebar option styling */
.sidebar-option { .sidebar-option {
margin-bottom: 10px; margin-bottom: 10px;
font-size: 2.2em; font-size: 1.1em;
color: #1565C0; color: #1565C0;
cursor: pointer;
transition: color 0.3s ease;
}
.sidebar-option:hover {
color: #1976D2;
} }
/* Content section styling */ /* Content section styling */
@@ -80,11 +82,11 @@ body {
margin-bottom: 30px; margin-bottom: 30px;
border-radius: 10px; border-radius: 10px;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
background-color: #f0f4f8; /* Change background to match body */ background-color: #ffffff;
} }
/* Input fields styling */ /* Input fields styling */
input[type="text"] { input[type="text"], input[type="file"], select {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
margin: 8px 0; margin: 8px 0;
@@ -92,11 +94,18 @@ input[type="text"] {
border: 2px solid #ddd; border: 2px solid #ddd;
border-radius: 4px; border-radius: 4px;
background-color: #f0f8ff; background-color: #f0f8ff;
font-size: 16px;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
input[type="text"]:focus, input[type="file"]:focus, select:focus {
border-color: #1565C0;
box-shadow: 0 0 5px rgba(21, 101, 192, 0.5);
} }
/* Custom button styling */ /* Custom button styling */
div.stButton > button:first-child { div.stButton > button:first-child {
background: #1565C0; /* Match tab color */ background: #1565C0;
color: white; color: white;
border: none; border: none;
padding: 12px 24px; padding: 12px 24px;
@@ -113,7 +122,7 @@ div.stButton > button:first-child {
} }
div.stButton > button:hover:first-child { div.stButton > button:hover:first-child {
background-color: #1976A2; /* Match tab hover color */ background-color: #1976A2;
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3);
} }
@@ -166,15 +175,17 @@ div.row-widget.stRadio > div[role="radiogroup"] > label[data-baseweb="radio"] {
margin-bottom: 10px; margin-bottom: 10px;
color: #333; color: #333;
} }
/* Audio player styling */
audio::-webkit-media-controls-panel, audio::-webkit-media-controls-panel,
audio::-webkit-media-controls-enclosure { audio::-webkit-media-controls-enclosure {
background-color:#532b5a;} background-color: #532b5a;
}
audio::-webkit-media-controls-time-remaining-display, audio::-webkit-media-controls-time-remaining-display,
audio::-webkit-media-controls-current-time-display { audio::-webkit-media-controls-current-time-display {
color: white; color: white;
text-shadow: none; text-shadow: none;
} }
audio::-webkit-media-controls-timeline { audio::-webkit-media-controls-timeline {
@@ -183,3 +194,28 @@ audio::-webkit-media-controls-timeline {
margin-left: 10px; margin-left: 10px;
margin-right: 10px; margin-right: 10px;
} }
/* Select input styling */
.stSelectbox > div[data-baseweb="select"] > div {
padding: 12px;
margin: 8px 0;
border: 2px solid #ddd;
border-radius: 4px;
background-color: #f0f8ff;
font-size: 16px;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
appearance: none;
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 12px;
}
select:focus {
border-color: #1565C0;
box-shadow: 0 0 5px rgba(21, 101, 192, 0.5);
}
select option {
padding: 10px;
}