wui
组件

AI 任务清单 AI Todo

展示 Agent 计划、执行进度与任务说明。

基础示例

Loading…
pnpm dlx wui@latest add @wui/ai-todo
components/ui/ai-todo.tsx
"use client"

import * as React from "react"
import { cva } from "class-variance-authority"
import {
  BanIcon,
  CheckIcon,
  CircleIcon,
  LoaderCircleIcon,
  ListTodoIcon,
} from "lucide-react"

import { cn } from "@/lib/utils"

export type AiTodoStatus =
  | "pending"
  | "in-progress"
  | "completed"
  | "cancelled"

const aiTodoIndicatorVariants = cva(
  "flex size-5 shrink-0 items-center justify-center rounded-full border",
  {
    variants: {
      status: {
        pending: "border-border text-muted-foreground",
        "in-progress": "border-info/40 bg-info/10 text-info",
        completed: "border-success bg-success text-success-foreground",
        cancelled: "border-border bg-muted text-muted-foreground",
      },
    },
    defaultVariants: { status: "pending" },
  }
)

const todoIcons = {
  pending: CircleIcon,
  "in-progress": LoaderCircleIcon,
  completed: CheckIcon,
  cancelled: BanIcon,
} as const

function AiTodo({ className, ...props }: React.ComponentProps<"section">) {
  return (
    <section
      data-slot="ai-todo"
      className={cn("overflow-hidden rounded-md border bg-background", className)}
      {...props}
    />
  )
}

function AiTodoHeader({
  className,
  children,
  ...props
}: React.ComponentProps<"header">) {
  return (
    <header
      data-slot="ai-todo-header"
      className={cn(
        "flex items-center gap-2 border-b px-3 py-2.5 text-sm font-medium",
        className
      )}
      {...props}
    >
      <ListTodoIcon className="size-4 text-muted-foreground" />
      {children}
    </header>
  )
}

function AiTodoList({ className, ...props }: React.ComponentProps<"ol">) {
  return (
    <ol
      data-slot="ai-todo-list"
      className={cn("divide-y", className)}
      {...props}
    />
  )
}

export interface AiTodoItemProps
  extends Omit<React.ComponentProps<"li">, "title"> {
  /** Main task label. */
  title: React.ReactNode
  /** Optional supporting detail. */
  description?: React.ReactNode
  /** Task progression state. @default "pending" */
  status?: AiTodoStatus
  /** Called when the status control is pressed. */
  onStatusChange?: (status: AiTodoStatus) => void
}

/** A readable task row with optional status interaction. */
function AiTodoItem({
  className,
  title,
  description,
  status = "pending",
  onStatusChange,
  ...props
}: AiTodoItemProps) {
  const Icon = todoIcons[status]
  const nextStatus = status === "completed" ? "pending" : "completed"

  return (
    <li
      data-slot="ai-todo-item"
      data-status={status}
      className={cn("flex items-start gap-3 px-3 py-3", className)}
      {...props}
    >
      <button
        type="button"
        data-slot="ai-todo-indicator"
        aria-label={status === "completed" ? "标记为未完成" : "标记为已完成"}
        disabled={!onStatusChange}
        className={cn(
          aiTodoIndicatorVariants({ status }),
          onStatusChange
            ? "cursor-pointer outline-none focus-visible:ring-[3px] focus-visible:ring-ring/35"
            : "cursor-default"
        )}
        onClick={() => onStatusChange?.(nextStatus)}
      >
        <Icon
          className={cn(
            "size-3",
            status === "in-progress" && "motion-safe:animate-spin"
          )}
        />
      </button>
      <div className="min-w-0 flex-1">
        <div
          data-slot="ai-todo-title"
          className={cn(
            "text-sm font-medium leading-5",
            (status === "completed" || status === "cancelled") &&
              "text-muted-foreground line-through"
          )}
        >
          {title}
        </div>
        {description ? (
          <div
            data-slot="ai-todo-description"
            className="mt-0.5 text-xs leading-5 text-muted-foreground"
          >
            {description}
          </div>
        ) : null}
      </div>
    </li>
  )
}

export {
  AiTodo,
  AiTodoHeader,
  AiTodoItem,
  AiTodoList,
  aiTodoIndicatorVariants,
}

组件作用

AiTodo 用于展示 Agent 已形成的计划和实时执行进度,让用户知道当前完成了什么、正在处理什么、还剩什么。它适合短到中等长度的工作流,不负责排序、持久化或任务依赖。

需要拖拽流转的团队任务应使用 Kanban,需要按时间说明事件则使用 Timeline

组件属性

No documented props for "ai-todo".

事件

AiTodoItem 传入 onStatusChange 后,状态图标会成为可交互按钮,并在点击时在 completedpending 之间切换建议状态。实际状态仍由调用方控制,便于接入消息流或后端任务数据。

扩展使用

只读执行计划

省略 onStatusChange 即得到只读任务行,适合展示模型生成的计划。in-progress 用于唯一的当前任务;同时出现多个执行中状态时,应确认 Agent 是否真的并行工作。

进度汇总

AiTodoHeader 右侧放入“已完成 / 总数”,即可提供稳定的总体进度。取消任务使用 cancelled,不要把取消和失败混成同一个状态。