发布于 2026-01-05 5 阅读
0

Thesys React SDK:将 LLM 响应转换为实时用户界面

Thesys React SDK:将 LLM 响应转换为实时用户界面

逻辑逻辑模型(LLM)正逐渐成为现代开发工具和人工智能代理的核心。它已经能够生成代码。但问题在于:输出是静态的。

你仍然需要复制代码,粘贴到项目中,启动开发服务器,修复构建问题,并手动连接逻辑或状态。如果几十个 UI 组件都需要这样做,很快就会变得一团糟。

Thesys React SDK 可以让你跳过这一步骤。你可以将 LLM 响应实时渲染成交互式 UI。

今天,我们将学习“生成式 UI”的真正含义,Thesys 如何将 LLM 与前端层连接起来,以及如何使用 C1 API 和 React SDK 将其与您的 AI 堆栈集成。

让我们开始吧。

系统


涵盖哪些内容?

总而言之,我们将详细介绍这些主题。

1) 什么是生成式用户界面?
2) Thesys 的工作原理:C1 API 和 GenUI React SDK
3) 使用 Thesys 构建生成式用户界面的分步指南。
4) 所有可与 Thesys 一起使用的主流 AI 技术栈。

如果您有兴趣自行探索,请查看thesys.dev并在playground上进行实际尝试


1. 什么是生成式用户界面?

如果你最近使用过 Slack、Gmail 或任何仪表盘工具之类的应用程序,你会注意到一件事they all look insanely similar

传统用户界面是静态的。无论用户是谁,无论出于何种目的,每个用户看到的都是相同的布局。而生成式用户界面则允许界面根据每个用户的具体情况动态地进行调整。

但并非所有基于提示的用户界面系统都相同。它们之间的区别如下:

  • Prompt to design它将线性线性模型(LLM)与计算机视觉模型(例如扩散模型或生成对抗网络)相结合,用于版面生成。它通常嵌入在 Figma 等设计工具中。

  • Prompt to UI:使用 LLM(如 GPT-4、Claude)来理解提示并生成生产级前端/后端代码。

  • Generative UI它需要一个可定制的 UI 库、用于存储用户上下文的内存,以及一个能够持续重塑 UI 的实时 GenUI 引擎。Thesys 就属于这一类。

如果您有兴趣了解更多幕后运作原理,请查看“生成式 UI vs 提示式 UI vs 提示式设计”

生成式用户界面

 

让我们简要了解一下生成式用户界面(Generationative UI)的工作原理(概念上):

✅ 1. 系统了解您的上下文

在生成式用户界面系统中,布局并非硬编码。它会根据用户的身份、操作以及与应用的交互方式而变化。系统会收集以下上下文信息:

  • 你过去的行
  • 您的偏好(例如深色模式)
  • 设备类型、屏幕尺寸、位置
  • 甚至一天中的时间或辅助功能设置

例子?

假设你正在使用一款网约车应用。该应用检测到运动和位置信号,意识到你正在驾驶,并立即切换到语音优先的用户界面。

大按钮、极少的干扰和语音提示引导您完成取货和导航等操作。

这不仅仅是响应式设计,

而是understands your context and adapts itself能够实时适应不同情况的用户界面。

 

✅ 2. AI 使用现有的 UI 元素库,不会产生幻觉。

现在,人工智能不会凭空想象出新的组件。它不会从零开始设计按钮。

相反,设计师和开发人员提供了一个结构化的 UI 元素库,例如卡片、图表、模态框、文本块、表单等等。

这些都是经过审核的可复用组件。人工智能只需根据布局和品牌限制,从这些模块中进行选择即可。

 

✅ 3. AI层组装用户界面。

AI 层利用现有上下文和预定义的 UI 库,生成符合当前情况的结构化 UI 规范。

它定义了要渲染哪些组件、如何排列它们以及要传递哪些属性或数据。例如:Render a balance chart at the top, followed by a currency selector and a transfer form pre-filled with the last used account.

人工智能可以使用训练好的机器学习模型、简单的启发式方法,甚至两者结合来做出这些决策。例如If the system detects visually impaired users, it automatically enables high-contrast mode

随着用户上下文的变化,界面也会随之更新。

工作原理

现在我们已经从概念上了解了生成式用户界面的工作原理,接下来让我们来看看 Thesys 的技术方面,以及它是如何使这一切成为可能的。


