Gaykar commited on
Commit
0a0e1fc
Β·
1 Parent(s): 477695a
Files changed (2) hide show
  1. app/gradio_ui.py +598 -0
  2. authenticate_gmail.py +21 -21
app/gradio_ui.py ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ import requests
3
+ import gradio as gr
4
+
5
+ # ─── Config ─────────────────────────────────────────────────────
6
+ BASE_URL = "https://gaykar-emailagentwithmemory.hf.space"
7
+
8
+
9
+ # ─── Theme: dark navy / electric-blue, matching the HTML frontend ─
10
+ THEME_CSS = """
11
+ :root {
12
+ --bg: #0D1117;
13
+ --surface: #161B22;
14
+ --surface2: #1C2333;
15
+ --border: #30363D;
16
+ --accent: #58A6FF;
17
+ --accent-dim:#1a2738;
18
+ --success: #3FB950;
19
+ --success-dim: #122a18;
20
+ --warning: #D29922;
21
+ --warning-dim: #2c2310;
22
+ --danger: #F85149;
23
+ --danger-dim: #2c1414;
24
+ --text: #E6EDF3;
25
+ --text-muted:#8B949E;
26
+ --text-dim: #6E7681;
27
+ }
28
+
29
+ .gradio-container {
30
+ background: var(--bg) !important;
31
+ color: var(--text) !important;
32
+ font-family: 'Inter', -apple-system, sans-serif !important;
33
+ }
34
+
35
+ /* Headings / markdown */
36
+ .gradio-container h1, .gradio-container h2, .gradio-container h3 {
37
+ color: var(--text) !important;
38
+ letter-spacing: -0.02em;
39
+ }
40
+ .gradio-container p, .gradio-container label span {
41
+ color: var(--text-muted) !important;
42
+ }
43
+
44
+ /* Cards / blocks */
45
+ .gr-block, .gr-box, .block {
46
+ background: var(--surface) !important;
47
+ border: 1px solid var(--border) !important;
48
+ border-radius: 12px !important;
49
+ }
50
+
51
+ /* Inputs */
52
+ textarea, input[type="text"], input[type="email"] {
53
+ background: var(--bg) !important;
54
+ border: 1px solid var(--border) !important;
55
+ color: var(--text) !important;
56
+ border-radius: 8px !important;
57
+ font-family: 'JetBrains Mono', monospace !important;
58
+ font-size: 13px !important;
59
+ }
60
+ textarea:focus, input:focus {
61
+ border-color: var(--accent) !important;
62
+ box-shadow: 0 0 0 3px rgba(88,166,255,0.1) !important;
63
+ }
64
+
65
+ /* Buttons */
66
+ button {
67
+ border-radius: 8px !important;
68
+ font-weight: 500 !important;
69
+ border: none !important;
70
+ transition: all 0.15s !important;
71
+ }
72
+ .primary, button.primary {
73
+ background: var(--accent) !important;
74
+ color: #0D1117 !important;
75
+ }
76
+ .primary:hover { background: #79BAFF !important; }
77
+
78
+ #approve-btn button, #approve-btn {
79
+ background: var(--success) !important;
80
+ color: #0D1117 !important;
81
+ }
82
+ #approve-btn button:hover { background: #56D364 !important; }
83
+
84
+ #reject-btn button, #reject-btn {
85
+ background: var(--danger) !important;
86
+ color: #fff !important;
87
+ }
88
+ #reject-btn button:hover { background: #FF7B72 !important; }
89
+
90
+ #changes-btn button, #changes-btn {
91
+ background: var(--warning) !important;
92
+ color: #0D1117 !important;
93
+ }
94
+ #changes-btn button:hover { background: #E3AD40 !important; }
95
+
96
+ #send-btn button {
97
+ background: var(--success) !important;
98
+ color: #0D1117 !important;
99
+ }
100
+
101
+ /* Pipeline status pills */
102
+ .pipeline-pill {
103
+ display: inline-block;
104
+ padding: 4px 12px;
105
+ border-radius: 20px;
106
+ font-family: 'JetBrains Mono', monospace;
107
+ font-size: 11px;
108
+ font-weight: 600;
109
+ letter-spacing: 0.05em;
110
+ margin-right: 6px;
111
+ border: 1px solid var(--border);
112
+ color: var(--text-dim);
113
+ background: var(--surface2);
114
+ }
115
+ .pipeline-pill.active {
116
+ border-color: var(--accent);
117
+ color: var(--accent);
118
+ background: var(--accent-dim);
119
+ }
120
+ .pipeline-pill.done {
121
+ border-color: var(--success);
122
+ color: var(--success);
123
+ background: var(--success-dim);
124
+ }
125
+
126
+ /* Triage badge */
127
+ .triage-badge {
128
+ display: inline-block;
129
+ font-family: 'JetBrains Mono', monospace;
130
+ font-size: 11px;
131
+ font-weight: 700;
132
+ letter-spacing: 0.05em;
133
+ padding: 4px 10px;
134
+ border-radius: 6px;
135
+ margin-top: 6px;
136
+ }
137
+ .triage-follow-up {
138
+ background: var(--warning-dim);
139
+ color: var(--warning);
140
+ border: 1px solid var(--warning);
141
+ }
142
+ .triage-no-action {
143
+ background: var(--success-dim);
144
+ color: var(--success);
145
+ border: 1px solid var(--success);
146
+ }
147
+ .triage-other {
148
+ background: var(--accent-dim);
149
+ color: var(--accent);
150
+ border: 1px solid var(--accent);
151
+ }
152
+
153
+ /* Draft display card */
154
+ .draft-card {
155
+ background: var(--surface) !important;
156
+ border: 1px solid var(--border) !important;
157
+ border-radius: 12px !important;
158
+ padding: 18px !important;
159
+ }
160
+ .draft-meta-label {
161
+ font-family: 'JetBrains Mono', monospace;
162
+ font-size: 10px;
163
+ letter-spacing: 0.1em;
164
+ color: var(--text-dim);
165
+ text-transform: uppercase;
166
+ }
167
+
168
+ /* Status / error text */
169
+ .status-ok { color: var(--success) !important; }
170
+ .status-err { color: var(--danger) !important; }
171
+ """
172
+
173
+
174
+ # ─── Helpers ────────────────────────────────────────────────────
175
+ def _gen_thread_id() -> str:
176
+ return str(uuid.uuid4())[:8]
177
+
178
+
179
+ def _pipeline_html(active_step: str, done_steps=None) -> str:
180
+ """Render the TRIAGE β†’ DRAFT β†’ REVIEW β†’ SEND pill strip."""
181
+ done_steps = done_steps or []
182
+ steps = ["TRIAGE", "DRAFT", "REVIEW", "SEND"]
183
+ html = ""
184
+ for s in steps:
185
+ cls = "pipeline-pill"
186
+ if s == active_step:
187
+ cls += " active"
188
+ elif s in done_steps:
189
+ cls += " done"
190
+ html += f'<span class="{cls}">{s}</span>'
191
+ return html
192
+
193
+
194
+ def _triage_badge_html(label: str) -> str:
195
+ if not label:
196
+ return ""
197
+ cls = "triage-other"
198
+ if label == "FOLLOW_UP_REQUIRED":
199
+ cls = "triage-follow-up"
200
+ elif label == "NO_ACTION":
201
+ cls = "triage-no-action"
202
+ return f'<span class="triage-badge {cls}">{label}</span>'
203
+
204
+
205
+ def _draft_card_html(to, subject, body) -> str:
206
+ to = to or "β€”"
207
+ subject = subject or "β€”"
208
+ body = (body or "β€”").replace("\n", "<br>")
209
+ return f"""
210
+ <div class="draft-card">
211
+ <div style="display:flex; gap:32px; margin-bottom:14px;">
212
+ <div>
213
+ <div class="draft-meta-label">TO</div>
214
+ <div style="font-family:'JetBrains Mono',monospace; font-size:13px;">{to}</div>
215
+ </div>
216
+ <div>
217
+ <div class="draft-meta-label">SUBJECT</div>
218
+ <div style="font-size:13px;">{subject}</div>
219
+ </div>
220
+ </div>
221
+ <div style="height:1px; background:var(--border); margin-bottom:14px;"></div>
222
+ <div style="font-size:13px; line-height:1.7; color:var(--text-muted);">{body}</div>
223
+ </div>
224
+ """
225
+
226
+
227
+ # ─── API calls (talk to this same backend's endpoints) ──────────
228
+ def api_get_user_data(user_email: str):
229
+ res = requests.post(f"{BASE_URL}/get-user-data", params={"user_email": user_email})
230
+ res.raise_for_status()
231
+ return res.json()
232
+
233
+
234
+ def api_process_email(token, thread_id, sender_email, subject, body):
235
+ res = requests.post(
236
+ f"{BASE_URL}/process-email",
237
+ headers={"Authorization": f"Bearer {token}"},
238
+ json={
239
+ "thread_id": thread_id,
240
+ "sender_email_id": sender_email,
241
+ "sender_subject": subject,
242
+ "sender_email_body": body,
243
+ },
244
+ )
245
+ res.raise_for_status()
246
+ return res.json()
247
+
248
+
249
+ def api_review_action(token, thread_id, status, feedback=None):
250
+ payload = {"thread_id": thread_id, "status": status}
251
+ if feedback:
252
+ payload["feedback"] = feedback
253
+ res = requests.post(
254
+ f"{BASE_URL}/review-action",
255
+ headers={"Authorization": f"Bearer {token}"},
256
+ json=payload,
257
+ )
258
+ res.raise_for_status()
259
+ return res.json()
260
+
261
+
262
+ def api_send_email(token, thread_id, command_text):
263
+ """
264
+ `command_text` is a short instruction for the agent about WHEN/HOW
265
+ to send the already-approved draft β€” e.g. "send now", "send later
266
+ today", "hold until tomorrow morning". It does NOT edit draft content.
267
+ """
268
+ res = requests.post(
269
+ f"{BASE_URL}/send_email",
270
+ headers={"Authorization": f"Bearer {token}"},
271
+ json={"thread_id": thread_id, "human_message": command_text or "Send now."},
272
+ )
273
+ res.raise_for_status()
274
+ return res.json()
275
+
276
+
277
+ # ─── Gradio App ─────────────────────────────────────────────────
278
+ def build_demo() -> gr.Blocks:
279
+ with gr.Blocks(css=THEME_CSS, title="Email Agent β€” AI Triage & Reply") as demo:
280
+
281
+ # ─── Session state ──────────────────────────────────────
282
+ token_state = gr.State(None)
283
+ thread_state = gr.State(_gen_thread_id())
284
+ triage_label_state = gr.State(None)
285
+
286
+ gr.Markdown("# ⚑ Email Agent\n_AI-powered triage, drafting & sending β€” with a human in the loop._")
287
+
288
+ # ─── Auth row ────────────────────────────────────────────
289
+ with gr.Group(visible=True) as auth_group:
290
+ gr.Markdown("### Sign In")
291
+ with gr.Row():
292
+ email_input = gr.Textbox(label="Your Email", placeholder="you@example.com", scale=3)
293
+ login_btn = gr.Button("Sign In β†’", variant="primary", scale=1)
294
+ auth_status = gr.Markdown("")
295
+
296
+ # ─── Main workspace (hidden until signed in) ─────────────
297
+ with gr.Group(visible=False) as workspace_group:
298
+ with gr.Row():
299
+ user_pill = gr.Markdown("")
300
+ with gr.Row():
301
+ thread_display = gr.Textbox(label="Thread ID", interactive=True, scale=3)
302
+ new_thread_btn = gr.Button("β†Ί New Thread", scale=1)
303
+
304
+ pipeline_html = gr.HTML(_pipeline_html("TRIAGE"))
305
+ triage_badge_html = gr.HTML("")
306
+
307
+ gr.Markdown("---")
308
+
309
+ # ─── Step 1: Process Email ────────────────────────────
310
+ with gr.Group(visible=True) as process_group:
311
+ gr.Markdown("### πŸ“₯ Incoming Email")
312
+ with gr.Row():
313
+ sender_email = gr.Textbox(label="From (sender email)", placeholder="sender@company.com")
314
+ sender_subject = gr.Textbox(label="Subject", placeholder="Email subject line")
315
+ sender_body = gr.Textbox(label="Email Body", lines=8, placeholder="Paste the full email body here…")
316
+ process_btn = gr.Button("⚑ Process Email", variant="primary")
317
+ process_status = gr.Markdown("")
318
+
319
+ # ─── Step 2: Review Draft ──────────────────────────────
320
+ with gr.Group(visible=False) as review_group:
321
+ gr.Markdown("### ✍️ Review Draft\nThe agent has drafted a reply. Approve, request changes, or reject.")
322
+ draft_html = gr.HTML("")
323
+
324
+ with gr.Row():
325
+ approve_btn = gr.Button("βœ“ Approve & Finalize Draft", elem_id="approve-btn")
326
+ changes_btn = gr.Button("β†Ί Request Changes", elem_id="changes-btn")
327
+ reject_btn = gr.Button("βœ• Reject", elem_id="reject-btn")
328
+
329
+ with gr.Group(visible=False) as feedback_group:
330
+ feedback_text = gr.Textbox(
331
+ label="Feedback for agent",
332
+ lines=4,
333
+ placeholder="Tell the agent what to change in the draft…",
334
+ )
335
+ submit_feedback_btn = gr.Button("Submit Feedback & Redraft", elem_id="changes-btn")
336
+
337
+ review_status = gr.Markdown("")
338
+
339
+ # ─── Step 3: Send Email ────────────────────────────────
340
+ with gr.Group(visible=False) as send_group:
341
+ gr.Markdown(
342
+ "### πŸ“¨ Send Email\n"
343
+ "Draft approved. The agent can only **draft** and **send** β€” "
344
+ "it can't edit content here. Tell it **when/how to send** "
345
+ "(e.g. *\"send now\"*, *\"send later today\"*, *\"hold off till tomorrow\"*)."
346
+ )
347
+ send_command = gr.Textbox(
348
+ label="Send Command",
349
+ placeholder='e.g. "send now" / "send later" / "hold off until tomorrow"',
350
+ )
351
+ send_btn = gr.Button("πŸ“¨ Send Email", elem_id="send-btn", variant="primary")
352
+ send_status = gr.Markdown("")
353
+
354
+ # ─── Step 4: Result ─────────────────────────────────────
355
+ with gr.Group(visible=False) as result_group:
356
+ result_html = gr.HTML("")
357
+ new_email_btn = gr.Button("Process Another Email", variant="primary")
358
+
359
+ # ══════════════════════════════════════════════════════════
360
+ # Callbacks
361
+ # ══════════════════════════════════════════════════════════
362
+
363
+ def do_login(email):
364
+ if not email or "@" not in email:
365
+ return (
366
+ gr.update(), gr.update(), None, None,
367
+ "<span class='status-err'>Enter a valid email address.</span>",
368
+ gr.update(), "",
369
+ )
370
+ try:
371
+ data = api_get_user_data(email)
372
+ token = data["token"]
373
+ pill_md = f"**Signed in as:** `{data['email']}`"
374
+ return (
375
+ gr.update(visible=False), # auth_group
376
+ gr.update(visible=True), # workspace_group
377
+ token, # token_state
378
+ data["user_id"], # (kept in case needed)
379
+ "", # auth_status
380
+ gr.update(), # placeholder
381
+ pill_md, # user_pill
382
+ )
383
+ except Exception as e:
384
+ return (
385
+ gr.update(), gr.update(), None, None,
386
+ f"<span class='status-err'>Sign-in failed: {e}</span>",
387
+ gr.update(), "",
388
+ )
389
+
390
+ login_btn.click(
391
+ do_login,
392
+ inputs=[email_input],
393
+ outputs=[auth_group, workspace_group, token_state, gr.State(), auth_status, gr.State(), user_pill],
394
+ )
395
+
396
+ def new_thread():
397
+ tid = _gen_thread_id()
398
+ return tid, tid
399
+
400
+ new_thread_btn.click(new_thread, outputs=[thread_state, thread_display])
401
+
402
+ def sync_thread_id(tid):
403
+ return tid
404
+
405
+ thread_display.change(sync_thread_id, inputs=[thread_display], outputs=[thread_state])
406
+
407
+ # ─── Process Email ──────────────────────────────────────
408
+ def do_process(token, thread_id, s_email, s_subject, s_body):
409
+ if not (s_email and s_subject and s_body):
410
+ return (
411
+ gr.update(), gr.update(), gr.update(), None,
412
+ "<span class='status-err'>Fill in sender email, subject, and body.</span>",
413
+ _pipeline_html("TRIAGE"), "",
414
+ gr.update(), gr.update(),
415
+ )
416
+ try:
417
+ data = api_process_email(token, thread_id, s_email, s_subject, s_body)
418
+ label = data.get("triage_label")
419
+ badge = _triage_badge_html(label)
420
+
421
+ if data.get("status") == "needs_review":
422
+ draft = data.get("email_draft", {})
423
+ return (
424
+ gr.update(visible=False), # process_group
425
+ gr.update(visible=True), # review_group
426
+ gr.update(visible=False), # send_group
427
+ label, # triage_label_state
428
+ "", # process_status
429
+ _pipeline_html("REVIEW", ["TRIAGE", "DRAFT"]),
430
+ badge,
431
+ _draft_card_html(draft.get("to"), draft.get("subject"), draft.get("body")),
432
+ gr.update(visible=False), # result_group
433
+ )
434
+ else:
435
+ # No action needed β€” triage only, nothing to review/send
436
+ detail = "No reply needed β€” the agent filed this email." if label == "NO_ACTION" else f"Triage complete. Label: {label}"
437
+ result = f"""
438
+ <div class="draft-card" style="text-align:center;">
439
+ <div style="font-size:32px; margin-bottom:8px;">{'βœ…' if label == 'NO_ACTION' else 'πŸ“‹'}</div>
440
+ <div style="font-size:16px; font-weight:600; margin-bottom:6px;">Triage Complete</div>
441
+ <div style="color:var(--text-muted); font-size:13px; margin-bottom:10px;">{detail}</div>
442
+ {badge}
443
+ </div>
444
+ """
445
+ return (
446
+ gr.update(visible=False),
447
+ gr.update(visible=False),
448
+ gr.update(visible=False),
449
+ label,
450
+ "",
451
+ _pipeline_html("SEND", ["TRIAGE", "DRAFT", "REVIEW"]),
452
+ badge,
453
+ "",
454
+ gr.update(visible=True, value=result),
455
+ )
456
+ except Exception as e:
457
+ return (
458
+ gr.update(), gr.update(), gr.update(), None,
459
+ f"<span class='status-err'>Error processing email: {e}</span>",
460
+ _pipeline_html("TRIAGE"), "", gr.update(), gr.update(),
461
+ )
462
+
463
+ process_btn.click(
464
+ do_process,
465
+ inputs=[token_state, thread_state, sender_email, sender_subject, sender_body],
466
+ outputs=[
467
+ process_group, review_group, send_group, triage_label_state,
468
+ process_status, pipeline_html, triage_badge_html, draft_html, result_group,
469
+ ],
470
+ )
471
+
472
+ # ─── Review: toggle feedback box ────────────────────────
473
+ def toggle_feedback():
474
+ return gr.update(visible=True)
475
+
476
+ changes_btn.click(toggle_feedback, outputs=[feedback_group])
477
+
478
+ # ─── Review: approve / reject / submit feedback ─────────
479
+ def do_review(token, thread_id, status, feedback):
480
+ try:
481
+ data = api_review_action(token, thread_id, status, feedback)
482
+
483
+ if data.get("status") == "needs_review":
484
+ # Redrafted β€” show new draft, stay on review step
485
+ draft = data.get("email_draft", {})
486
+ return (
487
+ gr.update(visible=True), # review_group
488
+ gr.update(visible=False), # send_group
489
+ _draft_card_html(draft.get("to"), draft.get("subject"), draft.get("body")),
490
+ gr.update(visible=False), # feedback_group (collapse after redraft)
491
+ "", # feedback_text clear
492
+ "<span class='status-ok'>Agent redrafted β€” review the new version.</span>",
493
+ _pipeline_html("REVIEW", ["TRIAGE", "DRAFT"]),
494
+ )
495
+ elif data.get("draft_id"):
496
+ return (
497
+ gr.update(visible=False), # review_group
498
+ gr.update(visible=True), # send_group
499
+ gr.update(),
500
+ gr.update(visible=False),
501
+ "",
502
+ "<span class='status-ok'>Draft approved! Ready to send.</span>",
503
+ _pipeline_html("SEND", ["TRIAGE", "DRAFT", "REVIEW"]),
504
+ )
505
+ else:
506
+ return (
507
+ gr.update(visible=False), gr.update(visible=True), gr.update(),
508
+ gr.update(visible=False), "", "", _pipeline_html("SEND", ["TRIAGE", "DRAFT", "REVIEW"]),
509
+ )
510
+ except Exception as e:
511
+ return (
512
+ gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
513
+ f"<span class='status-err'>Review action failed: {e}</span>", gr.update(),
514
+ )
515
+
516
+ approve_btn.click(
517
+ lambda token, tid: do_review(token, tid, "approved", None),
518
+ inputs=[token_state, thread_state],
519
+ outputs=[review_group, send_group, draft_html, feedback_group, feedback_text, review_status, pipeline_html],
520
+ )
521
+
522
+ reject_btn.click(
523
+ lambda token, tid: do_review(token, tid, "rejected", None),
524
+ inputs=[token_state, thread_state],
525
+ outputs=[review_group, send_group, draft_html, feedback_group, feedback_text, review_status, pipeline_html],
526
+ )
527
+
528
+ submit_feedback_btn.click(
529
+ lambda token, tid, fb: do_review(token, tid, "rejected", fb),
530
+ inputs=[token_state, thread_state, feedback_text],
531
+ outputs=[review_group, send_group, draft_html, feedback_group, feedback_text, review_status, pipeline_html],
532
+ )
533
+
534
+ # ─── Send Email ──────────────────────────────────────────
535
+ def do_send(token, thread_id, command_text):
536
+ try:
537
+ data = api_send_email(token, thread_id, command_text)
538
+ msg_id = data.get("sent_message_id")
539
+ detail = f"Message ID: <code>{msg_id}</code>" if msg_id else "Email dispatched."
540
+ result = f"""
541
+ <div class="draft-card" style="text-align:center;">
542
+ <div style="font-size:32px; margin-bottom:8px;">πŸ“¨</div>
543
+ <div style="font-size:16px; font-weight:600; margin-bottom:6px;">Email Sent</div>
544
+ <div style="color:var(--text-muted); font-size:12px; font-family:'JetBrains Mono',monospace;">{detail}</div>
545
+ </div>
546
+ """
547
+ return (
548
+ gr.update(visible=False), # send_group
549
+ gr.update(visible=True, value=result), # result_group
550
+ "",
551
+ _pipeline_html("SEND", ["TRIAGE", "DRAFT", "REVIEW", "SEND"]),
552
+ )
553
+ except Exception as e:
554
+ return (
555
+ gr.update(), gr.update(),
556
+ f"<span class='status-err'>Failed to send: {e}</span>", gr.update(),
557
+ )
558
+
559
+ send_btn.click(
560
+ do_send,
561
+ inputs=[token_state, thread_state, send_command],
562
+ outputs=[send_group, result_group, send_status, pipeline_html],
563
+ )
564
+
565
+ # ─── Start New ───────────────────────────────────────────
566
+ def start_new():
567
+ new_tid = _gen_thread_id()
568
+ return (
569
+ new_tid, new_tid,
570
+ gr.update(visible=True), # process_group
571
+ gr.update(visible=False), # review_group
572
+ gr.update(visible=False), # send_group
573
+ gr.update(visible=False), # result_group
574
+ "", "", "", # clear sender fields
575
+ "", "", # clear send command, draft html
576
+ gr.update(visible=False), "", # feedback group + text
577
+ _pipeline_html("TRIAGE"), "",
578
+ )
579
+
580
+ new_email_btn.click(
581
+ start_new,
582
+ outputs=[
583
+ thread_state, thread_display,
584
+ process_group, review_group, send_group, result_group,
585
+ sender_email, sender_subject, sender_body,
586
+ send_command, draft_html,
587
+ feedback_group, feedback_text,
588
+ pipeline_html, triage_badge_html,
589
+ ],
590
+ )
591
+
592
+ return demo
593
+
594
+
595
+
596
+
597
+
598
+
authenticate_gmail.py CHANGED
@@ -1,29 +1,29 @@
1
- # import os
2
- # from google_auth_oauthlib.flow import InstalledAppFlow
3
 
