""" app.py — Second Brain Gradio Application ========================================== Modular structure: core/database.py — SQLite persistence (users, tasks, life areas, goals, AI context) core/ai_engine.py — All Groq API calls (task parsing, scheduling, journaling) core/styles.py — CSS Run locally: pip install -r requirements.txt GROQ_API_KEY=gsk_... python app.py HuggingFace Spaces: Add GROQ_API_KEY in Space Settings → Repository Secrets """ import os import gradio as gr from datetime import date, datetime # ── Core modules ────────────────────────────────────────────────────────────── from core.database import ( init_db, register_user, login_user, get_username, create_default_life_areas, save_goals, get_goals, get_life_areas, get_life_area_names, add_life_area, delete_life_area, get_tasks, save_task, toggle_task_complete, delete_task, get_today_stats, load_user_context, save_user_context, spawn_due_habits, ) from core.ai_engine import ( init_groq, create_blank_context, parse_task_with_groq, generate_schedule, build_opening_question, get_next_journal_question, synthesize_journal, ) from core.styles import CSS # ── Boot ────────────────────────────────────────────────────────────────────── init_db() init_groq() # reads GROQ_API_KEY from env TASK_HEADERS = ["ID", "Done", "Title", "Area", "Urgency", "Importance", "Mind", "Min", "Date"] # ═══════════════════════════════════════════════════════════════════════════════ # SHARED HELPERS # ═══════════════════════════════════════════════════════════════════════════════ def _ok(msg): return f'✓ {msg}' def _err(msg): return f'⚠ {msg}' def _info(msg): return f'ℹ {msg}' def _stat(val, label, color): return f'
{val}
{label}
' def _fmt_tasks(tasks: list) -> list: rows = [] for t in tasks: rows.append([ t["id"], "✅" if t["is_completed"] else "⬜", ("🔁 " if t["is_habit"] else "") + t["title"], t["life_area"] or "—", t["urgency"] or "—", t["importance"] or "—", t["state_of_mind"] or "—", str(t["time_estimate"]) + "m" if t["time_estimate"] else "—", t["scheduled_date"] or "—", ]) return rows def _area_choices(user_id): if not user_id: return ["All"] return ["All"] + get_life_area_names(user_id) def _ensure_context(user_id): """Load or create blank AI context for a user.""" ctx = load_user_context(user_id) if not ctx: ctx = create_blank_context(user_id) save_user_context(user_id, ctx) return ctx # ═══════════════════════════════════════════════════════════════════════════════ # AUTH HANDLERS # ═══════════════════════════════════════════════════════════════════════════════ def handle_login(username, password): uid, msg = login_user(username, password) if not uid: return None, "", _err(msg), gr.update(visible=True), gr.update(visible=False) spawn_due_habits(uid) uname = get_username(uid) return uid, uname, _ok(msg), gr.update(visible=False), gr.update(visible=True) def handle_register(username, password, wake, sleep, focus, goals_text): uid, msg = register_user(username, password) if not uid: return None, "", _err(msg), gr.update(visible=True), gr.update(visible=False) create_default_life_areas(uid) if goals_text.strip(): save_goals(uid, goals_text) prefs = {"wake_time": wake or "07:30", "sleep_time": sleep or "23:00", "focus_peak": focus or "Morning"} ctx = create_blank_context(uid, prefs) save_user_context(uid, ctx) spawn_due_habits(uid) uname = get_username(uid) return uid, uname, _ok(msg + " Logged in!"), gr.update(visible=False), gr.update(visible=True) def handle_logout(user_id): return None, "", gr.update(visible=True), gr.update(visible=False) # ═══════════════════════════════════════════════════════════════════════════════ # TODAY TAB HANDLERS # ═══════════════════════════════════════════════════════════════════════════════ def refresh_today(user_id): if not user_id: return [], _stat("—","Today","#a78bfa"), _stat("—","Done","#4ade80"), _stat("—","Left","#f87171") tasks = get_tasks(user_id, only_today=True) s = get_today_stats(user_id) c = "#4ade80" if s["remaining"] == 0 and s["total"] > 0 else "#f87171" return ( _fmt_tasks(tasks), _stat(s["total"], "Today", "#a78bfa"), _stat(s["done"], "Done", "#4ade80"), _stat(s["remaining"], "Left", c), ) def show_text_panel(): return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False) def show_voice_panel(): return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False) def show_plan_panel(): return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True) def hide_panels(): return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) def handle_parse_text(task_text, user_id): """Call Groq to classify a typed task, return pre-filled fields.""" if not task_text.strip(): return (gr.update(visible=False), "", "", "Work", "Not Urgent", "Important", "Easy", 30, str(date.today()), False, "Daily", "") ctx = _ensure_context(user_id) goals = get_goals(user_id) areas = get_life_area_names(user_id) if user_id else [] result = parse_task_with_groq(task_text, ctx, goals, areas) clarifications = result.get("clarifications_needed", []) clar_html = "" if clarifications: items = "".join(f"
  • {q}
  • " for q in clarifications) clar_html = ( f'
    ' f'
    ⚠ Please clarify:
    ' f'
    ' ) area_choices = areas if areas else ["Work", "Health", "Finance", "Learning", "Personal", "Family", "Other"] area_val = result.get("life_area") or (area_choices[0] if area_choices else "Work") if area_val not in area_choices: area_choices = [area_val] + area_choices return ( gr.update(visible=True), result.get("title", task_text), clar_html, area_val, result.get("urgency", "Not Urgent"), result.get("importance", "Important"), result.get("state_of_mind", "Easy"), int(result.get("time_estimate") or 30), str(date.today()), False, "Daily", _ok("AI classified your task"), ) def handle_transcribe_voice(audio_path, user_id): """Whisper transcription then Groq parse — same pipeline as notebook.""" if audio_path is None: return gr.update(visible=False), _err("No audio recorded.") try: import whisper global _whisper_model if _whisper_model is None: _whisper_model = whisper.load_model("small") result = _whisper_model.transcribe(audio_path) text = result["text"].strip() return gr.update(visible=True, value=text), _ok(f'Transcribed: "{text[:60]}..."') except Exception as e: return gr.update(visible=False), _err(f"Transcription error: {e}") _whisper_model = None def handle_confirm_task(user_id, title, area, urgency, importance, state, time_est, sched_date, is_habit, habit_interval): if not user_id: return _err("Not logged in."), [], _stat("—","Today","#a78bfa"), _stat("—","Done","#4ade80"), _stat("—","Left","#f87171"), gr.update(visible=False) if not title.strip(): return _err("Title cannot be empty."), [], _stat("—","Today","#a78bfa"), _stat("—","Done","#4ade80"), _stat("—","Left","#f87171"), gr.update(visible=True) task = { "title": title, "life_area": area, "urgency": urgency, "importance": importance, "state_of_mind": state, "time_estimate": int(time_est or 30), "is_habit": is_habit, "habit_interval": habit_interval if is_habit else "", } save_task(user_id, task, sched_date) rows, s1, s2, s3 = refresh_today(user_id) return _ok("Task saved!"), rows, s1, s2, s3, gr.update(visible=False) def handle_generate_schedule(user_id, prompt): if not user_id: return _err("Not logged in.") tasks = get_tasks(user_id, only_today=True, include_completed=False) if not tasks: return _err("No incomplete tasks today to schedule.") ctx = _ensure_context(user_id) goals = get_goals(user_id) sched = generate_schedule(ctx, tasks, prompt or "Schedule my day sensibly.", goals) if "error" in sched and "scheduled_tasks" not in sched: return _err(f"Scheduling failed: {sched.get('error')}") COLOR = {"Flow": "#0ea5e9", "Easy": "#4ade80", "Quick": "#a78bfa", "Personal": "#f87171"} html = ( f'
    ' f'📅 {sched.get("schedule_date","Today")}' f'

    {sched.get("day_summary","")}

    ' f'
    ' ) for t in sched.get("scheduled_tasks", []): sm = t.get("state_of_mind", "Easy") c = COLOR.get(sm, "#6366f1") html += ( f'
    ' f'
    {t.get("start_time")} – {t.get("end_time")}
    ' f'
    {t.get("title")}
    ' f'
    {t.get("life_area","—")} · {sm} · {t.get("duration_minutes")}min
    ' f'
    {t.get("scheduling_reason","")}
    ' f'
    ' ) if sched.get("deferred_tasks"): html += '
    Deferred
    ' for t in sched["deferred_tasks"]: html += f'
    ✗ {t["title"]} — {t.get("reason","")}
    ' for w in sched.get("warnings", []): html += f'
    ⚠ {w}
    ' return html def handle_toggle_today(task_id, user_id): if task_id and user_id: toggle_task_complete(int(task_id), user_id) rows, s1, s2, s3 = refresh_today(user_id) return rows, s1, s2, s3, _ok("Updated") def handle_delete_today(task_id, user_id): if task_id and user_id: delete_task(int(task_id), user_id) rows, s1, s2, s3 = refresh_today(user_id) return rows, s1, s2, s3, _ok("Deleted") # ═══════════════════════════════════════════════════════════════════════════════ # ALL TASKS TAB HANDLERS # ═══════════════════════════════════════════════════════════════════════════════ def refresh_all_tasks(user_id, filter_area="All", only_today=False): if not user_id: return [] return _fmt_tasks(get_tasks(user_id, filter_area=filter_area, only_today=only_today)) def handle_toggle_all(task_id, user_id, filter_area, only_today): if task_id and user_id: toggle_task_complete(int(task_id), user_id) return refresh_all_tasks(user_id, filter_area, only_today), _ok("Updated") def handle_delete_all(task_id, user_id, filter_area, only_today): if task_id and user_id: delete_task(int(task_id), user_id) return refresh_all_tasks(user_id, filter_area, only_today), _ok("Deleted") # ═══════════════════════════════════════════════════════════════════════════════ # JOURNAL TAB HANDLERS # ═══════════════════════════════════════════════════════════════════════════════ def handle_load_journal_tasks(user_id): if not user_id: return "

    Not logged in.

    ", gr.update(visible=False), [] tasks = get_tasks(user_id, only_today=True) if not tasks: return "

    No tasks for today.

    ", gr.update(visible=False), [] rows = "" state = [] for t in tasks: status = "✅" if t["is_completed"] else "⬜" actual = f" ({t['actual_duration']}m actual)" if t["actual_duration"] else "" rows += ( f'
    ' f'#{t["id"]}' f'{status}' f'{t["title"]}' f'{t["life_area"] or "—"}' f'{t["time_estimate"]}m{actual}' f'
    ' ) state.append({ "task_id": t["id"], "title": t["title"], "state_of_mind": t["state_of_mind"], "start_time": "", "end_time": "", "time_estimate": t["time_estimate"], "completed": bool(t["is_completed"]), "actual_duration": t["actual_duration"], }) return f'
    {rows}
    ', gr.update(visible=True), state def handle_journal_mark(task_id, actual_mins, user_id, tasks_state, mark_done: bool): if not task_id or not user_id: return tasks_state, _err("Enter a task ID.") tid = int(task_id) if mark_done: toggle_task_complete(tid, user_id, int(actual_mins) if actual_mins else None) else: toggle_task_complete(tid, user_id) for t in tasks_state: if t["task_id"] == tid: t["completed"] = mark_done if mark_done and actual_mins: t["actual_duration"] = int(actual_mins) return tasks_state, _ok("Marked ✅" if mark_done else "Unmarked") def handle_start_journal(user_id, tasks_state): if not user_id: return [], [], False, gr.update(interactive=False), gr.update(interactive=False), _err("Not logged in.") if not tasks_state: return [], [], False, gr.update(interactive=False), gr.update(interactive=False), _err("Load today's tasks first.") ctx = _ensure_context(user_id) opening = build_opening_question(ctx, tasks_state) chat = [(None, opening["question"])] hist = [{"role": "assistant", "content": opening["question"], "focus": opening["question_focus"]}] return ( chat, hist, True, gr.update(interactive=True), gr.update(interactive=True), _ok("Session started — type your answer below"), ) def handle_send_answer(user_id, answer, chat, hist, tasks_state, active): if not active or not answer.strip(): return chat, hist, "", "" chat = list(chat) + [(answer, None)] hist = list(hist) + [{"role": "user", "content": answer}] ctx = _ensure_context(user_id) result = get_next_journal_question(ctx, tasks_state, hist) if result.get("session_complete"): chat[-1] = (answer, "✅ That's enough for today. Click **Finish & Save** to update your profile.") return chat, hist, "", _ok("Session complete — save when ready") next_q = result.get("question", "") hist.append({"role": "assistant", "content": next_q, "focus": result.get("question_focus", "")}) chat[-1] = (answer, next_q) return chat, hist, "", "" def handle_finish_journal(user_id, chat, hist, tasks_state): if not user_id or not hist: return chat, _err("Nothing to save.") ctx = _ensure_context(user_id) updated = synthesize_journal(ctx, tasks_state, hist) save_user_context(user_id, updated) total = len(tasks_state) done = sum(1 for t in tasks_state if t.get("completed")) rate = round(done / total * 100) if total else 0 notes = updated.get("learned_patterns", {}).get("notes", []) note_html = "".join(f"
  • {n}
  • " for n in notes[-3:]) if notes else "
  • No new patterns yet
  • " return ( chat, _ok(f"Insights saved! {done}/{total} tasks ({rate}%) completed.") + f'' ) def handle_restart_journal(): return [], [], False, gr.update(interactive=False), gr.update(interactive=False), "" # ═══════════════════════════════════════════════════════════════════════════════ # LIFE AREAS & GOALS HANDLERS # ═══════════════════════════════════════════════════════════════════════════════ def render_areas(user_id): if not user_id: return "", [], [] areas = get_life_areas(user_id) chips = "" for a in areas: chips += ( f'' f'{a["name"]} ' ) html = f'
    {chips}
    ' if chips else '

    No areas yet.

    ' names = [a["name"] for a in areas] return html, names, names def handle_add_area(user_id, name, color): ok, msg = add_life_area(user_id, name, color) css = "ok" if ok else "err" html, names, _ = render_areas(user_id) return ( f'{msg}', html, gr.update(choices=names, value=None), gr.update(value=""), ) def handle_del_area(user_id, name): if not name: return _err("Select an area first."), "", [] ok, msg = delete_life_area(user_id, name) html, names, _ = render_areas(user_id) return ( f'{msg}', html, gr.update(choices=names, value=None), ) def handle_save_goals(user_id, goals_text): if not user_id: return _err("Not logged in.") save_goals(user_id, goals_text) return _ok("Goals saved!") def load_goals_txt(user_id): if not user_id: return "" return "\n".join(get_goals(user_id)) # ═══════════════════════════════════════════════════════════════════════════════ # PREFERENCES HANDLERS # ═══════════════════════════════════════════════════════════════════════════════ def load_prefs(user_id): ctx = load_user_context(user_id) if user_id else None if not ctx: return "07:30", "23:00", "Morning", 10, 90 p = ctx.get("preferences", {}) return ( p.get("wake_time", "07:30"), p.get("sleep_time", "23:00"), p.get("focus_peak", "Morning"), p.get("break_duration_minutes", 10), p.get("max_flow_block_minutes", 90), ) def handle_save_prefs(user_id, wake, sleep, focus, brk, flow_max): if not user_id: return _err("Not logged in.") ctx = _ensure_context(user_id) ctx["preferences"].update({ "wake_time": wake or "07:30", "sleep_time": sleep or "23:00", "focus_peak": focus or "Morning", "break_duration_minutes": int(brk or 10), "max_flow_block_minutes": int(flow_max or 90), }) save_user_context(user_id, ctx) return _ok("Preferences saved!") def render_context_html(user_id): if not user_id: return "

    Not logged in.

    " ctx = load_user_context(user_id) if not ctx: return "

    No AI context yet. Complete a journaling session to start building it.

    " lp = ctx.get("learned_patterns", {}) sf = ctx.get("scheduling_feedback", {}) pref = ctx.get("preferences", {}) def row(label, val): return ( f'
    ' f'{label}' f'{val}' f'
    ' ) def lst(v): return ", ".join(v) if v else "—" html = ( f'
    ' f'
    ' f'
    Preferences
    ' f'{row("Wake", pref.get("wake_time","—"))}' f'{row("Sleep", pref.get("sleep_time","—"))}' f'{row("Peak focus", pref.get("focus_peak","—"))}' f'{row("Break", str(pref.get("break_duration_minutes","—")) + "min")}' f'{row("Max flow block", str(pref.get("max_flow_block_minutes","—")) + "min")}' f'
    ' f'
    ' f'
    Scheduling Stats
    ' f'{row("Days scheduled", sf.get("total_days_scheduled",0))}' f'{row("Avg completion", str(round(sf.get("avg_completion_rate",0)*100)) + "%")}' f'{row("Context version", ctx.get("version",1))}' f'{row("Last updated", (ctx.get("last_updated","—") or "—")[:16])}' f'
    ' f'
    ' f'
    ' f'
    Learned Patterns
    ' f'{row("Productive times", lst(lp.get("productive_times",[])))}' f'{row("Low energy times", lst(lp.get("low_energy_times",[])))}' f'{row("Avg task overrun", str(lp.get("avg_task_overrun_pct",0)) + "%")}' f'{row("Flow batchable", str(lp.get("flow_batch_capable","Unknown")))}' f'{row("Best morning areas", lst(lp.get("best_life_areas_morning",[])))}' f'{row("Skipped types", lst(lp.get("common_skipped_task_types",[])))}' f'
    ' ) notes = lp.get("notes", []) if notes: note_rows = "".join( f'
    • {n}
    ' for n in notes[-5:] ) html += f'
    AI Notes
    {note_rows}
    ' history = ctx.get("history_summary", []) if history: hist_rows = "".join( f'
    • {h}
    ' for h in history[-5:] ) html += f'
    Recent History
    {hist_rows}
    ' return html # ═══════════════════════════════════════════════════════════════════════════════ # UI BUILD # ═══════════════════════════════════════════════════════════════════════════════ with gr.Blocks(css=CSS, title="🧠 Second Brain") as demo: # ── Session state ────────────────────────────────────────────────────────── user_id_state = gr.State(None) username_state = gr.State("") # Journal states journal_chat_state = gr.State([]) journal_hist_state = gr.State([]) journal_tasks_state = gr.State([]) journal_active = gr.State(False) # ═════════════════════════════════════════════════════════════════════════ # AUTH SECTION # ═════════════════════════════════════════════════════════════════════════ with gr.Column(visible=True, elem_id="auth-card") as auth_section: gr.HTML('') gr.HTML('
    Your intelligent productivity companion
    ') with gr.Tabs(): # ── Sign In ──────────────────────────────────────────────────── with gr.Tab("Sign In"): login_user_in = gr.Textbox(label="Username", placeholder="your username") login_pass_in = gr.Textbox(label="Password", type="password", placeholder="••••••••") login_btn = gr.Button("Sign In", elem_classes="btn-primary") login_msg = gr.HTML("") # ── Create Account ───────────────────────────────────────────── with gr.Tab("Create Account"): reg_user_in = gr.Textbox(label="Username", placeholder="choose a username") reg_pass_in = gr.Textbox(label="Password (min 6 chars)", type="password", placeholder="••••••••") with gr.Row(): reg_wake = gr.Textbox(label="Wake time", value="07:30", scale=1) reg_sleep = gr.Textbox(label="Sleep time", value="23:00", scale=1) reg_focus = gr.Dropdown(label="Peak focus", choices=["Morning","Afternoon","Evening","Night"], value="Morning", scale=1) reg_goals = gr.Textbox( label="Big-picture goals (one per line, optional)", lines=3, placeholder="Launch my startup\nGet fit\nLearn machine learning" ) reg_btn = gr.Button("Create Account", elem_classes="btn-primary") reg_msg = gr.HTML("") # ═════════════════════════════════════════════════════════════════════════ # MAIN APP SECTION # ═════════════════════════════════════════════════════════════════════════ with gr.Column(visible=False) as app_section: # ── Top header ───────────────────────────────────────────────────── with gr.Row(elem_id="top-header"): header_html = gr.HTML('
    🧠 Second Brain
    ') logout_btn = gr.Button("Sign Out", elem_classes="btn-secondary", scale=0) # ── Tabs ──────────────────────────────────────────────────────────── with gr.Tabs() as main_tabs: # ───────────────────────────────────────────────────────────── # TAB 1: TODAY # ───────────────────────────────────────────────────────────── with gr.Tab("📅 Today"): # Stats row with gr.Row(): stat_total = gr.HTML(_stat("—","Today","#a78bfa")) stat_done = gr.HTML(_stat("—","Done","#4ade80")) stat_remain = gr.HTML(_stat("—","Left","#f87171")) # Action buttons with gr.Row(): btn_add_text = gr.Button("✏️ Add Task (Text)", elem_classes="btn-primary", scale=3) btn_add_voice = gr.Button("🎙️ Add Task (Voice)", elem_classes="btn-accent", scale=3) btn_plan_day = gr.Button("🗓 Plan My Day", elem_classes="btn-secondary", scale=3) btn_refresh = gr.Button("↻", elem_classes="btn-secondary", scale=1) # ── Text add panel ──────────────────────────────────────── with gr.Column(visible=False, elem_classes="panel") as text_panel: gr.HTML('
    Describe Your Task
    ') task_input = gr.Textbox( label="", placeholder="e.g. Finish the client proposal by tomorrow, it's really important", lines=2 ) with gr.Row(): btn_parse = gr.Button("🤖 Parse with AI", elem_classes="btn-primary") btn_cancel_text = gr.Button("Cancel", elem_classes="btn-secondary") with gr.Column(visible=False) as parsed_panel: parse_status = gr.HTML("") clar_html = gr.HTML("") gr.HTML('
    Review & Confirm
    ') with gr.Row(): f_title = gr.Textbox(label="Title", scale=3) f_area = gr.Dropdown(label="Life Area", choices=["Work","Health","Finance","Learning","Personal","Family","Other"], scale=1) with gr.Row(): f_urgency = gr.Dropdown(label="Urgency", choices=["Urgent","Not Urgent","Habit"], value="Not Urgent") f_importance = gr.Dropdown(label="Importance", choices=["Move the Needle","Important","Not Important"], value="Important") f_state = gr.Dropdown(label="State of Mind", choices=["Flow","Easy","Quick","Personal"], value="Easy") with gr.Row(): f_time = gr.Number(label="Minutes", value=30, minimum=5, scale=1) f_date = gr.Textbox(label="Scheduled Date", value=str(date.today()), scale=1) f_is_habit = gr.Checkbox(label="♻️ This is a habit", scale=1) f_interval = gr.Dropdown(label="Recurs", choices=["Daily","Weekly","Weekdays","Weekends","Monthly"], value="Daily", visible=False, scale=1) with gr.Row(): btn_confirm = gr.Button("✓ Save Task", elem_classes="btn-success") btn_discard = gr.Button("✗ Discard", elem_classes="btn-danger") save_msg = gr.HTML("") # ── Voice add panel ─────────────────────────────────────── with gr.Column(visible=False, elem_classes="panel") as voice_panel: gr.HTML('
    Record or Upload Audio
    ') gr.HTML('

    Whisper transcribes it, then AI classifies the task automatically.

    ') audio_input = gr.Audio(sources=["microphone","upload"], type="filepath", label="") transcribed = gr.Textbox(label="Transcribed text (editable before parsing)", visible=False, lines=2) with gr.Row(): btn_transcribe = gr.Button("🤖 Transcribe & Parse", elem_classes="btn-primary") btn_cancel_voice = gr.Button("Cancel", elem_classes="btn-secondary") voice_msg = gr.HTML("") # ── Plan My Day panel ───────────────────────────────────── with gr.Column(visible=False, elem_classes="panel") as plan_panel: gr.HTML('
    Plan My Day
    ') gr.HTML('

    The AI reads your tasks, context, and goals to build the optimal schedule.

    ') plan_prompt = gr.Textbox( label="Any fixed events or preferences today?", placeholder="e.g. I have a meeting at 10am, want deep work in the morning, done by 7pm", lines=3 ) with gr.Row(): btn_gen_sched = gr.Button("✨ Generate Schedule", elem_classes="btn-primary") btn_cancel_plan = gr.Button("Cancel", elem_classes="btn-secondary") schedule_html = gr.HTML("") # ── Today's task table ──────────────────────────────────── gr.HTML('
    Today\'s Tasks
    ') today_df = gr.Dataframe(headers=TASK_HEADERS, interactive=False, wrap=True) with gr.Row(): today_done_id = gr.Number(label="Task ID — toggle done", precision=0, scale=2) btn_today_done = gr.Button("✅ Mark Done", elem_classes="btn-success", scale=2) today_del_id = gr.Number(label="Task ID — delete", precision=0, scale=2) btn_today_del = gr.Button("🗑 Delete", elem_classes="btn-danger", scale=2) today_action_msg = gr.HTML("") # ───────────────────────────────────────────────────────────── # TAB 2: ALL TASKS # ───────────────────────────────────────────────────────────── with gr.Tab("📋 All Tasks"): with gr.Row(): all_filter = gr.Dropdown(label="Filter by Area", choices=["All"], value="All", scale=4) all_today_chk = gr.Checkbox(label="Today only", value=False, scale=1) btn_all_ref = gr.Button("↻ Refresh", elem_classes="btn-secondary", scale=1) all_df = gr.Dataframe(headers=TASK_HEADERS, interactive=False, wrap=True) with gr.Row(): all_done_id = gr.Number(label="Task ID — toggle done", precision=0, scale=2) btn_all_done = gr.Button("✅ Done", elem_classes="btn-success", scale=2) all_del_id = gr.Number(label="Task ID — delete", precision=0, scale=2) btn_all_del = gr.Button("🗑 Delete", elem_classes="btn-danger", scale=2) all_action_msg = gr.HTML("") # ───────────────────────────────────────────────────────────── # TAB 3: JOURNAL # ───────────────────────────────────────────────────────────── with gr.Tab("📓 Journal"): gr.HTML('

    Mark today\'s tasks complete, then reflect. The AI updates your profile to improve tomorrow\'s schedule.

    ') # Step 1 — Mark tasks with gr.Column(elem_classes="panel"): gr.HTML('
    Step 1 — Mark Tasks Complete
    ') journal_task_list = gr.HTML('

    Click "Load Tasks" to begin.

    ') btn_load_tasks = gr.Button("Load Today's Tasks", elem_classes="btn-secondary") with gr.Row(visible=False) as journal_mark_row: j_task_id = gr.Number(label="Task ID", precision=0, scale=2) j_actual_min = gr.Number(label="Actual time (min)", precision=0, value=0, scale=2) btn_j_done = gr.Button("✅ Mark Complete", elem_classes="btn-success", scale=2) btn_j_undone = gr.Button("↩ Unmark", elem_classes="btn-secondary", scale=1) journal_mark_msg = gr.HTML("") # Step 2 — Chat with gr.Column(elem_classes="panel"): gr.HTML('
    Step 2 — Reflect
    ') journal_chatbot = gr.Chatbot(label="", height=380, bubble_full_width=False) with gr.Row(): btn_j_start = gr.Button("▶ Start Reflection", elem_classes="btn-primary", scale=2) btn_j_finish = gr.Button("✓ Finish & Save Insights", elem_classes="btn-success", scale=2) btn_j_restart = gr.Button("↺ Restart", elem_classes="btn-secondary", scale=1) j_answer = gr.Textbox(label="Your answer", placeholder="Type here…", lines=2, interactive=False) btn_j_send = gr.Button("Send →", elem_classes="btn-accent", interactive=False) journal_msg = gr.HTML("") # ───────────────────────────────────────────────────────────── # TAB 4: LIFE AREAS & GOALS # ───────────────────────────────────────────────────────────── with gr.Tab("🗂 Areas & Goals"): gr.HTML('
    Your Life Areas
    ') areas_display = gr.HTML("") with gr.Row(): new_area_name = gr.Textbox(label="New area name", placeholder="e.g. Side Project", scale=3) new_area_color = gr.ColorPicker(label="Color", value="#6366f1", scale=1) btn_add_area = gr.Button("Add", elem_classes="btn-primary", scale=1) area_msg = gr.HTML("") gr.HTML('
    Remove Area
    ') with gr.Row(): del_area_dd = gr.Dropdown(label="Select area to remove", choices=[], scale=4) btn_del_area = gr.Button("Remove", elem_classes="btn-danger", scale=1) del_area_msg = gr.HTML("") gr.HTML('
    ') gr.HTML('
    Big-Picture Goals
    ') gr.HTML('

    The AI uses these when scheduling and prioritising your tasks.

    ') goals_input = gr.Textbox(label="One goal per line", lines=5, placeholder="Launch my SaaS\nRun 5K\nLearn ML") btn_save_goals = gr.Button("Save Goals", elem_classes="btn-primary") goals_msg = gr.HTML("") # ───────────────────────────────────────────────────────────── # TAB 5: PREFERENCES # ───────────────────────────────────────────────────────────── with gr.Tab("⚙️ Preferences"): gr.HTML('
    Scheduling Preferences
    ') with gr.Row(): pref_wake = gr.Textbox(label="Wake time", placeholder="07:30", scale=1) pref_sleep = gr.Textbox(label="Sleep time", placeholder="23:00", scale=1) pref_focus = gr.Dropdown(label="Peak focus", choices=["Morning","Afternoon","Evening","Night"], value="Morning", scale=1) with gr.Row(): pref_break = gr.Number(label="Break between tasks (min)", value=10, minimum=0, scale=1) pref_flow_max = gr.Number(label="Max Flow block (min)", value=90, minimum=30, scale=1) btn_save_prefs = gr.Button("Save Preferences", elem_classes="btn-primary") prefs_msg = gr.HTML("") gr.HTML('
    ') gr.HTML('
    AI Memory
    ') gr.HTML('

    Everything the AI has learned about you. Updates after each journaling session.

    ') ctx_display = gr.HTML("") btn_ref_ctx = gr.Button("↻ Refresh", elem_classes="btn-secondary") # ═══════════════════════════════════════════════════════════════════════ # EVENT WIRING # ═══════════════════════════════════════════════════════════════════════ # ── Auth ────────────────────────────────────────────────────────────── def _post_login(uid, uname): if not uid: return uid, uname spawn_due_habits(uid) return uid, uname login_btn.click( handle_login, [login_user_in, login_pass_in], [user_id_state, username_state, login_msg, auth_section, app_section] ).then( lambda uid, uname: f'
    🧠 Second Brain
    👤 {uname}
    ', [user_id_state, username_state], [header_html] ).then( refresh_today, [user_id_state], [today_df, stat_total, stat_done, stat_remain] ).then( lambda uid: render_areas(uid)[0:2], [user_id_state], [areas_display, del_area_dd] ).then( load_goals_txt, [user_id_state], [goals_input] ).then( lambda uid: load_prefs(uid), [user_id_state], [pref_wake, pref_sleep, pref_focus, pref_break, pref_flow_max] ) reg_btn.click( handle_register, [reg_user_in, reg_pass_in, reg_wake, reg_sleep, reg_focus, reg_goals], [user_id_state, username_state, reg_msg, auth_section, app_section] ).then( lambda uid, uname: f'
    🧠 Second Brain
    👤 {uname}
    ', [user_id_state, username_state], [header_html] ).then( refresh_today, [user_id_state], [today_df, stat_total, stat_done, stat_remain] ).then( lambda uid: render_areas(uid)[0:2], [user_id_state], [areas_display, del_area_dd] ) logout_btn.click( handle_logout, [user_id_state], [user_id_state, username_state, auth_section, app_section] ) # ── Today tab ───────────────────────────────────────────────────────── btn_add_text.click(show_text_panel, outputs=[text_panel, voice_panel, plan_panel]) btn_add_voice.click(show_voice_panel, outputs=[text_panel, voice_panel, plan_panel]) btn_plan_day.click(show_plan_panel, outputs=[text_panel, voice_panel, plan_panel]) btn_cancel_text.click(hide_panels, outputs=[text_panel, voice_panel, plan_panel]) btn_cancel_voice.click(hide_panels, outputs=[text_panel, voice_panel, plan_panel]) btn_cancel_plan.click(hide_panels, outputs=[text_panel, voice_panel, plan_panel]) btn_refresh.click( refresh_today, [user_id_state], [today_df, stat_total, stat_done, stat_remain] ) # Show habit interval dropdown only when habit checked f_is_habit.change( lambda v: gr.update(visible=v), [f_is_habit], [f_interval] ) btn_parse.click( handle_parse_text, [task_input, user_id_state], [parsed_panel, f_title, clar_html, f_area, f_urgency, f_importance, f_state, f_time, f_date, f_is_habit, f_interval, parse_status] ) btn_transcribe.click( handle_transcribe_voice, [audio_input, user_id_state], [transcribed, voice_msg] ) btn_discard.click( lambda: gr.update(visible=False), outputs=[parsed_panel] ) btn_confirm.click( handle_confirm_task, [user_id_state, f_title, f_area, f_urgency, f_importance, f_state, f_time, f_date, f_is_habit, f_interval], [save_msg, today_df, stat_total, stat_done, stat_remain, parsed_panel] ) btn_gen_sched.click( handle_generate_schedule, [user_id_state, plan_prompt], [schedule_html] ) btn_today_done.click( handle_toggle_today, [today_done_id, user_id_state], [today_df, stat_total, stat_done, stat_remain, today_action_msg] ) btn_today_del.click( handle_delete_today, [today_del_id, user_id_state], [today_df, stat_total, stat_done, stat_remain, today_action_msg] ) # ── All Tasks tab ────────────────────────────────────────────────────── btn_all_ref.click( refresh_all_tasks, [user_id_state, all_filter, all_today_chk], [all_df] ) all_filter.change( refresh_all_tasks, [user_id_state, all_filter, all_today_chk], [all_df] ) all_today_chk.change( refresh_all_tasks, [user_id_state, all_filter, all_today_chk], [all_df] ) btn_all_done.click( handle_toggle_all, [all_done_id, user_id_state, all_filter, all_today_chk], [all_df, all_action_msg] ) btn_all_del.click( handle_delete_all, [all_del_id, user_id_state, all_filter, all_today_chk], [all_df, all_action_msg] ) # ── Journal tab ──────────────────────────────────────────────────────── btn_load_tasks.click( handle_load_journal_tasks, [user_id_state], [journal_task_list, journal_mark_row, journal_tasks_state] ) btn_j_done.click( lambda tid, mins, uid, st: handle_journal_mark(tid, mins, uid, st, True), [j_task_id, j_actual_min, user_id_state, journal_tasks_state], [journal_tasks_state, journal_mark_msg] ).then( handle_load_journal_tasks, [user_id_state], [journal_task_list, journal_mark_row, journal_tasks_state] ) btn_j_undone.click( lambda tid, mins, uid, st: handle_journal_mark(tid, mins, uid, st, False), [j_task_id, j_actual_min, user_id_state, journal_tasks_state], [journal_tasks_state, journal_mark_msg] ).then( handle_load_journal_tasks, [user_id_state], [journal_task_list, journal_mark_row, journal_tasks_state] ) btn_j_start.click( handle_start_journal, [user_id_state, journal_tasks_state], [journal_chatbot, journal_hist_state, journal_active, j_answer, btn_j_send, journal_msg] ) btn_j_send.click( handle_send_answer, [user_id_state, j_answer, journal_chatbot, journal_hist_state, journal_tasks_state, journal_active], [journal_chatbot, journal_hist_state, j_answer, journal_msg] ) j_answer.submit( handle_send_answer, [user_id_state, j_answer, journal_chatbot, journal_hist_state, journal_tasks_state, journal_active], [journal_chatbot, journal_hist_state, j_answer, journal_msg] ) btn_j_finish.click( handle_finish_journal, [user_id_state, journal_chatbot, journal_hist_state, journal_tasks_state], [journal_chatbot, journal_msg] ) btn_j_restart.click( handle_restart_journal, outputs=[journal_chatbot, journal_hist_state, journal_active, j_answer, btn_j_send, journal_msg] ) # ── Areas & Goals tab ───────────────────────────────────────────────── btn_add_area.click( handle_add_area, [user_id_state, new_area_name, new_area_color], [area_msg, areas_display, del_area_dd, new_area_name] ).then( lambda uid: gr.update(choices=_area_choices(uid), value="All"), [user_id_state], [all_filter] ) btn_del_area.click( handle_del_area, [user_id_state, del_area_dd], [del_area_msg, areas_display, del_area_dd] ).then( lambda uid: gr.update(choices=_area_choices(uid), value="All"), [user_id_state], [all_filter] ) btn_save_goals.click( handle_save_goals, [user_id_state, goals_input], [goals_msg] ) # ── Preferences tab ─────────────────────────────────────────────────── btn_save_prefs.click( handle_save_prefs, [user_id_state, pref_wake, pref_sleep, pref_focus, pref_break, pref_flow_max], [prefs_msg] ) btn_ref_ctx.click( render_context_html, [user_id_state], [ctx_display] ) # Refresh AI context display whenever the refresh button is clicked # (Gradio Tabs.select doesn't support conditional output, so we use the button) # ── Launch ───────────────────────────────────────────────────────────────────── if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)