langwatch 0.2.0 → 0.3.0-prerelease.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/.editorconfig +16 -0
  2. package/LICENSE +7 -0
  3. package/README.md +268 -1
  4. package/copy-types.sh +19 -8
  5. package/examples/langchain/.env.example +2 -0
  6. package/examples/langchain/README.md +42 -0
  7. package/examples/langchain/package-lock.json +2930 -0
  8. package/examples/langchain/package.json +27 -0
  9. package/examples/langchain/src/cli-markdown.d.ts +137 -0
  10. package/examples/langchain/src/index.ts +109 -0
  11. package/examples/langchain/tsconfig.json +25 -0
  12. package/examples/langgraph/.env.example +2 -0
  13. package/examples/langgraph/README.md +42 -0
  14. package/examples/langgraph/package-lock.json +3031 -0
  15. package/examples/langgraph/package.json +28 -0
  16. package/examples/langgraph/src/cli-markdown.d.ts +137 -0
  17. package/examples/langgraph/src/index.ts +196 -0
  18. package/examples/langgraph/tsconfig.json +25 -0
  19. package/examples/mastra/.env.example +2 -0
  20. package/examples/mastra/README.md +57 -0
  21. package/examples/mastra/package-lock.json +5296 -0
  22. package/examples/mastra/package.json +32 -0
  23. package/examples/mastra/src/cli-markdown.d.ts +137 -0
  24. package/examples/mastra/src/index.ts +120 -0
  25. package/examples/mastra/src/mastra/agents/weather-agent.ts +30 -0
  26. package/examples/mastra/src/mastra/index.ts +21 -0
  27. package/examples/mastra/src/mastra/tools/weather-tool.ts +102 -0
  28. package/examples/mastra/tsconfig.json +25 -0
  29. package/examples/vercel-ai/.env.example +2 -0
  30. package/examples/vercel-ai/README.md +38 -0
  31. package/examples/vercel-ai/package-lock.json +2571 -0
  32. package/examples/vercel-ai/package.json +27 -0
  33. package/examples/vercel-ai/src/cli-markdown.d.ts +137 -0
  34. package/examples/vercel-ai/src/index.ts +110 -0
  35. package/examples/vercel-ai/src/instrumentation.ts +9 -0
  36. package/examples/vercel-ai/tsconfig.json +25 -0
  37. package/package.json +78 -34
  38. package/src/__tests__/client-browser.test.ts +92 -0
  39. package/src/__tests__/client-node.test.ts +76 -0
  40. package/src/__tests__/client.test.ts +71 -0
  41. package/src/__tests__/integration/client-browser.test.ts +46 -0
  42. package/src/__tests__/integration/client-node.test.ts +46 -0
  43. package/src/client-browser.ts +70 -0
  44. package/src/client-node.ts +82 -0
  45. package/src/client-shared.ts +72 -0
  46. package/src/client.ts +119 -0
  47. package/src/evaluation/__tests__/record-evaluation.test.ts +112 -0
  48. package/src/evaluation/__tests__/run-evaluation.test.ts +171 -0
  49. package/src/evaluation/index.ts +2 -0
  50. package/src/evaluation/record-evaluation.ts +101 -0
  51. package/src/evaluation/run-evaluation.ts +133 -0
  52. package/src/evaluation/tracer.ts +3 -0
  53. package/src/evaluation/types.ts +23 -0
  54. package/src/index.ts +10 -593
  55. package/src/internal/api/__tests__/errors.test.ts +98 -0
  56. package/src/internal/api/client.ts +30 -0
  57. package/src/internal/api/errors.ts +32 -0
  58. package/src/internal/generated/types/.gitkeep +0 -0
  59. package/src/observability/__tests__/integration/base.test.ts +74 -0
  60. package/src/observability/__tests__/integration/browser-setup-ordering.test.ts +60 -0
  61. package/src/observability/__tests__/integration/complex-nested-spans.test.ts +29 -0
  62. package/src/observability/__tests__/integration/error-handling.test.ts +24 -0
  63. package/src/observability/__tests__/integration/langwatch-disabled-otel.test.ts +24 -0
  64. package/src/observability/__tests__/integration/langwatch-first-then-vercel.test.ts +24 -0
  65. package/src/observability/__tests__/integration/multiple-setup-attempts.test.ts +27 -0
  66. package/src/observability/__tests__/integration/otel-ordering.test.ts +27 -0
  67. package/src/observability/__tests__/integration/vercel-configurations.test.ts +20 -0
  68. package/src/observability/__tests__/integration/vercel-first-then-langwatch.test.ts +27 -0
  69. package/src/observability/__tests__/span.test.ts +214 -0
  70. package/src/observability/__tests__/trace.test.ts +180 -0
  71. package/src/observability/exporters/index.ts +1 -0
  72. package/src/observability/exporters/langwatch-exporter.ts +53 -0
  73. package/src/observability/index.ts +4 -0
  74. package/src/observability/instrumentation/langchain/__tests__/integration/langchain-chatbot.test.ts +112 -0
  75. package/src/observability/instrumentation/langchain/__tests__/langchain.test.ts +284 -0
  76. package/src/observability/instrumentation/langchain/index.ts +624 -0
  77. package/src/observability/processors/__tests__/filterable-batch-span-exporter.test.ts +98 -0
  78. package/src/observability/processors/filterable-batch-span-processor.ts +99 -0
  79. package/src/observability/processors/index.ts +1 -0
  80. package/src/observability/semconv/attributes.ts +185 -0
  81. package/src/observability/semconv/events.ts +42 -0
  82. package/src/observability/semconv/index.ts +16 -0
  83. package/src/observability/semconv/values.ts +159 -0
  84. package/src/observability/span.ts +728 -0
  85. package/src/observability/trace.ts +301 -0
  86. package/src/prompt/__tests__/prompt.test.ts +139 -0
  87. package/src/prompt/get-prompt-version.ts +49 -0
  88. package/src/prompt/get-prompt.ts +44 -0
  89. package/src/prompt/index.ts +3 -0
  90. package/src/prompt/prompt.ts +133 -0
  91. package/src/prompt/service.ts +221 -0
  92. package/src/prompt/tracer.ts +3 -0
  93. package/src/prompt/types.ts +0 -0
  94. package/ts-to-zod.config.js +11 -0
  95. package/tsconfig.json +3 -9
  96. package/tsup.config.ts +11 -1
  97. package/vitest.config.ts +1 -0
  98. package/dist/chunk-LKD2K67J.mjs +0 -717
  99. package/dist/chunk-LKD2K67J.mjs.map +0 -1
  100. package/dist/index.d.mts +0 -1030
  101. package/dist/index.d.ts +0 -1030
  102. package/dist/index.js +0 -27310
  103. package/dist/index.js.map +0 -1
  104. package/dist/index.mjs +0 -963
  105. package/dist/index.mjs.map +0 -1
  106. package/dist/utils-Cv-rUjJ1.d.mts +0 -313
  107. package/dist/utils-Cv-rUjJ1.d.ts +0 -313
  108. package/dist/utils.d.mts +0 -2
  109. package/dist/utils.d.ts +0 -2
  110. package/dist/utils.js +0 -709
  111. package/dist/utils.js.map +0 -1
  112. package/dist/utils.mjs +0 -11
  113. package/dist/utils.mjs.map +0 -1
  114. package/example/.env.example +0 -12
  115. package/example/.eslintrc.json +0 -26
  116. package/example/LICENSE +0 -13
  117. package/example/README.md +0 -12
  118. package/example/app/(chat)/chat/[id]/page.tsx +0 -60
  119. package/example/app/(chat)/layout.tsx +0 -14
  120. package/example/app/(chat)/page.tsx +0 -27
  121. package/example/app/actions.ts +0 -156
  122. package/example/app/globals.css +0 -76
  123. package/example/app/guardrails/page.tsx +0 -26
  124. package/example/app/langchain/page.tsx +0 -27
  125. package/example/app/langchain-rag/page.tsx +0 -28
  126. package/example/app/late-update/page.tsx +0 -27
  127. package/example/app/layout.tsx +0 -64
  128. package/example/app/login/actions.ts +0 -71
  129. package/example/app/login/page.tsx +0 -18
  130. package/example/app/manual/page.tsx +0 -27
  131. package/example/app/new/page.tsx +0 -5
  132. package/example/app/opengraph-image.png +0 -0
  133. package/example/app/share/[id]/page.tsx +0 -58
  134. package/example/app/signup/actions.ts +0 -111
  135. package/example/app/signup/page.tsx +0 -18
  136. package/example/app/twitter-image.png +0 -0
  137. package/example/auth.config.ts +0 -42
  138. package/example/auth.ts +0 -45
  139. package/example/components/button-scroll-to-bottom.tsx +0 -36
  140. package/example/components/chat-history.tsx +0 -49
  141. package/example/components/chat-list.tsx +0 -52
  142. package/example/components/chat-message-actions.tsx +0 -40
  143. package/example/components/chat-message.tsx +0 -80
  144. package/example/components/chat-panel.tsx +0 -139
  145. package/example/components/chat-share-dialog.tsx +0 -95
  146. package/example/components/chat.tsx +0 -84
  147. package/example/components/clear-history.tsx +0 -75
  148. package/example/components/empty-screen.tsx +0 -38
  149. package/example/components/external-link.tsx +0 -29
  150. package/example/components/footer.tsx +0 -19
  151. package/example/components/header.tsx +0 -114
  152. package/example/components/login-button.tsx +0 -42
  153. package/example/components/login-form.tsx +0 -97
  154. package/example/components/markdown.tsx +0 -9
  155. package/example/components/prompt-form.tsx +0 -115
  156. package/example/components/providers.tsx +0 -17
  157. package/example/components/sidebar-actions.tsx +0 -125
  158. package/example/components/sidebar-desktop.tsx +0 -19
  159. package/example/components/sidebar-footer.tsx +0 -16
  160. package/example/components/sidebar-item.tsx +0 -124
  161. package/example/components/sidebar-items.tsx +0 -42
  162. package/example/components/sidebar-list.tsx +0 -38
  163. package/example/components/sidebar-mobile.tsx +0 -31
  164. package/example/components/sidebar-toggle.tsx +0 -24
  165. package/example/components/sidebar.tsx +0 -21
  166. package/example/components/signup-form.tsx +0 -95
  167. package/example/components/stocks/events-skeleton.tsx +0 -31
  168. package/example/components/stocks/events.tsx +0 -30
  169. package/example/components/stocks/index.tsx +0 -36
  170. package/example/components/stocks/message.tsx +0 -134
  171. package/example/components/stocks/spinner.tsx +0 -16
  172. package/example/components/stocks/stock-purchase.tsx +0 -146
  173. package/example/components/stocks/stock-skeleton.tsx +0 -22
  174. package/example/components/stocks/stock.tsx +0 -210
  175. package/example/components/stocks/stocks-skeleton.tsx +0 -9
  176. package/example/components/stocks/stocks.tsx +0 -67
  177. package/example/components/tailwind-indicator.tsx +0 -14
  178. package/example/components/theme-toggle.tsx +0 -31
  179. package/example/components/ui/alert-dialog.tsx +0 -141
  180. package/example/components/ui/badge.tsx +0 -36
  181. package/example/components/ui/button.tsx +0 -57
  182. package/example/components/ui/codeblock.tsx +0 -148
  183. package/example/components/ui/dialog.tsx +0 -122
  184. package/example/components/ui/dropdown-menu.tsx +0 -205
  185. package/example/components/ui/icons.tsx +0 -507
  186. package/example/components/ui/input.tsx +0 -25
  187. package/example/components/ui/label.tsx +0 -26
  188. package/example/components/ui/select.tsx +0 -164
  189. package/example/components/ui/separator.tsx +0 -31
  190. package/example/components/ui/sheet.tsx +0 -140
  191. package/example/components/ui/sonner.tsx +0 -31
  192. package/example/components/ui/switch.tsx +0 -29
  193. package/example/components/ui/textarea.tsx +0 -24
  194. package/example/components/ui/tooltip.tsx +0 -30
  195. package/example/components/user-menu.tsx +0 -53
  196. package/example/components.json +0 -17
  197. package/example/instrumentation.ts +0 -11
  198. package/example/lib/chat/guardrails.tsx +0 -181
  199. package/example/lib/chat/langchain-rag.tsx +0 -191
  200. package/example/lib/chat/langchain.tsx +0 -112
  201. package/example/lib/chat/late-update.tsx +0 -208
  202. package/example/lib/chat/manual.tsx +0 -605
  203. package/example/lib/chat/vercel-ai.tsx +0 -576
  204. package/example/lib/hooks/use-copy-to-clipboard.tsx +0 -33
  205. package/example/lib/hooks/use-enter-submit.tsx +0 -23
  206. package/example/lib/hooks/use-local-storage.ts +0 -24
  207. package/example/lib/hooks/use-scroll-anchor.tsx +0 -86
  208. package/example/lib/hooks/use-sidebar.tsx +0 -60
  209. package/example/lib/hooks/use-streamable-text.ts +0 -25
  210. package/example/lib/types.ts +0 -41
  211. package/example/lib/utils.ts +0 -89
  212. package/example/middleware.ts +0 -8
  213. package/example/next-env.d.ts +0 -5
  214. package/example/next.config.js +0 -16
  215. package/example/package-lock.json +0 -10917
  216. package/example/package.json +0 -84
  217. package/example/pnpm-lock.yaml +0 -5712
  218. package/example/postcss.config.js +0 -6
  219. package/example/prettier.config.cjs +0 -34
  220. package/example/public/apple-touch-icon.png +0 -0
  221. package/example/public/favicon-16x16.png +0 -0
  222. package/example/public/favicon.ico +0 -0
  223. package/example/public/next.svg +0 -1
  224. package/example/public/thirteen.svg +0 -1
  225. package/example/public/vercel.svg +0 -1
  226. package/example/tailwind.config.ts +0 -81
  227. package/example/tsconfig.json +0 -35
  228. package/src/LangWatchExporter.ts +0 -96
  229. package/src/evaluations.ts +0 -219
  230. package/src/index.test.ts +0 -402
  231. package/src/langchain.ts +0 -557
  232. package/src/typeUtils.ts +0 -89
  233. package/src/types.ts +0 -82
  234. package/src/utils.ts +0 -205
  235. /package/src/{server/types → internal/generated/openapi}/.gitkeep +0 -0
