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'
'
)
+ def lst(v): return ", ".join(v) if v else "—"
- # ── Overrun pill ───────────────────────────────────────────────────────
- overrun_col = "#4ade80" if overrun <= 10 else "#f59e0b" if overrun <= 30 else "#f87171"
-
- # ── Stats row ─────────────────────────────────────────────────────────
- def _pill(val, label, color):
- return (f'
'
- f'{_pill(days,"Days tracked","#a78bfa")}'
- f'{_pill(str(avg_rate)+"%","Avg completion","#4ade80")}'
- f'{_pill(str(overrun)+"%","Task overrun",overrun_col)}'
- f'{_pill(str(lp.get("flow_batch_capable","?")), "Flow batching","#60a5fa")}'
+ 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'
'
)
- # ── AI Notes with timestamps ───────────────────────────────────────────
- notes_html = ""
+ notes = lp.get("notes", [])
if notes:
- notes_html = '
AI Insights
'
- for n in notes[-8:]:
- # Notes may be plain strings or "YYYY-MM-DD: text"
- if ": " in str(n) and str(n)[:10].count("-") == 2:
- ts, txt = n.split(": ",1)
- else:
- ts, txt = _upd, str(n)
- notes_html += (
- f'
'
- f'{ts}'
- f'\u2022 {txt}'
- f'
'
- )
-
- # ── History summary ────────────────────────────────────────────────────
- hist_html = ""
+ note_rows = "".join(
+ f'
• {n}
'
+ for n in notes[-5:]
+ )
+ html += f'
'
+
+ history = ctx.get("history_summary", [])
if history:
- hist_html = '
Session History
'
- for h in history[-5:]:
- hist_html += f'
\u2022 {h}
'
+ hist_rows = "".join(
+ f'
• {h}
'
+ for h in history[-5:]
+ )
+ html += f'
Recent History
{hist_rows}
'
- html = (
- f'
'
- f'
v{_ver} \u00b7 last updated {_upd}
'
- f'{stats_row}'
- f'{chart_html}'
- f'{notes_html}'
- f'{hist_html}'
- f'
'
- )
return html
-# =============================================================================
-# UI
-# =============================================================================
+# ═══════════════════════════════════════════════════════════════════════════════
+# UI BUILD
+# ═══════════════════════════════════════════════════════════════════════════════
-with gr.Blocks(title="\U0001f9e0 Second Brain") as demo:
+with gr.Blocks(title="🧠 Second Brain") as demo:
- user_id_state = gr.State(None)
- username_state = gr.State("")
- # Journal
+ # ── 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)
- journal_phase_state = gr.State("marking")
- # Clarification — text panel
+ # Clarification states
clar_questions_state = gr.State([])
original_task_state = gr.State("")
- # Clarification — voice panel
- vclar_questions_state = gr.State([])
- voriginal_task_state = gr.State("")
- # Scheduler pending state
- sched_pending_state = gr.State("")
- # Selected task ids
- today_sel_state = gr.State(None)
- all_sel_state = gr.State(None)
-
- # ── AUTH ───────────────────────────────────────────────────────────────��─
+
+ # ═════════════════════════════════════════════════════════════════════════
+ # AUTH SECTION
+ # ═════════════════════════════════════════════════════════════════════════
with gr.Column(visible=True, elem_id="auth-card") as auth_section:
- gr.HTML('
\U0001f9e0 Second Brain
')
+ gr.HTML('
🧠 Second Brain
')
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="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022")
+ 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")
- reg_wake = gr.Textbox(label="Wake time", value="07:30")
- reg_sleep = gr.Textbox(label="Sleep time", value="23:00")
- reg_focus = gr.Dropdown(label="Peak focus", choices=["Morning","Afternoon","Evening","Night"], value="Morning")
- reg_goals = gr.Textbox(label="Big-picture goals (optional)", lines=3, placeholder="Launch my startup\nGet fit\nLearn ML")
- reg_btn = gr.Button("Create Account", elem_classes="btn-primary")
- reg_msg = gr.HTML("")
-
- # ── MAIN APP ─────────────────────────────────────────────────────────────
+ reg_user_in = gr.Textbox(label="Username", placeholder="choose a username")
+ reg_pass_in = gr.Textbox(label="Password (min 6 chars)", type="password", placeholder="••••••••")
+ reg_wake = gr.Textbox(label="Wake time", value="07:30")
+ reg_sleep = gr.Textbox(label="Sleep time", value="23:00")
+ reg_focus = gr.Dropdown(label="Peak focus",
+ choices=["Morning","Afternoon","Evening","Night"],
+ value="Morning")
+ 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('
\U0001f9e0 Second Brain
')
+ header_html = gr.HTML('
🧠 Second Brain
')
logout_btn = gr.Button("Sign Out", elem_classes="btn-secondary", scale=0)
- with gr.Tabs():
+ # ── Tabs ────────────────────────────────────────────────────────────
+ with gr.Tabs() as main_tabs:
- # ── TAB 1: TODAY ──────────────────���─────────────────────────────
- with gr.Tab("\U0001f4c5 Today"):
+ # ─────────────────────────────────────────────────────────────
+ # TAB 1: TODAY
+ # ─────────────────────────────────────────────────────────────
+ with gr.Tab("📅 Today"):
+ # Stats row
with gr.Row():
- stat_total = gr.HTML(_stat("\u2014","Today","#a78bfa"))
- stat_done = gr.HTML(_stat("\u2014","Done","#4ade80"))
- stat_remain = gr.HTML(_stat("\u2014","Left","#f87171"))
+ 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("\u270f\ufe0f Add Task (Text)", elem_classes="btn-primary", scale=3)
- btn_add_voice = gr.Button("\U0001f399\ufe0f Add Task (Voice)", elem_classes="btn-accent", scale=3)
- btn_plan_day = gr.Button("\U0001f9e0 Plan My Tasks", elem_classes="btn-secondary", scale=3)
- btn_refresh = gr.Button("\u21bb", elem_classes="btn-secondary", scale=1)
+ 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 capture panel ────────────────────────────────────
+ # ── 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 proposal by Friday, it's really urgent", lines=2)
+ 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("\U0001f916 Parse with AI", elem_classes="btn-primary")
- btn_cancel_text = gr.Button("Cancel", elem_classes="btn-secondary")
+ 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("")
+ # ── Clarification reply row (shown only when AI has questions) ──
with gr.Column(visible=False) as clar_reply_row:
- gr.HTML('
Answer the questions above
')
- with gr.Row():
- clar_text_input = gr.Textbox(label="Type your answer", lines=2, scale=4)
- clar_audio_input = gr.Audio(sources=["microphone"], type="filepath", label="\U0001f3a4", scale=1)
- btn_clar_submit = gr.Button("\U0001f504 Re-classify", elem_classes="btn-accent")
+ gr.HTML('
Your Answer
')
+ clar_reply_input = gr.Textbox(
+ label="",
+ placeholder="e.g. It's for a client deadline, very high priority, should take about 2 hours",
+ lines=2
+ )
+ btn_clar_submit = gr.Button("🔄 Re-classify with my answer", elem_classes="btn-accent")
gr.HTML('
Review & Confirm
')
- gr.HTML('
Deadline is auto-extracted from your text. Date will be assigned by the AI scheduler.
')
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, allow_custom_value=True)
+ f_area = gr.Dropdown(label="Life Area",
+ choices=["Work","Health","Finance","Learning","Personal","Family","Other"],
+ scale=1, allow_custom_value=True)
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")
+ 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_deadline = gr.Textbox(label="\U0001f3af Deadline (YYYY-MM-DD)", placeholder="auto-filled or type", scale=2)
- f_is_habit = gr.Checkbox(label="\u267b\ufe0f Habit", scale=1)
- f_interval = gr.Dropdown(label="Recurs", choices=["Daily","Weekly","Weekdays","Weekends","Monthly"], value="Daily", visible=False, scale=1)
+ 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("\u2713 Save Task", elem_classes="btn-success")
- btn_discard = gr.Button("\u2717 Discard", elem_classes="btn-danger")
+ btn_confirm = gr.Button("✓ Save Task", elem_classes="btn-success")
+ btn_discard = gr.Button("✗ Discard", elem_classes="btn-danger")
save_msg = gr.HTML("")
- # ── Voice capture panel ───────────────────────────────────
+ # ── Voice add panel ───────────────────────────────────────
with gr.Column(visible=False, elem_classes="panel") as voice_panel:
- gr.HTML('
Add Task by Voice
')
- gr.HTML('
Say your task naturally including any deadline \u2014 AI extracts it all.
')
- voice_audio_input = gr.Audio(sources=["microphone","upload"], type="filepath", label="")
+ 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_voice_parse = gr.Button("\U0001f916 Transcribe & Classify", elem_classes="btn-primary")
- btn_cancel_voice = gr.Button("Cancel", elem_classes="btn-secondary")
- voice_status = gr.HTML("")
- with gr.Column(visible=False) as voice_parsed_panel:
- voice_transcribed_txt = gr.Textbox(label="Transcribed text (editable)", lines=1)
- voice_clar_html = gr.HTML("")
- with gr.Column(visible=False) as voice_clar_row:
- gr.HTML('
Answer the questions above
')
- with gr.Row():
- vclar_text = gr.Textbox(label="Type your answer", lines=2, scale=4)
- vclar_audio = gr.Audio(sources=["microphone"], type="filepath", label="\U0001f3a4", scale=1)
- btn_vclar_submit = gr.Button("\U0001f504 Re-classify", elem_classes="btn-accent")
- gr.HTML('
Review & Confirm
')
- with gr.Row():
- vf_title = gr.Textbox(label="Title", scale=3)
- vf_area = gr.Dropdown(label="Life Area", choices=["Work","Health","Finance","Learning","Personal","Family","Other"], scale=1, allow_custom_value=True)
- with gr.Row():
- vf_urgency = gr.Dropdown(label="Urgency", choices=["Urgent","Not Urgent","Habit"], value="Not Urgent")
- vf_importance = gr.Dropdown(label="Importance", choices=["Move the Needle","Important","Not Important"], value="Important")
- vf_state = gr.Dropdown(label="State of Mind", choices=["Flow","Easy","Quick","Personal"], value="Easy")
- with gr.Row():
- vf_time = gr.Number(label="Minutes", value=30, minimum=5, scale=1)
- vf_deadline = gr.Textbox(label="\U0001f3af Deadline (YYYY-MM-DD)", placeholder="auto-filled or type", scale=2)
- vf_is_habit = gr.Checkbox(label="\u267b\ufe0f Habit", scale=1)
- vf_interval = gr.Dropdown(label="Recurs", choices=["Daily","Weekly","Weekdays","Weekends","Monthly"], value="Daily", visible=False, scale=1)
- with gr.Row():
- btn_voice_confirm = gr.Button("\u2713 Save Task", elem_classes="btn-success")
- btn_voice_discard = gr.Button("\u2717 Discard", elem_classes="btn-danger")
- voice_save_msg = gr.HTML("")
+ 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 Tasks panel ───────────────────────────────────
+ # ── Plan My Day panel ─────────────────────────────────────
with gr.Column(visible=False, elem_classes="panel") as plan_panel:
- gr.HTML('
Plan My Tasks
')
- gr.HTML('
'
- 'The AI reads your unscheduled tasks, patterns, goals and current time \u2014 '
- 'proposes a schedule with time blocks. Review before applying.
')
+ 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="Tell the AI what you want",
- placeholder="e.g. 'Clear today, schedule from tomorrow' or 'Focus on Work tasks this week, I have Thursday free'",
- lines=2
+ 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("\U0001f9e0 Generate Proposal", elem_classes="btn-primary")
- btn_cancel_plan = gr.Button("Cancel", elem_classes="btn-secondary")
+ 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("")
- # Confirm/Refine row — hidden until proposal generated
- with gr.Column(visible=False) as sched_confirm_row:
- gr.HTML('
'
- '\u26a0 Review the proposal above. Confirm to apply, or type a refinement.
')
- with gr.Row():
- btn_sched_confirm = gr.Button("\u2713 Confirm & Apply", elem_classes="btn-success", scale=2)
- btn_sched_cancel = gr.Button("\u2717 Discard", elem_classes="btn-danger", scale=1)
- refine_prompt = gr.Textbox(label="Or refine (optional)", placeholder="e.g. 'Move the Flow tasks to morning'", lines=1)
- btn_refine = gr.Button("\U0001f504 Re-generate with refinement", elem_classes="btn-accent")
- sched_status = gr.HTML("")
-
+ # ── Today's task table ────────────────────────────────────
gr.HTML('
Today\'s Tasks
')
today_df = gr.Dataframe(headers=TASK_HEADERS, interactive=False, wrap=True)
- gr.HTML('
Enter task ID then click action:
')
with gr.Row():
- today_task_id = gr.Number(label="Task ID", precision=0, scale=1, minimum=1)
- btn_today_done = gr.Button("\u2705 Mark Done", elem_classes="btn-success", scale=2)
- btn_today_del = gr.Button("\U0001f5d1 Delete", elem_classes="btn-danger", scale=2)
+ 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("\U0001f4cb All Tasks"):
- gr.HTML('
All tasks including unscheduled. Enter ID then click action.
')
+ # ─────────────────────────────────────────────────────────────
+ # 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=3)
- all_today_chk = gr.Checkbox(label="Today only", value=False, scale=1)
- all_unsched = gr.Checkbox(label="Unscheduled only", value=False, scale=1)
- btn_all_ref = gr.Button("\u21bb Refresh", elem_classes="btn-secondary", scale=1)
+ 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_task_id = gr.Number(label="Task ID", precision=0, scale=1, minimum=1)
- btn_all_done = gr.Button("\u2705 Done", elem_classes="btn-success", scale=2)
- btn_all_del = gr.Button("\U0001f5d1 Delete", elem_classes="btn-danger", scale=2)
+ 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("\U0001f4d3 Journal"):
- gr.HTML('
End-of-day reflection. Chat naturally \u2014 AI marks tasks, asks questions, updates your profile.
')
- journal_chatbot = gr.Chatbot(label="", height=440)
- with gr.Row():
- j_answer = gr.Textbox(label="", placeholder="Type or use the mic\u2026", lines=1, scale=5, interactive=False, show_label=False)
- j_audio_in = gr.Audio(sources=["microphone"], type="filepath", label="\U0001f3a4", scale=1, interactive=False)
- btn_j_send = gr.Button("Send \u2192", elem_classes="btn-accent", scale=1, interactive=False)
- with gr.Row():
- btn_j_start = gr.Button("\u25b6 Start Session", elem_classes="btn-primary", scale=3)
- btn_j_finish = gr.Button("\u2713 Finish & Save", elem_classes="btn-success", scale=3)
- btn_j_restart = gr.Button("\u21ba Reset", elem_classes="btn-secondary", scale=1)
- journal_msg = gr.HTML("")
-
- # ── TAB 4: AREAS & GOALS ──────────────────────────────────────
- with gr.Tab("\U0001f5c2 Areas & Goals"):
+ # ─────────────────────────────────────────────────────────────
+ # 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)
+ 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("")
- gr.HTML('
To remove an area, select it from the dropdown and click Remove:
')
- with gr.Row():
- del_area_dd = gr.Dropdown(label="Select area to remove", choices=[], scale=4, allow_custom_value=False)
- btn_del_area = gr.Button("\u2717 Remove", elem_classes="btn-danger", scale=1)
- del_area_msg = gr.HTML("")
- gr.HTML('
Add New Area
')
+
with gr.Row():
- new_area_name = gr.Textbox(label="Name", placeholder="e.g. Side Project", scale=3)
+ 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)
+ 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 factors these into scheduling and prioritisation.
')
- 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("\u2699\ufe0f Preferences"):
+ 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)
+ 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('
Stats, charts, and insights the AI has learned about you. Updates after each journal session.
')
- ctx_display = gr.HTML("")
- btn_ref_ctx = gr.Button("\u21bb Refresh", elem_classes="btn-secondary")
+ 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
- # =========================================================================
+ # ═══════════════════════════════════════════════════════════════════════
- def _header_html(uid, uname):
- return f'
\U0001f9e0 Second Brain
'
+ # ── Auth ──────────────────────────────────────────────────────────────
- # Auth
- login_btn.click(handle_login, [login_user_in, login_pass_in],
+ 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(_header_html, [user_id_state, username_state], [header_html]
- ).then(refresh_today, [user_id_state], [today_df, stat_total, stat_done, stat_remain]
- ).then(refresh_all_tasks_auto, [user_id_state], [all_df]
- ).then(lambda uid: render_areas_list(uid), [user_id_state], [areas_display, del_area_dd]
- ).then(load_prefs, [user_id_state], [pref_wake, pref_sleep, pref_focus, pref_break, pref_flow_max]
- ).then(load_goals_txt, [user_id_state], [goals_input])
-
- reg_btn.click(handle_register, [reg_user_in, reg_pass_in, reg_wake, reg_sleep, reg_focus, reg_goals],
+ ).then(
+ lambda uid, uname: f'
🧠 Second Brain
',
+ [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(_header_html, [user_id_state, username_state], [header_html]
- ).then(refresh_today, [user_id_state], [today_df, stat_total, stat_done, stat_remain]
- ).then(refresh_all_tasks_auto, [user_id_state], [all_df]
- ).then(lambda uid: render_areas_list(uid), [user_id_state], [areas_display, del_area_dd])
+ ).then(
+ lambda uid, uname: f'
🧠 Second Brain
',
+ [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]
+ )
- logout_btn.click(handle_logout, [user_id_state], [user_id_state, username_state, auth_section, app_section])
+ # ── Today tab ─────────────────────────────────────────────────────────
- # Panels
- btn_add_text.click(show_text_panel, outputs=[text_panel, voice_panel, plan_panel])
+ 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])
- f_is_habit.change(lambda v: gr.update(visible=v), [f_is_habit], [f_interval])
- vf_is_habit.change(lambda v: gr.update(visible=v), [vf_is_habit], [vf_interval])
+ 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]
+ )
- # ── Text task flow ────────────────────────────────────────────────────
- _TEXT_PARSE_OUT = [parsed_panel, f_title, clar_html, f_area, f_urgency, f_importance,
- f_state, f_time, f_deadline, f_is_habit, f_interval, parse_status,
- clar_reply_row, clar_questions_state, original_task_state]
- _TEXT_CLAR_OUT = [f_title, clar_html, f_area, f_urgency, f_importance,
- f_state, f_time, f_deadline, clar_reply_row, clar_questions_state]
+ 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,
+ clar_reply_row, clar_questions_state, original_task_state]
+ )
- btn_parse.click(handle_parse_text, [task_input, user_id_state], _TEXT_PARSE_OUT)
btn_clar_submit.click(
handle_clarification_reply,
- [clar_text_input, original_task_state, clar_questions_state, user_id_state],
- _TEXT_CLAR_OUT
- ).then(lambda: ("", None), outputs=[clar_text_input, clar_audio_input])
- clar_audio_input.change(
- handle_clar_voice,
- [clar_audio_input, original_task_state, clar_questions_state, user_id_state],
- _TEXT_CLAR_OUT
- ).then(lambda: None, outputs=[clar_audio_input])
- 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_deadline, f_is_habit, f_interval],
- [save_msg, today_df, stat_total, stat_done, stat_remain, all_df, parsed_panel]
+ [clar_reply_input, original_task_state, clar_questions_state, user_id_state],
+ [f_title, clar_html, f_area, f_urgency, f_importance,
+ f_state, f_time, clar_reply_row, clar_questions_state]
+ ).then(
+ lambda: "",
+ outputs=[clar_reply_input]
)
- # ── Voice task flow ───────────────────────────────────────────────────
- _VOICE_PARSE_OUT = [voice_parsed_panel, voice_transcribed_txt,
- voice_clar_html, vf_area, vf_urgency, vf_importance,
- vf_state, vf_time, vf_deadline, vf_is_habit, vf_interval,
- voice_status, voice_clar_row, vclar_questions_state, voriginal_task_state]
- _VOICE_CLAR_OUT = [vf_title, voice_clar_html, vf_area, vf_urgency, vf_importance,
- vf_state, vf_time, vf_deadline, voice_clar_row, vclar_questions_state]
+ btn_transcribe.click(
+ handle_transcribe_voice,
+ [audio_input, user_id_state],
+ [transcribed, voice_msg]
+ )
- btn_voice_parse.click(handle_voice_parse, [voice_audio_input, user_id_state], _VOICE_PARSE_OUT)
- btn_vclar_submit.click(
- handle_clarification_reply,
- [vclar_text, voriginal_task_state, vclar_questions_state, user_id_state],
- _VOICE_CLAR_OUT
- ).then(lambda: ("", None), outputs=[vclar_text, vclar_audio])
- vclar_audio.change(
- handle_clar_voice,
- [vclar_audio, voriginal_task_state, vclar_questions_state, user_id_state],
- _VOICE_CLAR_OUT
- ).then(lambda: None, outputs=[vclar_audio])
- btn_voice_discard.click(lambda: gr.update(visible=False), outputs=[voice_parsed_panel])
- btn_voice_confirm.click(
- handle_confirm_task,
- [user_id_state, vf_title, vf_area, vf_urgency, vf_importance, vf_state, vf_time, vf_deadline, vf_is_habit, vf_interval],
- [voice_save_msg, today_df, stat_total, stat_done, stat_remain, all_df, voice_parsed_panel]
+ btn_discard.click(
+ lambda: gr.update(visible=False),
+ outputs=[parsed_panel]
)
- # ── Scheduler with confirm flow ───────────────────────────────────────
- # Step 1: generate proposal (5 outputs)
- _SCHED_PROP_OUTS = [schedule_html, sched_pending_state, sched_confirm_row, sched_confirm_row, all_df]
+ 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_smart_schedule,
+ handle_generate_schedule,
[user_id_state, plan_prompt],
- [schedule_html, sched_pending_state, sched_confirm_row, btn_sched_confirm, all_df]
+ [schedule_html]
)
- # Step 2a: confirm → apply
- btn_sched_confirm.click(
- handle_confirm_schedule,
- [user_id_state, sched_pending_state, all_df],
- [sched_status, all_df, sched_confirm_row, btn_sched_confirm]
- ).then(refresh_today, [user_id_state], [today_df, stat_total, stat_done, stat_remain])
-
- # Step 2b: discard
- btn_sched_cancel.click(
- lambda: ("", gr.update(visible=False), gr.update(visible=False)),
- outputs=[schedule_html, sched_confirm_row, btn_sched_confirm]
- )
-
- # Step 2c: refine
- btn_refine.click(
- handle_refine_schedule,
- [user_id_state, refine_prompt, sched_pending_state],
- [schedule_html, sched_pending_state, sched_confirm_row, btn_sched_confirm, all_df]
- ).then(lambda: "", outputs=[refine_prompt])
-
- # ── Today task actions ────────────────────────────────────────────────
btn_today_done.click(
- handle_toggle_today_id, [today_task_id, user_id_state],
+ 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_id, [today_task_id, user_id_state],
+ handle_delete_today,
+ [today_del_id, user_id_state],
[today_df, stat_total, stat_done, stat_remain, today_action_msg]
)
- # ── All Tasks ─────────────────────────────────────────────────────��───
- def refresh_all_filtered(user_id, filter_area, only_today, only_unsched):
- if not user_id: return []
- return _fmt_tasks(get_tasks(user_id, filter_area=filter_area, only_today=only_today, only_unscheduled=only_unsched))
-
- btn_all_ref.click(refresh_all_filtered, [user_id_state, all_filter, all_today_chk, all_unsched], [all_df])
- all_filter.change(refresh_all_filtered, [user_id_state, all_filter, all_today_chk, all_unsched], [all_df])
- all_today_chk.change(refresh_all_filtered, [user_id_state, all_filter, all_today_chk, all_unsched], [all_df])
- all_unsched.change(refresh_all_filtered, [user_id_state, all_filter, all_today_chk, all_unsched], [all_df])
-
- btn_all_done.click(handle_toggle_all_id, [all_task_id, user_id_state], [all_df, all_action_msg])
- btn_all_del.click(handle_delete_all_id, [all_task_id, user_id_state], [all_df, all_action_msg])
-
- # ── Journal ───────────────────────────────────────────────────────────
- _J_START_OUT = [journal_chatbot, journal_hist_state, journal_active,
- j_answer, btn_j_send, j_audio_in, journal_tasks_state,
- journal_phase_state, journal_msg]
- _J_MSG_IN = [user_id_state, j_answer, j_audio_in, journal_chatbot,
- journal_hist_state, journal_tasks_state, journal_active, journal_phase_state]
- _J_MSG_OUT = [journal_chatbot, journal_hist_state, j_answer, j_audio_in,
- journal_tasks_state, journal_active, journal_phase_state, journal_msg]
-
- btn_j_start.click(handle_start_journal, [user_id_state], _J_START_OUT)
- btn_j_send.click(handle_journal_message, _J_MSG_IN, _J_MSG_OUT)
- j_answer.submit(handle_journal_message, _J_MSG_IN, _J_MSG_OUT)
- j_audio_in.change(handle_journal_message, _J_MSG_IN, _J_MSG_OUT)
+ # ── 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=_J_START_OUT)
- # ── Areas & Goals ───────────────────────────────────────���─────────────
+ 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],
+ 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])
+ ).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]
- ).then(lambda uid: render_areas_list(uid)[1], [user_id_state], [del_area_dd]
- ).then(lambda uid: gr.update(choices=_area_choices(uid), value="All"), [user_id_state], [all_filter])
+ 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]
+ )
- btn_save_goals.click(handle_save_goals, [user_id_state, goals_input], [goals_msg])
+ # ── Preferences tab ───────────────────────────────────────────────────
- # ── Preferences ───────────────────────────────────────────────────────
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])
+ 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, css=CSS)
\ No newline at end of file