2. Thesys 如何使用 C1 API 和 GenUI React SDK 工作。

正如您现在所知,Thesys 是一个用于构建生成式 UI 应用程序的开发者平台。它支持多种 LLM,例如 OpenAI 和 Anthropic。

主要包含两个部分:

✅ 1) C1 API(LLM 后端)

当您向 C1 API 发送提示时,它会返回基于 JSON 的 UI 规范 (JSON/XML),而不是纯文本。

它完全兼容 OpenAI(相同的端点/参数),因此您可以使用任何 OpenAI 客户端(JS 或 Python SDK)调用它,只需指向以下baseURL地址:

https://api.thesys.dev/v1/embed
Enter fullscreen mode Exit fullscreen mode

然后您就可以client.chat.completions.create({...})通过消息调用它。使用特殊的模型名称(例如"c1-nightly"),Thesys API 将调用 LLM 并返回一个 UI 规范。

client.chat.completions.create({
  model: "c1-nightly",
  messages: [...],
});
Enter fullscreen mode Exit fullscreen mode

c1 API

根据您的使用场景,您可以通过两种方式使用 C1:

a)将现有的 LLM 替换为 C1,以实现 UI 优先提示

如果您正在构建一个新应用程序并希望将延迟降到最低,您可以直接向其发送提示c1-nightly,它会返回一个 UI 规范以立即渲染。

b)将 C1 层叠加到您当前的 LLM 堆栈之上。

如果您已经拥有一个 LLM 流程(聊天机器人/代理),您可以将其输出作为第二步传递给 C1,以生成可视化布局。这样可以在不更改核心逻辑的情况下,为现有流程添加更丰富的用户界面。

 

✅ 2) GenUI SDK(前端)

收到 C1 提供的 UI 规范后,GenUI SDK 将处理前端的渲染。

该 SDK 是一个 React 框架(基于Crayon 的组件库构建),它读取基于 JSON 的 UI 规范并将其映射到可视化组件。

它的核心是<C1Component>(以及<C1Chat>聊天界面),它接受 C1 API 响应并将其转换为实时 UI。

以下是一个示例代码:

import { C1Component, ThemeProvider } from "@thesys/genui-sdk";

const App = () => {
  const response = await fetch("<your-backend-url>");
  return (
    <ThemeProvider>
      <C1Component c1Response={response.text()} />
    </ThemeProvider>
  );
};
Enter fullscreen mode Exit fullscreen mode

您可以阅读文档了解更多信息,并在Playground上进行实际尝试

这就是 C1 和 React SDK 如何协同工作,将 LLM 响应实时转换为实时 UI 的方式。


3. 使用 Thesys 构建生成式 UI 的分步指南。

在本节中,我们将讨论如何使用 Thesys 的完整流程。我将使用 Next.js。

为了保持实用性,我们将使用 C1 和 Thesys GenUI SDK 构建一个基于表单的实用助手。

它能让你构建:

  • 一个由用户提示驱动的用户界面,用户可以在其中请求执行以下操作:
    • “我想订购一条围巾”
    • “请显示库存中的帽子”
  • LLM 然后生成 UI 结构(输入框、下拉框、按钮)。
  • 表单数据通过预设工具提交回助手。

我们开始做吧。

步骤 1:创建一个 Next.js 应用程序并获取 API 密钥

我们将从头开始集成。如果您还没有 Next.js 应用程序,可以使用以下命令进行安装。

npx create-next-app@latest thesys-demo
Enter fullscreen mode Exit fullscreen mode

安装 Next.js 应用程序

以下是我们将要遵循的项目结构。

文件结构

您需要从 C1 控制台创建一个新的 API 密钥,并将其设置为环境变量。.env请按照以下约定将此 API 密钥添加到文件中。

THESYS_API_KEY=<your-api-key>
Enter fullscreen mode Exit fullscreen mode

API密钥

步骤二:在前端渲染聊天界面

我们需要一个基本的聊天界面,其用户界面由提示信息生成。以下是<C1Chat>在文件中使用该界面的方法src/app/page.tsx

<C1Component />只需要 C1 API 的原始 JSON/文本,但对于聊天应用程序,建议使用<C1Chat>组件,因为它支持消息流。

'use client'

