awacke1 commited on
Commit
2f49755
Β·
verified Β·
1 Parent(s): 0440df9

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +390 -0
app.py ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import json
4
+ import os
5
+ import datetime
6
+ import base64
7
+ from pathlib import Path
8
+ from PIL import Image
9
+ import time
10
+ from selenium import webdriver
11
+ from selenium.webdriver.chrome.options import Options
12
+ from webdriver_manager.chrome import ChromeDriverManager
13
+ from selenium.webdriver.chrome.service import Service
14
+ import re
15
+
16
+ # Set page configuration
17
+ st.set_page_config(
18
+ page_title="AI Tools Directory",
19
+ page_icon="πŸ€–",
20
+ layout="wide"
21
+ )
22
+
23
+ # Define paths
24
+ DATA_PATH = "data/ai_tools.json"
25
+ IMAGES_PATH = "images"
26
+
27
+ # Ensure directories exist
28
+ os.makedirs(os.path.dirname(DATA_PATH), exist_ok=True)
29
+ os.makedirs(IMAGES_PATH, exist_ok=True)
30
+
31
+ # CSS for styling
32
+ st.markdown("""
33
+ <style>
34
+ .card {
35
+ border: 1px solid #e0e0e0;
36
+ border-radius: 8px;
37
+ padding: 1rem;
38
+ margin-bottom: 1rem;
39
+ background-color: white;
40
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
41
+ transition: transform 0.3s ease;
42
+ }
43
+ .card:hover {
44
+ transform: translateY(-5px);
45
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
46
+ }
47
+ .logo-container {
48
+ display: flex;
49
+ align-items: center;
50
+ margin-bottom: 0.5rem;
51
+ }
52
+ .logo {
53
+ width: 30px;
54
+ height: 30px;
55
+ margin-right: 10px;
56
+ }
57
+ .tool-name {
58
+ font-size: 1.2rem;
59
+ font-weight: bold;
60
+ }
61
+ .votes {
62
+ margin-top: 0.5rem;
63
+ font-size: 0.9rem;
64
+ color: #666;
65
+ }
66
+ .capture-btn {
67
+ background-color: #4CAF50;
68
+ color: white;
69
+ border: none;
70
+ padding: 8px 12px;
71
+ text-align: center;
72
+ text-decoration: none;
73
+ display: inline-block;
74
+ border-radius: 4px;
75
+ margin-top: 0.5rem;
76
+ cursor: pointer;
77
+ }
78
+ .gallery-container {
79
+ display: flex;
80
+ flex-wrap: wrap;
81
+ gap: 10px;
82
+ }
83
+ .gallery-item {
84
+ border: 1px solid #ddd;
85
+ border-radius: 4px;
86
+ padding: 5px;
87
+ width: 150px;
88
+ }
89
+ .gallery-img {
90
+ width: 100%;
91
+ height: auto;
92
+ }
93
+ </style>
94
+ """, unsafe_allow_html=True)
95
+
96
+
97
+ # Function to initialize or load AI tools data
98
+ def load_ai_tools():
99
+ if os.path.exists(DATA_PATH):
100
+ with open(DATA_PATH, 'r') as file:
101
+ return json.load(file)
102
+ else:
103
+ # Initialize with the original HTML data
104
+ tools = [
105
+ {"id": 1, "name": "ChatGPT", "url": "https://chat.openai.com/", "votes": 0},
106
+ {"id": 2, "name": "deepseek", "url": "https://www.deepseek.com/", "votes": 0},
107
+ {"id": 3, "name": "character.ai", "url": "https://character.ai/", "votes": 0},
108
+ {"id": 4, "name": "perplexity", "url": "https://www.perplexity.ai/", "votes": 0},
109
+ {"id": 5, "name": "JanitorAI", "url": "https://www.janitorai.com/", "votes": 0},
110
+ {"id": 6, "name": "Claude", "url": "https://claude.ai/", "votes": 0},
111
+ {"id": 7, "name": "QuillBot", "url": "https://quillbot.com/", "votes": 0},
112
+ {"id": 8, "name": "SUNO", "url": "https://www.suno.ai/", "votes": 0},
113
+ {"id": 9, "name": "SPICYCHAT.AI", "url": "https://spicychat.ai/", "votes": 0},
114
+ {"id": 10, "name": "Doubao", "url": "https://doubao.com/", "votes": 0},
115
+ {"id": 11, "name": "Moonshot AI", "url": "https://moonshot.ai/", "votes": 0},
116
+ {"id": 12, "name": "Hailuo AI", "url": "https://hailuoai.com/", "votes": 0},
117
+ {"id": 13, "name": "Hugging Face", "url": "https://huggingface.co/", "votes": 0},
118
+ {"id": 14, "name": "Poe", "url": "https://poe.com/", "votes": 0},
119
+ {"id": 15, "name": "Adot", "url": "https://www.adotdev.com/", "votes": 0},
120
+ {"id": 16, "name": "Eden AI", "url": "https://eden.ai/", "votes": 0},
121
+ {"id": 17, "name": "PolyBuzz", "url": "https://polybuzz.com/", "votes": 0},
122
+ {"id": 18, "name": "SERRAT.AI", "url": "https://serrat.ai/", "votes": 0},
123
+ {"id": 19, "name": "liner", "url": "https://liner.ai/", "votes": 0},
124
+ {"id": 20, "name": "KLING AI", "url": "https://kling.ai/", "votes": 0},
125
+ {"id": 21, "name": "CIVITAI", "url": "https://civitai.com/", "votes": 0},
126
+ {"id": 22, "name": "IIElevenLabs", "url": "https://11labs.io/", "votes": 0},
127
+ {"id": 23, "name": "Sora", "url": "https://sora.ai/", "votes": 0},
128
+ {"id": 24, "name": "Crushon AI", "url": "https://crushon.ai/", "votes": 0},
129
+ {"id": 25, "name": "BLACKBOX AI", "url": "https://blackbox.ai/", "votes": 0},
130
+ {"id": 26, "name": "DeepAI", "url": "https://deepai.org/", "votes": 0},
131
+ {"id": 27, "name": "Gamma", "url": "https://gamma.app/", "votes": 0},
132
+ {"id": 28, "name": "Leonardo.Ai", "url": "https://leonardo.ai/", "votes": 0},
133
+ {"id": 29, "name": "cutout.pro", "url": "https://cutout.pro/", "votes": 0},
134
+ {"id": 30, "name": "BRAINLY", "url": "https://brainly.com/", "votes": 0},
135
+ {"id": 31, "name": "Photoroom", "url": "https://photoroom.com/", "votes": 0},
136
+ {"id": 32, "name": "Moescape AI", "url": "https://moescape.ai/", "votes": 0},
137
+ {"id": 33, "name": "Midjourney", "url": "https://www.midjourney.com/", "votes": 0},
138
+ {"id": 34, "name": "candy.ai", "url": "https://candy.ai/", "votes": 0},
139
+ {"id": 35, "name": "zeemo", "url": "https://zeemo.ai/", "votes": 0},
140
+ {"id": 36, "name": "VEED", "url": "https://veed.io/", "votes": 0},
141
+ {"id": 37, "name": "invideo AI", "url": "https://invideo.ai/", "votes": 0},
142
+ {"id": 38, "name": "Pixelcut", "url": "https://pixelcut.ai/", "votes": 0},
143
+ {"id": 39, "name": "talkie", "url": "https://talkie.ai/", "votes": 0},
144
+ {"id": 40, "name": "PixAI", "url": "https://pixa.ai/", "votes": 0},
145
+ {"id": 41, "name": "Monica", "url": "https://monica.im/", "votes": 0},
146
+ {"id": 42, "name": "CURSOR", "url": "https://cursor.sh/", "votes": 0},
147
+ {"id": 43, "name": "ideogram", "url": "https://ideogram.ai/", "votes": 0},
148
+ {"id": 44, "name": "CHUB", "url": "https://chub.ai/", "votes": 0},
149
+ {"id": 45, "name": "Clipchamp", "url": "https://clipchamp.com/", "votes": 0},
150
+ {"id": 46, "name": "Meta AI", "url": "https://meta.ai/", "votes": 0},
151
+ {"id": 47, "name": "StudyX", "url": "https://www.studyx.ai/", "votes": 0},
152
+ {"id": 48, "name": "bolt", "url": "https://bolt.ai/", "votes": 0},
153
+ {"id": 49, "name": "PicWish", "url": "https://picwish.com/", "votes": 0},
154
+ {"id": 50, "name": "Joyland", "url": "https://joyland.ai/", "votes": 0}
155
+ ]
156
+ save_ai_tools(tools)
157
+ return tools
158
+
159
+
160
+ # Function to save AI tools data
161
+ def save_ai_tools(tools):
162
+ with open(DATA_PATH, 'w') as file:
163
+ json.dump(tools, file, indent=4)
164
+
165
+
166
+ # Function to generate HTML from tools data
167
+ def generate_html(tools):
168
+ html = """<!DOCTYPE html>
169
+ <html lang="en">
170
+ <head>
171
+ <meta charset="UTF-8">
172
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
173
+ <title>AI Tools Table</title>
174
+ <style>
175
+ body {
176
+ font-family: Arial, sans-serif;
177
+ }
178
+ table {
179
+ width: 100%;
180
+ border-collapse: collapse;
181
+ }
182
+ td {
183
+ padding: 12px;
184
+ border: 1px solid #e0e0e0;
185
+ vertical-align: middle;
186
+ }
187
+ .tool {
188
+ display: flex;
189
+ align-items: center;
190
+ }
191
+ .number {
192
+ font-weight: bold;
193
+ margin-right: 12px;
194
+ }
195
+ .logo {
196
+ width: 24px;
197
+ height: 24px;
198
+ margin-right: 8px;
199
+ display: inline-flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ }
203
+ .name {
204
+ font-size: 16px;
205
+ font-weight: 500;
206
+ }
207
+ a {
208
+ color: #000;
209
+ text-decoration: none;
210
+ display: flex;
211
+ align-items: center;
212
+ }
213
+ </style>
214
+ </head>
215
+ <body>
216
+ <table>
217
+ <tbody>
218
+ """
219
+
220
+ # Sort tools by votes in descending order
221
+ sorted_tools = sorted(tools, key=lambda x: x['votes'], reverse=True)
222
+
223
+ # Create rows of 5 columns each
224
+ for i in range(0, len(sorted_tools), 5):
225
+ html += " <tr>\n"
226
+ for j in range(5):
227
+ if i+j < len(sorted_tools):
228
+ tool = sorted_tools[i+j]
229
+ html += f""" <td>
230
+ <div class="tool">
231
+ <span class="number">{i+j+1}.</span>
232
+ <a href="{tool['url']}" target="_blank">
233
+ <span class="logo">
234
+ <img src="https://via.placeholder.com/24" alt="{tool['name']} logo" />
235
+ </span>
236
+ <span class="name">{tool['name']}</span>
237
+ </a>
238
+ </div>
239
+ </td>
240
+ """
241
+ html += " </tr>\n"
242
+
243
+ html += """ </tbody>
244
+ </table>
245
+ </body>
246
+ </html>"""
247
+
248
+ return html
249
+
250
+
251
+ # Function to take a screenshot of a website
252
+ def capture_website(url, tool_name):
253
+ try:
254
+ chrome_options = Options()
255
+ chrome_options.add_argument("--headless")
256
+ chrome_options.add_argument("--no-sandbox")
257
+ chrome_options.add_argument("--disable-dev-shm-usage")
258
+ chrome_options.add_argument("--window-size=1920,1080")
259
+
260
+ service = Service(ChromeDriverManager().install())
261
+ driver = webdriver.Chrome(service=service, options=chrome_options)
262
+
263
+ # Navigate to the website
264
+ driver.get(url)
265
+
266
+ # Allow time for the page to load
267
+ time.sleep(3)
268
+
269
+ # Create a safe filename
270
+ safe_name = re.sub(r'[^\w\-_.]', '_', tool_name)
271
+ filename = f"{IMAGES_PATH}/{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}_{safe_name}.png"
272
+
273
+ # Take screenshot
274
+ driver.save_screenshot(filename)
275
+
276
+ # Close the browser
277
+ driver.quit()
278
+
279
+ return filename
280
+ except Exception as e:
281
+ st.error(f"Error capturing website: {str(e)}")
282
+ return None
283
+
284
+
285
+ # Display image gallery
286
+ def display_gallery():
287
+ st.subheader("πŸ“Έ Captured Screenshots Gallery")
288
+
289
+ images = sorted(Path(IMAGES_PATH).glob("*.png"), reverse=True)
290
+
291
+ if not images:
292
+ st.info("No captured screenshots yet. Use the Capture button on any AI tool to take a screenshot.")
293
+ return
294
+
295
+ # Display images in a grid
296
+ cols = st.columns(3)
297
+
298
+ for i, img_path in enumerate(images):
299
+ col_idx = i % 3
300
+ with cols[col_idx]:
301
+ img = Image.open(img_path)
302
+ st.image(img, caption=img_path.stem, use_column_width=True)
303
+
304
+ # Extract tool name from filename
305
+ parts = img_path.stem.split('_')
306
+ if len(parts) > 2:
307
+ tool_name = '_'.join(parts[2:])
308
+ else:
309
+ tool_name = parts[-1]
310
+
311
+ # Create download button for the image
312
+ with open(img_path, "rb") as file:
313
+ btn = st.download_button(
314
+ label="Download",
315
+ data=file,
316
+ file_name=img_path.name,
317
+ mime="image/png"
318
+ )
319
+
320
+
321
+ # Main app function
322
+ def main():
323
+ st.title("πŸ€– AI Tools Directory")
324
+
325
+ # Load AI tools data
326
+ ai_tools = load_ai_tools()
327
+
328
+ # Main navigation
329
+ tabs = st.tabs(["AI Tools", "Screenshot Gallery"])
330
+
331
+ with tabs[0]:
332
+ # Create two-column layout
333
+ col1, col2 = st.columns(2)
334
+
335
+ with col1:
336
+ st.subheader("AI Tools (Ranked by Votes)")
337
+
338
+ # Display AI tools list with voting and capture
339
+ for tool in sorted(ai_tools, key=lambda x: x['votes'], reverse=True):
340
+ with st.container():
341
+ st.markdown(f"""
342
+ <div class="card">
343
+ <div class="logo-container">
344
+ <img src="https://via.placeholder.com/30" class="logo" alt="{tool['name']} logo">
345
+ <span class="tool-name">{tool['name']}</span>
346
+ </div>
347
+ <div class="votes">πŸ‘ {tool['votes']} votes</div>
348
+ </div>
349
+ """, unsafe_allow_html=True)
350
+
351
+ # Add voting button and capture button in columns
352
+ vote_col, capture_col = st.columns([1, 1])
353
+
354
+ with vote_col:
355
+ if st.button(f"πŸ‘ Upvote", key=f"vote_{tool['id']}"):
356
+ # Increment votes
357
+ for t in ai_tools:
358
+ if t['id'] == tool['id']:
359
+ t['votes'] += 1
360
+ break
361
+
362
+ # Save updated data
363
+ save_ai_tools(ai_tools)
364
+ st.experimental_rerun()
365
+
366
+ with capture_col:
367
+ if st.button(f"πŸ“Έ Capture", key=f"capture_{tool['id']}"):
368
+ with st.spinner(f"Capturing {tool['name']}..."):
369
+ capture_website(tool['url'], tool['name'])
370
+ st.success("Captured!")
371
+
372
+ # Add visit button
373
+ if st.button(f"πŸ”— Visit {tool['name']}", key=f"visit_{tool['id']}"):
374
+ # Create iframe to visit the site
375
+ st.markdown(f"""
376
+ <iframe src="{tool['url']}" width="100%" height="600px" frameborder="0"></iframe>
377
+ """, unsafe_allow_html=True)
378
+
379
+ with col2:
380
+ # Generate and display HTML
381
+ html_content = generate_html(ai_tools)
382
+ st.subheader("HTML Rendered View")
383
+ st.components.v1.html(html_content, height=800, scrolling=True)
384
+
385
+ with tabs[1]:
386
+ display_gallery()
387
+
388
+
389
+ if __name__ == "__main__":
390
+ main()