-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path14_human_approval.js
More file actions
135 lines (102 loc) · 3.79 KB
/
Copy path14_human_approval.js
File metadata and controls
135 lines (102 loc) · 3.79 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
import { ChatGroq } from "@langchain/groq";
import { MemorySaver } from "@langchain/langgraph";
import { StateGraph, START, END, Annotation } from "@langchain/langgraph";
import * as readline from "readline";
import nodeMailer from "nodemailer";
import "dotenv/config";
//terminal input setup
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
//1. setup llm
const llm = new ChatGroq({
apiKey: process.env.GROQ_API_KEY,
model: "openai/gpt-oss-120b",
temperature: 0.2
});
//2. Email transporter setup(nodemailer)
const transporter = nodeMailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
}
});
//3. define graph state
const GraphState = Annotation.Root({
recipient_email: Annotation(), //target email address
topic: Annotation(), // topic of the email
drafted_email: Annotation(), //email content generated by AI
});
//4. Nodes (worker)
//node A: ai email drafter
async function drafterNode(state) {
console.log("\n [Ai drafter]: Writing professtional email for -> ", state.topic, "\n");
const prompt = `Write a short , professtional email body (no subject line ) about this topic :${state.topic}`;
const response = await llm.invoke(prompt);
return { drafted_email: response.content };
}
//node B: email sender
async function senderNode(state) {
console.log("Sending email...\n");
try {
const mailOptions = {
from: process.env.EMAIL_USER,
to: state.recipient_email,
subject: "AI Automated Message", //mail subject
text: state.drafted_email,
};
const info = await transporter.sendMail(mailOptions);
console.log(`[Success] email officially send to : ${state.recipient_email}...\n `);
console.log(`Message ID: ${info.messageId}`);
} catch (error) {
console.log("Error: ", error.message);
}
return {};
}
//build the graph
const memory = new MemorySaver();
const workflow = new StateGraph(GraphState)
.addNode("drafter", drafterNode)
.addNode("sender", senderNode)
.addEdge(START, "drafter")
.addEdge("drafter", "sender")
.addEdge("sender", END)
//interrupt before send
const app = workflow.compile({
checkpointer: memory,
interruptBefore: ["sender"]
});
async function main() {
const config = { configurable: { thread_id: "email_01" } };
const initialInput = {
recipient_email: "manikkori697@gmail.com",
topic: "Say good morning. "
}
//1. run graph(interrupt before sender)
await app.invoke(initialInput, config);
//2. get current state
const currentState = await app.getState(config);
const nextNode = currentState.next[0];
if (nextNode === "sender") {
console.log("\n==================================================");
console.log("⚠️ [SECURITY ALERT - APPROVAL REQUIRED] ⚠️");
console.log("==================================================");
console.log(`📤 To: ${currentState.values.recipient_email}`);
console.log(`✉️ AI Draft:\n\n${currentState.values.drafted_email}`);
console.log("==================================================\n");
// 3: Ask for real permission
const answer = await askQuestion(`Do you want to actually send this email? (Y/N): `);
if (answer.toLowerCase() === 'y') {
console.log("\n✅ Approved! Handing over to Nodemailer...");
// resume the graph(rerun))
await app.invoke(null, config);
} else {
console.log("\n❌ Cancelled! The email was safely destroyed.");
}
}
rl.close()
}
main().catch(console.error)