Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -15,6 +15,7 @@ HuggingFace Spaces:
|
|
| 15 |
"""
|
| 16 |
|
| 17 |
import os
|
|
|
|
| 18 |
import gradio as gr
|
| 19 |
from datetime import date, datetime
|
| 20 |
|
|
@@ -23,7 +24,7 @@ from core.database import (
|
|
| 23 |
init_db, register_user, login_user, get_username,
|
| 24 |
create_default_life_areas, save_goals, get_goals,
|
| 25 |
get_life_areas, get_life_area_names, add_life_area, delete_life_area,
|
| 26 |
-
get_tasks, save_task, toggle_task_complete, delete_task, get_today_stats,
|
| 27 |
load_user_context, save_user_context, spawn_due_habits,
|
| 28 |
)
|
| 29 |
from core.ai_engine import (
|
|
@@ -37,7 +38,7 @@ from core.styles import CSS
|
|
| 37 |
init_db()
|
| 38 |
init_groq() # reads GROQ_API_KEY from env
|
| 39 |
|
| 40 |
-
TASK_HEADERS = ["ID", "Done", "Title", "Area", "Urgency", "Importance", "Mind", "Min", "
|
| 41 |
|
| 42 |
|
| 43 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -52,18 +53,33 @@ def _stat(val, label, color):
|
|
| 52 |
return f'<div class="stat-card"><div class="stat-num" style="color:{color}">{val}</div><div class="stat-label">{label}</div></div>'
|
| 53 |
|
| 54 |
def _fmt_tasks(tasks: list) -> list:
|
|
|
|
| 55 |
rows = []
|
| 56 |
for t in tasks:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
rows.append([
|
| 58 |
t["id"],
|
| 59 |
"β
" if t["is_completed"] else "β¬",
|
| 60 |
("π " if t["is_habit"] else "") + t["title"],
|
| 61 |
-
t["life_area"]
|
| 62 |
-
t["urgency"]
|
| 63 |
-
t["importance"]
|
| 64 |
t["state_of_mind"] or "β",
|
| 65 |
str(t["time_estimate"]) + "m" if t["time_estimate"] else "β",
|
| 66 |
-
|
|
|
|
| 67 |
])
|
| 68 |
return rows
|
| 69 |
|
|
@@ -174,6 +190,9 @@ def handle_parse_text(task_text, user_id):
|
|
| 174 |
if area_val not in area_choices:
|
| 175 |
area_choices = [area_val] + area_choices
|
| 176 |
|
|
|
|
|
|
|
|
|
|
| 177 |
return (
|
| 178 |
gr.update(visible=True),
|
| 179 |
result.get("title", task_text),
|
|
@@ -183,13 +202,13 @@ def handle_parse_text(task_text, user_id):
|
|
| 183 |
result.get("importance", "Important"),
|
| 184 |
result.get("state_of_mind", "Easy"),
|
| 185 |
int(result.get("time_estimate") or 30),
|
| 186 |
-
|
| 187 |
False,
|
| 188 |
"Daily",
|
| 189 |
-
_ok("AI classified
|
| 190 |
-
gr.update(visible=has_clarifications),
|
| 191 |
-
clarifications,
|
| 192 |
-
task_text,
|
| 193 |
)
|
| 194 |
|
| 195 |
|
|
@@ -264,69 +283,121 @@ _whisper_model = None
|
|
| 264 |
|
| 265 |
|
| 266 |
def handle_confirm_task(user_id, title, area, urgency, importance, state,
|
| 267 |
-
time_est,
|
| 268 |
if not user_id:
|
| 269 |
-
return _err("Not logged in."), [], _stat("β","Today","#a78bfa"), _stat("β","Done","#4ade80"), _stat("β","Left","#f87171"), gr.update(visible=False)
|
| 270 |
if not title.strip():
|
| 271 |
-
return _err("Title cannot be empty."), [], _stat("β","Today","#a78bfa"), _stat("β","Done","#4ade80"), _stat("β","Left","#f87171"), gr.update(visible=True)
|
| 272 |
|
| 273 |
task = {
|
| 274 |
"title": title, "life_area": area,
|
| 275 |
"urgency": urgency, "importance": importance, "state_of_mind": state,
|
| 276 |
"time_estimate": int(time_est or 30),
|
|
|
|
| 277 |
"is_habit": is_habit,
|
| 278 |
"habit_interval": habit_interval if is_habit else "",
|
| 279 |
}
|
| 280 |
-
save_task(user_id, task,
|
| 281 |
rows, s1, s2, s3 = refresh_today(user_id)
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
def handle_generate_schedule(user_id, prompt):
|
| 286 |
-
if not user_id:
|
| 287 |
-
return _err("Not logged in.")
|
| 288 |
-
tasks = get_tasks(user_id, only_today=True, include_completed=False)
|
| 289 |
-
if not tasks:
|
| 290 |
-
return _err("No incomplete tasks today to schedule.")
|
| 291 |
-
|
| 292 |
-
ctx = _ensure_context(user_id)
|
| 293 |
-
goals = get_goals(user_id)
|
| 294 |
-
sched = generate_schedule(ctx, tasks, prompt or "Schedule my day sensibly.", goals)
|
| 295 |
|
| 296 |
-
if "error" in sched and "scheduled_tasks" not in sched:
|
| 297 |
-
return _err(f"Scheduling failed: {sched.get('error')}")
|
| 298 |
|
|
|
|
| 299 |
COLOR = {"Flow": "#0ea5e9", "Easy": "#4ade80", "Quick": "#a78bfa", "Personal": "#f87171"}
|
| 300 |
-
html
|
| 301 |
-
f'<div style="margin-bottom:
|
| 302 |
-
f'<
|
| 303 |
-
f'<
|
| 304 |
f'</div>'
|
| 305 |
)
|
| 306 |
-
|
| 307 |
for t in sched.get("scheduled_tasks", []):
|
| 308 |
sm = t.get("state_of_mind", "Easy")
|
| 309 |
c = COLOR.get(sm, "#6366f1")
|
| 310 |
html += (
|
| 311 |
-
f'<div class="sched-card" style="border-left-color:{c}">'
|
| 312 |
-
f'<div class="sched-time">{t.get("start_time")} β {t.get("end_time")}</div>'
|
| 313 |
-
f'<div class="sched-title">{t.get("title")}</div>'
|
| 314 |
-
f'<div class="sched-meta">{t.get("life_area","β")} Β· {sm} Β· {t.get("duration_minutes")}min</div>'
|
| 315 |
f'<div class="sched-why">{t.get("scheduling_reason","")}</div>'
|
| 316 |
f'</div>'
|
| 317 |
)
|
| 318 |
-
|
| 319 |
if sched.get("deferred_tasks"):
|
| 320 |
-
html += '<div style="
|
| 321 |
for t in sched["deferred_tasks"]:
|
| 322 |
-
html += f'<div style="color:#475569;font-size:12px;padding:
|
| 323 |
-
|
| 324 |
for w in sched.get("warnings", []):
|
| 325 |
-
html += f'<div style="
|
| 326 |
-
|
| 327 |
return html
|
| 328 |
|
| 329 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
def handle_toggle_today(task_id, user_id):
|
| 331 |
if task_id and user_id:
|
| 332 |
toggle_task_complete(int(task_id), user_id)
|
|
@@ -423,16 +494,8 @@ def handle_start_journal(user_id, tasks_state):
|
|
| 423 |
if not tasks_state:
|
| 424 |
return [], [], False, gr.update(interactive=False), gr.update(interactive=False), _err("Load today's tasks first.")
|
| 425 |
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
opening = build_opening_question(ctx, tasks_state)
|
| 429 |
-
except Exception as e:
|
| 430 |
-
return (
|
| 431 |
-
[{"role": "assistant", "content": f"β Could not start session: {e}"}],
|
| 432 |
-
[], False,
|
| 433 |
-
gr.update(interactive=False), gr.update(interactive=False),
|
| 434 |
-
_err(f"Start failed: {e}")
|
| 435 |
-
)
|
| 436 |
chat = [{"role": "assistant", "content": opening["question"]}]
|
| 437 |
hist = [{"role": "assistant", "content": opening["question"], "focus": opening["question_focus"]}]
|
| 438 |
|
|
@@ -448,24 +511,17 @@ def handle_send_answer(user_id, answer, chat, hist, tasks_state, active):
|
|
| 448 |
if not active or not answer.strip():
|
| 449 |
return chat, hist, "", ""
|
| 450 |
|
| 451 |
-
# Append user message as dict (Gradio 4.44 requires dict format for Chatbot)
|
| 452 |
chat = list(chat) + [{"role": "user", "content": answer}]
|
| 453 |
hist = list(hist) + [{"role": "user", "content": answer}]
|
| 454 |
|
| 455 |
-
ctx
|
| 456 |
-
|
| 457 |
-
result = get_next_journal_question(ctx, tasks_state, hist)
|
| 458 |
-
except Exception as e:
|
| 459 |
-
chat = list(chat) + [{"role": "assistant", "content": f"β AI error: {e}. Please try again."}]
|
| 460 |
-
return chat, hist, "", _err(f"AI error: {e}")
|
| 461 |
|
| 462 |
if result.get("session_complete"):
|
| 463 |
-
|
| 464 |
-
hist.append({"role": "assistant", "content": bot_reply})
|
| 465 |
-
chat = list(chat) + [{"role": "assistant", "content": bot_reply}]
|
| 466 |
return chat, hist, "", _ok("Session complete β save when ready")
|
| 467 |
|
| 468 |
-
next_q = result.get("question", "
|
| 469 |
hist.append({"role": "assistant", "content": next_q, "focus": result.get("question_focus", "")})
|
| 470 |
chat = list(chat) + [{"role": "assistant", "content": next_q}]
|
| 471 |
return chat, hist, "", ""
|
|
@@ -475,12 +531,9 @@ def handle_finish_journal(user_id, chat, hist, tasks_state):
|
|
| 475 |
if not user_id or not hist:
|
| 476 |
return chat, _err("Nothing to save.")
|
| 477 |
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
save_user_context(user_id, updated)
|
| 482 |
-
except Exception as e:
|
| 483 |
-
return chat, _err(f"Save failed: {e}")
|
| 484 |
|
| 485 |
total = len(tasks_state)
|
| 486 |
done = sum(1 for t in tasks_state if t.get("completed"))
|
|
@@ -654,6 +707,76 @@ def render_context_html(user_id):
|
|
| 654 |
return html
|
| 655 |
|
| 656 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 657 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 658 |
# UI BUILD
|
| 659 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -763,7 +886,7 @@ with gr.Blocks(title="π§ Second Brain") as demo:
|
|
| 763 |
f_title = gr.Textbox(label="Title", scale=3)
|
| 764 |
f_area = gr.Dropdown(label="Life Area",
|
| 765 |
choices=["Work","Health","Finance","Learning","Personal","Family","Other"],
|
| 766 |
-
scale=1
|
| 767 |
with gr.Row():
|
| 768 |
f_urgency = gr.Dropdown(label="Urgency",
|
| 769 |
choices=["Urgent","Not Urgent","Habit"], value="Not Urgent")
|
|
@@ -774,7 +897,7 @@ with gr.Blocks(title="π§ Second Brain") as demo:
|
|
| 774 |
choices=["Flow","Easy","Quick","Personal"], value="Easy")
|
| 775 |
with gr.Row():
|
| 776 |
f_time = gr.Number(label="Minutes", value=30, minimum=5, scale=1)
|
| 777 |
-
f_date = gr.Textbox(label="
|
| 778 |
f_is_habit = gr.Checkbox(label="β»οΈ This is a habit", scale=1)
|
| 779 |
f_interval = gr.Dropdown(label="Recurs",
|
| 780 |
choices=["Daily","Weekly","Weekdays","Weekends","Monthly"],
|
|
@@ -799,16 +922,34 @@ with gr.Blocks(title="π§ Second Brain") as demo:
|
|
| 799 |
# ββ Plan My Day panel βββββββββββββββββββββββββββββββββββββ
|
| 800 |
with gr.Column(visible=False, elem_classes="panel") as plan_panel:
|
| 801 |
gr.HTML('<div class="sec-label">Plan My Day</div>')
|
| 802 |
-
gr.HTML('<p style="color:#
|
| 803 |
plan_prompt = gr.Textbox(
|
| 804 |
-
label="
|
| 805 |
-
placeholder="e.g.
|
| 806 |
-
lines=
|
| 807 |
)
|
| 808 |
with gr.Row():
|
| 809 |
-
btn_gen_sched
|
| 810 |
btn_cancel_plan = gr.Button("Cancel", elem_classes="btn-secondary")
|
| 811 |
-
schedule_html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 812 |
|
| 813 |
# ββ Today's task table ββββββββββββββββββββββββββββββββββββ
|
| 814 |
gr.HTML('<div class="sec-label" style="margin-top:20px">Today\'s Tasks</div>')
|
|
@@ -838,7 +979,16 @@ with gr.Blocks(title="π§ Second Brain") as demo:
|
|
| 838 |
all_action_msg = gr.HTML("")
|
| 839 |
|
| 840 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 841 |
-
# TAB 3:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 842 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 843 |
with gr.Tab("π Journal"):
|
| 844 |
gr.HTML('<p style="color:#334155;font-size:13px;margin-bottom:16px">Mark today\'s tasks complete, then reflect. The AI updates your profile to improve tomorrow\'s schedule.</p>')
|
|
@@ -940,6 +1090,11 @@ with gr.Blocks(title="π§ Second Brain") as demo:
|
|
| 940 |
).then(
|
| 941 |
refresh_today, [user_id_state],
|
| 942 |
[today_df, stat_total, stat_done, stat_remain]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 943 |
).then(
|
| 944 |
lambda uid: render_areas(uid)[0:2],
|
| 945 |
[user_id_state], [areas_display, del_area_dd]
|
|
@@ -960,6 +1115,11 @@ with gr.Blocks(title="π§ Second Brain") as demo:
|
|
| 960 |
).then(
|
| 961 |
refresh_today, [user_id_state],
|
| 962 |
[today_df, stat_total, stat_done, stat_remain]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 963 |
).then(
|
| 964 |
lambda uid: render_areas(uid)[0:2],
|
| 965 |
[user_id_state], [areas_display, del_area_dd]
|
|
@@ -1024,25 +1184,56 @@ with gr.Blocks(title="π§ Second Brain") as demo:
|
|
| 1024 |
handle_confirm_task,
|
| 1025 |
[user_id_state, f_title, f_area, f_urgency, f_importance, f_state,
|
| 1026 |
f_time, f_date, f_is_habit, f_interval],
|
| 1027 |
-
[save_msg, today_df, stat_total, stat_done, stat_remain, parsed_panel]
|
| 1028 |
-
)
|
| 1029 |
|
| 1030 |
btn_gen_sched.click(
|
| 1031 |
handle_generate_schedule,
|
| 1032 |
[user_id_state, plan_prompt],
|
| 1033 |
-
[schedule_html]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1034 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1035 |
|
| 1036 |
btn_today_done.click(
|
| 1037 |
handle_toggle_today,
|
| 1038 |
[today_done_id, user_id_state],
|
| 1039 |
[today_df, stat_total, stat_done, stat_remain, today_action_msg]
|
| 1040 |
-
)
|
|
|
|
|
|
|
|
|
|
| 1041 |
btn_today_del.click(
|
| 1042 |
handle_delete_today,
|
| 1043 |
[today_del_id, user_id_state],
|
| 1044 |
[today_df, stat_total, stat_done, stat_remain, today_action_msg]
|
| 1045 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1046 |
|
| 1047 |
# ββ All Tasks tab ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1048 |
|
|
@@ -1065,12 +1256,12 @@ with gr.Blocks(title="π§ Second Brain") as demo:
|
|
| 1065 |
handle_toggle_all,
|
| 1066 |
[all_done_id, user_id_state, all_filter, all_today_chk],
|
| 1067 |
[all_df, all_action_msg]
|
| 1068 |
-
)
|
| 1069 |
btn_all_del.click(
|
| 1070 |
handle_delete_all,
|
| 1071 |
[all_del_id, user_id_state, all_filter, all_today_chk],
|
| 1072 |
[all_df, all_action_msg]
|
| 1073 |
-
)
|
| 1074 |
|
| 1075 |
# ββ Journal tab ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1076 |
|
|
|
|
| 15 |
"""
|
| 16 |
|
| 17 |
import os
|
| 18 |
+
import json as _json
|
| 19 |
import gradio as gr
|
| 20 |
from datetime import date, datetime
|
| 21 |
|
|
|
|
| 24 |
init_db, register_user, login_user, get_username,
|
| 25 |
create_default_life_areas, save_goals, get_goals,
|
| 26 |
get_life_areas, get_life_area_names, add_life_area, delete_life_area,
|
| 27 |
+
get_tasks, save_task, assign_task_date, toggle_task_complete, delete_task, get_today_stats,
|
| 28 |
load_user_context, save_user_context, spawn_due_habits,
|
| 29 |
)
|
| 30 |
from core.ai_engine import (
|
|
|
|
| 38 |
init_db()
|
| 39 |
init_groq() # reads GROQ_API_KEY from env
|
| 40 |
|
| 41 |
+
TASK_HEADERS = ["ID", "Done", "Title", "Area", "Urgency", "Importance", "Mind", "Min", "Deadline", "Scheduled"]
|
| 42 |
|
| 43 |
|
| 44 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 53 |
return f'<div class="stat-card"><div class="stat-num" style="color:{color}">{val}</div><div class="stat-label">{label}</div></div>'
|
| 54 |
|
| 55 |
def _fmt_tasks(tasks: list) -> list:
|
| 56 |
+
_today = date.today()
|
| 57 |
rows = []
|
| 58 |
for t in tasks:
|
| 59 |
+
dl = (t.get("deadline_date") or "").strip()
|
| 60 |
+
if dl and dl.lower() not in ("null", "none", ""):
|
| 61 |
+
try:
|
| 62 |
+
diff = (date.fromisoformat(dl) - _today).days
|
| 63 |
+
if diff < 0: dl = f"β {dl}"
|
| 64 |
+
elif diff == 0: dl = "π¨ TODAY"
|
| 65 |
+
elif diff == 1: dl = "β οΈ tomorrow"
|
| 66 |
+
elif diff <= 3: dl = f"β° {dl}"
|
| 67 |
+
except ValueError:
|
| 68 |
+
pass
|
| 69 |
+
else:
|
| 70 |
+
dl = "β"
|
| 71 |
+
sched = (t.get("scheduled_date") or "").strip()
|
| 72 |
rows.append([
|
| 73 |
t["id"],
|
| 74 |
"β
" if t["is_completed"] else "β¬",
|
| 75 |
("π " if t["is_habit"] else "") + t["title"],
|
| 76 |
+
t["life_area"] or "β",
|
| 77 |
+
t["urgency"] or "β",
|
| 78 |
+
t["importance"] or "β",
|
| 79 |
t["state_of_mind"] or "β",
|
| 80 |
str(t["time_estimate"]) + "m" if t["time_estimate"] else "β",
|
| 81 |
+
dl,
|
| 82 |
+
sched if sched else "β¬ unscheduled",
|
| 83 |
])
|
| 84 |
return rows
|
| 85 |
|
|
|
|
| 190 |
if area_val not in area_choices:
|
| 191 |
area_choices = [area_val] + area_choices
|
| 192 |
|
| 193 |
+
raw_dl = result.get("deadline_date") or ""
|
| 194 |
+
dl_val = "" if str(raw_dl).lower() in ("null", "none", "n/a", "") else str(raw_dl)
|
| 195 |
+
|
| 196 |
return (
|
| 197 |
gr.update(visible=True),
|
| 198 |
result.get("title", task_text),
|
|
|
|
| 202 |
result.get("importance", "Important"),
|
| 203 |
result.get("state_of_mind", "Easy"),
|
| 204 |
int(result.get("time_estimate") or 30),
|
| 205 |
+
dl_val,
|
| 206 |
False,
|
| 207 |
"Daily",
|
| 208 |
+
_ok("AI classified β β answer questions above." if has_clarifications else "AI classified β"),
|
| 209 |
+
gr.update(visible=has_clarifications),
|
| 210 |
+
clarifications,
|
| 211 |
+
task_text,
|
| 212 |
)
|
| 213 |
|
| 214 |
|
|
|
|
| 283 |
|
| 284 |
|
| 285 |
def handle_confirm_task(user_id, title, area, urgency, importance, state,
|
| 286 |
+
time_est, deadline, is_habit, habit_interval):
|
| 287 |
if not user_id:
|
| 288 |
+
return _err("Not logged in."), [], _stat("β","Today","#a78bfa"), _stat("β","Done","#4ade80"), _stat("β","Left","#f87171"), [], gr.update(visible=False)
|
| 289 |
if not title.strip():
|
| 290 |
+
return _err("Title cannot be empty."), [], _stat("β","Today","#a78bfa"), _stat("β","Done","#4ade80"), _stat("β","Left","#f87171"), [], gr.update(visible=True)
|
| 291 |
|
| 292 |
task = {
|
| 293 |
"title": title, "life_area": area,
|
| 294 |
"urgency": urgency, "importance": importance, "state_of_mind": state,
|
| 295 |
"time_estimate": int(time_est or 30),
|
| 296 |
+
"deadline_date": (deadline or "").strip(),
|
| 297 |
"is_habit": is_habit,
|
| 298 |
"habit_interval": habit_interval if is_habit else "",
|
| 299 |
}
|
| 300 |
+
save_task(user_id, task, scheduled_date="") # saved unscheduled β planner assigns date
|
| 301 |
rows, s1, s2, s3 = refresh_today(user_id)
|
| 302 |
+
all_rows = _fmt_tasks(get_tasks(user_id))
|
| 303 |
+
return _ok("β Task saved! Use 'Plan My Day' to schedule it."), rows, s1, s2, s3, all_rows, gr.update(visible=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
|
|
|
|
|
|
|
| 305 |
|
| 306 |
+
def _render_sched_html(sched):
|
| 307 |
COLOR = {"Flow": "#0ea5e9", "Easy": "#4ade80", "Quick": "#a78bfa", "Personal": "#f87171"}
|
| 308 |
+
html = (
|
| 309 |
+
f'<div style="padding:12px;background:#0c1a0c;border-radius:8px;margin-bottom:12px">'
|
| 310 |
+
f'<div style="color:#4ade80;font-size:12px;font-weight:700">π
{sched.get("schedule_date","Today")}</div>'
|
| 311 |
+
f'<div style="color:#94a3b8;font-size:13px;margin-top:4px">{sched.get("day_summary","")}</div>'
|
| 312 |
f'</div>'
|
| 313 |
)
|
|
|
|
| 314 |
for t in sched.get("scheduled_tasks", []):
|
| 315 |
sm = t.get("state_of_mind", "Easy")
|
| 316 |
c = COLOR.get(sm, "#6366f1")
|
| 317 |
html += (
|
| 318 |
+
f'<div class="sched-card" style="border-left-color:{c};margin-bottom:6px">'
|
| 319 |
+
f'<div class="sched-time">{t.get("start_time","?")} β {t.get("end_time","?")}</div>'
|
| 320 |
+
f'<div class="sched-title">{t.get("title","")}</div>'
|
| 321 |
+
f'<div class="sched-meta">{t.get("life_area","β")} Β· {sm} Β· {t.get("duration_minutes","?")}min</div>'
|
| 322 |
f'<div class="sched-why">{t.get("scheduling_reason","")}</div>'
|
| 323 |
f'</div>'
|
| 324 |
)
|
|
|
|
| 325 |
if sched.get("deferred_tasks"):
|
| 326 |
+
html += '<div style="color:#475569;font-size:11px;font-weight:600;margin:10px 0 4px;text-transform:uppercase">Deferred</div>'
|
| 327 |
for t in sched["deferred_tasks"]:
|
| 328 |
+
html += f'<div style="color:#475569;font-size:12px;padding:2px 0">β {t["title"]} β {t.get("reason","")}</div>'
|
|
|
|
| 329 |
for w in sched.get("warnings", []):
|
| 330 |
+
html += f'<div style="color:#f59e0b;font-size:12px;margin-top:6px">β {w}</div>'
|
|
|
|
| 331 |
return html
|
| 332 |
|
| 333 |
|
| 334 |
+
def handle_generate_schedule(user_id, prompt):
|
| 335 |
+
"""Step 1: generate proposal only. Nothing written to DB."""
|
| 336 |
+
if not user_id:
|
| 337 |
+
return _err("Not logged in."), "", gr.update(visible=False)
|
| 338 |
+
tasks = get_tasks(user_id, include_completed=False)
|
| 339 |
+
if not tasks:
|
| 340 |
+
return _err("No tasks found. Add some tasks first."), "", gr.update(visible=False)
|
| 341 |
+
ctx = _ensure_context(user_id)
|
| 342 |
+
goals = get_goals(user_id)
|
| 343 |
+
try:
|
| 344 |
+
sched = generate_schedule(ctx, tasks, prompt or "Schedule all my tasks intelligently across upcoming days.", goals)
|
| 345 |
+
except Exception as e:
|
| 346 |
+
return _err(f"Scheduling error: {e}"), "", gr.update(visible=False)
|
| 347 |
+
if "error" in sched and "scheduled_tasks" not in sched:
|
| 348 |
+
return _err(f"Scheduling failed: {sched.get('error')}"), "", gr.update(visible=False)
|
| 349 |
+
html = _render_sched_html(sched)
|
| 350 |
+
return html, _json.dumps(sched), gr.update(visible=True)
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def handle_confirm_schedule(user_id, pending_json):
|
| 354 |
+
"""Step 2a: apply the proposed schedule to the database."""
|
| 355 |
+
if not user_id or not pending_json:
|
| 356 |
+
return _err("Nothing to confirm."), gr.update(visible=False), [], []
|
| 357 |
+
try:
|
| 358 |
+
sched = _json.loads(pending_json)
|
| 359 |
+
except Exception:
|
| 360 |
+
return _err("Could not read proposal."), gr.update(visible=False), [], []
|
| 361 |
+
count = 0
|
| 362 |
+
sched_date = sched.get("schedule_date", str(date.today()))
|
| 363 |
+
for t in sched.get("scheduled_tasks", []):
|
| 364 |
+
tid = t.get("task_id") or t.get("id")
|
| 365 |
+
if tid:
|
| 366 |
+
try:
|
| 367 |
+
assign_task_date(int(tid), user_id, sched_date)
|
| 368 |
+
count += 1
|
| 369 |
+
except Exception:
|
| 370 |
+
pass
|
| 371 |
+
all_rows = _fmt_tasks(get_tasks(user_id))
|
| 372 |
+
today_rows = _fmt_tasks(get_tasks(user_id, only_today=True))
|
| 373 |
+
return _ok(f"β {count} tasks scheduled for {sched_date}!"), gr.update(visible=False), all_rows, today_rows
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
def handle_refine_schedule(user_id, refinement, pending_json):
|
| 377 |
+
"""Step 2b: re-run with a refinement instruction."""
|
| 378 |
+
if not user_id or not refinement.strip():
|
| 379 |
+
return gr.update(), gr.update(), gr.update()
|
| 380 |
+
hint = ""
|
| 381 |
+
if pending_json:
|
| 382 |
+
try:
|
| 383 |
+
old = _json.loads(pending_json)
|
| 384 |
+
hint = f"\n\nPrevious plan summary: {old.get('day_summary', '')}"
|
| 385 |
+
except Exception:
|
| 386 |
+
pass
|
| 387 |
+
return handle_generate_schedule(user_id, refinement + hint)
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
def handle_manual_assign(task_id, task_date, user_id):
|
| 391 |
+
"""Manually assign a date to a specific task."""
|
| 392 |
+
if not task_id or not task_date or not user_id:
|
| 393 |
+
return _err("Enter task ID and date.")
|
| 394 |
+
try:
|
| 395 |
+
assign_task_date(int(task_id), user_id, task_date.strip())
|
| 396 |
+
return _ok(f"Task #{int(task_id)} assigned to {task_date.strip()}")
|
| 397 |
+
except Exception as e:
|
| 398 |
+
return _err(f"Error: {e}")
|
| 399 |
+
|
| 400 |
+
|
| 401 |
def handle_toggle_today(task_id, user_id):
|
| 402 |
if task_id and user_id:
|
| 403 |
toggle_task_complete(int(task_id), user_id)
|
|
|
|
| 494 |
if not tasks_state:
|
| 495 |
return [], [], False, gr.update(interactive=False), gr.update(interactive=False), _err("Load today's tasks first.")
|
| 496 |
|
| 497 |
+
ctx = _ensure_context(user_id)
|
| 498 |
+
opening = build_opening_question(ctx, tasks_state)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
chat = [{"role": "assistant", "content": opening["question"]}]
|
| 500 |
hist = [{"role": "assistant", "content": opening["question"], "focus": opening["question_focus"]}]
|
| 501 |
|
|
|
|
| 511 |
if not active or not answer.strip():
|
| 512 |
return chat, hist, "", ""
|
| 513 |
|
|
|
|
| 514 |
chat = list(chat) + [{"role": "user", "content": answer}]
|
| 515 |
hist = list(hist) + [{"role": "user", "content": answer}]
|
| 516 |
|
| 517 |
+
ctx = _ensure_context(user_id)
|
| 518 |
+
result = get_next_journal_question(ctx, tasks_state, hist)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 519 |
|
| 520 |
if result.get("session_complete"):
|
| 521 |
+
chat = list(chat) + [{"role": "assistant", "content": "β
That's enough for today. Click **Finish & Save** to update your profile."}]
|
|
|
|
|
|
|
| 522 |
return chat, hist, "", _ok("Session complete β save when ready")
|
| 523 |
|
| 524 |
+
next_q = result.get("question", "")
|
| 525 |
hist.append({"role": "assistant", "content": next_q, "focus": result.get("question_focus", "")})
|
| 526 |
chat = list(chat) + [{"role": "assistant", "content": next_q}]
|
| 527 |
return chat, hist, "", ""
|
|
|
|
| 531 |
if not user_id or not hist:
|
| 532 |
return chat, _err("Nothing to save.")
|
| 533 |
|
| 534 |
+
ctx = _ensure_context(user_id)
|
| 535 |
+
updated = synthesize_journal(ctx, tasks_state, hist)
|
| 536 |
+
save_user_context(user_id, updated)
|
|
|
|
|
|
|
|
|
|
| 537 |
|
| 538 |
total = len(tasks_state)
|
| 539 |
done = sum(1 for t in tasks_state if t.get("completed"))
|
|
|
|
| 707 |
return html
|
| 708 |
|
| 709 |
|
| 710 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½β
|
| 711 |
+
# CALENDAR VIEW
|
| 712 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 713 |
+
|
| 714 |
+
def render_calendar(user_id):
|
| 715 |
+
if not user_id:
|
| 716 |
+
return "<p style='color:#475569;font-size:13px'>Sign in to view calendar.</p>"
|
| 717 |
+
tasks = get_tasks(user_id)
|
| 718 |
+
if not tasks:
|
| 719 |
+
return "<p style='color:#475569;font-size:13px'>No tasks yet.</p>"
|
| 720 |
+
_today = date.today()
|
| 721 |
+
COLOR = {"Flow":"#0ea5e9","Easy":"#4ade80","Quick":"#a78bfa","Personal":"#f87171"}
|
| 722 |
+
by_date, unscheduled = {}, []
|
| 723 |
+
for t in tasks:
|
| 724 |
+
sd = (t.get("scheduled_date") or "").strip()
|
| 725 |
+
if sd:
|
| 726 |
+
by_date.setdefault(sd, []).append(t)
|
| 727 |
+
else:
|
| 728 |
+
unscheduled.append(t)
|
| 729 |
+
html = ""
|
| 730 |
+
for d in sorted(by_date.keys()):
|
| 731 |
+
try:
|
| 732 |
+
dt = date.fromisoformat(d)
|
| 733 |
+
diff = (dt - _today).days
|
| 734 |
+
if diff == 0: label = f"TODAY β {dt.strftime('%a, %b %d')}"
|
| 735 |
+
elif diff == 1: label = f"TOMORROW β {dt.strftime('%a, %b %d')}"
|
| 736 |
+
elif diff < 0: label = dt.strftime('%a, %b %d') + " (past)"
|
| 737 |
+
else: label = dt.strftime('%a, %b %d')
|
| 738 |
+
hdr = "#a78bfa" if diff == 0 else "#60a5fa" if diff > 0 else "#334155"
|
| 739 |
+
except ValueError:
|
| 740 |
+
label, hdr = d, "#60a5fa"
|
| 741 |
+
html += (f'<div style="margin-bottom:20px">'
|
| 742 |
+
f'<div style="color:{hdr};font-size:11px;font-weight:700;letter-spacing:1px;'
|
| 743 |
+
f'text-transform:uppercase;padding:6px 0;border-bottom:1px solid #1e293b;margin-bottom:8px">'
|
| 744 |
+
f'π
{label}</div>')
|
| 745 |
+
for t in by_date[d]:
|
| 746 |
+
sm = t.get("state_of_mind","Easy")
|
| 747 |
+
c = COLOR.get(sm,"#6366f1")
|
| 748 |
+
done = t.get("is_completed")
|
| 749 |
+
dl = (t.get("deadline_date") or "").strip()
|
| 750 |
+
dl_badge = (f'<span style="background:#f8714422;color:#f87171;font-size:10px;'
|
| 751 |
+
f'padding:1px 5px;border-radius:4px;margin-left:6px">due {dl}</span>'
|
| 752 |
+
) if dl and dl.lower() not in ("null","none","") else ""
|
| 753 |
+
html += (f'<div style="padding:8px 10px;margin-bottom:4px;background:#0c0f1a;border-radius:8px;'
|
| 754 |
+
f'border-left:3px solid {c};opacity:{"0.4" if done else "1"}">'
|
| 755 |
+
f'<div style="color:#cbd5e1;font-size:13px">{"β
" if done else "β¬"} '
|
| 756 |
+
f'<b>#{t["id"]}</b> {t["title"]}{dl_badge}</div>'
|
| 757 |
+
f'<div style="color:#475569;font-size:11px;margin-top:2px">'
|
| 758 |
+
f'{t.get("life_area","β")} Β· {sm} Β· {t.get("time_estimate","?")}min</div></div>')
|
| 759 |
+
html += '</div>'
|
| 760 |
+
if unscheduled:
|
| 761 |
+
html += ('<div style="margin-top:10px"><div style="color:#334155;font-size:11px;font-weight:700;'
|
| 762 |
+
'letter-spacing:1px;text-transform:uppercase;padding:6px 0;border-bottom:1px solid #1e293b;margin-bottom:8px">'
|
| 763 |
+
'β¬ UNSCHEDULED β Use Plan My Day to assign dates</div>')
|
| 764 |
+
for t in unscheduled:
|
| 765 |
+
sm = t.get("state_of_mind","Easy")
|
| 766 |
+
c = COLOR.get(sm,"#6366f1")
|
| 767 |
+
dl = (t.get("deadline_date") or "").strip()
|
| 768 |
+
dl_badge = (f'<span style="background:#f8714422;color:#f87171;font-size:10px;'
|
| 769 |
+
f'padding:1px 5px;border-radius:4px;margin-left:6px">due {dl}</span>'
|
| 770 |
+
) if dl and dl.lower() not in ("null","none","") else ""
|
| 771 |
+
html += (f'<div style="padding:8px 10px;margin-bottom:4px;background:#0c0f1a;border-radius:8px;'
|
| 772 |
+
f'border-left:3px solid {c}">'
|
| 773 |
+
f'<div style="color:#94a3b8;font-size:13px">β¬ <b>#{t["id"]}</b> {t["title"]}{dl_badge}</div>'
|
| 774 |
+
f'<div style="color:#475569;font-size:11px;margin-top:2px">'
|
| 775 |
+
f'{t.get("life_area","β")} Β· {sm} Β· {t.get("time_estimate","?")}min</div></div>')
|
| 776 |
+
html += '</div>'
|
| 777 |
+
return html or "<p style='color:#475569'>No tasks.</p>"
|
| 778 |
+
|
| 779 |
+
|
| 780 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 781 |
# UI BUILD
|
| 782 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 886 |
f_title = gr.Textbox(label="Title", scale=3)
|
| 887 |
f_area = gr.Dropdown(label="Life Area",
|
| 888 |
choices=["Work","Health","Finance","Learning","Personal","Family","Other"],
|
| 889 |
+
scale=1)
|
| 890 |
with gr.Row():
|
| 891 |
f_urgency = gr.Dropdown(label="Urgency",
|
| 892 |
choices=["Urgent","Not Urgent","Habit"], value="Not Urgent")
|
|
|
|
| 897 |
choices=["Flow","Easy","Quick","Personal"], value="Easy")
|
| 898 |
with gr.Row():
|
| 899 |
f_time = gr.Number(label="Minutes", value=30, minimum=5, scale=1)
|
| 900 |
+
f_date = gr.Textbox(label="Deadline (YYYY-MM-DD, optional)", placeholder="e.g. 2026-03-15", value="", scale=1)
|
| 901 |
f_is_habit = gr.Checkbox(label="β»οΈ This is a habit", scale=1)
|
| 902 |
f_interval = gr.Dropdown(label="Recurs",
|
| 903 |
choices=["Daily","Weekly","Weekdays","Weekends","Monthly"],
|
|
|
|
| 922 |
# ββ Plan My Day panel βββββββββββββββββββββββββββββββββββββ
|
| 923 |
with gr.Column(visible=False, elem_classes="panel") as plan_panel:
|
| 924 |
gr.HTML('<div class="sec-label">Plan My Day</div>')
|
| 925 |
+
gr.HTML('<p style="color:#94a3b8;font-size:13px;margin-bottom:10px">AI schedules ALL your unscheduled tasks using your patterns and goals. Review before applying.</p>')
|
| 926 |
plan_prompt = gr.Textbox(
|
| 927 |
+
label="Instructions (optional)",
|
| 928 |
+
placeholder="e.g. 'Schedule everything for this week' or 'I have a meeting at 2pm, focus on Work tasks'",
|
| 929 |
+
lines=2
|
| 930 |
)
|
| 931 |
with gr.Row():
|
| 932 |
+
btn_gen_sched = gr.Button("π§ Generate Plan", elem_classes="btn-primary")
|
| 933 |
btn_cancel_plan = gr.Button("Cancel", elem_classes="btn-secondary")
|
| 934 |
+
schedule_html = gr.HTML("")
|
| 935 |
+
sched_pending = gr.State("")
|
| 936 |
+
with gr.Column(visible=False) as sched_confirm_row:
|
| 937 |
+
gr.HTML('<div style="padding:10px;background:#0c1a0c;border-radius:8px;margin:8px 0">'
|
| 938 |
+
'<span style="color:#4ade80;font-weight:600;font-size:13px">π Review the plan above, then:</span></div>')
|
| 939 |
+
with gr.Row():
|
| 940 |
+
btn_sched_ok = gr.Button("β
Confirm & Apply", elem_classes="btn-success", scale=2)
|
| 941 |
+
btn_sched_discard = gr.Button("β Discard", elem_classes="btn-danger", scale=1)
|
| 942 |
+
gr.HTML('<div style="color:#475569;font-size:12px;margin:10px 0 4px">π¬ Or refine it:</div>')
|
| 943 |
+
with gr.Row():
|
| 944 |
+
sched_refine_txt = gr.Textbox(label="", placeholder="e.g. Move Flow tasks to morning, skip Fridays", scale=4)
|
| 945 |
+
btn_sched_refine = gr.Button("π Re-plan", elem_classes="btn-accent", scale=1)
|
| 946 |
+
gr.HTML('<div style="color:#475569;font-size:12px;margin:10px 0 4px">π Or manually assign a task:</div>')
|
| 947 |
+
with gr.Row():
|
| 948 |
+
manual_tid = gr.Number(label="Task ID", precision=0, scale=1)
|
| 949 |
+
manual_date = gr.Textbox(label="Date (YYYY-MM-DD)", placeholder=str(date.today()), scale=2)
|
| 950 |
+
btn_manual = gr.Button("Assign Date", elem_classes="btn-secondary", scale=1)
|
| 951 |
+
manual_msg = gr.HTML("")
|
| 952 |
+
sched_status = gr.HTML("")
|
| 953 |
|
| 954 |
# ββ Today's task table ββββββββββββββββββββββββββββββββββββ
|
| 955 |
gr.HTML('<div class="sec-label" style="margin-top:20px">Today\'s Tasks</div>')
|
|
|
|
| 979 |
all_action_msg = gr.HTML("")
|
| 980 |
|
| 981 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 982 |
+
# TAB 3: CALENDAR
|
| 983 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 984 |
+
with gr.Tab("π Calendar"):
|
| 985 |
+
gr.HTML('<p style="color:#475569;font-size:13px;margin-bottom:10px">All tasks grouped by scheduled date. Unscheduled tasks appear at the bottom β use Plan My Day to assign them.</p>')
|
| 986 |
+
with gr.Row():
|
| 987 |
+
btn_cal_refresh = gr.Button("β» Refresh", elem_classes="btn-secondary", scale=1)
|
| 988 |
+
calendar_html = gr.HTML("")
|
| 989 |
+
|
| 990 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 991 |
+
# TAB 4: JOURNAL
|
| 992 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 993 |
with gr.Tab("π Journal"):
|
| 994 |
gr.HTML('<p style="color:#334155;font-size:13px;margin-bottom:16px">Mark today\'s tasks complete, then reflect. The AI updates your profile to improve tomorrow\'s schedule.</p>')
|
|
|
|
| 1090 |
).then(
|
| 1091 |
refresh_today, [user_id_state],
|
| 1092 |
[today_df, stat_total, stat_done, stat_remain]
|
| 1093 |
+
).then(
|
| 1094 |
+
lambda uid: _fmt_tasks(get_tasks(uid)) if uid else [],
|
| 1095 |
+
[user_id_state], [all_df]
|
| 1096 |
+
).then(
|
| 1097 |
+
render_calendar, [user_id_state], [calendar_html]
|
| 1098 |
).then(
|
| 1099 |
lambda uid: render_areas(uid)[0:2],
|
| 1100 |
[user_id_state], [areas_display, del_area_dd]
|
|
|
|
| 1115 |
).then(
|
| 1116 |
refresh_today, [user_id_state],
|
| 1117 |
[today_df, stat_total, stat_done, stat_remain]
|
| 1118 |
+
).then(
|
| 1119 |
+
lambda uid: _fmt_tasks(get_tasks(uid)) if uid else [],
|
| 1120 |
+
[user_id_state], [all_df]
|
| 1121 |
+
).then(
|
| 1122 |
+
render_calendar, [user_id_state], [calendar_html]
|
| 1123 |
).then(
|
| 1124 |
lambda uid: render_areas(uid)[0:2],
|
| 1125 |
[user_id_state], [areas_display, del_area_dd]
|
|
|
|
| 1184 |
handle_confirm_task,
|
| 1185 |
[user_id_state, f_title, f_area, f_urgency, f_importance, f_state,
|
| 1186 |
f_time, f_date, f_is_habit, f_interval],
|
| 1187 |
+
[save_msg, today_df, stat_total, stat_done, stat_remain, all_df, parsed_panel]
|
| 1188 |
+
).then(render_calendar, [user_id_state], [calendar_html])
|
| 1189 |
|
| 1190 |
btn_gen_sched.click(
|
| 1191 |
handle_generate_schedule,
|
| 1192 |
[user_id_state, plan_prompt],
|
| 1193 |
+
[schedule_html, sched_pending, sched_confirm_row]
|
| 1194 |
+
)
|
| 1195 |
+
btn_sched_ok.click(
|
| 1196 |
+
handle_confirm_schedule,
|
| 1197 |
+
[user_id_state, sched_pending],
|
| 1198 |
+
[sched_status, sched_confirm_row, all_df, today_df]
|
| 1199 |
+
).then(render_calendar, [user_id_state], [calendar_html])
|
| 1200 |
+
btn_sched_discard.click(
|
| 1201 |
+
lambda: ("", gr.update(visible=False)),
|
| 1202 |
+
outputs=[schedule_html, sched_confirm_row]
|
| 1203 |
)
|
| 1204 |
+
btn_sched_refine.click(
|
| 1205 |
+
handle_refine_schedule,
|
| 1206 |
+
[user_id_state, sched_refine_txt, sched_pending],
|
| 1207 |
+
[schedule_html, sched_pending, sched_confirm_row]
|
| 1208 |
+
).then(lambda: "", outputs=[sched_refine_txt])
|
| 1209 |
+
btn_manual.click(
|
| 1210 |
+
handle_manual_assign,
|
| 1211 |
+
[manual_tid, manual_date, user_id_state],
|
| 1212 |
+
[manual_msg]
|
| 1213 |
+
).then(
|
| 1214 |
+
lambda uid: _fmt_tasks(get_tasks(uid)) if uid else [],
|
| 1215 |
+
[user_id_state], [all_df]
|
| 1216 |
+
).then(render_calendar, [user_id_state], [calendar_html])
|
| 1217 |
|
| 1218 |
btn_today_done.click(
|
| 1219 |
handle_toggle_today,
|
| 1220 |
[today_done_id, user_id_state],
|
| 1221 |
[today_df, stat_total, stat_done, stat_remain, today_action_msg]
|
| 1222 |
+
).then(
|
| 1223 |
+
lambda uid: _fmt_tasks(get_tasks(uid)) if uid else [],
|
| 1224 |
+
[user_id_state], [all_df]
|
| 1225 |
+
).then(render_calendar, [user_id_state], [calendar_html])
|
| 1226 |
btn_today_del.click(
|
| 1227 |
handle_delete_today,
|
| 1228 |
[today_del_id, user_id_state],
|
| 1229 |
[today_df, stat_total, stat_done, stat_remain, today_action_msg]
|
| 1230 |
+
).then(
|
| 1231 |
+
lambda uid: _fmt_tasks(get_tasks(uid)) if uid else [],
|
| 1232 |
+
[user_id_state], [all_df]
|
| 1233 |
+
).then(render_calendar, [user_id_state], [calendar_html])
|
| 1234 |
+
|
| 1235 |
+
# ββ Calendar tab ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1236 |
+
btn_cal_refresh.click(render_calendar, [user_id_state], [calendar_html])
|
| 1237 |
|
| 1238 |
# ββ All Tasks tab ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1239 |
|
|
|
|
| 1256 |
handle_toggle_all,
|
| 1257 |
[all_done_id, user_id_state, all_filter, all_today_chk],
|
| 1258 |
[all_df, all_action_msg]
|
| 1259 |
+
).then(render_calendar, [user_id_state], [calendar_html])
|
| 1260 |
btn_all_del.click(
|
| 1261 |
handle_delete_all,
|
| 1262 |
[all_del_id, user_id_state, all_filter, all_today_chk],
|
| 1263 |
[all_df, all_action_msg]
|
| 1264 |
+
).then(render_calendar, [user_id_state], [calendar_html])
|
| 1265 |
|
| 1266 |
# ββ Journal tab ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1267 |
|