Use streamUI instead of render (#324)
This commit is contained in:
parent
d5f736128d
commit
095550d4dc
3 changed files with 92 additions and 47 deletions
|
|
@ -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,29 +137,25 @@ 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 />,
|
||||||
|
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: [
|
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) => ({
|
...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()
|
||||||
|
|
|
||||||
|
|
@ -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
74
pnpm-lock.yaml
generated
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue