eraz3r commited on
Commit
20c25c8
·
verified ·
1 Parent(s): 8f1937f

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1229
app.py DELETED
@@ -1,1229 +0,0 @@
1
- """
2
- Second Brain — Gradio Application
3
- Requires: pip install gradio requests bcrypt
4
- """
5
-
6
- import gradio as gr
7
- import sqlite3
8
- import bcrypt
9
- import json
10
- import requests
11
- import os
12
- from datetime import datetime, date
13
- from typing import Optional
14
-
15
- # ─────────────────────────────────────────────────────────────────────────────
16
- # CONFIG
17
- # ─────────────────────────────────────────────────────────────────────────────
18
-
19
- DB_PATH = "second_brain.db"
20
-
21
- # Replace with your actual Colab ngrok URL when running
22
- TASK_API_URL = "http://localhost:8000" # e.g. "https://xxxx.ngrok.io"
23
-
24
- DEFAULT_LIFE_AREAS = [
25
- {"name": "Work", "color": "#4F8EF7"},
26
- {"name": "Health", "color": "#4CAF87"},
27
- {"name": "Finance", "color": "#F7A84F"},
28
- ]
29
-
30
- URGENCY_EMOJI = {"Urgent": "🔴", "Not Urgent": "🟡", "Habit": "🔵"}
31
- IMPORTANCE_EMOJI= {"Move the Needle": "⭐", "Important": "🔸", "Not Important": "⚪"}
32
- STATE_EMOJI = {"Flow": "🌊", "Easy": "😌", "Quick": "⚡", "Personal": "🏠"}
33
-
34
-
35
- # ─────────────────────────────────────────────────────────────────────────────
36
- # DATABASE
37
- # ─────────────────────────────────────────────────────────────────────────────
38
-
39
- def get_db():
40
- conn = sqlite3.connect(DB_PATH)
41
- conn.row_factory = sqlite3.Row
42
- return conn
43
-
44
-
45
- def init_db():
46
- conn = get_db()
47
- c = conn.cursor()
48
-
49
- c.execute("""
50
- CREATE TABLE IF NOT EXISTS users (
51
- id INTEGER PRIMARY KEY AUTOINCREMENT,
52
- username TEXT UNIQUE NOT NULL,
53
- password_hash TEXT NOT NULL,
54
- created_at TEXT DEFAULT (datetime('now'))
55
- )
56
- """)
57
-
58
- c.execute("""
59
- CREATE TABLE IF NOT EXISTS life_areas (
60
- id INTEGER PRIMARY KEY AUTOINCREMENT,
61
- user_id INTEGER NOT NULL,
62
- name TEXT NOT NULL,
63
- color TEXT DEFAULT '#888888',
64
- created_at TEXT DEFAULT (datetime('now')),
65
- FOREIGN KEY (user_id) REFERENCES users(id)
66
- )
67
- """)
68
-
69
- c.execute("""
70
- CREATE TABLE IF NOT EXISTS tasks (
71
- id INTEGER PRIMARY KEY AUTOINCREMENT,
72
- user_id INTEGER NOT NULL,
73
- life_area TEXT,
74
- title TEXT NOT NULL,
75
- description TEXT DEFAULT '',
76
- urgency TEXT DEFAULT 'Not Urgent',
77
- importance TEXT DEFAULT 'Important',
78
- state_of_mind TEXT DEFAULT 'Easy',
79
- time_estimate INTEGER DEFAULT 30,
80
- scheduled_date TEXT DEFAULT (date('now')),
81
- is_completed INTEGER DEFAULT 0,
82
- raw_input TEXT DEFAULT '',
83
- created_at TEXT DEFAULT (datetime('now')),
84
- FOREIGN KEY (user_id) REFERENCES users(id)
85
- )
86
- """)
87
-
88
- c.execute("""
89
- CREATE TABLE IF NOT EXISTS preferences (
90
- id INTEGER PRIMARY KEY AUTOINCREMENT,
91
- user_id INTEGER NOT NULL UNIQUE,
92
- wake_time TEXT DEFAULT '08:00',
93
- sleep_time TEXT DEFAULT '23:00',
94
- focus_peak TEXT DEFAULT 'Morning',
95
- notes TEXT DEFAULT '',
96
- updated_at TEXT DEFAULT (datetime('now')),
97
- FOREIGN KEY (user_id) REFERENCES users(id)
98
- )
99
- """)
100
-
101
- conn.commit()
102
- conn.close()
103
-
104
-
105
- # ─────────────────────────────────────────────────────────────────────────────
106
- # AUTH
107
- # ─────────────────────────────────────────────────────────────────────────────
108
-
109
- def register_user(username: str, password: str):
110
- if not username.strip() or not password.strip():
111
- return None, "Username and password cannot be empty."
112
- if len(password) < 6:
113
- return None, "Password must be at least 6 characters."
114
- conn = get_db()
115
- try:
116
- pw_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
117
- conn.execute(
118
- "INSERT INTO users (username, password_hash) VALUES (?, ?)",
119
- (username.strip().lower(), pw_hash)
120
- )
121
- conn.commit()
122
- user_id = conn.execute(
123
- "SELECT id FROM users WHERE username = ?", (username.strip().lower(),)
124
- ).fetchone()["id"]
125
- # Create default life areas
126
- for area in DEFAULT_LIFE_AREAS:
127
- conn.execute(
128
- "INSERT INTO life_areas (user_id, name, color) VALUES (?, ?, ?)",
129
- (user_id, area["name"], area["color"])
130
- )
131
- # Create default preferences
132
- conn.execute(
133
- "INSERT OR IGNORE INTO preferences (user_id) VALUES (?)", (user_id,)
134
- )
135
- conn.commit()
136
- return user_id, "Account created successfully!"
137
- except sqlite3.IntegrityError:
138
- return None, "Username already exists. Please choose another."
139
- finally:
140
- conn.close()
141
-
142
-
143
- def login_user(username: str, password: str):
144
- if not username.strip() or not password.strip():
145
- return None, "Please enter username and password."
146
- conn = get_db()
147
- row = conn.execute(
148
- "SELECT id, password_hash FROM users WHERE username = ?",
149
- (username.strip().lower(),)
150
- ).fetchone()
151
- conn.close()
152
- if not row:
153
- return None, "Username not found."
154
- if not bcrypt.checkpw(password.encode(), row["password_hash"].encode()):
155
- return None, "Incorrect password."
156
- return row["id"], f"Welcome back, {username.strip()}!"
157
-
158
-
159
- # ─────────────────────────────────────────────────────────────────────────────
160
- # LIFE AREAS
161
- # ─────────────────────────────────────────────────────────────────────────────
162
-
163
- def get_life_areas(user_id: int):
164
- conn = get_db()
165
- rows = conn.execute(
166
- "SELECT name, color FROM life_areas WHERE user_id = ? ORDER BY id",
167
- (user_id,)
168
- ).fetchall()
169
- conn.close()
170
- return [dict(r) for r in rows]
171
-
172
-
173
- def add_life_area(user_id: int, name: str, color: str = "#888888"):
174
- if not name.strip():
175
- return False, "Area name cannot be empty."
176
- conn = get_db()
177
- existing = conn.execute(
178
- "SELECT id FROM life_areas WHERE user_id = ? AND LOWER(name) = LOWER(?)",
179
- (user_id, name.strip())
180
- ).fetchone()
181
- if existing:
182
- conn.close()
183
- return False, f'"{name}" already exists.'
184
- conn.execute(
185
- "INSERT INTO life_areas (user_id, name, color) VALUES (?, ?, ?)",
186
- (user_id, name.strip(), color)
187
- )
188
- conn.commit()
189
- conn.close()
190
- return True, f'"{name}" added!'
191
-
192
-
193
- def delete_life_area(user_id: int, name: str):
194
- if name in [a["name"] for a in DEFAULT_LIFE_AREAS]:
195
- return False, "Cannot delete default life areas."
196
- conn = get_db()
197
- conn.execute(
198
- "DELETE FROM life_areas WHERE user_id = ? AND name = ?",
199
- (user_id, name)
200
- )
201
- conn.commit()
202
- conn.close()
203
- return True, f'"{name}" deleted.'
204
-
205
-
206
- # ─────────────────────────────────────────────────────────────────────────────
207
- # TASK API
208
- # ─────────────────────────────────────────────────────────────────────────────
209
-
210
- def call_task_api(text: str) -> dict:
211
- """Call the task capture API. Falls back to a basic local parse if API is down."""
212
- try:
213
- r = requests.post(
214
- f"{TASK_API_URL}/capture/text",
215
- json={"text": text},
216
- timeout=30
217
- )
218
- if r.status_code == 200:
219
- data = r.json()
220
- data["_api_used"] = True
221
- return data
222
- except Exception:
223
- pass
224
-
225
- # ── Local fallback (basic keyword matching) ───────────────────────────────
226
- text_lower = text.lower()
227
- urgency = "Urgent" if any(w in text_lower for w in ["urgent", "asap", "deadline", "tomorrow", "today", "before"]) \
228
- else "Habit" if any(w in text_lower for w in ["every", "daily", "weekly", "morning", "always", "routine"]) \
229
- else "Not Urgent"
230
-
231
- importance = "Move the Needle" if any(w in text_lower for w in ["important", "critical", "launch", "client", "report", "exam", "project"]) \
232
- else "Not Important" if any(w in text_lower for w in ["maybe", "someday", "nice to"]) \
233
- else "Important"
234
-
235
- state = "Flow" if any(w in text_lower for w in ["write", "code", "design", "study", "develop", "build", "create", "research"]) \
236
- else "Quick" if any(w in text_lower for w in ["call", "email", "reply", "message", "book", "schedule"]) \
237
- else "Personal" if any(w in text_lower for w in ["grocery", "clean", "cook", "shop", "pay bill", "laundry"]) \
238
- else "Easy"
239
-
240
- area = "Work" if any(w in text_lower for w in ["meeting", "client", "report", "project", "deadline", "work", "office"]) \
241
- else "Health" if any(w in text_lower for w in ["run", "gym", "exercise", "meditat", "sleep", "doctor", "health"]) \
242
- else "Finance" if any(w in text_lower for w in ["pay", "bill", "budget", "invest", "tax", "bank", "money"]) \
243
- else "Learning" if any(w in text_lower for w in ["study", "course", "read", "learn", "book", "practice"]) \
244
- else None
245
-
246
- clarifications = []
247
- if not area:
248
- clarifications.append("Which life area does this belong to? (Work, Health, Finance, Learning, Personal, Family)")
249
-
250
- # Clean up title
251
- title = text.strip().rstrip(".")
252
- if len(title) > 60:
253
- title = title[:57] + "..."
254
-
255
- return {
256
- "title": title,
257
- "life_area": area,
258
- "urgency": urgency,
259
- "importance": importance,
260
- "state_of_mind": state,
261
- "time_estimate": 30,
262
- "clarifications_needed": clarifications,
263
- "raw_input": text,
264
- "input_type": "text",
265
- "_api_used": False
266
- }
267
-
268
-
269
- # ─────────────────────────────────────────────────────────────────────────────
270
- # TASKS
271
- # ─────────────────────────────────────────────────────────────────────────────
272
-
273
- def save_task(user_id: int, task: dict, scheduled_date: str = None):
274
- conn = get_db()
275
- conn.execute("""
276
- INSERT INTO tasks (user_id, life_area, title, description, urgency,
277
- importance, state_of_mind, time_estimate, scheduled_date, raw_input)
278
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
279
- """, (
280
- user_id,
281
- task.get("life_area", ""),
282
- task.get("title", "Untitled Task"),
283
- task.get("description", ""),
284
- task.get("urgency", "Not Urgent"),
285
- task.get("importance", "Important"),
286
- task.get("state_of_mind", "Easy"),
287
- task.get("time_estimate", 30),
288
- scheduled_date or str(date.today()),
289
- task.get("raw_input", ""),
290
- ))
291
- conn.commit()
292
- conn.close()
293
-
294
-
295
- def get_tasks(user_id: int, filter_area: str = "All", only_today: bool = False):
296
- conn = get_db()
297
- query = "SELECT * FROM tasks WHERE user_id = ?"
298
- params = [user_id]
299
- if filter_area and filter_area != "All":
300
- query += " AND life_area = ?"
301
- params.append(filter_area)
302
- if only_today:
303
- query += " AND scheduled_date = ?"
304
- params.append(str(date.today()))
305
- query += " ORDER BY is_completed ASC, created_at DESC"
306
- rows = conn.execute(query, params).fetchall()
307
- conn.close()
308
- return [dict(r) for r in rows]
309
-
310
-
311
- def toggle_task_complete(task_id: int, user_id: int):
312
- conn = get_db()
313
- conn.execute("""
314
- UPDATE tasks SET is_completed = 1 - is_completed
315
- WHERE id = ? AND user_id = ?
316
- """, (task_id, user_id))
317
- conn.commit()
318
- conn.close()
319
-
320
-
321
- def delete_task(task_id: int, user_id: int):
322
- conn = get_db()
323
- conn.execute("DELETE FROM tasks WHERE id = ? AND user_id = ?", (task_id, user_id))
324
- conn.commit()
325
- conn.close()
326
-
327
-
328
- def format_tasks_for_display(tasks: list) -> list:
329
- """Convert task rows to display-friendly format for gr.Dataframe."""
330
- rows = []
331
- for t in tasks:
332
- status = "✅" if t["is_completed"] else "⬜"
333
- urgency = URGENCY_EMOJI.get(t["urgency"], "❓") + " " + (t["urgency"] or "—")
334
- importance = IMPORTANCE_EMOJI.get(t["importance"], "❓") + " " + (t["importance"] or "—")
335
- state = STATE_EMOJI.get(t["state_of_mind"], "❓") + " " + (t["state_of_mind"] or "—")
336
- rows.append([
337
- t["id"],
338
- status,
339
- t["title"],
340
- t["life_area"] or "—",
341
- urgency,
342
- importance,
343
- state,
344
- f"{t['time_estimate']} min" if t["time_estimate"] else "—",
345
- t["scheduled_date"] or "—",
346
- ])
347
- return rows
348
-
349
-
350
- TASK_COLS = ["ID", "Done", "Task", "Area", "Urgency", "Importance", "State of Mind", "Est. Time", "Date"]
351
-
352
-
353
- # ─────────────────────────────────────────────────────────────────────────────
354
- # PREFERENCES
355
- # ─────────────────────────────────────────────────────────────────────────────
356
-
357
- def get_preferences(user_id: int) -> dict:
358
- conn = get_db()
359
- row = conn.execute(
360
- "SELECT * FROM preferences WHERE user_id = ?", (user_id,)
361
- ).fetchone()
362
- conn.close()
363
- if row:
364
- return dict(row)
365
- return {"wake_time": "08:00", "sleep_time": "23:00", "focus_peak": "Morning", "notes": ""}
366
-
367
-
368
- def save_preferences(user_id: int, wake: str, sleep: str, focus: str, notes: str):
369
- conn = get_db()
370
- conn.execute("""
371
- INSERT INTO preferences (user_id, wake_time, sleep_time, focus_peak, notes, updated_at)
372
- VALUES (?, ?, ?, ?, ?, datetime('now'))
373
- ON CONFLICT(user_id) DO UPDATE SET
374
- wake_time = excluded.wake_time,
375
- sleep_time = excluded.sleep_time,
376
- focus_peak = excluded.focus_peak,
377
- notes = excluded.notes,
378
- updated_at = excluded.updated_at
379
- """, (user_id, wake, sleep, focus, notes))
380
- conn.commit()
381
- conn.close()
382
-
383
-
384
- def save_schedule_request(user_id: int, prompt: str):
385
- """Store the user's scheduling request so it can be used by the schedule API later."""
386
- conn = get_db()
387
- conn.execute("""
388
- UPDATE preferences SET notes = ?, updated_at = datetime('now')
389
- WHERE user_id = ?
390
- """, (f"[Schedule Request - {date.today()}]: {prompt}", user_id))
391
- conn.commit()
392
- conn.close()
393
-
394
-
395
- # ─────────────────────────────────────────────────────────────────────────────
396
- # GRADIO UI
397
- # ─────────────────────────────────────────────────────────────────────────────
398
-
399
- init_db()
400
-
401
- CSS = """
402
- /* ── Fonts ───────────────────────────────── */
403
- @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
404
-
405
- /* ── Base ────────────────────────────────── */
406
- * { box-sizing: border-box; }
407
- body, .gradio-container {
408
- font-family: 'DM Sans', sans-serif !important;
409
- background: #0f1117 !important;
410
- color: #e2e8f0 !important;
411
- }
412
-
413
- /* ── Auth card ───────────────────────────── */
414
- #auth-card {
415
- max-width: 420px;
416
- margin: 80px auto;
417
- background: #1a1f2e;
418
- border: 1px solid #2d3448;
419
- border-radius: 16px;
420
- padding: 40px;
421
- }
422
- #auth-title {
423
- font-size: 28px;
424
- font-weight: 700;
425
- text-align: center;
426
- color: #f8c967;
427
- margin-bottom: 6px;
428
- }
429
- #auth-sub {
430
- text-align: center;
431
- color: #718096;
432
- font-size: 14px;
433
- margin-bottom: 28px;
434
- }
435
-
436
- /* ── Header ──────────────────────────────── */
437
- #app-header {
438
- background: #1a1f2e;
439
- border-bottom: 1px solid #2d3448;
440
- padding: 16px 28px;
441
- display: flex;
442
- align-items: center;
443
- justify-content: space-between;
444
- border-radius: 12px;
445
- margin-bottom: 20px;
446
- }
447
- #header-title {
448
- font-size: 22px;
449
- font-weight: 700;
450
- color: #f8c967;
451
- letter-spacing: -0.5px;
452
- }
453
- #header-user {
454
- font-size: 13px;
455
- color: #718096;
456
- }
457
-
458
- /* ── Stat cards ──────────────────────────── */
459
- .stat-card {
460
- background: #1a1f2e;
461
- border: 1px solid #2d3448;
462
- border-radius: 12px;
463
- padding: 20px;
464
- text-align: center;
465
- }
466
- .stat-number { font-size: 36px; font-weight: 700; color: #f8c967; }
467
- .stat-label { font-size: 13px; color: #718096; margin-top: 4px; }
468
-
469
- /* ── Panels ──────────────────────────────── */
470
- .panel {
471
- background: #1a1f2e;
472
- border: 1px solid #2d3448;
473
- border-radius: 12px;
474
- padding: 24px;
475
- }
476
-
477
- /* ── Buttons ─────────────────────────────── */
478
- .btn-primary button {
479
- background: #f8c967 !important;
480
- color: #0f1117 !important;
481
- font-weight: 600 !important;
482
- border: none !important;
483
- border-radius: 8px !important;
484
- font-family: 'DM Sans', sans-serif !important;
485
- }
486
- .btn-primary button:hover {
487
- background: #f5b830 !important;
488
- transform: translateY(-1px);
489
- }
490
- .btn-secondary button {
491
- background: #2d3448 !important;
492
- color: #e2e8f0 !important;
493
- border: 1px solid #3d4560 !important;
494
- border-radius: 8px !important;
495
- font-family: 'DM Sans', sans-serif !important;
496
- }
497
- .btn-danger button {
498
- background: #3d1f1f !important;
499
- color: #f87171 !important;
500
- border: 1px solid #7f1d1d !important;
501
- border-radius: 8px !important;
502
- font-family: 'DM Sans', sans-serif !important;
503
- }
504
- .btn-success button {
505
- background: #1a3a2e !important;
506
- color: #4ade80 !important;
507
- border: 1px solid #166534 !important;
508
- border-radius: 8px !important;
509
- font-family: 'DM Sans', sans-serif !important;
510
- }
511
-
512
- /* ── Inputs ───────────────────��──────────── */
513
- input, textarea, select {
514
- background: #0f1117 !important;
515
- border: 1px solid #2d3448 !important;
516
- border-radius: 8px !important;
517
- color: #e2e8f0 !important;
518
- font-family: 'DM Sans', sans-serif !important;
519
- }
520
- input:focus, textarea:focus {
521
- border-color: #f8c967 !important;
522
- outline: none !important;
523
- box-shadow: 0 0 0 2px rgba(248,201,103,0.15) !important;
524
- }
525
- label { color: #94a3b8 !important; font-size: 13px !important; font-weight: 500 !important; }
526
-
527
- /* ── Tabs ────────────────────────────────── */
528
- .tab-nav button {
529
- color: #718096 !important;
530
- font-family: 'DM Sans', sans-serif !important;
531
- font-weight: 500 !important;
532
- border: none !important;
533
- background: transparent !important;
534
- }
535
- .tab-nav button.selected {
536
- color: #f8c967 !important;
537
- border-bottom: 2px solid #f8c967 !important;
538
- }
539
-
540
- /* ── Dataframe ───────────────────────────── */
541
- .dataframe {
542
- font-family: 'DM Mono', monospace !important;
543
- font-size: 13px !important;
544
- }
545
- .dataframe thead th {
546
- background: #1a1f2e !important;
547
- color: #94a3b8 !important;
548
- font-size: 11px !important;
549
- text-transform: uppercase !important;
550
- letter-spacing: 0.5px !important;
551
- }
552
- .dataframe tbody tr:nth-child(even) { background: #131720 !important; }
553
- .dataframe tbody tr:hover { background: #1e2436 !important; }
554
-
555
- /* ── Section labels ──────────────────────── */
556
- .section-label {
557
- font-size: 11px;
558
- text-transform: uppercase;
559
- letter-spacing: 1px;
560
- color: #718096;
561
- font-weight: 600;
562
- margin-bottom: 12px;
563
- }
564
-
565
- /* ── API result card ─────────────────────── */
566
- #api-result-box {
567
- background: #111827;
568
- border: 1px solid #2d3448;
569
- border-left: 3px solid #f8c967;
570
- border-radius: 8px;
571
- padding: 16px;
572
- font-family: 'DM Mono', monospace;
573
- font-size: 13px;
574
- white-space: pre-wrap;
575
- }
576
-
577
- /* ── Life area chips ─────────────────────── */
578
- .area-chip {
579
- display: inline-block;
580
- padding: 4px 12px;
581
- border-radius: 20px;
582
- font-size: 12px;
583
- font-weight: 600;
584
- margin: 3px;
585
- }
586
-
587
- /* ── Accordion ───────────────────────────── */
588
- .gr-accordion {
589
- background: #1a1f2e !important;
590
- border: 1px solid #2d3448 !important;
591
- border-radius: 12px !important;
592
- }
593
-
594
- /* ── Messages/alerts ─────────────────────── */
595
- .msg-success { color: #4ade80; font-size: 13px; }
596
- .msg-error { color: #f87171; font-size: 13px; }
597
- .msg-info { color: #60a5fa; font-size: 13px; }
598
- """
599
-
600
- with gr.Blocks(title="Second Brain") as demo:
601
-
602
- # ── Session state ──────────────────────────────────────────────────────
603
- user_id_state = gr.State(None)
604
- username_state = gr.State("")
605
- parsed_task_state = gr.State({}) # holds the last parsed task from API
606
-
607
- # ══════════════════════════════════════════════════════════════════════
608
- # AUTH SECTION
609
- # ══════════════════════════════════════════════════════════════════════
610
- with gr.Column(visible=True, elem_id="auth-card") as auth_section:
611
- gr.HTML('<div id="auth-title">🧠 Second Brain</div>')
612
- gr.HTML('<div id="auth-sub">Your intelligent productivity companion</div>')
613
-
614
- with gr.Tabs():
615
- with gr.Tab("Sign In"):
616
- login_user_input = gr.Textbox(label="Username", placeholder="your username")
617
- login_pass_input = gr.Textbox(label="Password", type="password", placeholder="••••••••")
618
- login_btn = gr.Button("Sign In", elem_classes="btn-primary")
619
- login_msg = gr.HTML("")
620
-
621
- with gr.Tab("Create Account"):
622
- reg_user_input = gr.Textbox(label="Choose a username", placeholder="your username")
623
- reg_pass_input = gr.Textbox(label="Choose a password (min 6 chars)", type="password", placeholder="••••••••")
624
- reg_btn = gr.Button("Create Account", elem_classes="btn-primary")
625
- reg_msg = gr.HTML("")
626
-
627
- # ══════════════════════════════════════════════════════════════════════
628
- # MAIN APP SECTION
629
- # ═════���════════════════════════════════════════════════════════════════
630
- with gr.Column(visible=False) as app_section:
631
-
632
- # ── Header ────────────────────────────────────────────────────────
633
- with gr.Row(elem_id="app-header"):
634
- gr.HTML('<span id="header-title">🧠 Second Brain</span>')
635
- header_user_html = gr.HTML('<span id="header-user">—</span>')
636
- logout_btn = gr.Button("Sign out", size="sm", elem_classes="btn-secondary")
637
-
638
- # ── Main Tabs ─────────────────────────────────────────────────────
639
- with gr.Tabs() as main_tabs:
640
-
641
- # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
642
- # TAB 1: TODAY
643
- # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
644
- with gr.Tab("📅 Today"):
645
-
646
- with gr.Row():
647
- today_total_html = gr.HTML('<div class="stat-card"><div class="stat-number">—</div><div class="stat-label">Tasks Today</div></div>')
648
- today_done_html = gr.HTML('<div class="stat-card"><div class="stat-number">—</div><div class="stat-label">Completed</div></div>')
649
- today_remain_html = gr.HTML('<div class="stat-card"><div class="stat-number">—</div><div class="stat-label">Remaining</div></div>')
650
-
651
- with gr.Row():
652
- add_task_btn = gr.Button("+ Add Task", elem_classes="btn-primary", scale=2)
653
- plan_day_btn = gr.Button("🗓 Plan My Day", elem_classes="btn-secondary", scale=2)
654
- refresh_today = gr.Button("↻ Refresh", elem_classes="btn-secondary", scale=1)
655
-
656
- # ── Add Task Panel ─────────────────────────────────────────
657
- with gr.Column(visible=False, elem_classes="panel") as add_task_panel:
658
- gr.HTML('<div class="section-label">Add New Task</div>')
659
- task_input_text = gr.Textbox(
660
- label="Describe your task",
661
- placeholder="e.g. Finish the project proposal by Thursday — it's critical for the client",
662
- lines=2
663
- )
664
- with gr.Row():
665
- parse_task_btn = gr.Button("🤖 Parse with AI", elem_classes="btn-primary")
666
- cancel_add_btn = gr.Button("Cancel", elem_classes="btn-secondary")
667
-
668
- # ── API Result / Edit ──────────────────────────────────
669
- with gr.Column(visible=False) as parsed_result_section:
670
- gr.HTML('<div class="section-label" style="margin-top:16px">AI Parsed Result — Review & Edit</div>')
671
- api_source_html = gr.HTML("")
672
- with gr.Row():
673
- res_title = gr.Textbox(label="Title")
674
- res_area = gr.Dropdown(label="Life Area", choices=["Work","Health","Finance","Learning","Personal","Family","Other"])
675
- with gr.Row():
676
- res_urgency = gr.Dropdown(label="Urgency", choices=["Urgent","Not Urgent","Habit"])
677
- res_importance = gr.Dropdown(label="Importance", choices=["Move the Needle","Important","Not Important"])
678
- res_state = gr.Dropdown(label="State of Mind",choices=["Flow","Easy","Quick","Personal"])
679
- with gr.Row():
680
- res_time = gr.Number(label="Time Estimate (min)", value=30, minimum=5)
681
- res_date = gr.Textbox(label="Scheduled Date", value=str(date.today()))
682
- clarification_html = gr.HTML("")
683
- with gr.Row():
684
- confirm_task_btn = gr.Button("✓ Save Task", elem_classes="btn-success")
685
- discard_btn = gr.Button("✗ Discard", elem_classes="btn-danger")
686
- save_task_msg = gr.HTML("")
687
-
688
- # ── Plan Day Panel ─────────────────────────────────────────
689
- with gr.Column(visible=False, elem_classes="panel") as plan_day_panel:
690
- gr.HTML('<div class="section-label">Plan My Day</div>')
691
- gr.HTML('<p style="color:#718096;font-size:13px;margin-bottom:16px">Tell the AI how you want your day structured. Your preferences will be saved and used for smarter scheduling.</p>')
692
- with gr.Row():
693
- pref_wake = gr.Textbox(label="Wake up time", placeholder="08:00", scale=1)
694
- pref_sleep = gr.Textbox(label="Sleep time", placeholder="23:00", scale=1)
695
- pref_focus = gr.Dropdown(label="Peak focus time", choices=["Morning","Afternoon","Evening","Night"], scale=1)
696
- plan_prompt = gr.Textbox(
697
- label="Anything else? (optional)",
698
- placeholder="e.g. I have a meeting at 10am, prefer to do deep work before lunch, want to leave evenings free",
699
- lines=3
700
- )
701
- with gr.Row():
702
- save_plan_btn = gr.Button("💾 Save Preferences", elem_classes="btn-primary")
703
- cancel_plan_btn = gr.Button("Cancel", elem_classes="btn-secondary")
704
- plan_msg = gr.HTML("")
705
- gr.HTML('<div style="margin-top:16px;padding:12px;background:#111827;border-radius:8px;border-left:3px solid #4F8EF7"><span style="color:#60a5fa;font-size:13px">🔧 Auto-scheduling API coming soon — your preferences are being saved for when it\'s ready.</span></div>')
706
-
707
- gr.HTML('<div class="section-label" style="margin-top:20px">Today\'s Tasks</div>')
708
- today_tasks_df = gr.Dataframe(
709
- headers=TASK_COLS,
710
- datatype=["number","str","str","str","str","str","str","str","str"],
711
- interactive=False,
712
- wrap=True,
713
- )
714
-
715
- with gr.Row():
716
- complete_id_today = gr.Number(label="Task ID — toggle done", precision=0)
717
- complete_btn_today = gr.Button("✅ Toggle Complete", elem_classes="btn-success")
718
- delete_id_today = gr.Number(label="Task ID — delete", precision=0)
719
- delete_btn_today = gr.Button("🗑 Delete", elem_classes="btn-danger")
720
- action_msg_today = gr.HTML("")
721
-
722
- # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
723
- # TAB 2: ALL TASKS
724
- # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
725
- with gr.Tab("📋 All Tasks"):
726
-
727
- with gr.Row():
728
- area_filter = gr.Dropdown(label="Filter by Life Area", choices=["All"], value="All", scale=3)
729
- refresh_all = gr.Button("↻ Refresh", elem_classes="btn-secondary", scale=1)
730
-
731
- all_tasks_df = gr.Dataframe(
732
- headers=TASK_COLS,
733
- datatype=["number","str","str","str","str","str","str","str","str"],
734
- interactive=False,
735
- wrap=True,
736
- )
737
-
738
- with gr.Row():
739
- complete_id_all = gr.Number(label="Task ID — toggle done", precision=0)
740
- complete_btn_all = gr.Button("✅ Toggle Complete", elem_classes="btn-success")
741
- delete_id_all = gr.Number(label="Task ID — delete", precision=0)
742
- delete_btn_all = gr.Button("🗑 Delete", elem_classes="btn-danger")
743
- action_msg_all = gr.HTML("")
744
-
745
- # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
746
- # TAB 3: LIFE AREAS
747
- # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
748
- with gr.Tab("🗂 Life Areas"):
749
- areas_html = gr.HTML("")
750
- gr.HTML('<div class="section-label" style="margin-top:24px">Add New Life Area</div>')
751
- with gr.Row():
752
- new_area_name = gr.Textbox(label="Area Name", placeholder="e.g. Relationships", scale=3)
753
- new_area_color = gr.ColorPicker(label="Color", value="#8B5CF6", scale=1)
754
- add_area_btn = gr.Button("Add Area", elem_classes="btn-primary", scale=1)
755
- area_msg = gr.HTML("")
756
-
757
- gr.HTML('<div class="section-label" style="margin-top:24px">Remove a Life Area</div>')
758
- with gr.Row():
759
- del_area_dropdown = gr.Dropdown(label="Select area to delete", choices=[], scale=3)
760
- del_area_btn = gr.Button("Delete", elem_classes="btn-danger", scale=1)
761
- del_area_msg = gr.HTML("")
762
-
763
- # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
764
- # TAB 4: PREFERENCES
765
- # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
766
- with gr.Tab("⚙️ Preferences"):
767
- gr.HTML('<div class="section-label">Scheduling Preferences</div>')
768
- gr.HTML('<p style="color:#718096;font-size:13px;margin-bottom:20px">These preferences are stored and will be used by the auto-scheduling AI to personalize your day.</p>')
769
- with gr.Row():
770
- pref_wake_settings = gr.Textbox(label="Usual wake time", placeholder="08:00")
771
- pref_sleep_settings = gr.Textbox(label="Usual sleep time", placeholder="23:00")
772
- pref_focus_settings = gr.Dropdown(label="Peak focus period", choices=["Morning","Afternoon","Evening","Night"])
773
- pref_notes = gr.Textbox(label="Notes / patterns you've noticed about yourself", lines=4,
774
- placeholder="e.g. I work best in the morning, can't do deep work after 3pm...")
775
- save_prefs_btn = gr.Button("💾 Save Preferences", elem_classes="btn-primary")
776
- prefs_msg = gr.HTML("")
777
-
778
- gr.HTML('<div class="section-label" style="margin-top:32px">API Configuration</div>')
779
- api_url_input = gr.Textbox(
780
- label="Task Capture API URL",
781
- value=TASK_API_URL,
782
- placeholder="https://xxxx.ngrok.io",
783
- info="Paste your Colab ngrok URL here when running your notebook"
784
- )
785
- save_api_btn = gr.Button("Save API URL", elem_classes="btn-secondary")
786
- api_msg = gr.HTML("")
787
-
788
- # ══════════════════════════════════════════════════════════════════════
789
- # HELPER FUNCTIONS
790
- # ══════════════════════════════════════════════════════════════════════
791
-
792
- def build_stat_html(number, label, color="#f8c967"):
793
- return f'<div class="stat-card"><div class="stat-number" style="color:{color}">{number}</div><div class="stat-label">{label}</div></div>'
794
-
795
- def build_areas_html(areas):
796
- if not areas:
797
- return '<p style="color:#718096">No life areas yet.</p>'
798
- chips = ""
799
- for a in areas:
800
- chips += f'<span class="area-chip" style="background:{a["color"]}22;color:{a["color"]};border:1px solid {a["color"]}44">{a["name"]}</span> '
801
- return f'<div style="margin-bottom:16px"><div class="section-label">Your Life Areas</div>{chips}</div>'
802
-
803
- def refresh_today_tasks(user_id):
804
- if not user_id:
805
- return [], build_stat_html("—","Tasks Today"), build_stat_html("—","Completed"), build_stat_html("—","Remaining")
806
- tasks = get_tasks(user_id, only_today=True)
807
- total = len(tasks)
808
- done = sum(1 for t in tasks if t["is_completed"])
809
- rem = total - done
810
- rows = format_tasks_for_display(tasks)
811
- return (
812
- rows,
813
- build_stat_html(total, "Tasks Today"),
814
- build_stat_html(done, "Completed", "#4ade80"),
815
- build_stat_html(rem, "Remaining", "#f87171" if rem > 0 else "#4ade80"),
816
- )
817
-
818
- def refresh_all_tasks(user_id, filter_area="All"):
819
- if not user_id:
820
- return []
821
- tasks = get_tasks(user_id, filter_area=filter_area)
822
- return format_tasks_for_display(tasks)
823
-
824
- def get_area_choices(user_id):
825
- areas = get_life_areas(user_id)
826
- return ["All"] + [a["name"] for a in areas]
827
-
828
- def get_area_choices_no_all(user_id):
829
- areas = get_life_areas(user_id)
830
- return [a["name"] for a in areas] + ["Other"]
831
-
832
- # ── AUTH HANDLERS ──────────────────────────────────────────────────────
833
-
834
- def handle_login(username, password):
835
- uid, msg = login_user(username, password)
836
- if not uid:
837
- return (
838
- gr.update(), # auth_section stays visible
839
- gr.update(visible=False),
840
- f'<span class="msg-error">⚠ {msg}</span>',
841
- None, "",
842
- gr.update(), gr.update(), gr.update(), gr.update(),
843
- gr.update(), [], [], "<span></span>", gr.update(), gr.update()
844
- )
845
- return build_logged_in_state(uid, username.strip())
846
-
847
- def handle_register(username, password):
848
- uid, msg = register_user(username, password)
849
- if not uid:
850
- return (
851
- gr.update(),
852
- gr.update(visible=False),
853
- gr.update(), f'<span class="msg-error">⚠ {msg}</span>',
854
- None, "",
855
- gr.update(), gr.update(), gr.update(), gr.update(),
856
- gr.update(), [], [], "<span></span>", gr.update(), gr.update()
857
- )
858
- uid2, _ = login_user(username, password)
859
- return build_logged_in_state(uid2, username.strip())
860
-
861
- def build_logged_in_state(uid, uname):
862
- areas = get_life_areas(uid)
863
- area_choices = ["All"] + [a["name"] for a in areas]
864
- area_del_choices = [a["name"] for a in areas]
865
- area_choices_no_all = [a["name"] for a in areas] + ["Other"]
866
- areas_html_content = build_areas_html(areas)
867
- prefs = get_preferences(uid)
868
-
869
- today_rows, stat1, stat2, stat3 = refresh_today_tasks(uid)
870
- all_rows = refresh_all_tasks(uid)
871
-
872
- return (
873
- gr.update(visible=False), # hide auth
874
- gr.update(visible=True), # show app
875
- gr.update(value=""), # login msg
876
- gr.update(value=""), # reg msg
877
- uid, # user_id_state
878
- uname, # username_state
879
- f'<span id="header-user">Signed in as <strong style="color:#f8c967">{uname}</strong></span>',
880
- gr.update(choices=area_choices, value="All"), # area_filter
881
- gr.update(choices=area_choices_no_all), # res_area
882
- gr.update(choices=area_del_choices), # del_area_dropdown
883
- areas_html_content,
884
- today_rows,
885
- all_rows,
886
- stat1, stat2, stat3,
887
- gr.update(value=prefs.get("wake_time","08:00")),
888
- gr.update(value=prefs.get("sleep_time","23:00")),
889
- gr.update(value=prefs.get("focus_peak","Morning")),
890
- gr.update(value=prefs.get("notes","")),
891
- )
892
-
893
- def handle_logout():
894
- return (
895
- gr.update(visible=True),
896
- gr.update(visible=False),
897
- None, "",
898
- "", "", "",
899
- gr.update(value=""), gr.update(value="")
900
- )
901
-
902
- # ── ADD TASK HANDLERS ──────────────────────────────────────────────────
903
-
904
- def show_add_panel():
905
- return gr.update(visible=True), gr.update(visible=False)
906
-
907
- def hide_add_panel():
908
- return gr.update(visible=False), gr.update(visible=False), gr.update(value="")
909
-
910
- def handle_parse_task(text, user_id):
911
- if not text.strip():
912
- return (
913
- gr.update(visible=False), {}, "", "", "", "", "", 30, str(date.today()),
914
- '<span class="msg-error">Please describe a task first.</span>', ""
915
- )
916
- result = call_task_api(text)
917
- api_src = (
918
- '<span style="color:#4ade80;font-size:12px">✓ Classified by your AI API</span>'
919
- if result.get("_api_used")
920
- else '<span style="color:#f8c967;font-size:12px">⚠ API offline — used local fallback classifier</span>'
921
- )
922
- clarifications = result.get("clarifications_needed", [])
923
- clar_html = ""
924
- if clarifications:
925
- items = "".join(f"<li>{q}</li>" for q in clarifications)
926
- clar_html = f'<div style="margin-top:12px;padding:12px;background:#1c1f2e;border-left:3px solid #f8c967;border-radius:6px"><div style="color:#f8c967;font-size:12px;font-weight:600;margin-bottom:6px">⚠ Please clarify:</div><ul style="color:#94a3b8;font-size:13px;margin:0;padding-left:20px">{items}</ul></div>'
927
-
928
- areas = get_area_choices_no_all(user_id) if user_id else ["Work","Health","Finance","Other"]
929
-
930
- return (
931
- gr.update(visible=True),
932
- result,
933
- result.get("title", text),
934
- result.get("life_area") or (areas[0] if areas else "Work"),
935
- result.get("urgency", "Not Urgent"),
936
- result.get("importance", "Important"),
937
- result.get("state_of_mind", "Easy"),
938
- result.get("time_estimate", 30) or 30,
939
- str(date.today()),
940
- api_src,
941
- clar_html,
942
- )
943
-
944
- def get_area_choices_no_all(user_id):
945
- areas = get_life_areas(user_id)
946
- return [a["name"] for a in areas] + ["Other"]
947
-
948
- def handle_confirm_task(user_id, title, area, urgency, importance, state, time_est, sched_date):
949
- if not user_id:
950
- return '<span class="msg-error">Not logged in.</span>', gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
951
- task = {
952
- "title": title,
953
- "life_area": area,
954
- "urgency": urgency,
955
- "importance": importance,
956
- "state_of_mind": state,
957
- "time_estimate": int(time_est) if time_est else 30,
958
- }
959
- save_task(user_id, task, sched_date)
960
- today_rows, s1, s2, s3 = refresh_today_tasks(user_id)
961
- all_rows = refresh_all_tasks(user_id)
962
- return (
963
- '<span class="msg-success">✓ Task saved!</span>',
964
- gr.update(visible=False), # hide add panel
965
- gr.update(visible=False), # hide parsed section
966
- gr.update(value=""), # clear input
967
- today_rows, all_rows, s1, s2, s3
968
- )
969
-
970
- # ── PLAN DAY HANDLERS ──────────────────────────────────────────────────
971
-
972
- def show_plan_panel(user_id):
973
- prefs = get_preferences(user_id) if user_id else {}
974
- return (
975
- gr.update(visible=True),
976
- gr.update(value=prefs.get("wake_time","08:00")),
977
- gr.update(value=prefs.get("sleep_time","23:00")),
978
- gr.update(value=prefs.get("focus_peak","Morning")),
979
- )
980
-
981
- def hide_plan_panel():
982
- return gr.update(visible=False)
983
-
984
- def handle_save_plan(user_id, wake, sleep, focus, notes):
985
- if not user_id:
986
- return '<span class="msg-error">Not logged in.</span>'
987
- save_preferences(user_id, wake or "08:00", sleep or "23:00", focus or "Morning", notes or "")
988
- if notes:
989
- save_schedule_request(user_id, notes)
990
- return '<span class="msg-success">✓ Preferences saved! They will be used for auto-scheduling.</span>'
991
-
992
- # ── LIFE AREA HANDLERS ─────────────────────────────────────────────────
993
-
994
- def handle_add_area(user_id, name, color):
995
- ok, msg = add_life_area(user_id, name, color)
996
- areas = get_life_areas(user_id)
997
- choices = ["All"] + [a["name"] for a in areas]
998
- choices_no_all = [a["name"] for a in areas] + ["Other"]
999
- del_choices = [a["name"] for a in areas]
1000
- css_class = "msg-success" if ok else "msg-error"
1001
- return (
1002
- f'<span class="{css_class}">{msg}</span>',
1003
- build_areas_html(areas),
1004
- gr.update(choices=choices, value="All"),
1005
- gr.update(choices=choices_no_all),
1006
- gr.update(choices=del_choices),
1007
- gr.update(value=""),
1008
- )
1009
-
1010
- def handle_del_area(user_id, name):
1011
- if not name:
1012
- return '<span class="msg-error">Select an area first.</span>', gr.update(), gr.update(), gr.update(), gr.update()
1013
- ok, msg = delete_life_area(user_id, name)
1014
- areas = get_life_areas(user_id)
1015
- choices = ["All"] + [a["name"] for a in areas]
1016
- choices_no_all = [a["name"] for a in areas] + ["Other"]
1017
- del_choices = [a["name"] for a in areas]
1018
- css_class = "msg-success" if ok else "msg-error"
1019
- return (
1020
- f'<span class="{css_class}">{msg}</span>',
1021
- build_areas_html(areas),
1022
- gr.update(choices=choices, value="All"),
1023
- gr.update(choices=choices_no_all),
1024
- gr.update(choices=del_choices),
1025
- )
1026
-
1027
- # ── TASK ACTION HANDLERS ───────────────────────────────────────────────
1028
-
1029
- def handle_toggle_today(task_id, user_id):
1030
- if task_id:
1031
- toggle_task_complete(int(task_id), user_id)
1032
- rows, s1, s2, s3 = refresh_today_tasks(user_id)
1033
- return rows, s1, s2, s3, '<span class="msg-success">✓ Updated</span>'
1034
-
1035
- def handle_delete_today(task_id, user_id):
1036
- if task_id:
1037
- delete_task(int(task_id), user_id)
1038
- rows, s1, s2, s3 = refresh_today_tasks(user_id)
1039
- return rows, s1, s2, s3, '<span class="msg-success">✓ Deleted</span>'
1040
-
1041
- def handle_toggle_all(task_id, user_id, filter_area):
1042
- if task_id:
1043
- toggle_task_complete(int(task_id), user_id)
1044
- rows = refresh_all_tasks(user_id, filter_area)
1045
- return rows, '<span class="msg-success">✓ Updated</span>'
1046
-
1047
- def handle_delete_all(task_id, user_id, filter_area):
1048
- if task_id:
1049
- delete_task(int(task_id), user_id)
1050
- rows = refresh_all_tasks(user_id, filter_area)
1051
- return rows, '<span class="msg-success">✓ Deleted</span>'
1052
-
1053
- def handle_filter_change(user_id, filter_area):
1054
- return refresh_all_tasks(user_id, filter_area)
1055
-
1056
- # ── PREFERENCES HANDLERS ───────────────────────────────────────────────
1057
-
1058
- def handle_save_prefs(user_id, wake, sleep, focus, notes):
1059
- if not user_id:
1060
- return '<span class="msg-error">Not logged in.</span>'
1061
- save_preferences(user_id, wake or "08:00", sleep or "23:00", focus or "Morning", notes or "")
1062
- return '<span class="msg-success">✓ Preferences saved!</span>'
1063
-
1064
- def handle_save_api_url(url):
1065
- global TASK_API_URL
1066
- TASK_API_URL = url.strip()
1067
- return f'<span class="msg-success">✓ API URL updated to: {TASK_API_URL}</span>'
1068
-
1069
- # ══════════════════════════════════════════════════════════════════════
1070
- # EVENT WIRING
1071
- # ══════════════════════════════════════════════════════════════════════
1072
-
1073
- # Auth
1074
- login_btn.click(
1075
- handle_login,
1076
- inputs=[login_user_input, login_pass_input],
1077
- outputs=[
1078
- auth_section, app_section,
1079
- login_msg, reg_msg,
1080
- user_id_state, username_state,
1081
- header_user_html,
1082
- area_filter, res_area, del_area_dropdown,
1083
- areas_html,
1084
- today_tasks_df, all_tasks_df,
1085
- today_total_html, today_done_html, today_remain_html,
1086
- pref_wake_settings, pref_sleep_settings, pref_focus_settings, pref_notes,
1087
- ]
1088
- )
1089
-
1090
- reg_btn.click(
1091
- handle_register,
1092
- inputs=[reg_user_input, reg_pass_input],
1093
- outputs=[
1094
- auth_section, app_section,
1095
- login_msg, reg_msg,
1096
- user_id_state, username_state,
1097
- header_user_html,
1098
- area_filter, res_area, del_area_dropdown,
1099
- areas_html,
1100
- today_tasks_df, all_tasks_df,
1101
- today_total_html, today_done_html, today_remain_html,
1102
- pref_wake_settings, pref_sleep_settings, pref_focus_settings, pref_notes,
1103
- ]
1104
- )
1105
-
1106
- logout_btn.click(
1107
- handle_logout,
1108
- outputs=[auth_section, app_section, user_id_state, username_state,
1109
- header_user_html, login_user_input, login_pass_input,
1110
- login_msg, reg_msg]
1111
- )
1112
-
1113
- # Add task panel toggle
1114
- add_task_btn.click(show_add_panel, outputs=[add_task_panel, plan_day_panel])
1115
- cancel_add_btn.click(
1116
- hide_add_panel,
1117
- outputs=[add_task_panel, parsed_result_section, task_input_text]
1118
- )
1119
- discard_btn.click(
1120
- lambda: (gr.update(visible=False), gr.update(visible=False), gr.update(value="")),
1121
- outputs=[parsed_result_section, add_task_panel, task_input_text]
1122
- )
1123
-
1124
- # Parse task
1125
- parse_task_btn.click(
1126
- handle_parse_task,
1127
- inputs=[task_input_text, user_id_state],
1128
- outputs=[
1129
- parsed_result_section, parsed_task_state,
1130
- res_title, res_area, res_urgency, res_importance, res_state, res_time, res_date,
1131
- api_source_html, clarification_html,
1132
- ]
1133
- )
1134
-
1135
- # Confirm task save
1136
- confirm_task_btn.click(
1137
- handle_confirm_task,
1138
- inputs=[user_id_state, res_title, res_area, res_urgency, res_importance, res_state, res_time, res_date],
1139
- outputs=[
1140
- save_task_msg, add_task_panel, parsed_result_section, task_input_text,
1141
- today_tasks_df, all_tasks_df,
1142
- today_total_html, today_done_html, today_remain_html,
1143
- ]
1144
- )
1145
-
1146
- # Plan day panel
1147
- plan_day_btn.click(
1148
- show_plan_panel,
1149
- inputs=[user_id_state],
1150
- outputs=[plan_day_panel, pref_wake, pref_sleep, pref_focus]
1151
- )
1152
- plan_day_btn.click(lambda: gr.update(visible=False), outputs=[add_task_panel])
1153
- cancel_plan_btn.click(hide_plan_panel, outputs=[plan_day_panel])
1154
- save_plan_btn.click(
1155
- handle_save_plan,
1156
- inputs=[user_id_state, pref_wake, pref_sleep, pref_focus, plan_prompt],
1157
- outputs=[plan_msg]
1158
- )
1159
-
1160
- # Life areas
1161
- add_area_btn.click(
1162
- handle_add_area,
1163
- inputs=[user_id_state, new_area_name, new_area_color],
1164
- outputs=[area_msg, areas_html, area_filter, res_area, del_area_dropdown, new_area_name]
1165
- )
1166
- del_area_btn.click(
1167
- handle_del_area,
1168
- inputs=[user_id_state, del_area_dropdown],
1169
- outputs=[del_area_msg, areas_html, area_filter, res_area, del_area_dropdown]
1170
- )
1171
-
1172
- # Refresh buttons
1173
- refresh_today.click(
1174
- refresh_today_tasks,
1175
- inputs=[user_id_state],
1176
- outputs=[today_tasks_df, today_total_html, today_done_html, today_remain_html]
1177
- )
1178
- refresh_all.click(
1179
- lambda uid, fa: refresh_all_tasks(uid, fa),
1180
- inputs=[user_id_state, area_filter],
1181
- outputs=[all_tasks_df]
1182
- )
1183
-
1184
- # Task actions — Today tab
1185
- complete_btn_today.click(
1186
- handle_toggle_today,
1187
- inputs=[complete_id_today, user_id_state],
1188
- outputs=[today_tasks_df, today_total_html, today_done_html, today_remain_html, action_msg_today]
1189
- )
1190
- delete_btn_today.click(
1191
- handle_delete_today,
1192
- inputs=[delete_id_today, user_id_state],
1193
- outputs=[today_tasks_df, today_total_html, today_done_html, today_remain_html, action_msg_today]
1194
- )
1195
-
1196
- # Task actions — All Tasks tab
1197
- complete_btn_all.click(
1198
- handle_toggle_all,
1199
- inputs=[complete_id_all, user_id_state, area_filter],
1200
- outputs=[all_tasks_df, action_msg_all]
1201
- )
1202
- delete_btn_all.click(
1203
- handle_delete_all,
1204
- inputs=[delete_id_all, user_id_state, area_filter],
1205
- outputs=[all_tasks_df, action_msg_all]
1206
- )
1207
-
1208
- # Filter
1209
- area_filter.change(
1210
- handle_filter_change,
1211
- inputs=[user_id_state, area_filter],
1212
- outputs=[all_tasks_df]
1213
- )
1214
-
1215
- # Preferences
1216
- save_prefs_btn.click(
1217
- handle_save_prefs,
1218
- inputs=[user_id_state, pref_wake_settings, pref_sleep_settings, pref_focus_settings, pref_notes],
1219
- outputs=[prefs_msg]
1220
- )
1221
- save_api_btn.click(
1222
- handle_save_api_url,
1223
- inputs=[api_url_input],
1224
- outputs=[api_msg]
1225
- )
1226
-
1227
-
1228
- if __name__ == "__main__":
1229
- demo.launch(css=CSS, theme=gr.themes.Base())