Spaces:
Build error
Build error
| 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() |