Use streamUI instead of render (#324)

This commit is contained in:
Jeremy 2024-05-03 13:24:00 -07:00 committed by GitHub
parent d5f736128d
commit 095550d4dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 92 additions and 47 deletions

View file

@ -5,10 +5,10 @@ import {
createStreamableUI,
getMutableAIState,
getAIState,
render,
streamUI,
createStreamableValue
} from 'ai/rsc'
import OpenAI from 'openai'
import { openai } from '@ai-sdk/openai'
import {
spinner,
@ -36,10 +36,6 @@ import { SpinnerMessage, UserMessage } from '@/components/stocks/message'
import { Chat } from '@/lib/types'
import { auth } from '@/auth'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || ''
})
async function confirmPurchase(symbol: string, price: number, amount: number) {
'use server'
@ -141,29 +137,25 @@ async function submitUserMessage(content: string) {
let textStream: undefined | ReturnType<typeof createStreamableValue<string>>
let textNode: undefined | React.ReactNode
const ui = render({
model: 'gpt-3.5-turbo',
provider: openai,
const result = await streamUI({
model: openai('gpt-3.5-turbo'),
initial: <SpinnerMessage />,
system: `\
You are a stock trading conversation bot and you can help users buy stocks, step by step.
You and the user can discuss stock prices and the user can adjust the amount of stocks they want to buy, or place an order, in the UI.
Messages inside [] means that it's a UI element or a user event. For example:
- "[Price of AAPL = 100]" means that an interface of the stock price of AAPL is shown to the user.
- "[User has changed the amount of AAPL to 10]" means that the user has changed the amount of AAPL to 10 in the UI.
If the user requests purchasing a stock, call \`show_stock_purchase_ui\` to show the purchase UI.
If the user just wants the price, call \`show_stock_price\` to show the price.
If you want to show trending stocks, call \`list_stocks\`.
If you want to show events, call \`get_events\`.
If the user wants to sell stock, or complete another impossible task, respond that you are a demo and cannot do that.
Besides that, you can also chat with users and do some calculations if needed.`,
messages: [
{
role: 'system',
content: `\
You are a stock trading conversation bot and you can help users buy stocks, step by step.
You and the user can discuss stock prices and the user can adjust the amount of stocks they want to buy, or place an order, in the UI.
Messages inside [] means that it's a UI element or a user event. For example:
- "[Price of AAPL = 100]" means that an interface of the stock price of AAPL is shown to the user.
- "[User has changed the amount of AAPL to 10]" means that the user has changed the amount of AAPL to 10 in the UI.
If the user requests purchasing a stock, call \`show_stock_purchase_ui\` to show the purchase UI.
If the user just wants the price, call \`show_stock_price\` to show the price.
If you want to show trending stocks, call \`list_stocks\`.
If you want to show events, call \`get_events\`.
If the user wants to sell stock, or complete another impossible task, respond that you are a demo and cannot do that.
Besides that, you can also chat with users and do some calculations if needed.`
},
...aiState.get().messages.map((message: any) => ({
role: message.role,
content: message.content,
@ -195,7 +187,7 @@ Besides that, you can also chat with users and do some calculations if needed.`
return textNode
},
functions: {
tools: {
listStocks: {
description: 'List three imaginary stocks that are trending.',
parameters: z.object({
@ -207,7 +199,7 @@ Besides that, you can also chat with users and do some calculations if needed.`
})
)
}),
render: async function* ({ stocks }) {
generate: async function* ({ stocks }) {
yield (
<BotCard>
<StocksSkeleton />
@ -248,7 +240,7 @@ Besides that, you can also chat with users and do some calculations if needed.`
price: z.number().describe('The price of the stock.'),
delta: z.number().describe('The change in price of the stock')
}),
render: async function* ({ symbol, price, delta }) {
generate: async function* ({ symbol, price, delta }) {
yield (
<BotCard>
<StockSkeleton />
@ -293,7 +285,7 @@ Besides that, you can also chat with users and do some calculations if needed.`
'The **number of shares** for a stock or currency to purchase. Can be optional if the user did not specify it.'
)
}),
render: async function* ({ symbol, price, numberOfShares = 100 }) {
generate: async function* ({ symbol, price, numberOfShares = 100 }) {
if (numberOfShares <= 0 || numberOfShares > 1000) {
aiState.done({
...aiState.get(),
@ -355,7 +347,7 @@ Besides that, you can also chat with users and do some calculations if needed.`
})
)
}),
render: async function* ({ events }) {
generate: async function* ({ events }) {
yield (
<BotCard>
<EventsSkeleton />
@ -389,7 +381,7 @@ Besides that, you can also chat with users and do some calculations if needed.`
return {
id: nanoid(),
display: ui
display: result.value
}
}
@ -417,7 +409,7 @@ export const AI = createAI<AIState, UIState>({
},
initialUIState: [],
initialAIState: { chatId: nanoid(), messages: [] },
unstable_onGetUIState: async () => {
onGetUIState: async () => {
'use server'
const session = await auth()
@ -433,7 +425,7 @@ export const AI = createAI<AIState, UIState>({
return
}
},
unstable_onSetAIState: async ({ state, done }) => {
onSetAIState: async ({ state, done }) => {
'use server'
const session = await auth()

View file

@ -13,6 +13,7 @@
"format:check": "prettier --check \"{app,lib,components}**/*.{ts,tsx,mdx}\" --cache"
},
"dependencies": {
"@ai-sdk/openai": "^0.0.9",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
@ -26,7 +27,7 @@
"@vercel/analytics": "^1.1.2",
"@vercel/kv": "^1.0.1",
"@vercel/og": "^0.6.2",
"ai": "^3.0.12",
"ai": "^3.1.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"d3-scale": "^4.0.2",

74
pnpm-lock.yaml generated
View file

@ -5,6 +5,9 @@ settings:
excludeLinksFromLockfile: false
dependencies:
'@ai-sdk/openai':
specifier: ^0.0.9
version: 0.0.9(zod@3.22.4)
'@radix-ui/react-alert-dialog':
specifier: ^1.0.5
version: 1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0)
@ -45,8 +48,8 @@ dependencies:
specifier: ^0.6.2
version: 0.6.2
ai:
specifier: ^3.0.12
version: 3.0.12(react@18.2.0)(solid-js@1.8.11)(svelte@4.2.9)(vue@3.4.15)(zod@3.22.4)
specifier: ^3.1.1
version: 3.1.1(react@18.2.0)(solid-js@1.8.11)(svelte@4.2.9)(vue@3.4.15)(zod@3.22.4)
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@ -183,6 +186,43 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/@ai-sdk/openai@0.0.9(zod@3.22.4):
resolution: {integrity: sha512-SSZGtX4KFDXWYmQ9JuhVumo1XOx1JAdHybYy08iwVXuCud9xdjZjjxgZkNPytQK9gRxFsYDOw1h0V/WXO7XgfQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
peerDependenciesMeta:
zod:
optional: true
dependencies:
'@ai-sdk/provider': 0.0.3
'@ai-sdk/provider-utils': 0.0.5(zod@3.22.4)
zod: 3.22.4
dev: false
/@ai-sdk/provider-utils@0.0.5(zod@3.22.4):
resolution: {integrity: sha512-VVy9eQS+vS2j6cqTEQ9htMHz2nW/HFAkDXLvNFPoi1pZkviknJZEzb+DZUna6Od+jBf/TVA0HZwYnyGDaeI9cQ==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
peerDependenciesMeta:
zod:
optional: true
dependencies:
'@ai-sdk/provider': 0.0.3
eventsource-parser: 1.1.2
nanoid: 3.3.6
secure-json-parse: 2.7.0
zod: 3.22.4
dev: false
/@ai-sdk/provider@0.0.3:
resolution: {integrity: sha512-0B8P6VZpJ6F9yS9BpmJBYSqIaIfeRtL5tD5SP+qgR8y0pPwalIbRMUFiLz9YUT6g70MJsCLpm/2/fX3cfAYCJw==}
engines: {node: '>=18'}
dependencies:
json-schema: 0.4.0
dev: false
/@alloc/quick-lru@5.2.0:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@ -1553,9 +1593,9 @@ packages:
humanize-ms: 1.2.1
dev: false
/ai@3.0.12(react@18.2.0)(solid-js@1.8.11)(svelte@4.2.9)(vue@3.4.15)(zod@3.22.4):
resolution: {integrity: sha512-cP/Moag7PcDOE3kA7WU00YS+mQiuPpAxY+uf57lkWwnqSB1K3/RzwnRF+LD1FqgJfCubI4WEbajMPbnnCr8lAg==}
engines: {node: '>=14.6'}
/ai@3.1.1(react@18.2.0)(solid-js@1.8.11)(svelte@4.2.9)(vue@3.4.15)(zod@3.22.4):
resolution: {integrity: sha512-pJZc6q7SLd2/NenxN62iagMw9HHQ4Q8FyKqkrZUJntupRTHHgN3fL7exzJU/ICHDAbtn/EcJXOau6P61QgUtKQ==}
engines: {node: '>=18'}
peerDependencies:
react: ^18.2.0
solid-js: ^1.7.7
@ -1574,10 +1614,14 @@ packages:
zod:
optional: true
dependencies:
eventsource-parser: 1.0.0
'@ai-sdk/provider': 0.0.3
'@ai-sdk/provider-utils': 0.0.5(zod@3.22.4)
eventsource-parser: 1.1.2
json-schema: 0.4.0
jsondiffpatch: 0.6.0
nanoid: 3.3.6
react: 18.2.0
secure-json-parse: 2.7.0
solid-js: 1.8.11
solid-swr-store: 0.10.7(solid-js@1.8.11)(swr-store@0.10.6)
sswr: 2.0.0(svelte@4.2.9)
@ -1587,7 +1631,7 @@ packages:
swrv: 1.0.4(vue@3.4.15)
vue: 3.4.15(typescript@5.3.3)
zod: 3.22.4
zod-to-json-schema: 3.22.4(zod@3.22.4)
zod-to-json-schema: 3.22.5(zod@3.22.4)
dev: false
/ajv@6.12.6:
@ -2668,8 +2712,8 @@ packages:
engines: {node: '>=6'}
dev: false
/eventsource-parser@1.0.0:
resolution: {integrity: sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==}
/eventsource-parser@1.1.2:
resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==}
engines: {node: '>=14.18'}
dev: false
@ -3359,6 +3403,10 @@ packages:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
dev: true
/json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
dev: false
/json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
dev: true
@ -4787,6 +4835,10 @@ packages:
loose-envify: 1.4.0
dev: false
/secure-json-parse@2.7.0:
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
dev: false
/semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@ -5623,8 +5675,8 @@ packages:
resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==}
dev: false
/zod-to-json-schema@3.22.4(zod@3.22.4):
resolution: {integrity: sha512-2Ed5dJ+n/O3cU383xSY28cuVi0BCQhF8nYqWU5paEpl7fVdqdAmiLdqLyfblbNdfOFwFfi/mqU4O1pwc60iBhQ==}
/zod-to-json-schema@3.22.5(zod@3.22.4):
resolution: {integrity: sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==}
peerDependencies:
zod: ^3.22.4
dependencies: