提示输入

允许用户向大型语言模型发送带有文件附件的消息。它包括一个文本区域、文件上传功能、提交按钮和用于选择模型的下拉菜单。

PromptInput 组件允许用户向大型语言模型发送带有文件附件的消息。它包括一个文本区域、文件上传功能、提交按钮和用于选择模型的下拉菜单。

使用 CLI 安装

AI Elements Vue
shadcn-vue CLI
npx ai-elements-vue@latest add prompt-input

手动安装

将以下代码复制并粘贴到同一文件夹中。

PromptInputProvider.vue
PromptInput.vue
PromptInputTextarea.vue
PromptInputTools.vue
PromptInputButton.vue
PromptInputSubmit.vue
PromptInputBody.vue
PromptInputAttachments.vue
PromptInputAttachment.vue
PromptInputHeader.vue
PromptInputFooter.vue
PromptInputActionMenu.vue
PromptInputActionMenuTrigger.vue
PromptInputActionMenuContent.vue
PromptInputActionMenuItem.vue
PromptInputActionAddAttachments.vue
PromptInputSpeechButton.vue
PromptInputSelect.vue
PromptInputSelectTrigger.vue
PromptInputSelectContent.vue
PromptInputSelectItem.vue
PromptInputSelectValue.vue
PromptInputHoverCard.vue
PromptInputHoverCardTrigger.vue
PromptInputHoverCardContent.vue
PromptInputTabsList.vue
PromptInputTab.vue
PromptInputTabLabel.vue
PromptInputTabBody.vue
PromptInputTabItem.vue
PromptInputCommand.vue
PromptInputCommandInput.vue
PromptInputCommandList.vue
PromptInputCommandEmpty.vue
PromptInputCommandGroup.vue
PromptInputCommandItem.vue
PromptInputCommandSeparator.vue
context.ts
types.ts
index.ts
<script setup lang="ts">
import type { PromptInputMessage } from './types'
import { usePromptInputProvider } from './context'

const props = defineProps<{
  initialInput?: string
  maxFiles?: number
  maxFileSize?: number
  accept?: string
}>()

const emit = defineEmits<{
  (e: 'submit', payload: PromptInputMessage): void
  (e: 'error', payload: { code: string, message: string }): void
}>()

usePromptInputProvider({
  initialInput: props.initialInput,
  maxFiles: props.maxFiles,
  maxFileSize: props.maxFileSize,
  accept: props.accept,
  onSubmit: msg => emit('submit', msg),
  onError: err => emit('error', err),
})
</script>

<template>
  <slot />
</template>

与 AI SDK 一起使用

使用 PromptInputConversation 和模型选择器构建一个功能完整的聊天应用:

将以下组件添加到你的前端:

pages/index.vue
<script setup lang="ts">
import type { PromptInputMessage } from '@/components/ai-elements/prompt-input'
import { useChat } from '@ai-sdk/vue'
import { GlobeIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import {
  Conversation,
  ConversationContent,
  ConversationScrollButton,
} from '@/components/ai-elements/conversation'
import { Message, MessageContent, MessageResponse } from '@/components/ai-elements/message'
import {
  PromptInput,
  PromptInputActionAddAttachments,
  PromptInputActionMenu,
  PromptInputActionMenuContent,
  PromptInputActionMenuTrigger,
  PromptInputAttachment,
  PromptInputAttachments,
  PromptInputBody,
  PromptInputButton,
  PromptInputFooter,
  PromptInputHeader,
  PromptInputSelect,
  PromptInputSelectContent,
  PromptInputSelectItem,
  PromptInputSelectTrigger,
  PromptInputSelectValue,
  PromptInputSpeechButton,
  PromptInputSubmit,
  PromptInputTextarea,
  PromptInputTools
} from '@/components/ai-elements/prompt-input'

const models = [
  { id: 'gpt-4o', name: 'GPT-4o' },
  { id: 'claude-opus-4-20250514', name: 'Claude 4 Opus' },
]

const text = ref<string>('')
const model = ref<string>(models[0].id)
const useWebSearch = ref<boolean>(false)
const textareaRef = ref<HTMLTextAreaElement | null>(null)

const { messages, status, sendMessage } = useChat()

function handleSubmit(message: PromptInputMessage) {
  const hasText = Boolean(message.text)
  const hasAttachments = Boolean(message.files?.length)

  if (!(hasText || hasAttachments)) {
    return
  }

  sendMessage(
    {
      text: message.text || 'Sent with attachments',
      files: message.files
    },
    {
      body: {
        model: model.value,
        webSearch: useWebSearch.value,
      },
    },
  )

  text.value = ''
}
</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">
      <Conversation>
        <ConversationContent>
          <Message
            v-for="message in messages"
            :key="message.id"
            :from="message.role"
          >
            <MessageContent>
              <template v-for="(part, i) in message.parts" :key="`${message.id}-${i}`">
                <MessageResponse v-if="part.type === 'text'">
                  {{ part.text }}
                </MessageResponse>
              </template>
            </MessageContent>
          </Message>
        </ConversationContent>
        <ConversationScrollButton />
      </Conversation>

      <PromptInput
        class="mt-4"
        global-drop
        multiple
        @submit="handleSubmit"
      >
        <PromptInputHeader>
          <PromptInputAttachments>
            <template #default="{ attachment }">
              <PromptInputAttachment :data="attachment" />
            </template>
          </PromptInputAttachments>
        </PromptInputHeader>

        <PromptInputBody>
          <PromptInputTextarea
            ref="textareaRef"
            :model-value="text"
            @update:model-value="text = $event"
          />
        </PromptInputBody>

        <PromptInputFooter>
          <PromptInputTools>
            <PromptInputActionMenu>
              <PromptInputActionMenuTrigger />
              <PromptInputActionMenuContent>
                <PromptInputActionAddAttachments />
              </PromptInputActionMenuContent>
            </PromptInputActionMenu>

            <PromptInputSpeechButton
              :textarea-ref="textareaRef?.$el || null"
              @transcription-change="text = $event"
            />

            <PromptInputButton
              :variant="useWebSearch ? 'default' : 'ghost'"
              @click="useWebSearch = !useWebSearch"
            >
              <GlobeIcon :size="16" />
              <span>Search</span>
            </PromptInputButton>

            <PromptInputSelect v-model="model">
              <PromptInputSelectTrigger>
                <PromptInputSelectValue />
              </PromptInputSelectTrigger>
              <PromptInputSelectContent>
                <PromptInputSelectItem
                  v-for="m in models"
                  :key="m.id"
                  :value="m.id"
                >
                  {{ m.name }}
                </PromptInputSelectItem>
              </PromptInputSelectContent>
            </PromptInputSelect>
          </PromptInputTools>

          <PromptInputSubmit
            :disabled="!text && !status"
            :status="status"
          />
        </PromptInputFooter>
      </PromptInput>
    </div>
  </div>
</template>

将以下路由添加到你的后端:

server/api/chat.ts
import { convertToModelMessages, streamText, UIMessage } from 'ai'

// Allow streaming responses up to 30 seconds
export const maxDuration = 30

export default defineEventHandler(async (event) => {
  const {
    model,
    messages,
    webSearch
  }: {
    messages: UIMessage[]
    model: string
    webSearch?: boolean
  } = await readBody(event)

  const result = streamText({
    model: webSearch ? 'perplexity/sonar' : model,
    messages: convertToModelMessages(messages),
  })

  return result.toUIMessageStreamResponse()
})

特性

  • 根据内容自动调整高度的自动调整大小的文本区域
  • 支持拖放的文件附件
  • 图片附件的图片预览
  • 可配置的文件约束(最大文件数、最大大小、接受的类型)
  • 基于状态的自动提交按钮图标
  • 支持键盘快捷键(Enter 提交,Shift+Enter 换行)
  • 可自定义的文本区域最小/最大高度
  • 灵活的工具栏,支持自定义操作和工具
  • 内置模型选择下拉菜单
  • 内置原生语音识别按钮(Web Speech API)
  • 可选的提供者,用于提升状态管理
  • 提交时表单自动重置
  • 响应式设计,具有移动友好的控件
  • 简洁、现代的样式,具有可自定义的主题
  • 基于表单的提交处理
  • 隐藏的文件输入同步,用于原生表单提交
  • 全局文档拖放支持(可选)

示例

光标样式

Props

<PromptInputProvider />

initialInputstring
初始文本输入值。

<PromptInput />

acceptstring
接受的文件类型(例如,"image/*")。留空表示接受任何类型。
multipleboolean
是否允许多文件选择。
globalDropboolean
为 true 时,接受文档上任何位置的文件拖放。
maxFilesnumber
允许的最大文件数。
maxFileSizenumber
文件的最大大小(以字节为单位)。
...propsHTMLAttributes
额外的 props 会传播到根表单元素。

<PromptInputTextarea />

...propsInstanceType<typeof InputGroupTextarea>
任何其他 props 都会传播到底层 Textarea 组件。

<PromptInputTools />

...propsHTMLAttributes
额外的 props 会传播到底层 div。

<PromptInputButton />

variant'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link'
'ghost'
按钮的视觉样式。
size'default' | 'sm' | 'lg' | 'icon'
auto
按钮大小。当提供单个子元素时默认为 'icon',否则为 'default'。
...propsInstanceType<typeof InputGroupButton>
额外的 props 会传播到底层 InputGroupButton 组件。

<PromptInputSubmit />

statusChatStatus
当前聊天状态;控制图标(submitted、streaming、error、ready)。
variant'default' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link'
'default'
提交按钮的视觉样式。
size'default' | 'sm' | 'lg' | 'icon'
'icon'
提交按钮的大小。
...propsInstanceType<typeof InputGroupButton>
额外的 props 会传播到底层 InputGroupButton 组件。

<PromptInputBody />

...propsHTMLAttributes
任何其他 props 都会传播到底层 body div。

<PromptInputAttachments />

...propsHTMLAttributes
任何其他 props 都会传播到底层附件容器。

<PromptInputAttachment />

fileAttachmentFile
要显示的附件数据。
...propsHTMLAttributes
任何其他 props 都会传播到底层附件 div。

<PromptInputHeader />

...props InstanceType<typeof InputGroupAddon>
任何其他 props(除了 align)都会传播到底层 InputGroupAddon 组件。

<PromptInputFooter />

...props InstanceType<typeof InputGroupAddon>
任何其他 props(除了 align)都会传播到底层 InputGroupAddon 组件。

<PromptInputActionMenu />

...props InstanceType<typeof DropdownMenu>
任何其他 props 都会传播到底层 DropdownMenu 组件。

<PromptInputActionMenuTrigger />

...props InstanceType<typeof DropdownMenuTrigger>
任何其他 props 都会传播到底层 DropdownMenuTrigger 组件。

<PromptInputActionMenuContent />

...props InstanceType<typeof DropdownMenuContent>
任何其他 props 都会传播到底层 DropdownMenuContent 组件。

<PromptInputActionMenuItem />

...props InstanceType<typeof DropdownMenuItem>
任何其他 props 都会传播到底层 DropdownMenuItem 组件。

<PromptInputActionAddAttachments />

labelstring
菜单项的标签。
...props InstanceType<typeof DropdownMenuItem>
任何其他 props 都会传播到底层 DropdownMenuItem 组件。

<PromptInputSpeechButton />

...props InstanceType<typeof PromptInputButton >
任何其他 props 都会传播到底层 PromptInputButton 组件。

<PromptInputSelect />

...props InstanceType<typeof Select>
任何其他 props 都会传播到底层 Select 组件。

<PromptInputSelectTrigger />

...propsInstanceType<typeof SelectTrigger>
任何其他 props 都会传播到底层 SelectTrigger 组件。

<PromptInputSelectContent />

...propsInstanceType<typeof SelectContent>
任何其他 props 都会传播到底层 SelectContent 组件。

<PromptInputSelectItem />

...propsInstanceType<typeof SelectItem>
任何其他 props 都会传播到底层 SelectItem 组件。

<PromptInputSelectValue />

...propsInstanceType<typeof SelectValue>
任何其他 props 都会传播到底层 SelectValue 组件。

<PromptInputHoverCard />

openDelaynumber
0
打开前的延迟(毫秒)。
closeDelaynumber
0
关闭前的延迟(毫秒)。
...propsInstanceType<typeof HoverCard>
任何其他 props 都会传播到 HoverCard 组件。

<PromptInputHoverCardTrigger />

...propsInstanceType<typeof HoverCardTrigger>
任何其他 props 都会传播到 HoverCardTrigger 组件。

<PromptInputHoverCardContent />

align'start' | 'center' | 'end'
'start'
悬停卡片内容的对齐方式。
...propsInstanceType<typeof HoverCardContent>
任何其他 props 都会传播到 HoverCardContent 组件。

<PromptInputTabsList />

...propsHTMLAttributes
任何其他 props 都会传播到 div 元素。

<PromptInputTab />

...propsHTMLAttributes
任何其他 props 都会传播到 div 元素。

<PromptInputTabLabel />

...propsHTMLAttributes
任何其他 props 都会传播到 h3 元素。

<PromptInputTabBody />

...propsHTMLAttributes
任何其他 props 都会传播到 div 元素。

<PromptInputTabItem />

...propsHTMLAttributes
任何其他 props 都会传播到 div 元素。

<PromptInputCommand />

...propsInstanceType<typeof Command>
任何其他 props 都会传播到 Command 组件。

<PromptInputCommandInput />

...propsInstanceType<typeof CommandInput>
任何其他 props 都会传播到 CommandInput 组件。

<PromptInputCommandList />

...propsInstanceType<typeof CommandList>
任何其他 props 都会传播到 CommandList 组件。

<PromptInputCommandEmpty />

...propsInstanceType<typeof CommandEmpty>
任何其他 props 都会传播到 CommandEmpty 组件。

<PromptInputCommandGroup />

...propsInstanceType<typeof CommandGroup>
任何其他 props 都会传播到 CommandGroup 组件。

<PromptInputCommandItem />

...propsInstanceType<typeof CommandItem>
任何其他 props 都会传播到 CommandItem 组件。

<PromptInputCommandSeparator />

...propsInstanceType<typeof CommandSeparator>
任何其他 props 都会传播到 CommandSeparator 组件。