Skip to content

Commit

Permalink
Copy ai/rsc example from docs
Browse files Browse the repository at this point in the history
  • Loading branch information
unstubbable committed Mar 19, 2024
1 parent 3866987 commit ef44eb0
Show file tree
Hide file tree
Showing 5 changed files with 740 additions and 21 deletions.
116 changes: 116 additions & 0 deletions app/action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { OpenAI } from "openai";
import { createAI, getMutableAIState, render } from "ai/rsc";
import { z } from "zod";

interface FlightInfo {
readonly flightNumber: string;
readonly departure: string;
readonly arrival: string;
}

interface FlightCardProps {
readonly flightInfo: FlightInfo;
}

type AIStateItem =
| {
readonly role: "user" | "assistant" | "system";
readonly content: string;
}
| {
readonly role: "function";
readonly content: string;
readonly name: string;
};

interface UIStateItem {
readonly id: number;
readonly display: React.ReactNode;
}

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

async function getFlightInfo(flightNumber: string): Promise<FlightInfo> {
return {
flightNumber,
departure: "New York",
arrival: "San Francisco",
};
}

function Spinner() {
return <div>Loading...</div>;
}

function FlightCard({ flightInfo }: FlightCardProps) {
return (
<div>
<h2>Flight Information</h2>
<p>Flight Number: {flightInfo.flightNumber}</p>
<p>Departure: {flightInfo.departure}</p>
<p>Arrival: {flightInfo.arrival}</p>
</div>
);
}

async function submitUserMessage(userInput: string): Promise<UIStateItem> {
"use server";

const aiState = getMutableAIState<typeof AI>();

aiState.update([...aiState.get(), { role: "user", content: userInput }]);

const ui = render({
model: "gpt-4-0125-preview",
provider: openai,
messages: [
{ role: "system", content: "You are a flight assistant" },
{ role: "user", content: userInput },

This comment has been minimized.

Copy link
@unstubbable

unstubbable Mar 30, 2024

Author Owner

Whoops, this line can be deleted.

...aiState.get(),
],
text: ({ content, done }) => {
if (done) {
aiState.done([...aiState.get(), { role: "assistant", content }]);
}

return <p>{content}</p>;
},
tools: {
get_flight_info: {
description: "Get the information for a flight",
parameters: z
.object({
flightNumber: z.string().describe("the number of the flight"),
})
.required(),
render: async function* ({ flightNumber }) {
yield <Spinner />;

const flightInfo = await getFlightInfo(flightNumber);

aiState.done([
...aiState.get(),
{
role: "function",
name: "get_flight_info",
content: JSON.stringify(flightInfo),
},
]);

return <FlightCard flightInfo={flightInfo} />;
},
},
},
});

return { id: Date.now(), display: ui };
}

const initialAIState: AIStateItem[] = [];
const initialUIState: UIStateItem[] = [];

export const AI = createAI({
actions: { submitUserMessage },
initialUIState,
initialAIState,
});
5 changes: 4 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { AI } from "./action";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });
Expand All @@ -16,7 +17,9 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
<body className={inter.className}>
<AI>{children}</AI>
</body>
</html>
);
}
47 changes: 45 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
export default function Home() {
return <main>TODO: Add ai/rsc example here.</main>;
"use client";

import { useState } from "react";
import { useUIState, useActions } from "ai/rsc";
import type { AI } from "./action";

export default function Page() {
const [inputValue, setInputValue] = useState("");
const [messages, setMessages] = useUIState<typeof AI>();
const { submitUserMessage } = useActions<typeof AI>();

return (
<div>
{messages.map((message) => (
<div key={message.id}>{message.display}</div>
))}

<form
onSubmit={async (e) => {
e.preventDefault();

setMessages((currentMessages) => [
...currentMessages,
{ id: Date.now(), display: <div>{inputValue}</div> },
]);

const responseMessage = await submitUserMessage(inputValue);
setMessages((currentMessages) => [
...currentMessages,
responseMessage,
]);

setInputValue("");
}}
>
<input
placeholder="Send a message..."
value={inputValue}
onChange={(event) => {
setInputValue(event.target.value);
}}
/>
</form>
</div>
);
}
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@
"lint": "next lint"
},
"dependencies": {
"ai": "^3.0.13",
"next": "14.2.0-canary.30",
"openai": "^4.29.1",
"react": "^18",
"react-dom": "^18",
"next": "14.2.0-canary.30"
"zod": "^3.22.4"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.0-canary.30",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"eslint": "^8",
"eslint-config-next": "14.2.0-canary.30"
"typescript": "^5"
}
}
Loading

0 comments on commit ef44eb0

Please sign in to comment.