Skip to content

Commit 074357d

Browse files
DX-1580: Add Agent Examples (#59)
* add agents examples * update agentic agents example * update email parser example * introduce social media manager example * remove base url * fmt * indent with space * indent routes with space * update pattern * remove base url * remove browserbase examples * update post generator * add: env.example folders --------- Co-authored-by: fahreddinozcan <[email protected]>
1 parent ce6bd7e commit 074357d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1438
-0
lines changed

.prettierrc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"singleQuote": true,
3+
"printWidth": 80,
4+
"editor.formatOnSave": true,
5+
"proseWrap": "always",
6+
"tabWidth": 4,
7+
"requireConfig": false,
8+
"useTabs": false,
9+
"trailingComma": "none",
10+
"bracketSpacing": true,
11+
"jsxBracketSameLine": false,
12+
"semi": true
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
OPENAI_API_KEY=
2+
FIRECRAWL_API_KEY=
3+
SERPAPI_API_KEY=
4+
QSTASH_TOKEN=
5+
RESEND_API_KEY=
6+
7+
UPSTASH_REDIS_REST_URL=
8+
UPSTASH_REDIS_REST_TOKEN=
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"singleQuote": true,
3+
"printWidth": 80,
4+
"editor.formatOnSave": true,
5+
"proseWrap": "always",
6+
"tabWidth": 4,
7+
"requireConfig": false,
8+
"useTabs": false,
9+
"trailingComma": "none",
10+
"bracketSpacing": true,
11+
"jsxBracketSameLine": false,
12+
"semi": true
13+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
# or
14+
bun dev
15+
```
16+
17+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
19+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20+
21+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22+
23+
## Learn More
24+
25+
To learn more about Next.js, take a look at the following resources:
26+
27+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29+
30+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31+
32+
## Deploy on Vercel
33+
34+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35+
36+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Post } from '@/app/page';
2+
import { Redis } from '@upstash/redis';
3+
import { NextRequest, NextResponse } from 'next/server';
4+
5+
const redis = Redis.fromEnv();
6+
7+
export async function POST(request: NextRequest) {
8+
try {
9+
const { callKey } = await request.json();
10+
11+
if (!callKey) {
12+
return NextResponse.json(
13+
{ error: 'No callKey provided' },
14+
{ status: 400 }
15+
);
16+
}
17+
18+
const posts = await redis.lrange<Post>(`${callKey}-posts`, 0, -1);
19+
20+
if (!posts || posts.length === 0) {
21+
return NextResponse.json(null);
22+
}
23+
24+
return NextResponse.json({
25+
posts
26+
});
27+
} catch (error) {
28+
console.error('Error checking workflow:', error);
29+
return NextResponse.json(
30+
{ error: 'Failed to check workflow status' },
31+
{ status: 500 }
32+
);
33+
}
34+
}
25.3 KB
Binary file not shown.
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import { serve } from "@upstash/workflow/nextjs";
2+
import { SerpAPIClient } from '@agentic/serpapi';
3+
import { FirecrawlClient } from '@agentic/firecrawl';
4+
import { tool } from 'ai';
5+
import { z } from "zod";
6+
import { Redis } from "@upstash/redis"
7+
import { ImagesResponse } from "../types";
8+
9+
const redis = Redis.fromEnv();
10+
const serpapi = new SerpAPIClient();
11+
const firecrawl = new FirecrawlClient();
12+
13+
type Payload = {
14+
productWebsite: string;
15+
productDetails: string;
16+
}
17+
18+
export const { POST } = serve<Payload>(async (context) => {
19+
const { productDetails, productWebsite } = context.requestPayload
20+
const model = context.agents.openai('gpt-4-turbo');
21+
22+
const productAnalysisAgent = context.agents.agent({
23+
model,
24+
name: 'productAnalysisAgent',
25+
maxSteps: 3,
26+
background: `You are a Lead Market Analyst at a premier digital marketing firm.
27+
You specialize in dissecting online business landscapes and providing
28+
in-depth insights to guide marketing strategies.`,
29+
tools: {
30+
searchWeb: tool({
31+
32+
description: 'Search the web using SerpAPI',
33+
parameters: z.object({
34+
query: z.string().describe('The search query')
35+
}),
36+
execute: async ({ query }) => {
37+
const results = await serpapi.search(query);
38+
const organicResults = results.organic_results || [];
39+
return {
40+
content: organicResults
41+
.slice(0, 3)
42+
.map(result => `Title: ${result.title}\nKey Points: ${result.snippet}\nSource: ${result.link}`)
43+
.join('\n\n')
44+
};
45+
}
46+
}),
47+
scrapeWebsite: tool({
48+
description: 'Scrape content from a webpage',
49+
parameters: z.object({
50+
url: z.string().describe('The URL to scrape')
51+
}),
52+
execute: async ({ url }) => {
53+
const result = await firecrawl.scrapeUrl({ url });
54+
return { content: result.data };
55+
}
56+
})
57+
}
58+
});
59+
60+
// Strategy Planner Agent
61+
const strategyPlannerAgent = context.agents.agent({
62+
model,
63+
name: 'strategyPlannerAgent',
64+
maxSteps: 3,
65+
background: `You are the Chief Marketing Strategist at a leading digital marketing agency,
66+
known for crafting bespoke strategies that drive success.`,
67+
tools: {
68+
searchInstagram: tool({
69+
description: 'Search Instagram trends',
70+
parameters: z.object({
71+
query: z.string().describe('The search query')
72+
}),
73+
execute: async ({ query }) => {
74+
const results = await serpapi.search(`site:instagram.com ${query}`);
75+
return { content: results.organic_results?.slice(0, 3) || [] };
76+
}
77+
})
78+
}
79+
});
80+
81+
const creativeAgent = context.agents.agent({
82+
model,
83+
name: 'creativeAgent',
84+
maxSteps: 3,
85+
background: `You are a Creative Content Creator who excels in crafting narratives
86+
that resonate with social media audiences.`,
87+
tools: {}
88+
});
89+
90+
const photographerAgent = context.agents.agent({
91+
model,
92+
name: 'photographerAgent',
93+
maxSteps: 2,
94+
background: `You are a Senior Photographer specialized in creating compelling
95+
visual narratives for social media campaigns.`,
96+
tools: {}
97+
});
98+
99+
const productAnalysis = await context.agents.task({
100+
agent: productAnalysisAgent,
101+
prompt: `Analyze the product website: ${productWebsite}.
102+
Additional details: ${productDetails}
103+
Focus on identifying unique features, benefits, and overall narrative.`
104+
}).run();
105+
106+
const marketAnalysis = await context.agents.task({
107+
agent: productAnalysisAgent,
108+
prompt: `Analyze competitors of ${productWebsite}.
109+
Identify top 3 competitors and their strategies.
110+
Consider: ${productAnalysis.text}`
111+
}).run();
112+
113+
const campaignStrategy = await context.agents.task({
114+
agent: strategyPlannerAgent,
115+
prompt: `Develop a marketing campaign strategy based on:
116+
Product Analysis: ${productAnalysis.text}
117+
Market Analysis: ${marketAnalysis.text}
118+
Create a strategy that will resonate with the target audience.`
119+
}).run();
120+
121+
const instagramCaptions = await context.agents.task({
122+
agent: creativeAgent,
123+
prompt: `Create exactly 3 engaging Instagram post captions based on:
124+
Campaign Strategy: ${campaignStrategy.text}
125+
Make them punchy, captivating, and aligned with the strategy. Dont use emojis or special characters.
126+
Return exactly 3 distinct copies, no more and no less.`
127+
}).run();
128+
129+
const photoDescription = await context.agents.task({
130+
agent: photographerAgent,
131+
prompt: `Create exactly 3 detailed photo concepts for Instagram posts using:
132+
Captions: ${instagramCaptions.text}
133+
Product: ${productWebsite}
134+
Details: ${productDetails}
135+
Format each description like: "high tech airplane in beautiful blue sky at sunset, 4k, professional wide shot"
136+
Return exactly 3 concepts, no more and no less.`
137+
}).run();
138+
139+
// 2. Limit the array to 3 items when splitting
140+
const photoPrompts = photoDescription.text.split('\n\n').slice(0, 3);
141+
142+
const instagramPostResults = await Promise.all(
143+
photoPrompts.slice(0, 3).map(async (prompt, index) =>
144+
await context.call<ImagesResponse>(
145+
`generate-image-${index + 1}`,
146+
{
147+
url: "https://api.openai.com/v1/images/generations",
148+
method: "POST",
149+
headers: {
150+
"Content-Type": "application/json",
151+
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
152+
},
153+
body: {
154+
model: "dall-e-3",
155+
prompt: `${prompt}. Make it look professional and Instagram-worthy.`,
156+
n: 1,
157+
size: "1024x1024",
158+
quality: "hd",
159+
style: "natural"
160+
}
161+
}
162+
)
163+
)
164+
);
165+
166+
await Promise.all(
167+
instagramPostResults.map((async (post, index) => {
168+
await context.run(`persist post to redis ${index}`, async () => {
169+
const callKey = context.headers.get('callKey');
170+
171+
const { url, revised_prompt } = post.body.data[0]
172+
const result = {
173+
imageUrl: url,
174+
prompt: revised_prompt,
175+
caption: instagramCaptions.text.split('\n\n')[index]
176+
}
177+
178+
if (callKey) {
179+
await redis.lpush(
180+
`${callKey}-posts`,
181+
JSON.stringify(result)
182+
);
183+
}
184+
})
185+
}))
186+
)
187+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
:root {
6+
--background: #ffffff;
7+
--foreground: #171717;
8+
}
9+
10+
@media (prefers-color-scheme: dark) {
11+
:root {
12+
--background: #0a0a0a;
13+
--foreground: #ededed;
14+
}
15+
}
16+
17+
body {
18+
color: var(--foreground);
19+
background: var(--background);
20+
font-family: Arial, Helvetica, sans-serif;
21+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { Metadata } from "next";
2+
import { Geist, Geist_Mono } from "next/font/google";
3+
import "./globals.css";
4+
5+
const geistSans = Geist({
6+
variable: "--font-geist-sans",
7+
subsets: ["latin"],
8+
});
9+
10+
const geistMono = Geist_Mono({
11+
variable: "--font-geist-mono",
12+
subsets: ["latin"],
13+
});
14+
15+
export const metadata: Metadata = {
16+
title: "Create Next App",
17+
description: "Generated by create next app",
18+
};
19+
20+
export default function RootLayout({
21+
children,
22+
}: Readonly<{
23+
children: React.ReactNode;
24+
}>) {
25+
return (
26+
<html lang="en">
27+
<body
28+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29+
>
30+
{children}
31+
</body>
32+
</html>
33+
);
34+
}

0 commit comments

Comments
 (0)