import '@crayonai/react-ui/styles/index.css'
import { C1Chat } from '@thesysai/genui-sdk'

export default function Home() {
  return <C1Chat theme={{ mode: 'dark' }} apiUrl="/api/chat" />
}
Enter fullscreen mode Exit fullscreen mode

这段代码的功能如下:

  • 使用该<C1Chat />组件渲染完整的聊天界面。
  • apiUrl指向将处理提示提交的后端路由。

如您所见,这就是整个前端,没有任何手动编写的 HTML 或表单代码。

 

步骤 3:内存消息存储工具

表单数据将通过已定义的渠道提交回助手tools。我们将构建自定义工具,例如createOrder[getInventory此处应填写工具名称in-memory message store]。

为了处理表单数据并维护对话上下文,我们将首先创建一个消息存储库。该工具用于按对话线程存储助手的消息。

安装openai软件包。它是用于与 OpenAI 的 API(例如 GPT-4 或 Thesys C1,后者与 OpenAI 兼容)交互的官方 Node.js SDK。

npm install openai
Enter fullscreen mode Exit fullscreen mode

创建一个新文件,src/app/api/chat/messageStore.ts并添加以下代码。

import OpenAI from 'openai'

export type DBMessage = OpenAI.Chat.ChatCompletionMessageParam & {
  id?: string
}

const messagesStore: {
  [threadId: string]: DBMessage[]
} = {}