4
- # # Gmail API scope
5
- # SCOPES = [
6
- # "https://mail.google.com/"
7
- # ]
8
 
9
- # def run_authentication():
10
- # print("Starting Google OAuth Authentication Flow...")
11
 
12
- # if not os.path.exists("credentials.json"):
13
- # print("ERROR: Please place your 'credentials.json' file in this folder first!")
14
- # return
15
 
16
- # flow = InstalledAppFlow.from_client_secrets_file(
17
- # "credentials.json",
18
- # SCOPES
19
- # )
20
 
21
- # creds = flow.run_local_server(port=8080)
22
 
23
- # with open("token.json", "w") as token_file:
24
- # token_file.write(creds.to_json())
25
 
26
- # print("\nSUCCESS: 'token.json' has been created successfully!")
27
 
28
- # if __name__ == "__main__":
29
- # run_authentication()
 
1
+ import os
2
+ from google_auth_oauthlib.flow import InstalledAppFlow
3
 
4
+ # Gmail API scope
5
+ SCOPES = [
6
+ "https://mail.google.com/"
7
+ ]
8
 
9
+ def run_authentication():
10
+ print("Starting Google OAuth Authentication Flow...")
11
 
12
+ if not os.path.exists("credentials.json"):
13
+ print("ERROR: Please place your 'credentials.json' file in this folder first!")
14
+ return
15
 
16
+ flow = InstalledAppFlow.from_client_secrets_file(
17
+ "credentials.json",
18
+ SCOPES
19
+ )
20
 
21
+ creds = flow.run_local_server(port=8080)
22
 
23
+ with open("token.json", "w") as token_file:
24
+ token_file.write(creds.to_json())
25
 
26
+ print("\nSUCCESS: 'token.json' has been created successfully!")
27
 
28
+ if __name__ == "__main__":
29
+ run_authentication()