Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import os | |
| CSV_FILE = "apps.csv" | |
| ADMIN_PASSWORD = os.getenv("PORTFOLIO_ADMIN_PASSWORD", "admin") | |
| # Create CSV if not exists | |
| if not os.path.exists(CSV_FILE): | |
| pd.DataFrame(columns=[ | |
| "section", "app_name", "ai_model", "app_link", "model_link" | |
| ]).to_csv(CSV_FILE, index=False) | |
| # ---------- Data Helpers ---------- # | |
| def get_sections(): | |
| df = pd.read_csv(CSV_FILE) | |
| if df.empty: | |
| return [] | |
| return sorted(df["section"].dropna().unique().tolist()) | |
| def load_html(): | |
| df = pd.read_csv(CSV_FILE) | |
| if df.empty: | |
| return "<h2 style='text-align:center;'>No Apps Added Yet</h2>" | |
| sections = df['section'].unique() | |
| html = """ | |
| <div class="container"> | |
| <h1>Rahul AI Portfolio</h1> | |
| <p class="subtitle">Professional AI Applications Showcase</p> | |
| """ | |
| for section in sections: | |
| html += f"<h2>{section}</h2>" | |
| html += """ | |
| <table> | |
| <tr> | |
| <th>App Name</th> | |
| <th>AI Model</th> | |
| <th>Links</th> | |
| </tr> | |
| """ | |
| section_df = df[df["section"] == section] | |
| for _, row in section_df.iterrows(): | |
| link_html = "" | |
| if pd.notna(row["app_link"]) and row["app_link"]: | |
| link_html += f'<div><a href="{row["app_link"]}" target="_blank">{row["app_link"]}</a></div>' | |
| if pd.notna(row["model_link"]) and row["model_link"]: | |
| link_html += f'<div><a href="{row["model_link"]}" target="_blank">{row["model_link"]}</a></div>' | |
| html += f""" | |
| <tr> | |
| <td>{row['app_name']}</td> | |
| <td>{row['ai_model']}</td> | |
| <td>{link_html}</td> | |
| </tr> | |
| """ | |
| html += "</table>" | |
| html += "</div>" | |
| return html | |
| def add_entry(selected_section, new_section, app_name, ai_model, app_link, model_link): | |
| section = new_section.strip() if new_section else selected_section | |
| if not section: | |
| return load_html(), gr.update(choices=get_sections()) | |
| new_row = pd.DataFrame([{ | |
| "section": section, | |
| "app_name": app_name, | |
| "ai_model": ai_model, | |
| "app_link": app_link, | |
| "model_link": model_link | |
| }]) | |
| df = pd.read_csv(CSV_FILE) | |
| df = pd.concat([df, new_row], ignore_index=True) | |
| df.to_csv(CSV_FILE, index=False) | |
| return load_html(), gr.update(choices=get_sections()) | |
| # ---------- UI ---------- # | |
| custom_css = """ | |
| body { background: #f5f7fa; font-family: 'Segoe UI', sans-serif; } | |
| .container { max-width: 1100px; margin: auto; padding: 40px 20px; } | |
| h1 { text-align: center; font-size: 42px; color: #1f2937; } | |
| .subtitle { text-align: center; color: #6b7280; margin-bottom: 40px; } | |
| h2 { margin-top: 40px; border-bottom: 2px solid #2563eb; padding-bottom: 6px; } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin-top: 15px; | |
| margin-bottom: 40px; | |
| background: white; | |
| border-radius: 10px; | |
| overflow: hidden; | |
| } | |
| th { background: #e5e7eb; padding: 12px; text-align: left; } | |
| td { padding: 12px; border-top: 1px solid #e5e7eb; } | |
| a { | |
| color: #2563eb; | |
| text-decoration: none; | |
| word-break: break-word; | |
| } | |
| a:hover { text-decoration: underline; } | |
| /* Floating Button */ | |
| .floating-btn { | |
| position: fixed !important; | |
| bottom: 25px; | |
| right: 25px; | |
| width: 50px !important; | |
| height: 50px !important; | |
| border-radius: 50% !important; | |
| font-size: 20px !important; | |
| background: #2563eb !important; | |
| color: white !important; | |
| border: none !important; | |
| } | |
| /* Admin Panel Card */ | |
| .admin-card { | |
| background: white; | |
| padding: 25px; | |
| border-radius: 12px; | |
| box-shadow: 0 10px 25px rgba(0,0,0,0.08); | |
| margin-top: 30px; | |
| } | |
| """ | |
| with gr.Blocks(css=custom_css, theme=gr.themes.Base()) as demo: | |
| html_display = gr.HTML(load_html()) | |
| # Floating admin button | |
| admin_open_btn = gr.Button("⚙", elem_classes="floating-btn") | |
| # Password Modal | |
| with gr.Group(visible=False) as password_modal: | |
| gr.Markdown("### 🔐 Admin Login") | |
| password_box = gr.Textbox(label="Enter Password", type="password") | |
| login_btn = gr.Button("OK") | |
| login_message = gr.Markdown() | |
| # Admin Panel | |
| with gr.Group(visible=False, elem_classes="admin-card") as admin_panel: | |
| gr.Markdown("## ➕ Add New Portfolio Entry") | |
| section_dropdown = gr.Dropdown( | |
| choices=get_sections(), | |
| label="Select Existing Section" | |
| ) | |
| new_section = gr.Textbox( | |
| label="Or Enter New Section (optional)" | |
| ) | |
| app_name = gr.Textbox(label="App Name") | |
| ai_model = gr.Textbox(label="AI Model") | |
| app_link = gr.Textbox(label="App Link (Full URL)") | |
| model_link = gr.Textbox(label="Model Link (Full URL)") | |
| add_btn = gr.Button("Save Entry") | |
| close_btn = gr.Button("Close") | |
| # ---------- Auth Logic ---------- # | |
| def open_password(): | |
| return gr.update(visible=True) | |
| def authenticate(password): | |
| if password == ADMIN_PASSWORD: | |
| return ( | |
| gr.update(visible=False), # hide password modal | |
| gr.update(visible=True), # show admin panel | |
| "" | |
| ) | |
| return ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| "❌ Incorrect password" | |
| ) | |
| def close_admin(): | |
| return ( | |
| gr.update(visible=False), # hide admin panel | |
| gr.update(visible=False) # hide password modal | |
| ) | |
| # ---------- Events ---------- # | |
| admin_open_btn.click( | |
| open_password, | |
| outputs=password_modal | |
| ) | |
| login_btn.click( | |
| authenticate, | |
| inputs=password_box, | |
| outputs=[password_modal, admin_panel, login_message] | |
| ) | |
| close_btn.click( | |
| close_admin, | |
| outputs=[admin_panel, password_modal] | |
| ) | |
| add_btn.click( | |
| add_entry, | |
| inputs=[section_dropdown, new_section, app_name, ai_model, app_link, model_link], | |
| outputs=[html_display, section_dropdown] | |
| ) | |
| demo.launch() |