组件
AI 快捷提问建议 AI Prompt Suggestions
用于新会话空状态页面、输入框上方或追问引导的预设提问卡片与标签组。
基础用法
在新会话初始页面呈现精选推荐提问网格(Grid),帮助用户快速开启对话并直观了解 AI Agent 的能力边界:
Loading…
安装与引入
通过 CLI 自动添加组件,或手动复制源码至项目中:
pnpm dlx @wui-design/cli@latest add @wui/ai-prompt-suggestions安装基础依赖与图标库
pnpm add lucide-react class-variance-authority clsx tailwind-merge复制组件源码到
components/ui/ai-prompt-suggestions.tsx"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { ArrowUpRightIcon } from "lucide-react"
import { cn } from "@/lib/utils"
const aiPromptSuggestionsVariants = cva("w-full", {
variants: {
layout: {
grid: "grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3",
scroll:
"flex snap-x snap-mandatory items-stretch gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden [&>*]:shrink-0 [&>*]:snap-start",
chips: "flex flex-wrap items-center gap-1.5",
},
},
defaultVariants: {
layout: "grid",
},
})
export interface AiPromptSuggestionsProps
extends React.ComponentProps<"div">,
VariantProps<typeof aiPromptSuggestionsVariants> {
/** 快捷提示词组的排版布局方式(网格、横向滚动或胶囊标签)。 @default "grid" */
layout?: "grid" | "scroll" | "chips"
}
/** 用于引导用户开启对话的预设提问卡片与标签组。子项挂载时依次错峰进入。 */
function AiPromptSuggestions({
className,
layout = "grid",
children,
...props
}: AiPromptSuggestionsProps) {
let order = 0
return (
<div
data-slot="ai-prompt-suggestions"
data-layout={layout}
className={cn(aiPromptSuggestionsVariants({ layout }), className)}
{...props}
>
{React.Children.map(children, (child) => {
if (!React.isValidElement<{ style?: React.CSSProperties }>(child)) {
return child
}
const delay = `${Math.min(order++, 10) * 40}ms`
return React.cloneElement(child, {
style: {
"--tw-animation-delay": delay,
...child.props.style,
} as React.CSSProperties,
})
})}
</div>
)
}
const aiPromptSuggestionItemVariants = cva(
"group relative flex cursor-pointer select-none items-start text-left outline-none transition-[background-color,border-color,color,translate] duration-200 ease-out focus-visible:ring-[3px] focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-50 fill-mode-both animation-duration-300 motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-bottom-1",
{
variants: {
variant: {
card: "flex-col gap-1 rounded-lg border bg-background p-3 hover:border-foreground/20 hover:bg-muted/40 motion-safe:hover:-translate-y-px motion-safe:active:translate-y-0",
chip: "inline-flex items-center gap-1.5 rounded-full border bg-background px-3 py-1.5 text-xs text-muted-foreground hover:border-foreground/20 hover:bg-muted/60 hover:text-foreground",
ghost: "flex-col gap-1 rounded-md p-2.5 hover:bg-muted/60",
},
},
defaultVariants: {
variant: "card",
},
}
)
export interface AiPromptSuggestionItemProps
extends Omit<React.ComponentProps<"button">, "title">,
VariantProps<typeof aiPromptSuggestionItemVariants> {
/** Main prompt headline or question. */
title: React.ReactNode
/** Optional supporting description or detail. */
description?: React.ReactNode
/** Optional leading icon. */
icon?: React.ReactNode
/** Optional tag/badge indicator (e.g. "Popular", "Code"). */
badge?: React.ReactNode
/** Full prompt text to send when clicked. */
promptText?: string
/** Callback fired with the prompt text upon selection. */
onSelectPrompt?: (prompt: string) => void
}
function AiPromptSuggestionItem({
className,
variant = "card",
title,
description,
icon,
badge,
promptText,
onSelectPrompt,
onClick,
...props
}: AiPromptSuggestionItemProps) {
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event)
const text = promptText ?? (typeof title === "string" ? title : "")
if (text) {
onSelectPrompt?.(text)
}
}
if (variant === "chip") {
return (
<button
type="button"
data-slot="ai-prompt-suggestion-item"
data-variant={variant}
className={cn(aiPromptSuggestionItemVariants({ variant }), className)}
onClick={handleClick}
{...props}
>
{icon ? (
<span className="flex size-3.5 shrink-0 items-center justify-center text-muted-foreground transition-colors group-hover:text-foreground [&_svg]:size-3.5">
{icon}
</span>
) : null}
<span className="truncate font-medium">{title}</span>
{badge}
</button>
)
}
return (
<button
type="button"
data-slot="ai-prompt-suggestion-item"
data-variant={variant}
className={cn(aiPromptSuggestionItemVariants({ variant }), className)}
onClick={handleClick}
{...props}
>
<div className="flex w-full items-center justify-between gap-2">
<div className="flex min-w-0 items-center gap-2">
{icon && (
<span className="flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground transition-colors group-hover:text-foreground [&_svg]:size-3.5">
{icon}
</span>
)}
<span className="truncate text-sm font-medium text-foreground">
{title}
</span>
</div>
<div className="flex shrink-0 items-center gap-1">
{badge}
<ArrowUpRightIcon className="size-3.5 -translate-x-0.5 translate-y-0.5 text-muted-foreground opacity-0 transition-[opacity,translate] duration-200 ease-out group-hover:translate-x-0 group-hover:translate-y-0 group-hover:opacity-100 group-focus-visible:opacity-100" />
</div>
</div>
{description && (
<p className="line-clamp-2 text-xs leading-5 text-muted-foreground">
{description}
</p>
)}
</button>
)
}
export {
AiPromptSuggestionItem,
AiPromptSuggestions,
aiPromptSuggestionItemVariants,
aiPromptSuggestionsVariants,
}
属性 Props
AiPromptSuggestions Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| layout | "grid" | "scroll" | "chips" | "grid" | 快捷建议组的整体排版模式。grid 为多列响应式网格,scroll 为横向滚动条,chips 为紧凑胶囊标签流。 |
| className | string | — | 应用于外层容器的额外 CSS 类名。 |
AiPromptSuggestionItem Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | React.ReactNode | — | 建议项的主要标题或问题文案。 |
| description | React.ReactNode | — | 卡片模式下的辅助描述或示例细节说明。 |
| variant | "card" | "chip" | "ghost" | "card" | 单个条目的视觉外观变体。card 为独立卡片,chip 为紧凑圆角药丸,ghost 为扁平无边框风格。 |
| icon | React.ReactNode | — | 左侧展示的主题图标元素。 |
| badge | React.ReactNode | — | 右上角或末尾的标签徽章,如「热门」、「代码」等 Badge 元素。 |
| promptText | string | — | 点击后实际发送或填充的完整提示词字符串;若缺省则默认使用 title 文本。 |
| onSelectPrompt | (prompt: string) => void | — | 用户点击该建议项时的回调函数,参数为解析后的完整提示词。 |
事件 Events
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| onSelectPrompt | (prompt: string) => void | — | 用户点击建议卡片或按键触发时调用,返回 promptText 或 title 文本,可直接注入输入框或触发发送。 |
| onClick | (event: React.MouseEvent<HTMLButtonElement>) => void | — | 原生按钮点击事件透传。 |
| onKeyDown | (event: React.KeyboardEvent<HTMLButtonElement>) => void | — | 原生键盘按键事件透传。 |
使用场景与设计规范
AiPromptSuggestions 用于冷启动引导与上下文追问:
- 会话初始空状态 (Empty State):当用户首次打开聊天页面或新建对话时,空白界面容易造成“不知道该问什么”的困惑。提供 4~6 个代表性强、覆盖主要技能的 Prompt 卡片(
layout="grid"),可以大幅提升新用户交互转化率。 - 对话中途的上下文追问 (Follow-up Chips):在 AI 助手回答完毕后,可在输入框上方推荐 2~4 个相关的快捷追问标签(
layout="chips"),引导用户继续深入探索。 - 文案简明有力:卡片标题建议控制在 10 字以内,描述控制在 2 行以内,使用动宾结构(例如“生成代码架构”、“优化 SQL 查询”)。
场景示例
输入框上方的快捷追问胶囊
在输入框上方以 layout="chips" 展示紧凑药丸标签,点击后自动填入输入框:
Loading…
横向滑动的场景推荐列表
在空间受限的侧边栏或移动端界面中,使用 layout="scroll" 实现横向平滑滚动的提示词流:
Loading…
无障碍与交互 Accessibility
- 原生按钮语义:每个
AiPromptSuggestionItem均为原生<button type="button">,天然具备可点击与可聚焦语义。 - 键盘导航支持:
- Tab:在建议项之间顺次切换焦点。
- Enter / Space:选中并触发提示词填充。
- 屏幕阅读器:内置图标采用装饰性隐藏或通过 title 提供完整语义表达。