-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
159 lines (129 loc) · 5.31 KB
/
Copy pathmain.py
File metadata and controls
159 lines (129 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
main.py
-------
Entry point for the Email Agent CLI.
Run with: python main.py
"""
import sys
import typer
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
from rich.rule import Rule
from rich import print as rprint
from langchain_core.messages import HumanMessage
from agent.graph import graph
# ---------------------------------------------------------------------------
# App setup
# ---------------------------------------------------------------------------
app = typer.Typer(add_completion=False, help="Local Email Agent powered by Ollama + Gmail")
console = Console()
BANNER = Text.assemble(
(" ✉ ", "bold cyan"),
("Email Agent", "bold white"),
(" ✉ ", "bold cyan"),
justify="center",
)
SUBTITLE = Text(
"Local · Private · Agent",
style="dim",
justify="center",
)
def print_banner():
console.print()
console.print(Panel.fit(
Text.assemble(BANNER, "\n", SUBTITLE),
border_style="cyan",
padding=(1, 4),
))
console.print(Rule(style="dim"))
console.print(
" Type your request in plain English. "
"[dim]Type [bold]exit[/bold] or [bold]quit[/bold] to leave.[/dim]"
)
console.print(Rule(style="dim"))
console.print()
def print_response(text: str):
console.print()
console.print(Panel(
text,
title="[bold cyan]Agent[/bold cyan]",
border_style="cyan",
padding=(1, 2),
))
console.print()
# ---------------------------------------------------------------------------
# State initialiser
# ---------------------------------------------------------------------------
def initial_state():
return {
"messages": [],
"emails": [],
"pending_send": None,
"intent": "converse",
"response": "",
}
# ---------------------------------------------------------------------------
# CLI command
# ---------------------------------------------------------------------------
@app.command()
def main():
"""Start the interactive email agent."""
print_banner()
state = initial_state()
while True:
try:
raw = console.input("[bold cyan]You ›[/bold cyan] ").strip()
except (EOFError, KeyboardInterrupt):
console.print("\n[dim]Goodbye![/dim]")
break
if not raw:
continue
if raw.lower() in {"exit", "quit", "bye", "q"}:
console.print("\n[bold cyan]Goodbye! Have a great day. ✉[/bold cyan]\n")
break
# Add user message to state
state["messages"] = state["messages"] + [HumanMessage(content=raw)]
# Run through graph with dynamic execution updates
with console.status("[cyan]🧠 Understanding request…[/cyan]", spinner="dots") as status:
try:
for event in graph.stream(state):
# event is a dict { node_name: state_updates }
node_name = list(event.keys())[0]
node_state = event[node_name]
# Merge updates into local state
state.update(node_state)
# Adapt the status message dynamically based on the current context
if node_name == "classify_intent":
intent = state.get("intent", "")
if state.get("pending_send") and state["pending_send"].get("awaiting_confirm"):
status.update("[blue]🚀 Dispatching email via Gmail API…[/blue]")
elif state.get("pending_delete") and state["pending_delete"].get("awaiting_confirm"):
status.update("[blue]🗑️ Moving selected emails to trash…[/blue]")
elif intent == "list_search":
status.update("[cyan]📨 Searching and fetching emails…[/cyan]")
elif intent == "read":
status.update("[cyan]📖 Fetching full email content…[/cyan]")
elif intent == "summarize":
status.update("[cyan]📝 Analyzing emails and generating summary…[/cyan]")
elif intent == "send":
status.update("[cyan]🚀 Extracting email fields and preparing draft…[/cyan]")
elif intent == "delete":
status.update("[cyan]🗑️ Filtering emails for deletion…[/cyan]")
elif intent == "label":
status.update("[cyan]🏷️ Applying labels to email…[/cyan]")
elif intent == "converse":
status.update("[cyan]💬 Generating response…[/cyan]")
except Exception as exc:
print_response(f"[red]⚠ An error occurred:[/red] {exc}\n\nPlease try again.")
continue
response = state.get("response", "")
if response:
print_response(response)
else:
print_response("[dim](No response generated — please try again.)[/dim]")
# ---------------------------------------------------------------------------
# Entrypoint
# ---------------------------------------------------------------------------
if __name__ == "__main__":
app()