Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ examples/*/pnpm-lock.yaml
examples/ngrok.log
restart.sh

bootstrap.sh
bootstrap.sh
.env*.local
63 changes: 63 additions & 0 deletions examples/email-analyzer-o1/app/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use server"

import { Client as WorkflowClient } from '@upstash/workflow';

type EmailPayload = {
message: string;
subject: string;
to: string;
attachment?: string;
}

function getWorkflowClient(): WorkflowClient {
const token = process.env.QSTASH_TOKEN;

if (!token) {
throw new Error(
'QSTASH_TOKEN environment variable is required'
);
}

return new WorkflowClient({
token,
// VERCEL AUTOMATION BYPASS SECRET is used to bypass the verification of the request
headers: process.env.VERCEL_AUTOMATION_BYPASS_SECRET
? {
'Upstash-Forward-X-Vercel-Protection-Bypass':
process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
'x-vercel-protection-bypass':
process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
}
: undefined,
});
}

export async function triggerEmailAnalysis(formData: EmailPayload) {
try {
const workflowClient = getWorkflowClient()
const result = await workflowClient.trigger({
url: `${process.env.UPSTASH_WORKFLOW_URL ?? process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'http://localhost:3000'}/api/analyze`,
body: formData,
headers: process.env.VERCEL_AUTOMATION_BYPASS_SECRET
? {
'Upstash-Forward-X-Vercel-Protection-Bypass':
process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
'X-Vercel-Protection-Bypass':
process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
"upstash-callback-forward-X-Vercel-Protection-Bypass":
process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
"upstash-failure-callback-forward-X-Vercel-Protection-Bypass":
process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
}
: undefined,
});

return { success: true, workflowRunId: result.workflowRunId };
} catch (error) {
console.error('Error triggering workflow:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to trigger workflow'
};
}
}
142 changes: 87 additions & 55 deletions examples/email-analyzer-o1/app/api/analyze/route.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,103 @@
import { serve } from "@upstash/workflow/nextjs"
import pdf from "pdf-parse"

import { serve } from "@upstash/workflow/nextjs";
import pdf from "pdf-parse";
import { Client as QStashClient } from "@upstash/qstash";

type EmailPayload = {
message: string,
subject: string,
to: string
attachment?: string,
message: string;
subject: string;
to: string;
attachment?: string;
};

function getQStashClient(): QStashClient {
const token = process.env.QSTASH_TOKEN;

if (!token) {
throw new Error("QSTASH_TOKEN environment variable is required");
}

return new QStashClient({
token,
headers: process.env.VERCEL_AUTOMATION_BYPASS_SECRET
? {
"Upstash-Forward-X-Vercel-Protection-Bypass": process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
"x-vercel-protection-bypass": process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
"upstash-callback-forward-X-Vercel-Protection-Bypass":
process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
"upstash-failure-callback-forward-X-Vercel-Protection-Bypass":
process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
}
: undefined,
});
}

export const { POST } = serve<EmailPayload>(async (context) => {
const { message, subject, to, attachment } = context.requestPayload;
export const { POST } = serve<EmailPayload>(
async (context) => {
const { message, subject, to, attachment } = context.requestPayload;

const somethingThatWorks = await context.run("somethingThatWorks", async () => {
return "somethingThatWorks";
});

const pdfContent = await context.run("Process PDF Attachment", async () => {
if (!attachment) {
return '';
}
const pdfContent = await context.run("Process PDF Attachment", async () => {
if (!attachment) {
return "";
}
console.log(somethingThatWorks);

// Download file
const response = await fetch(attachment);
const fileContent = await response.arrayBuffer();
const buffer = Buffer.from(fileContent);
// Download file
const response = await fetch(attachment);
const fileContent = await response.arrayBuffer();
const buffer = Buffer.from(fileContent);

// Parse PDF
try {
const data = await pdf(buffer);
console.log(data)
return data.text;
} catch (error) {
console.error('Error parsing PDF:', error);
return 'Unable to extract PDF content';
}
});
// Parse PDF
try {
const data = await pdf(buffer);
console.log(data);
return data.text;
} catch (error) {
console.error("Error parsing PDF:", error);
return "Unable to extract PDF content";
}
});

const aiResponse = await context.api.openai.call("get ai response", {
token: process.env.OPENAI_API_KEY!,
operation: "chat.completions.create",
body: {
model: "o1",
messages: [
{
role: "system",
content: `You are an AI assistant that writes email responses. Write a natural, professional response
const aiResponse = await context.api.openai.call("get ai response", {
token: process.env.OPENAI_API_KEY!,
operation: "chat.completions.create",
body: {
model: "o1",
messages: [
{
role: "system",
content: `You are an AI assistant that writes email responses. Write a natural, professional response
that continues the email thread. The response should be concise but helpful, maintaining
the flow of the conversation.`
},
{
role: "user",
content: `
the flow of the conversation.`,
},
{
role: "user",
content: `
Here's the email thread context. Please write a response to this email thread that addresses the latest message:
${message}.

Here's the pdf attachment, if exists:
${pdfContent}
`,
}
],
},
})
},
],
},
});

await context.api.resend.call("Send LLM Proposal", {
token: process.env.RESEND_API_KEY!,
body: {
from: "Acme <onboarding@resend.dev>",
to,
subject,
text: aiResponse.body.choices[0].message.content
}
})
})
await context.api.resend.call("Send LLM Proposal", {
token: process.env.RESEND_API_KEY!,
body: {
from: "Acme <onboarding@resend.dev>",
to,
subject,
text: aiResponse.body.choices[0].message.content,
},
});
},
{
qstashClient: getQStashClient(),
}
);
Loading