diff --git a/components/stocks/events.tsx b/components/stocks/events.tsx
index eebf7632f..74e94279b 100644
--- a/components/stocks/events.tsx
+++ b/components/stocks/events.tsx
@@ -1,12 +1,7 @@
+import { GetEventProps } from '@/lib/types'
import { format, parseISO } from 'lib/utils'
-interface Event {
- date: string
- headline: string
- description: string
-}
-
-export function Events({ props: events }: { props: Event[] }) {
+export function Events({ events }: GetEventProps) {
return (
{events.map(event => (
diff --git a/components/stocks/stock-purchase.tsx b/components/stocks/stock-purchase.tsx
index 0bf922ead..ef7a387a6 100644
--- a/components/stocks/stock-purchase.tsx
+++ b/components/stocks/stock-purchase.tsx
@@ -5,19 +5,10 @@ import { useActions, useAIState, useUIState } from 'ai/rsc'
import { formatNumber } from '@/lib/utils'
import type { AI } from '@/lib/chat/actions'
+import { PurchaseProps } from '@/lib/types'
-interface Purchase {
- numberOfShares?: number
- symbol: string
- price: number
- status: 'requires_action' | 'completed' | 'expired'
-}
-export function Purchase({
- props: { numberOfShares, symbol, price, status = 'expired' }
-}: {
- props: Purchase
-}) {
+export function Purchase({ numberOfShares, symbol, price, status = 'expired' }: PurchaseProps) {
const [value, setValue] = useState(numberOfShares || 100)
const [purchasingUI, setPurchasingUI] = useState
(null)
const [aiState, setAIState] = useAIState()
diff --git a/components/stocks/stock.tsx b/components/stocks/stock.tsx
index e895357ad..23f6c6801 100644
--- a/components/stocks/stock.tsx
+++ b/components/stocks/stock.tsx
@@ -3,12 +3,8 @@
import { useState, useRef, useEffect, useId } from 'react'
import { subMonths, format } from 'lib/utils'
import { useAIState } from 'ai/rsc'
+import { ShowStockPriceProps } from '@/lib/types'
-interface Stock {
- symbol: string
- price: number
- delta: number
-}
function scaleLinear(domain: [number, number], range: [number, number]) {
const [d0, d1] = domain
@@ -47,7 +43,7 @@ function useResizeObserver(
return size
}
-export function Stock({ props: { symbol, price, delta } }: { props: Stock }) {
+export function Stock({ symbol, price, delta }: ShowStockPriceProps ) {
const [aiState, setAIState] = useAIState()
const id = useId()
diff --git a/components/stocks/stocks.tsx b/components/stocks/stocks.tsx
index a5f78262b..171f62d45 100644
--- a/components/stocks/stocks.tsx
+++ b/components/stocks/stocks.tsx
@@ -3,14 +3,8 @@
import { useActions, useUIState } from 'ai/rsc'
import type { AI } from '@/lib/chat/actions'
-
-interface Stock {
- symbol: string
- price: number
- delta: number
-}
-
-export function Stocks({ props: stocks }: { props: Stock[] }) {
+import { ListStockProps } from '@/lib/types'
+export function Stocks({ stocks }: ListStockProps) {
const [, setMessages] = useUIState()
const { submitUserMessage } = useActions()
@@ -27,9 +21,8 @@ export function Stocks({ props: stocks }: { props: Stock[] }) {
}}
>
0 ? 'text-green-600' : 'text-red-600'
- } flex w-11 flex-row justify-center rounded-md bg-white/10 p-2`}
+ className={`text-xl ${stock.delta > 0 ? 'text-green-600' : 'text-red-600'
+ } flex w-11 flex-row justify-center rounded-md bg-white/10 p-2`}
>
{stock.delta > 0 ? '↑' : '↓'}
@@ -41,16 +34,14 @@ export function Stocks({ props: stocks }: { props: Stock[] }) {
0 ? 'text-green-600' : 'text-red-600'
- } bold text-right uppercase`}
+ className={`${stock.delta > 0 ? 'text-green-600' : 'text-red-600'
+ } bold text-right uppercase`}
>
{` ${((stock.delta / stock.price) * 100).toExponential(1)}%`}
0 ? 'text-green-700' : 'text-red-700'
- } text-right text-base`}
+ className={`${stock.delta > 0 ? 'text-green-700' : 'text-red-700'
+ } text-right text-base`}
>
{stock.delta.toExponential(1)}
diff --git a/lib/chat/actions.tsx b/lib/chat/actions.tsx
index 0aa4313b1..23356782c 100644
--- a/lib/chat/actions.tsx
+++ b/lib/chat/actions.tsx
@@ -33,8 +33,9 @@ import {
} from '@/lib/utils'
import { saveChat } from '@/app/actions'
import { SpinnerMessage, UserMessage } from '@/components/stocks/message'
-import { Chat, Message } from '@/lib/types'
+import { Chat, GetEventProps, ListStockProps, Message, PurchaseProps, ShowStockPriceProps } from '@/lib/types'
import { auth } from '@/auth'
+import { getEventsSchema, listStockSchema, showStockPriceSchema, showStockPurchaseSchema } from '../schemas'
async function confirmPurchase(symbol: string, price: number, amount: number) {
'use server'
@@ -89,9 +90,8 @@ async function confirmPurchase(symbol: string, price: number, amount: number) {
{
id: nanoid(),
role: 'system',
- content: `[User has purchased ${amount} shares of ${symbol} at ${price}. Total cost = ${
- amount * price
- }]`
+ content: `[User has purchased ${amount} shares of ${symbol} at ${price}. Total cost = ${amount * price
+ }]`
}
]
})
@@ -179,15 +179,7 @@ async function submitUserMessage(content: string) {
tools: {
listStocks: {
description: 'List three imaginary stocks that are trending.',
- parameters: z.object({
- stocks: z.array(
- z.object({
- symbol: z.string().describe('The symbol of the stock'),
- price: z.number().describe('The price of the stock'),
- delta: z.number().describe('The change in price of the stock')
- })
- )
- }),
+ parameters: listStockSchema,
generate: async function* ({ stocks }) {
yield (
@@ -232,7 +224,7 @@ async function submitUserMessage(content: string) {
return (
-
+
)
}
@@ -240,15 +232,7 @@ async function submitUserMessage(content: string) {
showStockPrice: {
description:
'Get the current stock price of a given stock or currency. Use this to show the price to the user.',
- parameters: z.object({
- symbol: z
- .string()
- .describe(
- 'The name or symbol of the stock or currency. e.g. DOGE/AAPL/USD.'
- ),
- price: z.number().describe('The price of the stock.'),
- delta: z.number().describe('The change in price of the stock')
- }),
+ parameters: showStockPriceSchema,
generate: async function* ({ symbol, price, delta }) {
yield (
@@ -293,7 +277,7 @@ async function submitUserMessage(content: string) {
return (
-
+
)
}
@@ -301,20 +285,7 @@ async function submitUserMessage(content: string) {
showStockPurchase: {
description:
'Show price and the UI to purchase a stock or currency. Use this if the user wants to purchase a stock or currency.',
- parameters: z.object({
- symbol: z
- .string()
- .describe(
- 'The name or symbol of the stock or currency. e.g. DOGE/AAPL/USD.'
- ),
- price: z.number().describe('The price of the stock.'),
- numberOfShares: z
- .number()
- .optional()
- .describe(
- 'The **number of shares** for a stock or currency to purchase. Can be optional if the user did not specify it.'
- )
- }),
+ parameters: showStockPurchaseSchema,
generate: async function* ({ symbol, price, numberOfShares = 100 }) {
const toolCallId = nanoid()
@@ -400,12 +371,10 @@ async function submitUserMessage(content: string) {
return (
)
@@ -415,17 +384,7 @@ async function submitUserMessage(content: string) {
getEvents: {
description:
'List funny imaginary events between user highlighted dates that describe stock activity.',
- parameters: z.object({
- events: z.array(
- z.object({
- date: z
- .string()
- .describe('The date of the event, in ISO-8601 format'),
- headline: z.string().describe('The headline of the event'),
- description: z.string().describe('The description of the event')
- })
- )
- }),
+ parameters: getEventsSchema,
generate: async function* ({ events }) {
yield (
@@ -470,7 +429,7 @@ async function submitUserMessage(content: string) {
return (
-
+
)
}
@@ -557,24 +516,19 @@ export const getUIStateFromAIState = (aiState: Chat) => {
message.content.map(tool => {
return tool.toolName === 'listStocks' ? (
- {/* TODO: Infer types based on the tool result*/}
- {/* @ts-expect-error */}
-
+
) : tool.toolName === 'showStockPrice' ? (
- {/* @ts-expect-error */}
-
+
) : tool.toolName === 'showStockPurchase' ? (
- {/* @ts-expect-error */}
-
+
) : tool.toolName === 'getEvents' ? (
- {/* @ts-expect-error */}
-
+
) : null
})
diff --git a/lib/schemas/index.ts b/lib/schemas/index.ts
new file mode 100644
index 000000000..ffac8d4ea
--- /dev/null
+++ b/lib/schemas/index.ts
@@ -0,0 +1,46 @@
+import { z } from "zod"
+const getEventsSchema = z.object({
+ events: z.array(
+ z.object({
+ date: z
+ .string()
+ .describe('The date of the event, in ISO-8601 format'),
+ headline: z.string().describe('The headline of the event'),
+ description: z.string().describe('The description of the event')
+ })
+ )
+})
+
+const showStockPurchaseSchema = z.object({
+ symbol: z
+ .string()
+ .describe(
+ 'The name or symbol of the stock or currency. e.g. DOGE/AAPL/USD.'
+ ),
+ price: z.number().describe('The price of the stock.'),
+ numberOfShares: z
+ .number()
+ .optional()
+ .describe(
+ 'The **number of shares** for a stock or currency to purchase. Can be optional if the user did not specify it.'
+ )
+})
+const showStockPriceSchema = z.object({
+ symbol: z
+ .string()
+ .describe(
+ 'The name or symbol of the stock or currency. e.g. DOGE/AAPL/USD.'
+ ),
+ price: z.number().describe('The price of the stock.'),
+ delta: z.number().describe('The change in price of the stock')
+})
+const listStockSchema = z.object({
+ stocks: z.array(
+ z.object({
+ symbol: z.string().describe('The symbol of the stock'),
+ price: z.number().describe('The price of the stock'),
+ delta: z.number().describe('The change in price of the stock')
+ })
+ )
+})
+export { getEventsSchema, showStockPurchaseSchema, showStockPriceSchema, listStockSchema }
\ No newline at end of file
diff --git a/lib/types.ts b/lib/types.ts
index d892a0c5a..e10fead00 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -1,4 +1,6 @@
import { CoreMessage } from 'ai'
+import { z } from 'zod'
+import { showStockPriceSchema, getEventsSchema, listStockSchema, showStockPurchaseSchema } from './schemas'
export type Message = CoreMessage & {
id: string
@@ -17,8 +19,8 @@ export interface Chat extends Record {
export type ServerActionResult = Promise<
| Result
| {
- error: string
- }
+ error: string
+ }
>
export interface Session {
@@ -39,3 +41,11 @@ export interface User extends Record {
password: string
salt: string
}
+export type ShowStockPriceProps = z.infer
+export type GetEventProps = z.infer
+export type ListStockProps = z.infer
+export type ShowStockPurchaseProps = z.infer
+
+export type PurchaseProps = {
+ status: 'requires_action' | 'completed' | 'expired'
+} & ShowStockPurchaseProps
\ No newline at end of file