export const getMessageStore = (id: string) => {
  if (!messagesStore[id]) {
    messagesStore[id] = []
  }
  const messageList = messagesStore[id]
  return {
    addMessage: (message: DBMessage) => {
      messageList.push(message)
    },
    messageList,
    getOpenAICompatibleMessageList: () => {
      return messageList.map((m) => {
        const message = {
          ...m,
        }
        delete message.id
        return message
      })
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

以下是代码各部分的功能:

  • 维护一个内存中的聊天消息存储,按以下方式组织threadId
  • messagesStore是一个普通的 JS 对象,它将每个元素映射threadId到一个消息数组。
  • 公开一个getMessageStore(threadId)返回辅助函数的实用程序:
    • addMessage(message)→ 向主题中添加一条新消息。
    • messageList→ 返回该线程中存储的所有消息。
    • getOpenAICompatibleMessageList()→ 返回不带字段的消息列表id,使其与 OpenAI 的ChatCompletionAPI 兼容。

该存储并非持久化存储,服务器重启后所有数据都会丢失。您可以将其替换为数据库(例如 Redis 或 PostgreSQL),以用于生产环境。

 

第四步:订单管理工具

该工具将帮助助手创建并列出不同产品(例如手套、帽子、围巾)的客户订单。它使用Zod进行运行时验证和模式安全,并将订单存储在内存中。

您无需安装 zod,因为它是一个传递依赖项。如果您检查其内部package.json(在node_modules/@thesysai/genui-sdk/package.json),您可能会发现类似这样的内容:

"dependencies": {
  "zod": "^3.24.1"
}
Enter fullscreen mode Exit fullscreen mode

Genui SDK

创建一个新文件,src/app/api/chat/orderManagement.ts并添加以下代码。

import { z } from 'zod'

export const gloveOrderSchema = z.object({
  kind: z.literal('gloves'),
  quantity: z.number(),
  unit: z.enum(['boxes', 'pairs']),
  deliveryDate: z.string(),
  shipping: z.enum(['normal', 'express']),
})

export const hats = z.object({
  kind: z.literal('hats'),
  quantity: z.number(),
  variants: z.enum(['top', 'beanie', 'cap']),
  deliveryDate: z.string(),
  shipping: z.enum(['normal', 'express']),
})

export const scarfOrderSchema = z.object({
  kind: z.literal('scarves'),
  quantity: z.number(),
  colors: z.enum(['red', 'blue', 'green', 'yellow', 'purple', 'orange']),
  deliveryDate: z.string(),
  shipping: z.enum(['normal', 'express']),
})

export const orderSchema = z.object({
  order: z.discriminatedUnion('kind', [
    gloveOrderSchema,
    hats,
    scarfOrderSchema,
  ]),
})

type Order = z.infer<typeof orderSchema>

const orders: Order['order'][] = []

export const createOrder = async (orderJson: unknown) => {
  const order = orderSchema.safeParse(orderJson)
  if (!order.success) {
    console.error('Invalid order', order.error)
    return {
      success: false,
      error: order.error.message,
    }
  }
  const deliveryDate = new Date(order.data.order.deliveryDate)
  console.log('Creating order', { ...order.data.order, deliveryDate })
  orders.push(order.data.order)
  return {
    success: true,
  }
}

export const getOrderSchema = z.object({
  number: z.number().optional().default(10),
})

export const getOrders = (params: unknown) => {
  const parsedParams = getOrderSchema.safeParse(params)
  if (!parsedParams.success) {
    console.error('Invalid params', parsedParams.error)
    return {
      success: false,
      error: parsedParams.error.message,
    }
  }
  return {
    success: true,
    orders: orders.slice(0, parsedParams.data.number),
  }
}
Enter fullscreen mode Exit fullscreen mode

这段代码的功能如下:

  • 支持为gloveshatsscarves和具有唯一模式字段的动态订单创建。

  • 使用z.discriminatedUnion("kind", [...])基于其顺序的订单kind。在一个统一的模式中对多个产品表单进行推理。

  • 定义一个顶层orderSchema,该顶层封装了各个模式,并确保输入与有效的订单类型之一匹配。

  • 创建一个内存数组orders[]来临时存储传入的订单。

  • 公开一个createOrder()函数:

    • 接受原始输入(orderJson
    • 使用 Zod 解析并验证它
    • 如果订单有效,则记录并存储订单。
    • 返回成功/失败响应
  • 公开一个getOrders()函数:

    • 接受一个可选number参数(默认值:10)
    • 返回最新n订单
    • 可用于列出聊天或用户界面中的近期用户操作。
  • 我们已将safeParse()其用于创建和检索,以防止因错误输入而导致运行时崩溃。

 

步骤五:库存查询工具

让我们创建一个工具,帮助助手根据产品类型(例如手套、帽子、围巾或所有这些)获取可用的产品库存。

创建一个新文件,并src/app/api/chat/inventory.ts添加以下代码。由于我们只是在本地使用,所以这里使用的是模拟数据。

import { z } from 'zod'

export const inventoryQuerySchema = z.object({
  productType: z
    .enum(['gloves', 'hats', 'scarves', 'all'])
    .optional()
    .default('all'),
})

const allInventory = [
  {
    productType: 'gloves',
    quantity: 100,
    priceInUSD: 10.0,
    urgentDeliveryDate: '2025-04-15',
    normalDeliveryDate: '2025-04-20',
    imageSrc: 'https://images.unsplash.com/photo-1617118602199-d3c05ae37ed8',
  },
  {
    productType: 'hats',
    quantity: 200,
    priceInUSD: 15.0,
    urgentDeliveryDate: '2025-04-15',
    normalDeliveryDate: '2025-04-20',
    imageSrc: 'https://images.unsplash.com/photo-1556306535-0f09a537f0a3',
  },
  {
    productType: 'scarves',
    quantity: 300,
    priceInUSD: 5.0,
    urgentDeliveryDate: '2025-04-15',
    normalDeliveryDate: '2025-04-20',
    imageSrc: 'https://images.unsplash.com/photo-1457545195570-67f207084966',
  },
]

export const getInventory = (params: unknown) => {
  const parsedParams = inventoryQuerySchema.safeParse(params)
  if (!parsedParams.success) {
    console.error('Invalid params', parsedParams.error)
    return { success: false, error: parsedParams.error.message }
  }
  return {
    success: true,
    inventory: allInventory.filter(
      (item) =>
        parsedParams.data.productType === 'all' ||
        item.productType === parsedParams.data.productType
    ),
  }
}
Enter fullscreen mode Exit fullscreen mode

以上代码的作用如下:

  • inventoryQuerySchema使用 Zod定义输入模式:

    • 接受一个可选productType字段"gloves",,"hats""scarves""all"
    • "all"如果未提供,则使用默认值
  • 声明一个allInventory包含模拟数据的静态数组。每个项目包含数量、价格、交货日期和图片(图片数据来自数据库或 API)。

  • 导出getInventory(params)函数:

    • 使用以下方式验证输入safeParse()
    • 如果指定,则按类型筛选库存。
    • 返回success: true包含筛选后项目的响应
    • 如果输入无效,则返回错误消息

对于“有哪些帽子有库存?”或“显示所有产品”之类的查询,此功能将显示可售商品及其价格和运费信息。

 

步骤 6:使用聊天 API 路由的后端

现在我们需要一个简单的后端来处理OpenAI-compatibleAPI 调用。

现在我们需要构建一个后端来处理 AI 交互(/api/chat),使用OpenAI-compatibleAPI 调用和流式响应,并支持自定义工具。

在构建后端之前,让我们先来了解一下系统提示,它是 C1 行为控制的关键部分。

虽然 C1 本身功能强大,但使用合适的提示来引导 LLM 对于获得一致的用户界面至关重要。Thesys 支持系统提示,以实现:

  • 明确助理的角色(例如“你是一位乐于助人的库存管理员”)或具体的语气/措辞
  • 指定渲染行为(例如始终在列表中包含图像)
  • <ui_rules>使用标签强制执行格式规则

请阅读官方文档,获取有关如何向 AI 应用程序添加系统提示的分步指南。

以下是官方文档中的一个示例。实际上,您需要在每个 API 调用前添加此提示:

const systemPrompt = `
You are a data assistant. Use tables for related info,
charts for comparisons, and carousels for lists of items.
`;

const resp = await client.chat.completions.create({
  model: 'c1-nightly',
  messages: [
    { role: 'system', content: systemPrompt },
    { role: 'user', content: userQuery }
  ],
  stream: true
});
Enter fullscreen mode Exit fullscreen mode

以下是代码src/app/api/chat/route.ts

import { NextRequest, NextResponse } from 'next/server'
import OpenAI from 'openai'
import { transformStream } from '@crayonai/stream'
import { DBMessage, getMessageStore } from './messageStore'
import {
  createOrder,
  getOrders,
  getOrderSchema,
  orderSchema,
} from './orderManagement'
import { zodToJsonSchema } from 'zod-to-json-schema'
import { JSONSchema } from 'openai/lib/jsonschema.mjs'
import { inventoryQuerySchema } from './inventory'
import { getInventory } from './inventory'

const SYSTEM_MESSAGE = `
You are a helpful assistant who can help with placing orders and checking inventory.

<ui_rules>
- When showing inventory, use the list component to show the inventory along with its image.
  Always add the imageSrc to the list component.
</ui_rules>
`

export async function POST(req: NextRequest) {
  const { prompt, threadId, responseId } = (await req.json()) as {
    prompt: DBMessage
    threadId: string
    responseId: string
  }
  const client = new OpenAI({
    baseURL: 'https://api.thesys.dev/v1/embed/',
    apiKey: process.env.THESYS_API_KEY,
  })
  const messageStore = getMessageStore(threadId)
  if (messageStore.getOpenAICompatibleMessageList().length === 0) {
    messageStore.addMessage({
      role: 'system',
      content: SYSTEM_MESSAGE,
    })
  }

  messageStore.addMessage(prompt)

  const llmStream = client.chat.completions.runTools({ // OpenAI SDK v5+
    model: 'c1-nightly',
    messages: messageStore.getOpenAICompatibleMessageList(),
    stream: true,
    tools: [
      {
        type: 'function',
        function: {
          name: 'createOrder',
          description: 'Create an order',
          parameters: zodToJsonSchema(orderSchema) as JSONSchema,
          function: createOrder,
          parse: JSON.parse,
        },
      },
      {
        type: 'function',
        function: {
          name: 'getOrders',
          description: 'Get all orders',
          parameters: zodToJsonSchema(getOrderSchema) as JSONSchema,
          function: getOrders,
          parse: JSON.parse,
        },
      },
      {
        type: 'function',
        function: {
          name: 'getInventory',
          description: 'Get the current inventory',
          parameters: zodToJsonSchema(inventoryQuerySchema) as JSONSchema,
          function: getInventory,
          parse: JSON.parse,
        },
      },
    ],
  })

  const responseStream = transformStream(
    llmStream,
    (chunk) => {
      return chunk.choices[0].delta.content
    },
    {
      onEnd: ({ accumulated }) => {
        const message = accumulated.filter((message) => message).join('')
        messageStore.addMessage({
          role: 'assistant',
          content: message,
          id: responseId,
        })
      },
    }
  ) as ReadableStream<string>

  return new NextResponse(responseStream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache, no-transform',
      Connection: 'keep-alive',
    },
  })
}
Enter fullscreen mode Exit fullscreen mode

以上代码的作用如下:

  • 处理传入的 POST 请求,/api/chat包含用户提示、线程 ID 和响应 ID。

  • 初始化系统提示,告诉助手如何运行以及如何渲染用户界面(imageSrc显示库存时始终包含)。

  • 使用内存中的聊天记录getMessageStore()来保持消息与主题相关且与 OpenAI 兼容。

  • 使用以下方法调用 Thesys 的聊天完成 API ( /v1/embed/) runTools()

    • 实时传输LLM响应。
    • 支持多种自定义工具:
      • createOrder:用于下单
      • getOrders:用于列出之前的订单
      • getInventory:用于查询股票信息
  • zod每个工具都通过转换为 JSON Schema 的模式进行注册,zodToJsonSchema()以便 LLM 了解输入/输出结构。

  • 使用以下方式将助手的输出流式传输回客户端transformStream()

    • 处理数据流以提取LLM内容。
    • 汇总最终回复并将其存储在消息历史记录中(responseId用于标记)。

最后返回一个text/event-stream响应,允许前端随着内容的生成逐步渲染内容。

太棒了!🎉 您已成功创建生成式用户界面。

 

步骤 7:输出

要启动本地开发服务器,请npm run dev在终端中运行。

现在前端、后端和所有辅助工具都已连接完毕,让我们来看看最终结果。

以下是使用样式设置的用户界面crayonai/react-ui

用户界面

hi你可以像使用“是”或“否”这样的自然语言与助手互动I want to place an order。LLM 会理解你的提示,并根据你的查询动态渲染相应的用户界面。

每个工具都使用 Zod 进行模式绑定,并实时响应有效的 UI 块。

如您所见,它列出了选项,甚至还提供了按钮:Place New OrderCheck InventoryView Orders

随意聊天

我们来看看库存情况。助手会使用该getInventory()工具获取库存信息,并渲染包含产品信息、价格、配送选项(硬编码值)的卡片。

库存查询

点击“帽子”之类的产品,即可生成完整的订单表格,该表格由 LLM 根据我们提供的模式实时构建。

该助手知道需要哪些输入,输入类型应该是什么,甚至还会添加下拉菜单、日期选择器和单选按钮组等用户体验元素。这些元素随后由 SDK 进行渲染。

帽子作为订单

帽子订单

提交后,表单数据将传递回助手,并由相应的后端函数(createOrder())处理,该函数存储结果并确认操作。

它还会列出其他下单选项和查看现有订单的选项。

订单已确认

你甚至没写一个 HTML 表单,用户就能使用功能齐全的输入框和动态流程。生成式 UI 太酷了,它解锁了许多强大的应用场景。


4. 所有主流的 AI 技术栈都可以与 Thesys 一起使用。

Thesys旨在与现代人工智能技术栈和工作流程无缝集成。例如:

  • LLM→ Anthropic、OpenAI(预览版)、自定义模型(通过结构化 JSON)

  • Client SDKs→ 标准 OpenAI 兼容客户端(Python、JS)

  • Chaining Tools→ LangChain、LlamaIndex 管道 → C1 渲染 UI

  • Agent Framework→ AutoGPT/BabyAGI 代理可以调用​​工具并渲染用户界面

  • Backend→ 任何 REST 框架,例如 Python、Node.js、FastAPI、Django、Next.js

  • Frontend→ 任何 React 框架,例如 Next.js

  • Deployment→ Vercel、Netlify、AWS、GCP、您的基础设施

他们目前为新注册用户提供 10 美元的免费积分,所以现在正是尝试的好时机。

免费积分


生成式用户界面是前端的未来。

借助 Thesys,您的前端可以根据上下文做出响应、适应和自我构建。

希望你从中有所收获。试试看,创造一些有用的东西。

祝你今天过得愉快!下次再见 :)

您可以在anmolbaranwal.com
查看 我的作品 感谢阅读!🥰
叽叽喳喳 GitHub 领英

结尾的GIF动画是挥手告别。

文章来源:https://dev.to/anmolbaranwal/thesys-react-sdk-turn-llm-responses-into-real-time-user-interfaces-30d5