Spaces:
Sleeping
Sleeping
import gradio as gr | |
from fastapi import FastAPI, Request | |
from datetime import datetime | |
import json | |
import pandas as pd | |
import os | |
from watchdog.observers import Observer | |
from watchdog.events import FileSystemEventHandler | |
from functools import wraps | |
shortcuts_list = [] | |
# Add file change handler | |
class JSONFileHandler(FileSystemEventHandler): | |
def on_modified(self, event): | |
if event.src_path.endswith('shortcuts.json'): | |
load_shortcuts() | |
# Add decorator for file operations | |
def ensure_fresh_data(func): | |
def wrapper(*args, **kwargs): | |
load_shortcuts() # Reload before each operation | |
result = func(*args, **kwargs) | |
return result | |
return wrapper | |
def save_shortcuts(): | |
with open('shortcuts.json', 'w') as f: | |
json.dump(shortcuts_list, f, default=str) | |
load_shortcuts() | |
def load_shortcuts(): | |
global shortcuts_list | |
if os.path.exists('shortcuts.json'): | |
# Open the file in binary mode with no buffering to avoid cached data | |
with open('shortcuts.json', 'rb', buffering=0) as f: | |
data = f.read() | |
shortcuts_list = json.loads(data.decode('utf-8')) | |
for shortcut in shortcuts_list: | |
shortcut['date_added'] = datetime.fromisoformat(shortcut['date_added']) | |
else: | |
shortcuts_list = [] | |
def add_shortcut(name, tags, link, emojis, color_from, color_to, short_description): | |
new_shortcut = { | |
'name': name.strip(), | |
'tags': [tag.strip() for tag in tags.split('/') if tag.strip()], | |
'link': link.strip(), | |
'emojis': emojis.strip(), | |
'color_from': color_from, | |
'color_to': color_to, | |
'short_description': short_description.strip(), | |
'pinned': False, | |
'favorited': False, | |
'date_added': datetime.now().isoformat() | |
} | |
shortcuts_list.append(new_shortcut) | |
save_shortcuts() | |
# Return updated HTML | |
return update_display() | |
def get_shortcuts_dataframe(sort_by='Recently Added', search_query='', filter_tags=[]): | |
datafra = pd.DataFrame(shortcuts_list) | |
if datafra.empty: | |
return datafra | |
# Apply search filter | |
if search_query: | |
datafra = datafra[datafra['name'].str.contains(search_query, case=False)] | |
# Apply tag filters | |
if filter_tags: | |
datafra = datafra[datafra['tags'].apply(lambda tags: any(tag in tags for tag in filter_tags))] | |
# Sort the DataFrame | |
if sort_by == 'Alphabetical': | |
datafra = datafra.sort_values('name') | |
elif sort_by == 'Recently Added': | |
datafra = datafra.sort_values('date_added', ascending=False) | |
elif sort_by == 'Favorites': | |
datafra = datafra.sort_values('favorited', ascending=False) | |
# Reset index | |
datafra = datafra.reset_index(drop=True) | |
return datafra | |
def generate_cards_html(datafra): | |
if datafra.empty: | |
return "<p>No shortcuts available.</p>" | |
cards_html = '<div style="display: flex; flex-wrap: wrap;">' | |
for idx, shortcut in datafra.iterrows(): | |
# Determine pin icon based on state | |
if shortcut['pinned']: | |
pin_icon = "π" | |
pin_style = "opacity: 1;" | |
else: | |
pin_icon = "π" | |
pin_style = "opacity: 0.3; text-decoration: line-through;" | |
# Determine favorite icon based on state | |
if shortcut['favorited']: | |
favorite_icon = "β€οΈ" | |
else: | |
favorite_icon = "π€" | |
style = f""" | |
background: linear-gradient(135deg, {shortcut['color_from']}, {shortcut['color_to']}); | |
padding: 20px; | |
border-radius: 10px; | |
position: relative; | |
color: white; | |
width: 300px; | |
margin: 10px; | |
cursor: pointer; | |
""" | |
labels_html = "" | |
if shortcut['pinned'] or shortcut['favorited']: | |
labels_html = f""" | |
<div style='position: absolute; top: 10px; right: 10px; font-size: 24px;'> | |
{pin_icon if shortcut['pinned'] else ''} | |
{favorite_icon if shortcut['favorited'] else ''} | |
</div> | |
""" | |
card_html = f""" | |
<div style="{style}" | |
onmouseover="window.handleHover(event, {idx})" | |
onmouseout="window.handleHoverOut(event, {idx})"> | |
{labels_html} | |
<div style='font-size: 40px; text-align: center;'>{shortcut['emojis']}</div> | |
<h3 style='text-align: center;'>{shortcut['name']}</h3> | |
<p style='text-align: center;'>{shortcut['short_description']}</p> | |
<div style='text-align: center;'> | |
<button style="background: none; border: none; cursor: pointer; {pin_style}" onclick="window.togglePin({idx})">{pin_icon}</button> | |
<button style="background: none; border: none; cursor: pointer;" onclick="window.toggleFavorite({idx})">{favorite_icon}</button> | |
<button onclick="window.open('{shortcut['link']}', '_blank')">π Open</button> | |
</div> | |
<div id="delete-{idx}" style="display: none; position: absolute; top: 10px; left: 10px; cursor: pointer;" onclick="window.deleteShortcut({idx})"> | |
ποΈ | |
</div> | |
</div> | |
""" | |
cards_html += card_html | |
cards_html += '</div>' | |
return cards_html | |
def update_display(sort_by='Recently Added', search_query='', filter_tags=[]): | |
datafra = get_shortcuts_dataframe(sort_by, search_query, filter_tags) | |
return generate_cards_html(datafra) | |
def toggle_pin(index): | |
index = int(index) | |
if 0 <= index < len(shortcuts_list): | |
shortcuts_list[index]['pinned'] = not shortcuts_list[index]['pinned'] | |
save_shortcuts() | |
# Return updated HTML | |
return update_display() | |
def toggle_favorite(index): | |
index = int(index) | |
if 0 <= index < len(shortcuts_list): | |
shortcuts_list[index]['favorited'] = not shortcuts_list[index]['favorited'] | |
save_shortcuts() | |
# Return updated HTML | |
return update_display() | |
def delete_shortcut(index): | |
index = int(index) | |
if 0 <= index < len(shortcuts_list): | |
del shortcuts_list[index] | |
save_shortcuts() | |
# Return updated HTML | |
return update_display() | |
load_shortcuts() | |
# JavaScript code attached to window object | |
js_code = f""" | |
function my_func() {{ | |
window.isCmdOrCtrl = false; | |
window.addEventListener('keydown', function(e) {{ | |
if (e.key === 'Meta' || e.key === 'Control') {{ | |
window.isCmdOrCtrl = true; | |
window.showDeleteIcons(); | |
}} | |
}}); | |
window.addEventListener('keyup', function(e) {{ | |
if (e.key === 'Meta' || e.key === 'Control') {{ | |
window.isCmdOrCtrl = false; | |
window.hideDeleteIcons(); | |
}} | |
}}); | |
window.handleHover = function(event, idx) {{ | |
if (window.isCmdOrCtrl) {{ | |
document.getElementById('card-' + idx).style.display = 'block'; | |
}} | |
}}; | |
window.handleHoverOut = function(event, idx) {{ | |
document.getElementById('card-'+ idx).style.display = 'none'; | |
}}; | |
window.showDeleteIcons = function() {{ | |
const deleteIcons = document.querySelectorAll('[id^="delete-"]'); | |
deleteIcons.forEach(icon => {{ | |
icon.style.display = 'block'; | |
}}); | |
}}; | |
window.hideDeleteIcons = function() {{ | |
const deleteIcons = document.querySelectorAll('[id^="delete-"]'); | |
deleteIcons.forEach(icon => {{ | |
icon.style.display = 'none'; | |
}}); | |
}}; | |
window.togglePin = function(idx) {{ | |
// Implement the delete functionality, e.g., call an API endpoint | |
fetch('/toggle_pin', {{ | |
method: 'POST', | |
headers: {{ | |
'Content-Type': 'application/json' | |
}}, | |
body: JSON.stringify({{ index: idx }}) | |
}}) | |
.then(response => response.json()) | |
.then(data => {{ | |
// Update the grid display | |
document.getElementById('grid_output').innerHTML = data.grid_html; | |
}}); | |
}}; | |
window.deleteShortcut = function(idx) {{ | |
// Implement the delete functionality, e.g., call an API endpoint | |
fetch('/delete_shortcut', {{ | |
method: 'POST', | |
headers: {{ | |
'Content-Type': 'application/json' | |
}}, | |
body: JSON.stringify({{ index: idx }}) | |
}}) | |
.then(response => response.json()) | |
.then(data => {{ | |
// Update the grid display | |
document.getElementById('grid_output').innerHTML = data.grid_html; | |
}}); | |
}}; | |
}} | |
""" | |
# Build the Gradio App | |
with gr.Blocks(theme="charbelgrower/Crystal", js=js_code) as demo: | |
gr.Markdown("## Website Shortcuts") | |
with gr.Row(): | |
search_bar = gr.Textbox(label="Search") | |
sort_options = gr.Dropdown(choices=['Recently Added', 'Alphabetical', 'Favorites'], label="Sort By", value='Recently Added') | |
# Collect all unique tags | |
def get_all_tags(): | |
return list(set(tag for shortcut in shortcuts_list for tag in shortcut['tags'])) | |
filter_tags = gr.CheckboxGroup(choices=get_all_tags(), label="Filter Tags") | |
grid_output = gr.HTML(value=update_display(), elem_id="grid_output") | |
gr.Markdown("## Add a New Website Shortcut") | |
with gr.Row(): | |
name = gr.Textbox(label="Name") | |
link = gr.Textbox(label="Link") | |
with gr.Row(): | |
tags = gr.Textbox(label="Tags (separate levels with '/')") | |
emojis = gr.Textbox(label="Emojis") | |
color_from = gr.ColorPicker(label="Gradient Color From") | |
color_to = gr.ColorPicker(label="Gradient Color To") | |
short_description = gr.Textbox(label="Short Description") | |
add_button = gr.Button("Add Shortcut") | |
# Update display when filters change | |
def refresh_display(search_query='', sort_by='Recently Added', filter_tags=[]): | |
grid_html = update_display(sort_by, search_query, filter_tags) | |
filter_tags_options = get_all_tags() | |
return grid_html, gr.update(choices=filter_tags_options) | |
search_bar.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output) | |
sort_options.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output) | |
filter_tags.change(fn=refresh_display, inputs=[search_bar, sort_options, filter_tags], outputs=grid_output) | |
# Add shortcut action | |
add_button.click( | |
fn=add_shortcut, | |
inputs=[name, tags, link, emojis, color_from, color_to, short_description], | |
outputs=grid_output | |
) | |
# Expose endpoints for custom functions using FastAPI | |
api = FastAPI() | |
async def delete_shortcut_endpoint(request: Request): | |
data = await request.json() | |
index = data.get('index') | |
grid_html = delete_shortcut(index) | |
return {'grid_html': grid_html} | |
async def toggle_pin_endpoint(request: Request): | |
data = await request.json() | |
index = data.get('index') | |
grid_html = toggle_pin(index) | |
return {'grid_html': grid_html} | |
async def toggle_favorite_endpoint(request: Request): | |
data = await request.json() | |
index = data.get('index') | |
grid_html = toggle_favorite(index) | |
return {'grid_html': grid_html} | |
app = gr.mount_gradio_app(api, demo,"/") | |
demo.launch(ssr_mode=False) | |
# Initialize file watcher | |
if __name__ == "__main__": | |
import uvicorn | |
demo.unload(fn=load_shortcuts) | |
event_handler = JSONFileHandler() | |
observer = Observer() | |
observer.schedule(event_handler, path='shortcuts.json', recursive=False) | |
observer.start() | |
try: | |
uvicorn.run(app, host="0.0.0.0", port=7640) | |
finally: | |
observer.stop() | |
observer.join() |