Spaces:
Sleeping
Sleeping
| import os | |
| import sys | |
| import time | |
| from typing import Dict | |
| import gradio as gr | |
| # Ensure submodules are importable | |
| sys.path.append(os.path.dirname(__file__)) | |
| from common import PoetryModel | |
| # Try to import sub-app factories | |
| APPS: Dict[str, callable] = {} | |
| try: | |
| from poem_generator.app import create_app as create_poem_generator | |
| APPS["Poem Generator"] = create_poem_generator | |
| except Exception: | |
| pass | |
| try: | |
| from collaborative_writer.app import create_app as create_collaborative_writer | |
| APPS["Collaborative Writer"] = create_collaborative_writer | |
| except Exception: | |
| pass | |
| try: | |
| from poem_tutor.app import create_app as create_poem_tutor | |
| APPS["Poem Tutor"] = create_poem_tutor | |
| except Exception: | |
| pass | |
| try: | |
| from vocabulary_game.app import create_app as create_vocabulary_game | |
| APPS["Vocabulary Game"] = create_vocabulary_game | |
| except Exception: | |
| pass | |
| def create_launcher(model: PoetryModel): | |
| """Create the main launcher with a simple login and rate limiting.""" | |
| VALID_USER = os.getenv("APP_USERNAME", "demo") | |
| VALID_PASS = os.getenv("APP_PASSWORD", "demo123") | |
| # basic per-session rate limiting: max N actions per T seconds | |
| MAX_ACTIONS = int(os.getenv("RATE_LIMIT_MAX_ACTIONS", "30")) | |
| WINDOW_SEC = int(os.getenv("RATE_LIMIT_WINDOW_SEC", "300")) # 5 minutes | |
| def check_login(username, password, state): | |
| if not isinstance(state, dict): | |
| state = {} | |
| ok = (username or "").strip() == VALID_USER and (password or "").strip() == VALID_PASS | |
| if ok: | |
| state.update({"authed": True, "actions": [], "username": username.strip()}) | |
| return { | |
| login_status: gr.update(value="✅ Login successful", visible=True), | |
| login_row: gr.update(visible=False), | |
| app_tabs: gr.update(visible=True), | |
| session_state: state, | |
| } | |
| else: | |
| return { | |
| login_status: gr.update(value="❌ Invalid credentials", visible=True), | |
| session_state: state, | |
| } | |
| def rate_gate(state): | |
| # Called before tab loads and occasionally via a button to enforce limits | |
| if not isinstance(state, dict) or not state.get("authed"): | |
| return { | |
| login_row: gr.update(visible=True), | |
| app_tabs: gr.update(visible=False), | |
| login_status: gr.update(value="🔒 Please log in", visible=True), | |
| session_state: state if isinstance(state, dict) else {}, | |
| } | |
| now = time.time() | |
| actions = [t for t in state.get("actions", []) if now - t < WINDOW_SEC] | |
| state["actions"] = actions | |
| if len(actions) >= MAX_ACTIONS: | |
| remaining = int(WINDOW_SEC - (now - actions[0])) if actions else WINDOW_SEC | |
| msg = f"⏱️ Rate limit exceeded. Try again in ~{remaining}s." | |
| return { | |
| limit_status: gr.update(value=msg, visible=True), | |
| app_tabs: gr.update(visible=False), | |
| session_state: state, | |
| } | |
| # allow access | |
| return { | |
| limit_status: gr.update(visible=False), | |
| app_tabs: gr.update(visible=True), | |
| session_state: state, | |
| } | |
| def record_action(state): | |
| if isinstance(state, dict) and state.get("authed"): | |
| now = time.time() | |
| actions = [t for t in state.get("actions", []) if now - t < WINDOW_SEC] | |
| actions.append(now) | |
| state["actions"] = actions | |
| return {session_state: state} | |
| with gr.Blocks(title="AI Poetry Apps Launcher") as demo: | |
| gr.Markdown("# 🎭 AI Poetry Apps Launcher") | |
| gr.Markdown("Login to access the apps.") | |
| login_status = gr.Markdown("", visible=False) | |
| limit_status = gr.Markdown("", visible=False) | |
| with gr.Row(visible=True) as login_row: | |
| username_in = gr.Textbox(label="Username", placeholder="Enter username", lines=1) | |
| password_in = gr.Textbox(label="Password", placeholder="Enter password", type="password", lines=1) | |
| login_btn = gr.Button("Login", variant="primary") | |
| session_state = gr.State({}) | |
| with gr.Tabs(visible=False) as app_tabs: | |
| # Render each available sub-app as a tab | |
| for title, factory in APPS.items(): | |
| with gr.Tab(title): | |
| try: | |
| subapp = factory(model) | |
| # Add a lightweight "ping" button to count actions when user triggers generation | |
| gr.Markdown("Accessing app… actions are rate-limited.") | |
| ping_btn = gr.Button("Record Action") | |
| ping_btn.click(record_action, inputs=[session_state], outputs=[session_state]) | |
| # Mount the existing Blocks app inside | |
| gr.HTML("<hr>") | |
| subapp.render() | |
| except Exception as e: | |
| gr.Markdown(f"⚠️ Failed to load {title}: {e}") | |
| login_btn.click( | |
| check_login, | |
| inputs=[username_in, password_in, session_state], | |
| outputs=[login_status, login_row, app_tabs, session_state], | |
| ).then( | |
| rate_gate, | |
| inputs=[session_state], | |
| outputs=[limit_status, app_tabs, session_state], | |
| ) | |
| # Gate access on load as well | |
| gr.Button("Re-check Access").click( | |
| rate_gate, | |
| inputs=[session_state], | |
| outputs=[limit_status, app_tabs, session_state], | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| model = PoetryModel() | |
| app = create_launcher(model) | |
| # Spaces expects `app.py` to launch | |
| app.launch() | |