CustomElement 类允许您渲染自定义 .jsx 代码片段。.jsx 文件应放置在 public/elements/ELEMENT_NAME.jsx 中。

属性

name
str

自定义元素的名称。应与您的 JSX 文件名匹配(不包含 .jsx 扩展名)。

props
Dict

要传递给 JSX 的属性(props)。

display
ElementDisplay
默认值:"inline"

确定文本元素在 UI 中的显示方式。选项包括“侧边”、“内联”或“页面”。

如何编写 JSX 文件

如果您不熟悉 UI 开发,可以将这些说明传递给 LLM,让它为您生成 .jsx 文件!

要为您的 Chainlit 自定义元素实现 jsx 文件,请按照以下说明进行。

组件定义

只写 JSX 代码,不写 TSX。每个 .jsx 文件应默认导出一个组件,例如

export default function MyComponent() {
    return <div>Hello World</div>
}

组件的 props 是全局注入的(而不是作为函数参数)。切勿 将它们作为函数参数传递。

使用 Tailwind 进行样式设置

在底层,代码将在 shadcn + tailwind 环境中渲染。主题依赖于 CSS 变量。

这是一个渲染具有主色背景和圆角边框的 div 的示例

export default function TailwindExample() {
    return <div className="bg-primary rounded-md h-4 w-full" />
}

仅使用允许的导入

仅使用可用的包进行导入。以下是完整列表

  • react
  • sonner
  • zod
  • recoil
  • react-hook-form
  • lucide-react
  • @/components/ui/accordion
  • @/components/ui/aspect-ratio
  • @/components/ui/avatar
  • @/components/ui/badge
  • @/components/ui/button
  • @/components/ui/card
  • @/components/ui/carousel
  • @/components/ui/checkbox
  • @/components/ui/command
  • @/components/ui/dialog
  • @/components/ui/dropdown-menu
  • @/components/ui/form
  • @/components/ui/hover-card
  • @/components/ui/input
  • @/components/ui/label
  • @/components/ui/pagination
  • @/components/ui/popover
  • @/components/ui/progress
  • @/components/ui/scroll-area
  • @/components/ui/separator
  • @/components/ui/select
  • @/components/ui/sheet
  • @/components/ui/skeleton
  • @/components/ui/switch
  • @/components/ui/table
  • @/components/ui/textarea
  • @/components/ui/tooltip
@/components/ui 的导入来自 Shadcn。

可用 API

Chainlit 全局公开以下 API,以使自定义元素具有交互性。

interface APIs {
    // Update the element props. This will re-render the element.
    updateElement: (nextProps: Record<string, any>) => Promise<{success: boolean}>;
    // Delete the element entirely.
    deleteElement: () => Promise<{success: boolean}>;
    // Call an action defined in the Chainlit app
    callAction: (action: {name: string, payload: Record<string, unknown>}) =>Promise<{success: boolean}>;
    // Send a user message
    sendUserMessage: (message: string) => void;
}

计数器元素示例

import { Button } from "@/components/ui/button"
import { X, Plus } from 'lucide-react';

export default function Counter() {
    return (
        <div id="custom-counter" className="mt-4 flex flex-col gap-2">
                <div>Count: {props.count}</div>
                <Button id="increment" onClick={() => updateElement(Object.assign(props, {count: props.count + 1}))}><Plus /> Increment</Button>
                <Button id="remove" onClick={deleteElement}><X /> Remove</Button>
        </div>
    );
}

完整示例

让我们构建一个自定义元素来渲染 Linear 票据的状态。

首先,我们编写一个小型 Chainlit 应用程序,模拟从 linear 获取数据

app.py
import chainlit as cl

async def get_ticket():
    """Pretending to fetch data from linear"""
    return {
        "title": "Fix Authentication Bug",
        "status": "in-progress",
        "assignee": "Sarah Chen",
        "deadline": "2025-01-15",
        "tags": ["security", "high-priority", "backend"]
    }

@cl.on_message
async def on_message(msg: cl.Message):
    # Let's pretend the user is asking about a linear ticket.
    # Usually an LLM with tool calling would be used to decide to render the component or not.
    
    props = await get_ticket()
    
    ticket_element = cl.CustomElement(name="LinearTicket", props=props)
    # Store the element if we want to update it server side at a later stage.
    cl.user_session.set("ticket_el", ticket_element)
    
    await cl.Message(content="Here is the ticket information!", elements=[ticket_element]).send()

其次,我们实现 Python 代码中引用的自定义元素

public/elements/LinearTicket.jsx
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Clock, User, Tag } from "lucide-react"

export default function TicketStatusCard() {
  const getProgressValue = (status) => {
    const progress = {
      'open': 25,
      'in-progress': 50,
      'resolved': 75,
      'closed': 100
    }
    return progress[status] || 0
  }

  return (
    <Card className="w-full max-w-md">
      <CardHeader className="pb-2">
        <div className="flex justify-between items-center">
          <CardTitle className="text-lg font-medium">
            {props.title || 'Untitled Ticket'}
          </CardTitle>
          <Badge 
            variant="outline" 
          >
            {props.status || 'Unknown'}
          </Badge>
        </div>
      </CardHeader>
      <CardContent>
        <div className="space-y-4">
          <Progress value={getProgressValue(props.status)} className="h-2" />
          
          <div className="grid grid-cols-2 gap-4 text-sm">
            <div className="flex items-center gap-2">
              <User className="h-4 w-4 opacity-70" />
              <span>{props.assignee || 'Unassigned'}</span>
            </div>
            <div className="flex items-center gap-2">
              <Clock className="h-4 w-4 opacity-70" />
              <span>{props.deadline || 'No deadline'}</span>
            </div>
            <div className="flex items-center gap-2 col-span-2">
              <Tag className="h-4 w-4 opacity-70" />
              <span>{props.tags?.join(', ') || 'No tags'}</span>
            </div>
          </div>
        </div>
      </CardContent>
    </Card>
  )
}

最后,我们使用 chainlit run app.py 启动应用程序并在 UI 中发送第一条消息。

已渲染 LinearTicket 自定义元素。

高级

从 Python 更新 Props

要从 Python 代码更新自定义元素的属性(props),您可以将元素实例存储在用户会话中并调用其上的 .update() 方法。

import chainlit as cl

@cl.on_chat_start
async def start():
    element = cl.CustomElement(name="Foo", props={"foo": "bar"})
    cl.user_session.set("element", element)

@cl.on_message
async def on_message():
    element = cl.user_session.get("element")
    element.props["foo"] = "baz"
    await element.update()

从 Python 调用函数

如果您需要直接从 Python 代码调用函数,可以使用 cl.CopilotFunction

call_func.py
import chainlit as cl

@cl.on_chat_start
async def start():
    element = cl.CustomElement(name="CallFn")
    await cl.Message(content="Hello", elements=[element]).send()
    
@cl.on_message
async def on_msg(msg: cl.Message):
    fn = cl.CopilotFunction(name="test", args={"content": msg.content})
    res = await fn.acall()
CallFn.jsx
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { callFnState } from '@chainlit/react-client';

export default function CallFnExample() {
    const callFn = useRecoilValue(callFnState);

    useEffect(() => {
        if (callFn?.name === "test") {
          // Replace the console log with your actual function
          console.log("Function called with", callFn.args.content)
          callFn.callback()
        }
      }, [callFn]);

      return null
}