WIP - Streamlit UI, firecrawl - V0.5
This commit is contained in:
@@ -307,6 +307,7 @@ def main():
|
||||
Welcome to Alwrity!
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# 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",
|
||||
f"web_research_report_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}")
|
||||
@@ -375,7 +376,7 @@ def write_blog():
|
||||
"AI Copywriter",
|
||||
"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":
|
||||
blog_from_keyword()
|
||||
|
||||
112
lib/ai_writers/image_ai_writer.py
Normal file
112
lib/ai_writers/image_ai_writer.py
Normal 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)
|
||||
@@ -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,
|
||||
blog_tags, blog_categories, generated_image_filepath)
|
||||
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")
|
||||
st.markdown(f"{blog_frontmatter}")
|
||||
st.image(generated_image_filepath)
|
||||
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):
|
||||
|
||||
@@ -29,7 +29,7 @@ def gemini_text_response(prompt, temperature, top_p, n, max_tokens):
|
||||
except Exception as err:
|
||||
logger.error(f"Failed to configure Gemini: {err}")
|
||||
logger.info(f"Temp: {temperature}, MaxTokens: {max_tokens}, TopP: {top_p}, N: {n}")
|
||||
# Set up the model
|
||||
# Set up AI model config
|
||||
generation_config = {
|
||||
"temperature": temperature,
|
||||
"top_p": top_p,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
import streamlit as st
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import configparser
|
||||
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.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.image_ai_writer import blog_from_image
|
||||
from lib.ai_writers.ai_story_writer import ai_story_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
|
||||
@@ -61,11 +63,15 @@ def process_input(input_text, uploaded_file):
|
||||
st.write("PDF file uploaded. Add your PDF processing logic here.")
|
||||
elif uploaded_file.type in ["application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/msword"]:
|
||||
st.write("Word document uploaded. Add your DOCX processing logic here.")
|
||||
|
||||
elif uploaded_file.type.startswith("image/"):
|
||||
st.image(uploaded_file)
|
||||
return("image_file")
|
||||
|
||||
elif uploaded_file.type.startswith("audio/"):
|
||||
st.audio(uploaded_file)
|
||||
return("audio_file")
|
||||
|
||||
elif uploaded_file.type.startswith("video/"):
|
||||
st.video(uploaded_file)
|
||||
|
||||
@@ -75,7 +81,7 @@ def blog_from_keyword():
|
||||
st.title("Blog Content Writer")
|
||||
col1, col2 = st.columns([2, 1.5])
|
||||
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.',
|
||||
placeholder="""Write Blog From:
|
||||
- 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.""")
|
||||
|
||||
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"],
|
||||
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"):
|
||||
# Clear the previous results from the screen
|
||||
st.empty()
|
||||
@@ -126,6 +140,8 @@ def blog_from_keyword():
|
||||
generate_audio_blog(user_input)
|
||||
elif 'web_url' in input_type:
|
||||
blog_from_url(user_input)
|
||||
elif 'image_file' in input_type:
|
||||
blog_from_image(user_input, temp_file_path)
|
||||
|
||||
|
||||
def ai_agents_team():
|
||||
|
||||
@@ -3,15 +3,13 @@ body {
|
||||
background: #f0f4f8;
|
||||
background-image: linear-gradient(to bottom right, #d0e1f9, #e1ebf9);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0; /* Remove any default margin */
|
||||
padding: 0; /* Remove any default padding */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.block-container {
|
||||
padding-top: 2.5rem;
|
||||
padding-bottom: 3rem;
|
||||
padding-left: 5rem;
|
||||
padding-right: 5rem;
|
||||
}
|
||||
padding: 2.5rem 5rem 3rem 5rem;
|
||||
}
|
||||
|
||||
/* Main header styling */
|
||||
.main-header {
|
||||
@@ -21,7 +19,7 @@ body {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
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 */
|
||||
@@ -36,11 +34,11 @@ body {
|
||||
|
||||
/* Navigation tabs styling */
|
||||
.stTabs [role="tab"] {
|
||||
font-size: 3.75em; /* Increased font size */
|
||||
font-size: 1.25em;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
background: #1565C0;
|
||||
padding: 16px 24px; /* Increased padding */
|
||||
padding: 16px 24px;
|
||||
margin: 5px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #ddd;
|
||||
@@ -57,11 +55,9 @@ body {
|
||||
border: 2px solid #1565C0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Sidebar header styling */
|
||||
.sidebar-header {
|
||||
font-size: 2.2em;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
@@ -70,8 +66,14 @@ body {
|
||||
/* Sidebar option styling */
|
||||
.sidebar-option {
|
||||
margin-bottom: 10px;
|
||||
font-size: 2.2em;
|
||||
font-size: 1.1em;
|
||||
color: #1565C0;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-option:hover {
|
||||
color: #1976D2;
|
||||
}
|
||||
|
||||
/* Content section styling */
|
||||
@@ -80,11 +82,11 @@ body {
|
||||
margin-bottom: 30px;
|
||||
border-radius: 10px;
|
||||
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[type="text"] {
|
||||
input[type="text"], input[type="file"], select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin: 8px 0;
|
||||
@@ -92,11 +94,18 @@ input[type="text"] {
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
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 */
|
||||
div.stButton > button:first-child {
|
||||
background: #1565C0; /* Match tab color */
|
||||
background: #1565C0;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
@@ -113,7 +122,7 @@ div.stButton > button: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);
|
||||
}
|
||||
|
||||
@@ -166,20 +175,47 @@ div.row-widget.stRadio > div[role="radiogroup"] > label[data-baseweb="radio"] {
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Audio player styling */
|
||||
audio::-webkit-media-controls-panel,
|
||||
audio::-webkit-media-controls-enclosure {
|
||||
background-color:#532b5a;}
|
||||
background-color: #532b5a;
|
||||
}
|
||||
|
||||
audio::-webkit-media-controls-time-remaining-display,
|
||||
audio::-webkit-media-controls-current-time-display {
|
||||
color: white;
|
||||
text-shadow: none;
|
||||
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
audio::-webkit-media-controls-timeline {
|
||||
background-color: #532b5a;
|
||||
border-radius: 25px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
background-color: #532b5a;
|
||||
border-radius: 25px;
|
||||
margin-left: 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user