任务
一个可折叠的任务列表组件,用于显示 AI 工作流进度,带有状态指示器和可选描述。
Task 组件提供了一种结构化的方式来显示任务列表或工作流进度,具有可折叠的详细信息、状态指示器和进度跟踪。它由一个主 Task 容器组成,带有用于可点击标题的 TaskTrigger 和用于可折叠内容区域的 TaskContent。
使用 CLI 安装
AI Elements Vue
shadcn-vue CLI
npx ai-elements-vue@latest add task
npx shadcn-vue@latest add https://registry.ai-elements-vue.com/task.json
手动安装
将以下代码复制并粘贴到同一文件夹中。
Task.vue
TaskTrigger.vue
TaskContent.vue
TaskItem.vue
TaskItemFile.vue
index.ts
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import {
Collapsible,
} from '@repo/shadcn-vue/components/ui/collapsible'
import { cn } from '@repo/shadcn-vue/lib/utils'
import { provide, ref } from 'vue'
interface TaskProps {
defaultOpen?: boolean
class?: HTMLAttributes['class']
}
const props = withDefaults(defineProps<TaskProps>(), {
defaultOpen: true,
})
const isOpen = ref(props.defaultOpen)
function toggleOpen() {
isOpen.value = !isOpen.value
}
provide('isOpen', isOpen)
provide('toggle', toggleOpen)
</script>
<template>
<Collapsible :default-open="isOpen" :class="cn(props.class)" as-child v-bind="$attrs">
<slot :is-open="isOpen" :toggle="toggleOpen" />
</collapsible>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import {
CollapsibleTrigger,
} from '@repo/shadcn-vue/components/ui/collapsible'
import { cn } from '@repo/shadcn-vue/lib/utils'
import { ChevronDown, Search } from 'lucide-vue-next'
interface TaskTriggerProps {
title: string
class?: HTMLAttributes['class']
}
const props = defineProps<TaskTriggerProps>()
</script>
<template>
<CollapsibleTrigger as-child :class="cn('group', props.class)">
<slot>
<div
class="flex w-full cursor-pointer items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground"
>
<Search class="size-4" />
<p class="text-sm">
{{ props.title }}
</p>
<ChevronDown
class="size-4 transition-transform group-data-[state=open]:rotate-180"
/>
</div>
</slot>
</CollapsibleTrigger>
</template>
<script setup lang='ts'>
import type { HTMLAttributes } from 'vue'
import {
CollapsibleContent,
} from '@repo/shadcn-vue/components/ui/collapsible'
import { cn } from '@repo/shadcn-vue/lib/utils'
interface TaskContentProps {
class?: HTMLAttributes['class']
}
const props = defineProps<TaskContentProps>()
</script>
<template>
<CollapsibleContent
:class="cn(
'data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in',
props.class,
)"
v-bind="$attrs"
>
<div class="mt-4 space-y-2 border-l-2 border-muted pl-4">
<slot />
</div>
</CollapsibleContent>
</template>
<script setup lang='ts'>
import type { HTMLAttributes } from 'vue'
import { cn } from '@repo/shadcn-vue/lib/utils'
interface TaskItemProps {
class?: HTMLAttributes['class']
}
const props = defineProps<TaskItemProps>()
</script>
<template>
<div :class="cn('text-sm text-muted-foreground', props.class)">
<slot />
</div>
</template>
<script setup lang='ts'>
import type { HTMLAttributes } from 'vue'
import { cn } from '@repo/shadcn-vue/lib/utils'
interface TaskItemFileProps {
class?: HTMLAttributes['class']
}
const props = defineProps<TaskItemFileProps>()
</script>
<template>
<div
:class="cn('inline-flex items-center gap-1 rounded-md border bg-secondary px-1.5 py-0.5 text-foreground text-xs', props.class)"
>
<slot />
</div>
</template>
export { default as Task } from './Task.vue'
export { default as TaskContent } from './TaskContent.vue'
export { default as TaskItem } from './TaskItem.vue'
export { default as TaskItemFile } from './TaskItemFile.vue'
export { default as TaskTrigger } from './TaskTrigger.vue'
与 AI SDK 一起使用
使用 experimental_generateObject 构建模拟异步编程代理。
将以下组件添加到你的前端:
pages/index.vue
<script setup lang="ts">
import { experimental_useObject as useObject } from '@ai-sdk/vue'
import {
SiCss,
SiHtml5,
SiJavascript,
SiJson,
SiMarkdown,
SiTypescript,
SiVue,
} from '@icons-pack/vue-simple-icons'
import {
Task,
TaskContent,
TaskItem,
TaskItemFile,
TaskTrigger,
} from '@/components/ai-elements/task'
import { Button } from '@/components/ui/button'
import { tasksSchema } from '@/server/api/agent'
const iconMap = {
vue: { component: SiVue, color: '#149ECA' },
typescript: { component: SiTypescript, color: '#3178C6' },
javascript: { component: SiJavascript, color: '#F7DF1E' },
css: { component: SiCss, color: '#1572B6' },
html: { component: SiHtml5, color: '#E34F26' },
json: { component: SiJson, color: '#000000' },
markdown: { component: SiMarkdown, color: '#000000' },
}
type IconKey = keyof typeof iconMap
const { object, submit, isLoading } = useObject({
api: '/api/agent',
schema: tasksSchema,
})
function handleSubmit(taskType: string) {
submit({ prompt: taskType })
}
</script>
<template>
<div class="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
<div class="flex flex-col h-full">
<div class="flex gap-2 mb-6 flex-wrap">
<Button
:disabled="isLoading"
variant="outline"
@click="handleSubmit('Vue component development')"
>
Vue Development
</Button>
</div>
<div class="flex-1 overflow-auto space-y-4">
<div v-if="isLoading && !object" class="text-muted-foreground">
Generating tasks...
</div>
<Task
v-for="(task, taskIndex) in object?.tasks"
:key="taskIndex"
:default-open="taskIndex === 0"
>
<TaskTrigger :title="task.title || 'Loading...'" />
<TaskContent>
<TaskItem v-for="(item, itemIndex) in task.items" :key="itemIndex">
<template v-if="item?.type === 'file' && item.file && iconMap[item.file.icon as IconKey]">
<span class="inline-flex items-center gap-1">
{{ item.text }}
<TaskItemFile>
<component
:is="iconMap[item.file.icon as IconKey].component"
:color="item.file.color || iconMap[item.file.icon as IconKey].color"
class="size-4"
/>
<span>{{ item.file.name }}</span>
</TaskItemFile>
</span>
</template>
<template v-else>
{{ item?.text || '' }}
</template>
</TaskItem>
</TaskContent>
</Task>
</div>
</div>
</div>
</template>
将以下路由添加到你的后端:
server/api/agent.ts
import { streamObject } from 'ai'
import { z } from 'zod'
const taskItemSchema = z.object({
type: z.enum(['text', 'file']),
text: z.string(),
file: z
.object({
name: z.string(),
icon: z.string(),
color: z.string().optional(),
})
.optional(),
})
const taskSchema = z.object({
title: z.string(),
items: z.array(taskItemSchema),
status: z.enum(['pending', 'in_progress', 'completed']),
})
const tasksSchema = z.object({
tasks: z.array(taskSchema),
})
export const maxDuration = 30
export default defineEventHandler(async (event) => {
const body = await readBody<{ prompt: string }>(event)
const result = streamObject({
model: 'openai/gpt-4o',
schema: tasksSchema,
prompt: `You are an AI assistant that generates realistic development task workflows. Generate a set of tasks that would occur during ${body.prompt}.
Each task should have:
- A descriptive title
- Multiple task items showing the progression
- Some items should be plain text, others should reference files
- Use realistic file names and appropriate file types
- Status should progress from pending to in_progress to completed
For file items, use these icon types: 'vue', 'typescript', 'javascript', 'css', 'html', 'json', 'markdown'
Generate 3-4 tasks total, with 4-6 items each.`,
})
return result.toTextStreamResponse()
})
特性
- 待处理、进行中、已完成和错误状态的可视化图标
- 可展开的内容,用于任务描述和附加信息
- 内置进度计数器,显示已完成与总任务数
- 可选的渐进式任务显示,具有可自定义的时序
- 支持任务项内的自定义内容
- 具有适当 TypeScript 定义的完整类型安全
- 键盘导航和屏幕阅读器支持
Props
<Task/>
defaultOpenboolean
trueclassstring
''<TaskTrigger/>
titlestring
''classstring
''<TaskContent/>
classstring
''<TaskItem/>
classstring
''<TaskItemFile/>
classstring
''