CVTop50AI / app.py
awacke1's picture
Update app.py
0bc1ff4 verified
import streamlit as st
import pandas as pd
import json
import os
import datetime
import base64
from pathlib import Path
from PIL import Image
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
import re
# Set page configuration
st.set_page_config(
page_title="AI Tools Directory",
page_icon="πŸ€–",
layout="wide"
)
# Define paths
DATA_PATH = "data/ai_tools.json"
IMAGES_PATH = "images"
# Ensure directories exist
os.makedirs(os.path.dirname(DATA_PATH), exist_ok=True)
os.makedirs(IMAGES_PATH, exist_ok=True)
# CSS for styling
st.markdown("""
<style>
.card {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.logo-container {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.logo {
width: 30px;
height: 30px;
margin-right: 10px;
}
.tool-name {
font-size: 1.2rem;
font-weight: bold;
}
.votes {
margin-top: 0.5rem;
font-size: 0.9rem;
color: #666;
}
.capture-btn {
background-color: #4CAF50;
color: white;
border: none;
padding: 8px 12px;
text-align: center;
text-decoration: none;
display: inline-block;
border-radius: 4px;
margin-top: 0.5rem;
cursor: pointer;
}
.gallery-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.gallery-item {
border: 1px solid #ddd;
border-radius: 4px;
padding: 5px;
width: 150px;
}
.gallery-img {
width: 100%;
height: auto;
}
</style>
""", unsafe_allow_html=True)
# Function to initialize or load AI tools data
def load_ai_tools():
if os.path.exists(DATA_PATH):
with open(DATA_PATH, 'r') as file:
return json.load(file)
else:
# Initialize with the original HTML data
tools = [
{"id": 1, "name": "ChatGPT", "url": "https://chat.openai.com/", "votes": 0},
{"id": 2, "name": "deepseek", "url": "https://www.deepseek.com/", "votes": 0},
{"id": 3, "name": "character.ai", "url": "https://character.ai/", "votes": 0},
{"id": 4, "name": "perplexity", "url": "https://www.perplexity.ai/", "votes": 0},
{"id": 5, "name": "JanitorAI", "url": "https://www.janitorai.com/", "votes": 0},
{"id": 6, "name": "Claude", "url": "https://claude.ai/", "votes": 0},
{"id": 7, "name": "QuillBot", "url": "https://quillbot.com/", "votes": 0},
{"id": 8, "name": "SUNO", "url": "https://www.suno.ai/", "votes": 0},
{"id": 9, "name": "SPICYCHAT.AI", "url": "https://spicychat.ai/", "votes": 0},
{"id": 10, "name": "Doubao", "url": "https://doubao.com/", "votes": 0},
{"id": 11, "name": "Moonshot AI", "url": "https://moonshot.ai/", "votes": 0},
{"id": 12, "name": "Hailuo AI", "url": "https://hailuoai.com/", "votes": 0},
{"id": 13, "name": "Hugging Face", "url": "https://huggingface.co/", "votes": 0},
{"id": 14, "name": "Poe", "url": "https://poe.com/", "votes": 0},
{"id": 15, "name": "Adot", "url": "https://www.adotdev.com/", "votes": 0},
{"id": 16, "name": "Eden AI", "url": "https://eden.ai/", "votes": 0},
{"id": 17, "name": "PolyBuzz", "url": "https://polybuzz.com/", "votes": 0},
{"id": 18, "name": "SERRAT.AI", "url": "https://serrat.ai/", "votes": 0},
{"id": 19, "name": "liner", "url": "https://liner.ai/", "votes": 0},
{"id": 20, "name": "KLING AI", "url": "https://kling.ai/", "votes": 0},
{"id": 21, "name": "CIVITAI", "url": "https://civitai.com/", "votes": 0},
{"id": 22, "name": "IIElevenLabs", "url": "https://11labs.io/", "votes": 0},
{"id": 23, "name": "Sora", "url": "https://sora.ai/", "votes": 0},
{"id": 24, "name": "Crushon AI", "url": "https://crushon.ai/", "votes": 0},
{"id": 25, "name": "BLACKBOX AI", "url": "https://blackbox.ai/", "votes": 0},
{"id": 26, "name": "DeepAI", "url": "https://deepai.org/", "votes": 0},
{"id": 27, "name": "Gamma", "url": "https://gamma.app/", "votes": 0},
{"id": 28, "name": "Leonardo.Ai", "url": "https://leonardo.ai/", "votes": 0},
{"id": 29, "name": "cutout.pro", "url": "https://cutout.pro/", "votes": 0},
{"id": 30, "name": "BRAINLY", "url": "https://brainly.com/", "votes": 0},
{"id": 31, "name": "Photoroom", "url": "https://photoroom.com/", "votes": 0},
{"id": 32, "name": "Moescape AI", "url": "https://moescape.ai/", "votes": 0},
{"id": 33, "name": "Midjourney", "url": "https://www.midjourney.com/", "votes": 0},
{"id": 34, "name": "candy.ai", "url": "https://candy.ai/", "votes": 0},
{"id": 35, "name": "zeemo", "url": "https://zeemo.ai/", "votes": 0},
{"id": 36, "name": "VEED", "url": "https://veed.io/", "votes": 0},
{"id": 37, "name": "invideo AI", "url": "https://invideo.ai/", "votes": 0},
{"id": 38, "name": "Pixelcut", "url": "https://pixelcut.ai/", "votes": 0},
{"id": 39, "name": "talkie", "url": "https://talkie.ai/", "votes": 0},
{"id": 40, "name": "PixAI", "url": "https://pixa.ai/", "votes": 0},
{"id": 41, "name": "Monica", "url": "https://monica.im/", "votes": 0},
{"id": 42, "name": "CURSOR", "url": "https://cursor.sh/", "votes": 0},
{"id": 43, "name": "ideogram", "url": "https://ideogram.ai/", "votes": 0},
{"id": 44, "name": "CHUB", "url": "https://chub.ai/", "votes": 0},
{"id": 45, "name": "Clipchamp", "url": "https://clipchamp.com/", "votes": 0},
{"id": 46, "name": "Meta AI", "url": "https://meta.ai/", "votes": 0},
{"id": 47, "name": "StudyX", "url": "https://www.studyx.ai/", "votes": 0},
{"id": 48, "name": "bolt", "url": "https://bolt.ai/", "votes": 0},
{"id": 49, "name": "PicWish", "url": "https://picwish.com/", "votes": 0},
{"id": 50, "name": "Joyland", "url": "https://joyland.ai/", "votes": 0}
]
save_ai_tools(tools)
return tools
# Function to save AI tools data
def save_ai_tools(tools):
with open(DATA_PATH, 'w') as file:
json.dump(tools, file, indent=4)
# Function to generate HTML from tools data
def generate_html(tools):
html = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Tools Table</title>
<style>
body {
font-family: Arial, sans-serif;
}
table {
width: 100%;
border-collapse: collapse;
}
td {
padding: 12px;
border: 1px solid #e0e0e0;
vertical-align: middle;
}
.tool {
display: flex;
align-items: center;
}
.number {
font-weight: bold;
margin-right: 12px;
}
.logo {
width: 24px;
height: 24px;
margin-right: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.name {
font-size: 16px;
font-weight: 500;
}
a {
color: #000;
text-decoration: none;
display: flex;
align-items: center;
}
</style>
</head>
<body>
<table>
<tbody>
"""
# Sort tools by votes in descending order
sorted_tools = sorted(tools, key=lambda x: x['votes'], reverse=True)
# Create rows of 5 columns each
for i in range(0, len(sorted_tools), 5):
html += " <tr>\n"
for j in range(5):
if i+j < len(sorted_tools):
tool = sorted_tools[i+j]
html += f""" <td>
<div class="tool">
<span class="number">{i+j+1}.</span>
<a href="{tool['url']}" target="_blank">
<span class="logo">
</span>
<span class="name">{tool['name']}</span>
</a>
</div>
</td>
"""
html += " </tr>\n"
html += """ </tbody>
</table>
</body>
</html>"""
return html
# Function to take a screenshot of a website
def capture_website(url, tool_name):
try:
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--window-size=1920,1080")
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
# Navigate to the website
driver.get(url)
# Allow time for the page to load
time.sleep(3)
# Create a safe filename
safe_name = re.sub(r'[^\w\-_.]', '_', tool_name)
filename = f"{IMAGES_PATH}/{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}_{safe_name}.png"
# Take screenshot
driver.save_screenshot(filename)
# Close the browser
driver.quit()
return filename
except Exception as e:
st.error(f"Error capturing website: {str(e)}")
return None
# Display image gallery
def display_gallery():
st.subheader("πŸ“Έ Captured Screenshots Gallery")
images = sorted(Path(IMAGES_PATH).glob("*.png"), reverse=True)
if not images:
st.info("No captured screenshots yet. Use the Capture button on any AI tool to take a screenshot.")
return
# Display images in a grid
cols = st.columns(3)
for i, img_path in enumerate(images):
col_idx = i % 3
with cols[col_idx]:
img = Image.open(img_path)
st.image(img, caption=img_path.stem, use_column_width=True)
# Extract tool name from filename
parts = img_path.stem.split('_')
if len(parts) > 2:
tool_name = '_'.join(parts[2:])
else:
tool_name = parts[-1]
# Create download button for the image
with open(img_path, "rb") as file:
btn = st.download_button(
label="Download",
data=file,
file_name=img_path.name,
mime="image/png"
)
# Main app function
def main():
st.title("πŸ€– AI Tools Directory")
# Load AI tools data
ai_tools = load_ai_tools()
# Main navigation
tabs = st.tabs(["AI Tools", "Screenshot Gallery"])
with tabs[0]:
# Create two-column layout
col1, col2 = st.columns(2)
with col1:
st.subheader("AI Tools (Ranked by Votes)")
# Display AI tools list with voting and capture
for tool in sorted(ai_tools, key=lambda x: x['votes'], reverse=True):
with st.container():
st.markdown(f"""
<div class="card">
<div class="logo-container">
<span class="tool-name">{tool['name']}</span>
</div>
<div class="votes">πŸ‘ {tool['votes']} votes</div>
</div>
""", unsafe_allow_html=True)
# Add voting button and capture button in columns
vote_col, capture_col = st.columns([1, 1])
with vote_col:
if st.button(f"πŸ‘ Upvote", key=f"vote_{tool['id']}"):
# Increment votes
for t in ai_tools:
if t['id'] == tool['id']:
t['votes'] += 1
break
# Save updated data
save_ai_tools(ai_tools)
st.rerun()
with capture_col:
if st.button(f"πŸ“Έ Capture", key=f"capture_{tool['id']}"):
with st.spinner(f"Capturing {tool['name']}..."):
capture_website(tool['url'], tool['name'])
st.success("Captured!")
# Add visit button
if st.button(f"πŸ”— Visit {tool['name']}", key=f"visit_{tool['id']}"):
# Create iframe to visit the site
st.markdown(f"""
<iframe src="{tool['url']}" width="100%" height="600px" frameborder="0"></iframe>
""", unsafe_allow_html=True)
with col2:
# Generate and display HTML
html_content = generate_html(ai_tools)
st.subheader("HTML Rendered View")
st.components.v1.html(html_content, height=800, scrolling=True)
with tabs[1]:
display_gallery()
if __name__ == "__main__":
main()