@@ -1,576 +0,0 @@
1
- import 'server-only'
2
-
3
- import { openai } from '@ai-sdk/openai'
4
- import {
5
- createAI,
6
- createStreamableUI,
7
- getAIState,
8
- getMutableAIState
9
- } from 'ai/rsc'
10
-
11
- import {
12
- BotCard,
13
- BotMessage,
14
- Purchase,
15
- spinner,
16
- Stock,
17
- SystemMessage
18
- } from '@/components/stocks'
19
-
20
- import { saveChat } from '@/app/actions'
21
- import { auth } from '@/auth'
22
- import { Events } from '@/components/stocks/events'
23
- import { UserMessage } from '@/components/stocks/message'
24
- import { Stocks } from '@/components/stocks/stocks'
25
- import { Chat, Message } from '@/lib/types'
26
- import {
27
- formatNumber,
28
- nanoid,
29
- runAsyncFnWithoutBlocking,
30
- sleep
31
- } from '@/lib/utils'
32
- import { generateText, streamText, tool } from 'ai'
33
- import { z } from 'zod'
34
- import { StockSkeleton } from '../../components/stocks/stock-skeleton'
35
- import { EventsSkeleton } from '../../components/stocks/events-skeleton'
36
-
37
- async function submitUserMessage(content: string) {
38
- 'use server'
39
-
40
- const aiState = getMutableAIState<typeof AI>()
41
-
42
- aiState.update({
43
- ...aiState.get(),
44
- messages: [
45
- ...aiState.get().messages,
46
- {
47
- id: nanoid(),
48
- role: 'user',
49
- content
50
- }
51
- ]
52
- })
53
-
54
- const system = `\
55
- You are a stock trading conversation bot and you can help users buy stocks, step by step.
56
- 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.
57
-
58
- To use tools, use the following format:
59
- - For stock price: show_stock_price(SYMBOL, PRICE, DELTA)
60
- - For listing stocks: list_stocks([{"symbol": "AAPL", "price": 150.5, "delta": 2.3}, ...])
61
- - For purchase UI: show_stock_purchase(SYMBOL, PRICE, NUMBER_OF_SHARES)
62
- - For events: get_events([{"date": "2024-01-01", "headline": "...", "description": "..."}, ...])
63
-
64
- Messages inside [] means that it's a UI element or a user event.`
65
-
66
- const ui = createStreamableUI(<BotMessage content="" />)
67
- // let textNode = <BotMessage content={textStream.value} />
68
- let fullContent = ''
69
-
70
- const onFinish = (output: Message[]) => {
71
- aiState.done({
72
- ...aiState.get(),
73
- messages: [...aiState.get().messages, ...output]
74
- })
75
- }
76
-
77
- const stream = streamText({
78
- model: openai('gpt-4o-mini'),
79
- messages: [
80
- {
81
- role: 'system',
82
- content: system
83
- },
84
- ...aiState.get().messages
85
- ],
86
- experimental_telemetry: {
87
- isEnabled: true,
88
- metadata: {
89
- threadId: aiState.get().chatId
90
- }
91
- },
92
- tools: {
93
- listStocks: tool({
94
- description: 'List three imaginary stocks that are trending.',
95
- parameters: z.object({
96
- stocks: z.array(
97
- z.object({
98
- symbol: z.string().describe('The symbol of the stock'),
99
- price: z.number().describe('The price of the stock'),
100
- delta: z.number().describe('The change in price of the stock')
101
- })
102
- )
103
- }),
104
- execute: async ({ stocks }) => {
105
- ui.update(
106
- <BotCard>
107
- <StockSkeleton />
108
- </BotCard>
109
- )
110
-
111
- await sleep(1000)
112
-
113
- const toolCallId = nanoid()
114
-
115
- onFinish([
116
- {
117
- id: nanoid(),
118
- role: 'assistant',
119
- content: [
120
- {
121
- type: 'tool-call',
122
- toolName: 'listStocks',
123
- toolCallId,
124
- args: { stocks }
125
- }
126
- ]
127
- },
128
- {
129
- id: nanoid(),
130
- role: 'tool',
131
- content: [
132
- {
133
- type: 'tool-result',
134
- toolName: 'listStocks',
135
- toolCallId,
136
- result: stocks
137
- }
138
- ]
139
- }
140
- ])
141
-
142
- ui.update(
143
- <BotCard>
144
- <Stocks props={stocks} />
145
- </BotCard>
146
- )
147
- }
148
- }),
149
- showStockPrice: tool({
150
- description:
151
- 'Get the current stock price of a given stock or currency. Use this to show the price to the user.',
152
- parameters: z.object({
153
- symbol: z
154
- .string()
155
- .describe(
156
- 'The name or symbol of the stock or currency. e.g. DOGE/AAPL/USD.'
157
- ),
158
- price: z.number().describe('The price of the stock.'),
159
- delta: z.number().describe('The change in price of the stock')
160
- }),
161
- execute: async ({ symbol, price, delta }) => {
162
- ui.update(
163
- <BotCard>
164
- <StockSkeleton />
165
- </BotCard>
166
- )
167
-
168
- await sleep(1000)
169
-
170
- const toolCallId = nanoid()
171
-
172
- onFinish([
173
- {
174
- id: nanoid(),
175
- role: 'assistant',
176
- content: [
177
- {
178
- type: 'tool-call',
179
- toolName: 'showStockPrice',
180
- toolCallId,
181
- args: { symbol, price, delta }
182
- }
183
- ]
184
- },
185
- {
186
- id: nanoid(),
187
- role: 'tool',
188
- content: [
189
- {
190
- type: 'tool-result',
191
- toolName: 'showStockPrice',
192
- toolCallId,
193
- result: { symbol, price, delta }
194
- }
195
- ]
196
- }
197
- ])
198
-
199
- ui.update(
200
- <BotCard>
201
- <Stock props={{ symbol, price, delta }} />
202
- </BotCard>
203
- )
204
- }
205
- }),
206
- showStockPurchase: tool({
207
- description:
208
- 'Show price and the UI to purchase a stock or currency. Use this if the user wants to purchase a stock or currency.',
209
- parameters: z.object({
210
- symbol: z
211
- .string()
212
- .describe(
213
- 'The name or symbol of the stock or currency. e.g. DOGE/AAPL/USD.'
214
- ),
215
- price: z.number().describe('The price of the stock.'),
216
- numberOfShares: z
217
- .number()
218
- .describe(
219
- 'The **number of shares** for a stock or currency to purchase. Can be optional if the user did not specify it.'
220
- )
221
- }),
222
- execute: async ({ symbol, price, numberOfShares = 100 }) => {
223
- const toolCallId = nanoid()
224
-
225
- if (numberOfShares <= 0 || numberOfShares > 1000) {
226
- onFinish([
227
- {
228
- id: nanoid(),
229
- role: 'assistant',
230
- content: [
231
- {
232
- type: 'tool-call',
233
- toolName: 'showStockPurchase',
234
- toolCallId,
235
- args: { symbol, price, numberOfShares }
236
- }
237
- ]
238
- },
239
- {
240
- id: nanoid(),
241
- role: 'tool',
242
- content: [
243
- {
244
- type: 'tool-result',
245
- toolName: 'showStockPurchase',
246
- toolCallId,
247
- result: {
248
- symbol,
249
- price,
250
- numberOfShares,
251
- status: 'expired'
252
- }
253
- }
254
- ]
255
- },
256
- {
257
- id: nanoid(),
258
- role: 'system',
259
- content: `[User has selected an invalid amount]`
260
- }
261
- ])
262
-
263
- ui.update(<BotMessage content={'Invalid amount'} />)
264
- } else {
265
- onFinish([
266
- {
267
- id: nanoid(),
268
- role: 'assistant',
269
- content: [
270
- {
271
- type: 'tool-call',
272
- toolName: 'showStockPurchase',
273
- toolCallId,
274
- args: { symbol, price, numberOfShares }
275
- }
276
- ]
277
- },
278
- {
279
- id: nanoid(),
280
- role: 'tool',
281
- content: [
282
- {
283
- type: 'tool-result',
284
- toolName: 'showStockPurchase',
285
- toolCallId,
286
- result: {
287
- symbol,
288
- price,
289
- numberOfShares
290
- }
291
- }
292
- ]
293
- }
294
- ])
295
-
296
- ui.update(
297
- <BotCard>
298
- <Purchase
299
- props={{
300
- numberOfShares,
301
- symbol,
302
- price: +price,
303
- status: 'requires_action'
304
- }}
305
- />
306
- </BotCard>
307
- )
308
- }
309
- }
310
- }),
311
- getEvents: tool({
312
- description:
313
- 'List funny imaginary events between user highlighted dates that describe stock activity.',
314
- parameters: z.object({
315
- events: z.array(
316
- z.object({
317
- date: z
318
- .string()
319
- .describe('The date of the event, in ISO-8601 format'),
320
- headline: z.string().describe('The headline of the event'),
321
- description: z.string().describe('The description of the event')
322
- })
323
- )
324
- }),
325
- execute: async ({ events }) => {
326
- ui.update(
327
- <BotCard>
328
- <EventsSkeleton />
329
- </BotCard>
330
- )
331
-
332
- await sleep(1000)
333
-
334
- const toolCallId = nanoid()
335
-
336
- onFinish([
337
- {
338
- id: nanoid(),
339
- role: 'assistant',
340
- content: [
341
- {
342
- type: 'tool-call',
343
- toolName: 'getEvents',
344
- toolCallId,
345
- args: { events }
346
- }
347
- ]
348
- },
349
- {
350
- id: nanoid(),
351
- role: 'tool',
352
- content: [
353
- {
354
- type: 'tool-result',
355
- toolName: 'getEvents',
356
- toolCallId,
357
- result: events
358
- }
359
- ]
360
- }
361
- ])
362
-
363
- ui.update(
364
- <BotCard>
365
- <Events props={events} />
366
- </BotCard>
367
- )
368
- }
369
- })
370
- }
371
- })
372
-
373
- setTimeout(async () => {
374
- // First, stream all text chunks
375
- for await (const chunk of stream.textStream) {
376
- ui.update(<BotMessage content={fullContent} />)
377
- fullContent += chunk
378
- }
379
-
380
- ui.done()
381
- const toolCalls = await stream.toolCalls
382
- if (!toolCalls || toolCalls.length == 0) {
383
- aiState.done({
384
- ...aiState.get(),
385
- messages: [
386
- ...aiState.get().messages,
387
- {
388
- id: nanoid(),
389
- role: 'assistant',
390
- content: fullContent
391
- }
392
- ]
393
- })
394
- }
395
- }, 0)
396
-
397
- return {
398
- id: nanoid(),
399
- display: ui.value
400
- }
401
- }
402
-
403
- async function confirmPurchase(symbol: string, price: number, amount: number) {
404
- 'use server'
405
-
406
- const aiState = getMutableAIState<typeof AI>()
407
-
408
- const purchasing = createStreamableUI(
409
- <div className="inline-flex items-start gap-1 md:items-center">
410
- {spinner}
411
- <p className="mb-2">
412
- Purchasing {amount} ${symbol}...
413
- </p>
414
- </div>
415
- )
416
-
417
- const systemMessage = createStreamableUI(null)
418
-
419
- runAsyncFnWithoutBlocking(async () => {
420
- await sleep(1000)
421
-
422
- purchasing.update(
423
- <div className="inline-flex items-start gap-1 md:items-center">
424
- {spinner}
425
- <p className="mb-2">
426
- Purchasing {amount} ${symbol}... working on it...
427
- </p>
428
- </div>
429
- )
430
-
431
- await sleep(1000)
432
-
433
- purchasing.done(
434
- <div>
435
- <p className="mb-2">
436
- You have successfully purchased {amount} ${symbol}. Total cost:{' '}
437
- {formatNumber(amount * price)}
438
- </p>
439
- </div>
440
- )
441
-
442
- systemMessage.done(
443
- <SystemMessage>
444
- You have purchased {amount} shares of {symbol} at ${price}. Total cost ={' '}
445
- {formatNumber(amount * price)}.
446
- </SystemMessage>
447
- )
448
-
449
- aiState.done({
450
- ...aiState.get(),
451
- messages: [
452
- ...aiState.get().messages,
453
- {
454
- id: nanoid(),
455
- role: 'system',
456
- content: `[User has purchased ${amount} shares of ${symbol} at ${price}. Total cost = ${
457
- amount * price
458
- }]`
459
- }
460
- ]
461
- })
462
- })
463
-
464
- return {
465
- purchasingUI: purchasing.value,
466
- newMessage: {
467
- id: nanoid(),
468
- display: systemMessage.value
469
- }
470
- }
471
- }
472
-
473
- export type AIState = {
474
- chatId: string
475
- messages: Message[]
476
- }
477
-
478
- export type UIState = {
479
- id: string
480
- display: React.ReactNode
481
- }[]
482
-
483
- export const AI = createAI<AIState, UIState>({
484
- actions: {
485
- submitUserMessage,
486
- confirmPurchase
487
- },
488
- initialUIState: [],
489
- initialAIState: { chatId: nanoid(), messages: [] },
490
- onGetUIState: async () => {
491
- 'use server'
492
-
493
- const session = await auth()
494
-
495
- if (session && session.user) {
496
- const aiState = getAIState()
497
-
498
- if (aiState) {
499
- // @ts-ignore
500
- const uiState = getUIStateFromAIState(aiState)
501
- return uiState
502
- }
503
- } else {
504
- return
505
- }
506
- },
507
- onSetAIState: async ({ state }) => {
508
- 'use server'
509
-
510
- const session = await auth()
511
-
512
- if (session && session.user) {
513
- const { chatId, messages } = state
514
-
515
- const createdAt = new Date()
516
- const userId = session.user.id as string
517
- const path = `/chat/${chatId}`
518
-
519
- const firstMessageContent = messages[0].content as string
520
- const title = firstMessageContent.substring(0, 100)
521
-
522
- const chat: Chat = {
523
- id: chatId,
524
- title,
525
- userId,
526
- createdAt,
527
- messages,
528
- path
529
- }
530
-
531
- await saveChat(chat)
532
- } else {
533
- return
534
- }
535
- }
536
- })
537
-
538
- export const getUIStateFromAIState = (aiState: Chat) => {
539
- return aiState.messages
540
- .filter(message => message.role !== 'system')
541
- .map((message, index) => ({
542
- id: `${aiState.chatId}-${index}`,
543
- display:
544
- message.role === 'tool' ? (
545
- message.content.map(tool => {
546
- return tool.toolName === 'listStocks' ? (
547
- <BotCard>
548
- {/* TODO: Infer types based on the tool result*/}
549
- {/* @ts-expect-error */}
550
- <Stocks props={tool.result} />
551
- </BotCard>
552
- ) : tool.toolName === 'showStockPrice' ? (
553
- <BotCard>
554
- {/* @ts-expect-error */}
555
- <Stock props={tool.result} />
556
- </BotCard>
557
- ) : tool.toolName === 'showStockPurchase' ? (
558
- <BotCard>
559
- {/* @ts-expect-error */}
560
- <Purchase props={tool.result} />
561
- </BotCard>
562
- ) : tool.toolName === 'getEvents' ? (
563
- <BotCard>
564
- {/* @ts-expect-error */}
565
- <Events props={tool.result} />
566
- </BotCard>
567
- ) : null
568
- })
569
- ) : message.role === 'user' ? (
570
- <UserMessage>{message.content as string}</UserMessage>
571
- ) : message.role === 'assistant' &&
572
- typeof message.content === 'string' ? (
573
- <BotMessage content={message.content} />
574
- ) : null
575
- }))
576
- }
@@ -1,33 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
-
5
- export interface useCopyToClipboardProps {
6
- timeout?: number
7
- }
8
-
9
- export function useCopyToClipboard({
10
- timeout = 2000
11
- }: useCopyToClipboardProps) {
12
- const [isCopied, setIsCopied] = React.useState<Boolean>(false)
13
-
14
- const copyToClipboard = (value: string) => {
15
- if (typeof window === 'undefined' || !navigator.clipboard?.writeText) {
16
- return
17
- }
18
-
19
- if (!value) {
20
- return
21
- }
22
-
23
- navigator.clipboard.writeText(value).then(() => {
24
- setIsCopied(true)
25
-
26
- setTimeout(() => {
27
- setIsCopied(false)
28
- }, timeout)
29
- })
30
- }
31
-
32
- return { isCopied, copyToClipboard }
33
- }
@@ -1,23 +0,0 @@
1
- import { useRef, type RefObject } from 'react'
2
-
3
- export function useEnterSubmit(): {
4
- formRef: RefObject<HTMLFormElement>
5
- onKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void
6
- } {
7
- const formRef = useRef<HTMLFormElement>(null)
8
-
9
- const handleKeyDown = (
10
- event: React.KeyboardEvent<HTMLTextAreaElement>
11
- ): void => {
12
- if (
13
- event.key === 'Enter' &&
14
- !event.shiftKey &&
15
- !event.nativeEvent.isComposing
16
- ) {
17
- formRef.current?.requestSubmit()
18
- event.preventDefault()
19
- }
20
- }
21
-
22
- return { formRef, onKeyDown: handleKeyDown }
23
- }
@@ -1,24 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
-
3
- export const useLocalStorage = <T>(
4
- key: string,
5
- initialValue: T
6
- ): [T, (value: T) => void] => {
7
- const [storedValue, setStoredValue] = useState(initialValue)
8
-
9
- useEffect(() => {
10
- // Retrieve from localStorage
11
- const item = window.localStorage.getItem(key)
12
- if (item) {
13
- setStoredValue(JSON.parse(item))
14
- }
15
- }, [key])
16
-
17
- const setValue = (value: T) => {
18
- // Save state
19
- setStoredValue(value)
20
- // Save to localStorage
21
- window.localStorage.setItem(key, JSON.stringify(value))
22
- }
23
- return [storedValue, setValue]
24
- }