diff --git a/lib/chat/actions.tsx b/lib/chat/actions.tsx index ac54c4d..6c89e31 100644 --- a/lib/chat/actions.tsx +++ b/lib/chat/actions.tsx @@ -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> 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: , + 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 ( @@ -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 ( @@ -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 ( @@ -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({ }, initialUIState: [], initialAIState: { chatId: nanoid(), messages: [] }, - unstable_onGetUIState: async () => { + onGetUIState: async () => { 'use server' const session = await auth() @@ -433,7 +425,7 @@ export const AI = createAI({ return } }, - unstable_onSetAIState: async ({ state, done }) => { + onSetAIState: async ({ state, done }) => { 'use server' const session = await auth() diff --git a/package.json b/package.json index b7fa99c..ce25b7a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e01545..1d7b590 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: