Skip to content
Merged
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
46 changes: 27 additions & 19 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
# Get your OpenAI API Key here for chat models: https://platform.openai.com/account/api-keys
OPENAI_API_KEY=****
# AI Model API Keys
OPENAI_API_KEY=**** # Get your OpenAI API Key here: https://platform.openai.com/account/api-keys

# Get your Fireworks AI API Key here for reasoning models: https://fireworks.ai/account/api-keys
FIREWORKS_API_KEY=****
# Storage
BLOB_READ_WRITE_TOKEN=**** # Instructions: https://vercel.com/docs/storage/vercel-blob

# Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32`
AUTH_SECRET=****
# Database
POSTGRES_URL=postgres://user:pass@host:5432/dbname # Instructions: https://vercel.com/docs/storage/vercel-postgres/quickstart
POSTGRES_URL_NON_POOLING=${POSTGRES_URL}?sslmode=require # For non-pooling connections

# The following keys below are automatically created and
# added to your environment when you deploy on vercel

# Instructions to create a Vercel Blob Store here: https://vercel.com/docs/storage/vercel-blob
BLOB_READ_WRITE_TOKEN=****

# Instructions to create a database here: https://vercel.com/docs/storage/vercel-postgres/quickstart
POSTGRES_URL=postgres://user:pass@host:5432/dbname

# Privy configuration - get these from your Privy dashboard
# Privy Configuration - get these from your Privy dashboard
PRIVY_APP_ID=****
PRIVY_APP_SECRET=****
PRIVY_WALLET_ID=****
PRIVY_WALLET_AUTHORIZATION_KEY=****

# OnchainKit API Key - get from OnchainKit dashboard
# OnchainKit Configuration
NEXT_PUBLIC_ONCHAINKIT_API_KEY=****
NEXT_PUBLIC_CDP_PROJECT_ID=****

# Authentication & Security
AUTH_SECRET=**** # Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32`
SESSION_SECRET=**** # Generate a random secret: https://generate-secret.vercel.app/32

# Coinbase Commerce Configuration
NEXT_PUBLIC_COINBASE_COMMERCE_PRODUCT_STARTER_KIT=****
NEXT_PUBLIC_COINBASE_COMMERCE_PRODUCT_STARTER_KIT_GIFT=****
COINBASE_COMMERCE_API_KEY=****

# Web3 Configuration
NEXT_PUBLIC_ACTIVE_CHAIN=base # or base-sepolia for testnet

# Alchemy Configuration
ALCHEMY_API_KEY=****

# Session secret for cookie encryption
SESSION_SECRET=****
# Pinata Configuration
PINATA_JWT=****
52 changes: 15 additions & 37 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {

import { auth } from "@/app/auth";
import { myProvider } from "@/lib/ai/models";
import { systemPrompt } from "@/lib/ai/prompts";
import { generateSystemPrompt } from "@/lib/ai/prompts";
import {
deleteChatById,
getChatById,
Expand All @@ -25,21 +25,15 @@ import { generateTitleFromUserMessage } from "../../actions";
import { createDocument } from "@/lib/ai/tools/create-document";
import { updateDocument } from "@/lib/ai/tools/update-document";
import { requestSuggestions } from "@/lib/ai/tools/request-suggestions";
import {
pythActionProvider,
walletActionProvider,
AgentKit,
} from "@coinbase/agentkit";
import { erc20ActionProvider } from "@/lib/web3/agentkit/action-providers/erc20/erc20ActionProvider";
import { PrivyWalletProvider } from "@/lib/web3/agentkit/wallet-providers/privyWalletProvider";
import { agentKitToTools } from "@/lib/web3/agentkit/framework-extensions/ai-sdk";
import { safeActionProvider } from "@/lib/web3/agentkit/action-providers/safe";
import { z } from "zod";
import {
saveUserInformation,
getUserInformation,
deleteUserInformationTool,
} from "@/lib/ai/tools/user-information";
import { setupAgentKit } from "@/lib/web3/agentkit/setup";
import { generateUserProfile } from "@/lib/ai/prompts/user";

export const maxDuration = 60;

Expand All @@ -51,17 +45,24 @@ export async function POST(request: Request) {
}: { id: string; messages: Array<Message>; selectedChatModel: string } =
await request.json();

const attachments = messages.flatMap(
(message) => message.experimental_attachments ?? []
);

let userProfile = "User is not signed in";
const session = await auth();
const userMessage = getMostRecentUserMessage(messages);

if (!userMessage) {
return new Response("No user message found", { status: 400 });
}

let userProfile = "User is not signed in";
// If user is authenticated, save chat history
if (session?.user?.id) {
userProfile = JSON.stringify(await getUser(session.user.id));
const userInfo = await getUser(session.user.id);
userProfile = generateUserProfile({
userInfo: userInfo[0],
attachments,
});

const chat = await getChatById({ id });
if (!chat) {
Expand All @@ -75,34 +76,15 @@ export async function POST(request: Request) {
});
}

const walletProvider = await PrivyWalletProvider.configureWithWallet({
appId: process.env.PRIVY_APP_ID as string,
appSecret: process.env.PRIVY_APP_SECRET as string,
networkId: "base-sepolia",
walletId: process.env.PRIVY_WALLET_ID as string,
authorizationKey: process.env.PRIVY_WALLET_AUTHORIZATION_KEY as string,
});

const agentKit = await AgentKit.from({
walletProvider,
actionProviders: [
pythActionProvider(),
walletActionProvider(),
erc20ActionProvider(),
safeActionProvider(),
],
});
const agentKit = await setupAgentKit();

const tools = agentKitToTools(agentKit);
const userSystemInformation = session?.user?.id
? `The user is signed in as ${session.user.id}.`
: `The user is not signed in.`;

return createDataStreamResponse({
execute: (dataStream) => {
const result = streamText({
model: myProvider.languageModel(selectedChatModel),
system: systemPrompt({ selectedChatModel }) + userProfile,
system: generateSystemPrompt({ selectedChatModel }) + userProfile,
messages,
maxSteps: 10,
// experimental_activeTools:
Expand Down Expand Up @@ -185,10 +167,6 @@ export async function POST(request: Request) {
}
}
},
experimental_telemetry: {
isEnabled: true,
functionId: "stream-text",
},
});

result.mergeIntoDataStream(dataStream, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { NextResponse } from "next/server";
import { type NextRequest, NextResponse } from "next/server";
import { auth } from "@/app/auth";
import {
createCharge,
getChargeById,
updateChargeStatus,
createStarterKit,
} from "@/lib/db/queries";
import type { CoinbaseChargeResponse } from "@/lib/types/coinbase";

export async function POST(
request: Request,
{ params }: { params: Promise<{ chargeId: string }> }
) {
const { chargeId } = await params;
export const POST = async (
request: NextRequest,
{ params }: { params: Promise<{ chargeId: string; productId: string }> }
) => {
const { chargeId, productId } = await params;
const session = await auth();

if (!session?.user?.id) {
Expand Down Expand Up @@ -43,9 +43,15 @@ export async function POST(
throw new Error("Failed to verify charge with Coinbase Commerce");
}

const data = await response.json();
const latestStatus = data.timeline[0]?.status;
const successEvent = data.web3_data?.success_events?.[0];
const { data } = (await response.json()) as {
data: CoinbaseChargeResponse;
};

const amount = data.pricing?.settlement?.amount || "10";
const currency = data.pricing?.settlement?.currency || "USDC";

// const latestStatus = data?.timeline?.[data.timeline.length - 1]?.status;
// const successEvent = data?.web3_data?.success_events?.[0];

// Check if charge exists in our database
const existingCharge = await getChargeById(chargeId);
Expand All @@ -55,46 +61,34 @@ export async function POST(
await createCharge({
id: chargeId,
userId: session.user.id,
amount: data.pricing.local.amount,
currency: data.pricing.local.currency,
amount,
currency,
product: "STARTERKIT",
});
}

// Update charge with latest status
await updateChargeStatus({
id: chargeId,
status: latestStatus,
payerAddress: successEvent?.sender,
transactionHash: successEvent?.tx_hash,
confirmedAt: data.confirmed_at ? new Date(data.confirmed_at) : undefined,
expiresAt: data.expires_at ? new Date(data.expires_at) : undefined,
});

// If charge is completed and it's a starter kit, create it
if (
latestStatus === "COMPLETED" &&
(!existingCharge || existingCharge[0]?.status !== "COMPLETED")
) {
const value = Number.parseInt(data.pricing.local.amount, 10);
const value = Number.parseInt(amount, 10);
const isGift =
data.pricing.product_id ===
productId ===
process.env.NEXT_PUBLIC_COINBASE_COMMERCE_PRODUCT_STARTER_KIT_GIFT;

await createStarterKit({
id: chargeId,
value,
userId: session.user.id,
chargeId,
claimerId: isGift ? undefined : session.user.id,
});
}

return NextResponse.json(data);
return NextResponse.json({
success: true,
message: "Charge verified successfully",
});
} catch (error) {
console.error("Error verifying charge:", error);
return NextResponse.json(
{ error: "Failed to verify charge" },
{ status: 500 }
);
}
}
};
4 changes: 2 additions & 2 deletions app/api/starter-kit/claim/[kitId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { claimStarterKit } from "@/lib/db/queries";

export async function POST(
request: Request,
{ params }: { params: { kitId: string } }
{ params }: { params: Promise<{ kitId: string }> }
) {
const session = await auth();
if (!session?.user?.id) {
Expand All @@ -13,7 +13,7 @@ export async function POST(

try {
await claimStarterKit({
kitId: params.kitId,
kitId: (await params).kitId,
userId: session.user.id,
});
return NextResponse.json({ success: true });
Expand Down
10 changes: 6 additions & 4 deletions app/api/starter-kit/give/[kitId]/[recipientId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { claimStarterKit, getCreatedStarterKits } from "@/lib/db/queries";

export async function POST(
request: Request,
{ params }: { params: { kitId: string; recipientId: string } }
{ params }: { params: Promise<{ kitId: string; recipientId: string }> }
) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const { kitId, recipientId } = await params;

try {
// Verify ownership
const kits = await getCreatedStarterKits(session.user.id);
const kit = kits.find((k) => k.id === params.kitId);
const kit = kits.find((k) => k.id === kitId);

if (!kit) {
return NextResponse.json(
Expand All @@ -24,8 +26,8 @@ export async function POST(
}

await claimStarterKit({
kitId: params.kitId,
userId: params.recipientId,
kitId,
userId: recipientId,
});
return NextResponse.json({ success: true });
} catch (error) {
Expand Down
1 change: 0 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { auth } from "@/app/auth";
import "./globals.css";
import "@coinbase/onchainkit/styles.css";
import { ConnectButton } from "@/components/connect-button";

export const metadata: Metadata = {
metadataBase: new URL("https://chat.vercel.ai"),
title: "Next.js Chatbot Template",
Expand Down
20 changes: 7 additions & 13 deletions components/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ function PureBlock({
scale: 1,
transition: {
delay: 0.2,
type: 'spring',
type: "spring",
stiffness: 200,
damping: 30,
},
Expand Down Expand Up @@ -333,16 +333,10 @@ function PureBlock({
<MultimodalInput
chatId={chatId}
input={input}
setInput={setInput}
handleSubmit={handleSubmit}
isLoading={isLoading}
stop={stop}
attachments={attachments}
setAttachments={setAttachments}
messages={messages}
append={append}
className="bg-background dark:bg-muted"
setMessages={setMessages}
/>
</form>
</div>
Expand Down Expand Up @@ -377,11 +371,11 @@ function PureBlock({
x: 0,
y: 0,
height: windowHeight,
width: windowWidth ? windowWidth : 'calc(100dvw)',
width: windowWidth ? windowWidth : "calc(100dvw)",
borderRadius: 0,
transition: {
delay: 0,
type: 'spring',
type: "spring",
stiffness: 200,
damping: 30,
duration: 5000,
Expand All @@ -394,11 +388,11 @@ function PureBlock({
height: windowHeight,
width: windowWidth
? windowWidth - 400
: 'calc(100dvw-400px)',
: "calc(100dvw-400px)",
borderRadius: 0,
transition: {
delay: 0,
type: 'spring',
type: "spring",
stiffness: 200,
damping: 30,
duration: 5000,
Expand All @@ -410,7 +404,7 @@ function PureBlock({
scale: 0.5,
transition: {
delay: 0.1,
type: 'spring',
type: "spring",
stiffness: 600,
damping: 30,
},
Expand All @@ -434,7 +428,7 @@ function PureBlock({
new Date(),
{
addSuffix: true,
},
}
)}`}
</div>
) : (
Expand Down
Loading