【免费下载链接】ZCodeZ.ais coding agent harness. Powerful, intelligent, extensible.项目地址https://gitcode.com/gh_mirrors/zco/ZCode点击查看免费下载PromptInput 是 ai-elementsVercel 出品的 AI 原生组件库ZCode 已将其完整集成到packages/ui中负责用户输入的核心组件它把多行文本域、文件上传、截图、模型选择下拉框、提交按钮与附件预览整合进一个表单让开发者几行代码就能搭建出可发送文本 附件给大模型、并携带模型选择与联网搜索开关的聊天输入栏。读完本文你将掌握 PromptInput 的安装、与 AI SDK 对接的完整前后端示例、全部子组件与 Props 语义、状态提升 Hooks 的用法以及从源码层面理解其快捷键、拖拽上传、截图采集与提交管线blob URL 转 data URL的实现原理。一、组件背景ai-elements 与 PromptInput 在 ZCode 中的定位ai-elements 是一个构建在 shadcn/ui 之上的组件库与自定义 registry用于加速 AI 原生应用的开发conversation、message、tool、prompt input 等组件开箱即用。ZCode 仓库通过.agents/skills/ai-elements/SKILL.md将整套组件与文档以 skill 的形式本地化集成SKILL.md本身说明安装、使用、扩展与排障而references/目录下按组件逐个给出参考文档。PromptInput正是其中负责输入侧的组件其官方定位是Allows a user to send a message with file attachments to a large language model. It includes a textarea, file upload capabilities, a submit button, and a dropdown for selecting the model.即让用户向大模型发送可附带文件的消息包含文本域、文件上传能力、提交按钮与模型选择下拉框。在 ZCode 中该组件的实际实现位于packages/ui/src/components/ai-elements/prompt-input.tsx并在本地集成时被拆分为五个源码文件职责如下文件职责prompt-input.tsx核心PromptInput、PromptInputProvider、上下文与四个 Hooksprompt-input-textarea.tsx自动高度文本域与快捷键、粘贴、IME 处理prompt-input-buttons.tsxPromptInputButton含 tooltip与PromptInputSubmit状态图标prompt-input-actions.tsx附件/截图动作菜单与getDisplayMedia截图实现prompt-input-primitives.tsxSelect / HoverCard / Tabs / Command 等原语封装二、安装与前置条件PromptInput 通过 ai-elements CLI 安装会将组件代码直接落入你的项目目录默认/components/ai-elements/npx ai-elementslatest add prompt-input根据项目packageManager不同也可以使用pnpm dlx ai-elementslatest add prompt-input或bunx --bun ai-elementslatest add prompt-input。安装前请确认环境满足以下条件详见 SKILL.mdNode.js 18一个安装了AI SDK的 Next.js 项目本组件依赖ai-sdk/react的useChat项目已配置shadcn/ui——若未安装运行 CLI 命令时会自动安装tsconfig.json中配置了/*路径别名baseUrl: .与paths: { /*: [./*] }否则组件 import 会报 module not found。安装完成后无需额外配置Tailwind 样式类与脚本已随代码一同落地直接 import 使用即可。三、快速上手与 AI SDK 组合的完整示例以下示例来自参考文档 prompt-input.md 的完整演示用PromptInputConversation 模型选择器构建一个支持附件、截图与联网搜索开关的完整聊天应用。3.1 前端组件app/page.tsxuse client; import { Attachment, AttachmentPreview, AttachmentRemove, Attachments, } from /components/ai-elements/attachments; import { PromptInput, PromptInputActionAddAttachments, PromptInputActionAddScreenshot, PromptInputActionMenu, PromptInputActionMenuContent, PromptInputActionMenuTrigger, PromptInputBody, PromptInputButton, PromptInputHeader, type PromptInputMessage, PromptInputSelect, PromptInputSelectContent, PromptInputSelectItem, PromptInputSelectTrigger, PromptInputSelectValue, PromptInputSubmit, PromptInputTextarea, PromptInputFooter, PromptInputTools, usePromptInputAttachments, } from /components/ai-elements/prompt-input; import { GlobeIcon } from lucide-react; import { useState } from react; import { useChat } from ai-sdk/react; import { Conversation, ConversationContent, ConversationScrollButton, } from /components/ai-elements/conversation; import { Message, MessageContent, MessageResponse } from /components/ai-elements/message; // 附件展示区读取 PromptInput 上下文中的附件列表 const PromptInputAttachmentsDisplay () { const attachments usePromptInputAttachments(); if (attachments.files.length 0) { return null; } return ( Attachments variantinline {attachments.files.map((attachment) ( Attachment data{attachment} key{attachment.id} onRemove{() attachments.remove(attachment.id)} AttachmentPreview / AttachmentRemove / /Attachment ))} /Attachments ); }; const models [ { id: gpt-4o, name: GPT-4o }, { id: claude-opus-4-20250514, name: Claude 4 Opus }, ]; const InputDemo () { const [text, setText] useStatestring(); const [model, setModel] useStatestring(models[0].id); const [useWebSearch, setUseWebSearch] useStateboolean(false); const { messages, status, sendMessage } useChat(); const 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, webSearch: useWebSearch, }, }, ); setText(); }; return ( div classNamemax-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px] div classNameflex flex-col h-full Conversation ConversationContent {messages.map((message) ( Message from{message.role} key{message.id} MessageContent {message.parts.map((part, i) { switch (part.type) { case text: return ( MessageResponse key{${message.id}-${i}}{part.text}/MessageResponse ); default: return null; } })} /MessageContent /Message ))} /ConversationContent ConversationScrollButton / /Conversation PromptInput onSubmit{handleSubmit} classNamemt-4 globalDrop multiple PromptInputHeader PromptInputAttachmentsDisplay / /PromptInputHeader PromptInputBody PromptInputTextarea onChange{(e) setText(e.target.value)} value{text} / /PromptInputBody PromptInputFooter PromptInputTools PromptInputActionMenu PromptInputActionMenuTrigger / PromptInputActionMenuContent PromptInputActionAddAttachments / PromptInputActionAddScreenshot / /PromptInputActionMenuContent /PromptInputActionMenu PromptInputButton onClick{() setUseWebSearch(!useWebSearch)} tooltip{{ content: Search the web, shortcut: ⌘K }} variant{useWebSearch ? default : ghost} GlobeIcon size{16} / spanSearch/span /PromptInputButton PromptInputSelect onValueChange{(value) { setModel(value); }} value{model} PromptInputSelectTrigger PromptInputSelectValue / /PromptInputSelectTrigger PromptInputSelectContent {models.map((model) ( PromptInputSelectItem key{model.id} value{model.id} {model.name} /PromptInputSelectItem ))} /PromptInputSelectContent /PromptInputSelect /PromptInputTools PromptInputSubmit disabled{!text !status} status{status} / /PromptInputFooter /PromptInput /div /div ); }; export default InputDemo;该示例在仓库 ui 包中同样有迹可循packages/ui/src/components/ai-elements/下的prompt-input.tsx、prompt-input-textarea.tsx、prompt-input-buttons.tsx、prompt-input-actions.tsx与prompt-input-primitives.tsx即为本示例使用的全部实现。3.2 后端路由app/api/chat/route.ts后端接收前端透传的model、messages与webSearch字段并据此切换模型联网搜索时使用perplexity/sonarimport { streamText, UIMessage, convertToModelMessages } from ai; // Allow streaming responses up to 30 seconds export const maxDuration 30; export async function POST(req: Request) { const { model, messages, webSearch, }: { messages: UIMessage[]; model: string; webSearch?: boolean; } await req.json(); const result streamText({ model: webSearch ? perplexity/sonar : model, messages: await convertToModelMessages(messages), }); return result.toUIMessageStreamResponse(); }注意convertToModelMessages会把包含FileUIPart的消息转换为模型可消费的多模态格式因此前端通过sendMessage({ text, files })发送的附件会原样进入对话上下文。四、组件体系一个表单、二十余个子组件PromptInput 采用容器 分区 工具的组合式结构一个输入框由以下层次拼装而成PromptInputform ├── PromptInputHeader → 附件预览区InputGroupAddon 变体 ├── PromptInputBody → 主体内含 PromptInputTextarea └── PromptInputFooter → 工具条内含 PromptInputTools ├── PromptInputTools → 左侧工具区 │ ├── PromptInputActionMenu / Trigger / Content / MenuItem │ │ ├── PromptInputActionAddAttachments │ │ └── PromptInputActionAddScreenshot │ ├── PromptInputButtontooltip 支持快捷键提示 │ └── PromptInputSelectSelectTrigger / SelectValue / SelectContent / SelectItem └── PromptInputSubmit → 右侧提交按钮状态驱动图标从 prompt-input.tsx 的渲染逻辑可以看到组件内部始终渲染一个隐藏的input typefile负责原生文件选择以及一个form并用InputGroup包裹所有子组件保证视觉上形成统一的输入组。此外还提供面向高级场景的 Tabs 与 Command 原语PromptInputTabsList、PromptInputTab、PromptInputCommand*等用于构建分页式输入例如文本 / 引用来源两个 Tab或命令面板式输入。4.1 PromptInput 与各子组件 Props 速查PromptInput /容器表单prompt-input.tsxProp类型默认值说明onSubmit(message: PromptInputMessage, event: FormEvent) void必填表单提交回调入参包含消息文本与文件列表acceptstring-允许的文件类型如image/*留空则接受任意文件multipleboolean-是否允许多选文件globalDropbooleanfalse为true时接受在文档任意位置拖放文件syncHiddenInputboolean-渲染指定 name 的隐藏 input 以支持原生表单提交当前源码实现中已标注由于安全原因无法编程赋值此 prop 不再生效见 prompt-input.tsxmaxFilesnumber-允许的最大文件数maxFileSizenumber-最大文件大小字节onError(err: { code: max_files \| max_file_size \| accept; message: string }) void-文件校验失败回调...propsReact.HTMLAttributesHTMLFormElement-其余属性透传到根form布局分区PromptInputHeader、PromptInputBody、PromptInputFooter、PromptInputTools均将...props透传到底层divHeader/Footer 基于InputGroupAddon用于自定义布局类名。PromptInputTextarea /透传React.ComponentPropstypeof Textarea并内置默认 placeholder「What would you like to know?」。PromptInputButton /Prop类型默认值说明tooltipstring \| { content: ReactNode; shortcut?: string; side?: top \| bottom \| ... }-悬停提示可传字符串或带content、shortcut、side的对象...propsReact.ComponentPropstypeof Button-透传到 shadcn/ui ButtonTooltip 三种写法// 简单字符串 tooltip PromptInputButton tooltipSearch the web GlobeIcon size{16} / /PromptInputButton // 带键盘快捷键提示 PromptInputButton tooltip{{ content: Search, shortcut: ⌘K }} GlobeIcon size{16} / /PromptInputButton // 自定义弹出位置 PromptInputButton tooltip{{ content: Search, side: bottom }} GlobeIcon size{16} / /PromptInputButtonPromptInputSubmit /Prop类型默认值说明statusChatStatusready \| submitted \| streaming \| error-决定按钮图标提交中转圈Spinner、流式中显示停止方块、出错显示 XonStop() void-流式/提交状态下点击时触发停止...propsReact.ComponentPropstypeof Button-透传到底层 Button实现细节见 prompt-input-buttons.tsxstatus streaming时按钮类型由submit切换为button并显示停止图标。Select 系列PromptInputSelect/PromptInputSelectTrigger/PromptInputSelectContent/PromptInputSelectItem/PromptInputSelectValue分别透传到 shadcn/ui 的Select系列组件用于模型选择等场景。Trigger 在 prompt-input-primitives.tsx 中被定制为无边框、透明背景的紧凑样式契合输入条内的工具栏观感。动作菜单系列PromptInputActionMenuDropdownMenu、PromptInputActionMenuTriggerButton Plus 图标、PromptInputActionMenuContentDropdownMenuContent、PromptInputActionMenuItemDropdownMenuItem均为透传封装PromptInputActionAddAttachments与PromptInputActionAddScreenshot额外提供label属性默认分别为 Add photos or files 与 Take screenshot点击后调用附件的openFileDialog()或触发屏幕截图。HoverCard 与 Tabs/Command 系列PromptInputHoverCard支持openDelay/closeDelay默认0msPromptInputTabsList、PromptInputTab、PromptInputTabLabel、PromptInputTabBody、PromptInputTabItem与PromptInputCommand*系列均为对应 shadcn/ui 原语的透传封装用于扩展型输入界面。4.2 附件展示组件附件展示已拆分为独立模块详见 attachments.md包括Attachments /容器variant可选grid/inline/list、Attachment /单个附件项接收data与onRemove、AttachmentPreview /媒体预览、AttachmentInfo /文件名与媒体类型、AttachmentRemove /悬停出现的删除按钮等同时提供getMediaCategory、getAttachmentLabel等工具函数。五、状态提升PromptInputProvider 与四个 Hooks默认情况下 PromptInput 完全自管理状态当需要把输入状态提升到组件外部例如在消息区点击回复时预填输入框文本可用PromptInputProvider包裹整个应用。其 Props 为initialInput初始文本与children。源码实现位于 prompt-input.tsx通过两个 ContextPromptInputController与ProviderAttachmentsContext向子树分发 controller。usePromptInputAttachments在 PromptInput 或 Provider 上下文中访问并管理附件const attachments usePromptInputAttachments(); attachments.files; // 当前附件数组 attachments.add(files); // 添加新文件 attachments.remove(id); // 按 ID 移除附件 attachments.clear(); // 清空所有附件 attachments.openFileDialog(); // 打开文件选择对话框usePromptInputController仅在使用 Provider 时可用访问完整 controllerconst controller usePromptInputController(); controller.textInput.value; // 当前文本值 controller.textInput.setInput(value); // 设置文本值 controller.textInput.clear(); // 清空文本 controller.attachments; // 与 usePromptInputAttachments 同接口useProviderAttachments仅在使用 Provider 时可用直接访问 Provider 层的附件上下文接口与usePromptInputAttachments一致。注意源码中usePromptInputAttachments优先取 PromptInput 局部的上下文带有校验逻辑Provider 上下文作为回退见 prompt-input.tsx。usePromptInputReferencedSources访问 PromptInput 局部的引用来源上下文用于展示/管理来自检索或工具调用的引用文档const sources usePromptInputReferencedSources(); sources.sources; // 当前引用来源数组 sources.add(sources); // 添加来源 sources.remove(id); // 按 ID 移除 sources.clear(); // 清空全部六、交互能力源码剖析快捷键、拖拽、粘贴与截图6.1 键盘快捷键与 IME 兼容prompt-input-textarea.tsx 内置了以下键位逻辑Enter 提交、ShiftEnter 换行非中文输入法组合状态isComposing下Enter 触发form.requestSubmit()提交前会检查页面中button[typesubmit]是否 disabled避免空内容误提交Backspace 删除最后一个附件文本为空且存在附件时Backspace 移除最近添加的附件IME 组合输入保护通过onCompositionStart/onCompositionEnd维护isComposing状态避免拼音等输入法选词时误触发提交该细节对中文用户尤其重要。6.2 粘贴即上传文本域监听onPaste当剪贴板数据含kind file的文件时阻止默认粘贴并调用attachments.add(files)实现截图/复制图片直接 CtrlV 上传见 prompt-input-textarea.tsx。6.3 拖拽上传局部与全局prompt-input.tsx 实现了两套拖拽监听默认globalDrop关闭仅监听表单自身的dragover/drop事件globalDrop开启改为在document上监听接受文档任意位置的拖放。两套逻辑都会先检查dataTransfer.types是否包含Files再调用带校验的add()。6.4 内置屏幕截图动作PromptInputActionAddScreenshot走完整个屏幕截图 → 文件链路见 prompt-input-actions.tsx通过navigator.mediaDevices.getDisplayMedia({ audio: false, video: true })请求录屏授权把媒体流挂到隐藏video并等待loadedmetadatavideo.play()后取videoWidth/videoHeight用canvas的drawImage截取当前帧canvas.toBlob(..., image/png)得到 Blob以screenshot-ISO时间戳.png命名生成File调用attachments.add([screenshot])追加为附件finally中停止所有媒体轨道并释放srcObject。用户拒绝授权或中断NotAllowedError/AbortError会被静默忽略不会抛出异常。6.5 自动高度文本域文本域基于InputGroupTextarea并应用field-sizing-content max-h-48 min-h-16样式类实现随内容自动增高、最小 16 / 最大 48约 12rem的效果附件过多时也可在 Header 区以 flex-wrap 自动换行展示PromptInputHeader默认类名含flex-wrap gap-1。七、附件校验与提交管线从 blob URL 到 data URL7.1 三级校验accept / 大小 / 数量PromptInput 在添加文件时执行三段校验源码见 prompt-input.tsx类型校验accept支持image/*这类通配模式pattern.endsWith(/*)时按f.type.startsWith(prefix)匹配也支持精确 MIME 匹配全部被过滤时触发onError({ code: accept, ... })大小校验maxFileSize超出限制时触发onError({ code: max_file_size, ... })数量校验maxFiles按剩余容量截断Math.max(0, maxFiles - prev.length)超出部分触发onError({ code: max_files, ... })。7.2 内存安全Object URL 的生命周期每个附件在加入时通过URL.createObjectURL(file)生成预览 URL在remove/clear/ 组件卸载时调用URL.revokeObjectURL释放Provider 模式在卸载时通过attachmentsRef引用清理见 prompt-input.tsx防止长时间会话造成内存泄漏。7.3 提交管线blob URL 转 data URL提交时handleSubmitprompt-input.tsx组件会立即form.reset()清空表单避免异步转换期间用户输入丢失的竞态问题将所有blob:前缀的附件 URL 通过fetchFileReader.readAsDataURL异步转换为 data URL转换失败则保留原 URL组装PromptInputMessage { text, files }调用onSubmitonSubmit返回 Promise 或同步完成后才clear()清空附件与文本若 Promise reject 或同步抛错则不清空输入便于用户重试。八、特性清单与扩展思路参考文档 prompt-input.md 归纳的完整特性包括自动调整高度的 textarea支持拖拽的文件附件内置屏幕截图动作图片附件即时预览可配置的文件约束数量、大小、类型基于聊天状态的提交按钮图标键盘快捷键Enter 提交、ShiftEnter 换行可定制的 textarea 最小/最大高度支持自定义动作与工具的灵活工具条内置模型选择下拉框内置 Web Speech API 语音识别按钮可选的状态提升 Provider提交后自动重置表单响应式与移动端友好的控件基于表单的提交处理隐藏文件输入同步原生表单提交可选的全局文档拖放opt-in。扩展方式所有组件都尽量透传底层原语的属性如PromptInputButton透传 shadcn/ui Button因此可以直接为任意按钮追加自定义className、onClick或自定义 tooltip动作菜单中也可以加入自己的PromptInputActionMenuItem实现引用仓库文件粘贴代码片段等业务动作。参考文档同时提供了 Cursor 风格与按钮 tooltip 两个示例场景scripts/prompt-input-cursor.tsx、scripts/prompt-input-tooltip.tsx前者展示极简输入条布局后者演示 tooltip 的快捷键提示用法。九、与 Conversation / Attachments 协同Conversation消息展示区使用Conversation自动滚动到底部 滚动按钮与PromptInput组合形成完整聊天界面组合示例见 conversation.md其中PromptInputSubmit的status由useChat()的status推导Attachments附件预览、信息与删除按钮全部来自独立的 attachments.md 模块PromptInput通过usePromptInputAttachments()与附件展示组件共享同一份上下文从而保证输入栏内的附件与提交后的消息附件数据一致。十、总结PromptInput 是 ai-elements 输入侧的基石组件它把文本 附件 模型选择 提交这一 AI 聊天应用最常见的输入交互收敛为一个可组合的表单且不牺牲可扩展性——20 余个子组件层层透传底层原语属性Provider 与 Hooks 提供了完整的状态控制面。在 ZCode 仓库中你可以直接阅读packages/ui/src/components/ai-elements/下的五个实现文件理解其附件校验、blob URL 生命周期、IME 兼容快捷键与截图上屏等细节也可以将参考文档中的完整示例前端page.tsx 后端route.ts复制到自己的 Next.js 项目中几分钟内跑通一个支持文件与联网搜索的 AI 聊天界面。赞分享【免费下载链接】ZCodeZ.ais coding agent harness. Powerful, intelligent, extensible.项目地址https://gitcode.com/gh_mirrors/zco/ZCode点击查看免费下载相关推荐从安装到源码用 ZCode 的 ai-elements Message 组件套件构建 AI 聊天界面从安装到源码用 ZCode 的 ai elements Message 组件套件构建 AI 聊天界面 导读 Message 是 ZCode 仓库内嵌的 aiZCode 中构建 AI 聊天界面AI Elements 组件库安装、组合与深度定制实战指南ZCode 中构建 AI 聊天界面AI Elements 组件库安装、组合与深度定制实战指南 本文以 ZCode 仓库内嵌的 ai elements 技能文档用 Genkit 与 Vercel AI Elements 构建流式 Agent 聊天 UI基于 genkit-ai/vercel-ai 传输层的端到端实战解析用 Genkit 与 Vercel AI Elements 构建流式 Agent 聊天 UI基于 genkit ai/vercel ai 传输层的端到端实战人工智能大模型后端AI AgentRAG工具调用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考