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, createStreamableUI,
getMutableAIState, getMutableAIState,
getAIState, getAIState,
render, streamUI,
createStreamableValue createStreamableValue
} from 'ai/rsc' } from 'ai/rsc'
import OpenAI from 'openai' import { openai } from '@ai-sdk/openai'
import { import {
spinner, spinner,
@ -36,10 +36,6 @@ import { SpinnerMessage, UserMessage } from '@/components/stocks/message'
import { Chat } from '@/lib/types' import { Chat } from '@/lib/types'
import { auth } from '@/auth' import { auth } from '@/auth'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || ''
})
async function confirmPurchase(symbol: string, price: number, amount: number) { async function confirmPurchase(symbol: string, price: number, amount: number) {
'use server' 'use server'
@ -141,14 +137,10 @@ async function submitUserMessage(content: string) {
let textStream: undefined | ReturnType<typeof createStreamableValue<string>> let textStream: undefined | ReturnType<typeof createStreamableValue<string>>
let textNode: undefined | React.ReactNode let textNode: undefined | React.ReactNode
const ui = render({ const result = await streamUI({
model: 'gpt-3.5-turbo', model: openai('gpt-3.5-turbo'),
provider: openai,
initial: <SpinnerMessage />, initial: <SpinnerMessage />,
messages: [ system: `\
{
role: 'system',
content: `\
You are a stock trading conversation bot and you can help users buy stocks, step by step. 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. 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.
@ -162,8 +154,8 @@ If you want to show trending stocks, call \`list_stocks\`.
If you want to show events, call \`get_events\`. 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. 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.` Besides that, you can also chat with users and do some calculations if needed.`,
}, messages: [
...aiState.get().messages.map((message: any) => ({ ...aiState.get().messages.map((message: any) => ({
role: message.role, role: message.role,
content: message.content, content: message.content,
@ -195,7 +187,7 @@ Besides that, you can also chat with users and do some calculations if needed.`
return textNode return textNode
}, },
functions: { tools: {
listStocks: { listStocks: {
description: 'List three imaginary stocks that are trending.', description: 'List three imaginary stocks that are trending.',
parameters: z.object({ 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 ( yield (
<BotCard> <BotCard>
<StocksSkeleton /> <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.'), price: z.number().describe('The price of the stock.'),
delta: z.number().describe('The change in 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 ( yield (
<BotCard> <BotCard>
<StockSkeleton /> <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.' '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) { if (numberOfShares <= 0 || numberOfShares > 1000) {
aiState.done({ aiState.done({
...aiState.get(), ...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 ( yield (
<BotCard> <BotCard>
<EventsSkeleton /> <EventsSkeleton />
@ -389,7 +381,7 @@ Besides that, you can also chat with users and do some calculations if needed.`
return { return {
id: nanoid(), id: nanoid(),
display: ui display: result.value
} }
} }
@ -417,7 +409,7 @@ export const AI = createAI<AIState, UIState>({
}, },
initialUIState: [], initialUIState: [],
initialAIState: { chatId: nanoid(), messages: [] }, initialAIState: { chatId: nanoid(), messages: [] },
unstable_onGetUIState: async () => { onGetUIState: async () => {
'use server' 'use server'
const session = await auth() const session = await auth()
@ -433,7 +425,7 @@ export const AI = createAI<AIState, UIState>({
return return
} }
}, },
unstable_onSetAIState: async ({ state, done }) => { onSetAIState: async ({ state, done }) => {
'use server' 'use server'
const session = await auth() const session = await auth()

View file

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

74
pnpm-lock.yaml generated
View file

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