"use client";
import {
PromptInput,
PromptInputAttachment,
PromptInputAttachments,
PromptInputBody,
PromptInputButton,
PromptInputCommand,
PromptInputCommandEmpty,
PromptInputCommandGroup,
PromptInputCommandInput,
PromptInputCommandItem,
PromptInputCommandList,
PromptInputCommandSeparator,
PromptInputFooter,
PromptInputHeader,
PromptInputHoverCard,
PromptInputHoverCardContent,
PromptInputHoverCardTrigger,
type PromptInputMessage,
PromptInputProvider,
PromptInputSubmit,
PromptInputTab,
PromptInputTabBody,
PromptInputTabItem,
PromptInputTabLabel,
PromptInputTextarea,
PromptInputTools,
} from "@/components/ai-elements/prompt-input";
import {
ModelSelector,
ModelSelectorContent,
ModelSelectorEmpty,
ModelSelectorGroup,
ModelSelectorInput,
ModelSelectorItem,
ModelSelectorList,
ModelSelectorLogo,
ModelSelectorLogoGroup,
ModelSelectorName,
ModelSelectorTrigger,
} from "@/components/ai-elements/model-selector";
import { Button } from "@/components/ui/button";
import {
AtSignIcon,
CheckIcon,
FilesIcon,
GlobeIcon,
ImageIcon,
RulerIcon,
} from "lucide-react";
import { useRef, useState } from "react";
const models = [
{
id: "gpt-4o",
name: "GPT-4o",
chef: "OpenAI",
chefSlug: "openai",
providers: ["openai", "azure"],
},
{
id: "gpt-4o-mini",
name: "GPT-4o Mini",
chef: "OpenAI",
chefSlug: "openai",
providers: ["openai", "azure"],
},
{
id: "claude-opus-4-20250514",
name: "Claude 4 Opus",
chef: "Anthropic",
chefSlug: "anthropic",
providers: ["anthropic", "azure", "google", "amazon-bedrock"],
},
{
id: "claude-sonnet-4-20250514",
name: "Claude 4 Sonnet",
chef: "Anthropic",
chefSlug: "anthropic",
providers: ["anthropic", "azure", "google", "amazon-bedrock"],
},
{
id: "gemini-2.0-flash-exp",
name: "Gemini 2.0 Flash",
chef: "Google",
chefSlug: "google",
providers: ["google"],
},
];
const SUBMITTING_TIMEOUT = 200;
const STREAMING_TIMEOUT = 2000;
const sampleFiles = {
activeTabs: [{ path: "prompt-input.tsx", location: "packages/elements/src" }],
recents: [
{ path: "queue.tsx", location: "apps/test/app/examples" },
{ path: "queue.tsx", location: "packages/elements/src" },
],
added: [
{ path: "prompt-input.tsx", location: "packages/elements/src" },
{ path: "queue.tsx", location: "apps/test/app/examples" },
{ path: "queue.tsx", location: "packages/elements/src" },
],
filesAndFolders: [
{ path: "prompt-input.tsx", location: "packages/elements/src" },
{ path: "queue.tsx", location: "apps/test/app/examples" },
],
code: [{ path: "prompt-input.tsx", location: "packages/elements/src" }],
docs: [{ path: "README.md", location: "packages/elements" }],
};
const sampleTabs = {
active: [{ path: "packages/elements/src/task-queue-panel.tsx" }],
recents: [
{ path: "apps/test/app/examples/task-queue-panel.tsx" },
{ path: "apps/test/app/page.tsx" },
{ path: "packages/elements/src/task.tsx" },
{ path: "apps/test/app/examples/prompt-input.tsx" },
{ path: "packages/elements/src/queue.tsx" },
{ path: "apps/test/app/examples/queue.tsx" },
],
};
const Example = () => {
const [model, setModel] = useState<string>(models[0].id);
const [modelSelectorOpen, setModelSelectorOpen] = useState(false);
const [status, setStatus] = useState<
"submitted" | "streaming" | "ready" | "error"
>("ready");
const textareaRef = useRef<HTMLTextAreaElement>(null);
const selectedModelData = models.find((m) => m.id === model);
const handleSubmit = (message: PromptInputMessage) => {
const hasText = Boolean(message.text);
const hasAttachments = Boolean(message.files?.length);
if (!(hasText || hasAttachments)) {
return;
}
setStatus("submitted");
console.log("Submitting message:", message);
setTimeout(() => {
setStatus("streaming");
}, SUBMITTING_TIMEOUT);
setTimeout(() => {
setStatus("ready");
}, STREAMING_TIMEOUT);
};
return (
<div className="flex size-full flex-col justify-end">
<PromptInputProvider>
<PromptInput globalDrop multiple onSubmit={handleSubmit}>
<PromptInputHeader>
<PromptInputHoverCard>
<PromptInputHoverCardTrigger>
<PromptInputButton
className="!h-8"
size="icon-sm"
variant="outline"
>
<AtSignIcon className="text-muted-foreground" size={12} />
</PromptInputButton>
</PromptInputHoverCardTrigger>
<PromptInputHoverCardContent className="w-[400px] p-0">
<PromptInputCommand>
<PromptInputCommandInput
className="border-none focus-visible:ring-0"
placeholder="Add files, folders, docs..."
/>
<PromptInputCommandList>
<PromptInputCommandEmpty className="p-3 text-muted-foreground text-sm">
No results found.
</PromptInputCommandEmpty>
<PromptInputCommandGroup heading="Added">
<PromptInputCommandItem>
<GlobeIcon />
<span>Active Tabs</span>
<span className="ml-auto text-muted-foreground">✓</span>
</PromptInputCommandItem>
</PromptInputCommandGroup>
<PromptInputCommandSeparator />
<PromptInputCommandGroup heading="Other Files">
{sampleFiles.added.map((file, index) => (
<PromptInputCommandItem key={`${file.path}-${index}`}>
<GlobeIcon className="text-primary" />
<div className="flex flex-col">
<span className="font-medium text-sm">
{file.path}
</span>
<span className="text-muted-foreground text-xs">
{file.location}
</span>
</div>
</PromptInputCommandItem>
))}
</PromptInputCommandGroup>
</PromptInputCommandList>
</PromptInputCommand>
</PromptInputHoverCardContent>
</PromptInputHoverCard>
<PromptInputHoverCard>
<PromptInputHoverCardTrigger>
<PromptInputButton size="sm" variant="outline">
<RulerIcon className="text-muted-foreground" size={12} />
<span>1</span>
</PromptInputButton>
</PromptInputHoverCardTrigger>
<PromptInputHoverCardContent className="divide-y overflow-hidden p-0">
<div className="space-y-2 p-3">
<p className="font-medium text-muted-foreground text-sm">
Attached Project Rules
</p>
<p className="ml-4 text-muted-foreground text-sm">
Always Apply:
</p>
<p className="ml-8 text-sm">ultracite.mdc</p>
</div>
<p className="bg-sidebar px-4 py-3 text-muted-foreground text-sm">
Click to manage
</p>
</PromptInputHoverCardContent>
</PromptInputHoverCard>
<PromptInputHoverCard>
<PromptInputHoverCardTrigger>
<PromptInputButton size="sm" variant="outline">
<FilesIcon className="text-muted-foreground" size={12} />
<span>1 Tab</span>
</PromptInputButton>
</PromptInputHoverCardTrigger>
<PromptInputHoverCardContent className="w-[300px] space-y-4 px-0 py-3">
<PromptInputTab>
<PromptInputTabLabel>Active Tabs</PromptInputTabLabel>
<PromptInputTabBody>
{sampleTabs.active.map((tab) => (
<PromptInputTabItem key={tab.path}>
<GlobeIcon className="text-primary" size={16} />
<span className="truncate" dir="rtl">
{tab.path}
</span>
</PromptInputTabItem>
))}
</PromptInputTabBody>
</PromptInputTab>
<PromptInputTab>
<PromptInputTabLabel>Recents</PromptInputTabLabel>
<PromptInputTabBody>
{sampleTabs.recents.map((tab) => (
<PromptInputTabItem key={tab.path}>
<GlobeIcon className="text-primary" size={16} />
<span className="truncate" dir="rtl">
{tab.path}
</span>
</PromptInputTabItem>
))}
</PromptInputTabBody>
</PromptInputTab>
<div className="border-t px-3 pt-2 text-muted-foreground text-xs">
Only file paths are included
</div>
</PromptInputHoverCardContent>
</PromptInputHoverCard>
<PromptInputAttachments>
{(attachment) => <PromptInputAttachment data={attachment} />}
</PromptInputAttachments>
</PromptInputHeader>
<PromptInputBody>
<PromptInputTextarea
placeholder="Plan, search, build anything"
ref={textareaRef}
/>
</PromptInputBody>
<PromptInputFooter>
<PromptInputTools>
<ModelSelector
onOpenChange={setModelSelectorOpen}
open={modelSelectorOpen}
>
<ModelSelectorTrigger asChild>
<PromptInputButton>
{selectedModelData?.chefSlug && (
<ModelSelectorLogo provider={selectedModelData.chefSlug} />
)}
{selectedModelData?.name && (
<ModelSelectorName>
{selectedModelData.name}
</ModelSelectorName>
)}
</PromptInputButton>
</ModelSelectorTrigger>
<ModelSelectorContent>
<ModelSelectorInput placeholder="Search models..." />
<ModelSelectorList>
<ModelSelectorEmpty>No models found.</ModelSelectorEmpty>
{["OpenAI", "Anthropic", "Google"].map((chef) => (
<ModelSelectorGroup key={chef} heading={chef}>
{models
.filter((m) => m.chef === chef)
.map((m) => (
<ModelSelectorItem
key={m.id}
onSelect={() => {
setModel(m.id);
setModelSelectorOpen(false);
}}
value={m.id}
>
<ModelSelectorLogo provider={m.chefSlug} />
<ModelSelectorName>{m.name}</ModelSelectorName>
<ModelSelectorLogoGroup>
{m.providers.map((provider) => (
<ModelSelectorLogo
key={provider}
provider={provider}
/>
))}
</ModelSelectorLogoGroup>
{model === m.id ? (
<CheckIcon className="ml-auto size-4" />
) : (
<div className="ml-auto size-4" />
)}
</ModelSelectorItem>
))}
</ModelSelectorGroup>
))}
</ModelSelectorList>
</ModelSelectorContent>
</ModelSelector>
</PromptInputTools>
<div className="flex items-center gap-2">
<Button size="icon-sm" variant="ghost">
<ImageIcon className="text-muted-foreground" size={16} />
</Button>
<PromptInputSubmit className="!h-8" status={status} />
</div>
</PromptInputFooter>
</PromptInput>
</PromptInputProvider>
</div>
);
};
export default Example;