"use client";
import {
Attachment,
AttachmentPreview,
AttachmentRemove,
Attachments,
} from "@/components/ai-elements/attachments";
import {
ModelSelector,
ModelSelectorContent,
ModelSelectorEmpty,
ModelSelectorGroup,
ModelSelectorInput,
ModelSelectorItem,
ModelSelectorList,
ModelSelectorLogo,
ModelSelectorLogoGroup,
ModelSelectorName,
ModelSelectorTrigger,
} from "@/components/ai-elements/model-selector";
import type { PromptInputMessage } from "@/components/ai-elements/prompt-input";
import {
PromptInput,
PromptInputActionAddAttachments,
PromptInputActionAddScreenshot,
PromptInputActionMenu,
PromptInputActionMenuContent,
PromptInputActionMenuTrigger,
PromptInputBody,
PromptInputButton,
PromptInputFooter,
PromptInputProvider,
PromptInputSubmit,
PromptInputTextarea,
PromptInputTools,
usePromptInputAttachments,
} from "@/components/ai-elements/prompt-input";
import { CheckIcon, GlobeIcon } from "lucide-react";
import { memo, useCallback, useState } from "react";
const models = [
{
chef: "OpenAI",
chefSlug: "openai",
id: "gpt-4o",
name: "GPT-4o",
providers: ["openai", "azure"],
},
{
chef: "OpenAI",
chefSlug: "openai",
id: "gpt-4o-mini",
name: "GPT-4o Mini",
providers: ["openai", "azure"],
},
{
chef: "Anthropic",
chefSlug: "anthropic",
id: "claude-opus-4-20250514",
name: "Claude 4 Opus",
providers: ["anthropic", "azure", "google", "amazon-bedrock"],
},
{
chef: "Anthropic",
chefSlug: "anthropic",
id: "claude-sonnet-4-20250514",
name: "Claude 4 Sonnet",
providers: ["anthropic", "azure", "google", "amazon-bedrock"],
},
{
chef: "Google",
chefSlug: "google",
id: "gemini-2.0-flash-exp",
name: "Gemini 2.0 Flash",
providers: ["google"],
},
];
const SUBMITTING_TIMEOUT = 200;
const STREAMING_TIMEOUT = 2000;
interface AttachmentItemProps {
attachment: {
id: string;
type: "file";
filename?: string;
mediaType?: string;
url: string;
};
onRemove: (id: string) => void;
}
const AttachmentItem = memo(({ attachment, onRemove }: AttachmentItemProps) => {
const handleRemove = useCallback(
() => onRemove(attachment.id),
[onRemove, attachment.id]
);
return (
);
});
AttachmentItem.displayName = "AttachmentItem";
interface ModelItemProps {
m: (typeof models)[0];
selectedModel: string;
onSelect: (id: string) => void;
}
const ModelItem = memo(({ m, selectedModel, onSelect }: ModelItemProps) => {
const handleSelect = useCallback(() => onSelect(m.id), [onSelect, m.id]);
return (
{m.name}
{m.providers.map((provider) => (
))}
{selectedModel === m.id ? (
) : (
)}
);
});
ModelItem.displayName = "ModelItem";
const PromptInputAttachmentsDisplay = () => {
const attachments = usePromptInputAttachments();
const handleRemove = useCallback(
(id: string) => attachments.remove(id),
[attachments]
);
if (attachments.files.length === 0) {
return null;
}
return (
{attachments.files.map((attachment) => (
))}
);
};
const Example = () => {
const [model, setModel] = useState(models[0].id);
const [modelSelectorOpen, setModelSelectorOpen] = useState(false);
const [status, setStatus] = useState<
"submitted" | "streaming" | "ready" | "error"
>("ready");
const selectedModelData = models.find((m) => m.id === model);
const handleModelSelect = useCallback((id: string) => {
setModel(id);
setModelSelectorOpen(false);
}, []);
const handleSubmit = useCallback((message: PromptInputMessage) => {
const hasText = Boolean(message.text);
const hasAttachments = Boolean(message.files?.length);
if (!(hasText || hasAttachments)) {
return;
}
setStatus("submitted");
// eslint-disable-next-line no-console
console.log("Submitting message:", message);
setTimeout(() => {
setStatus("streaming");
}, SUBMITTING_TIMEOUT);
setTimeout(() => {
setStatus("ready");
}, STREAMING_TIMEOUT);
}, []);
return (
Search
{selectedModelData?.chefSlug && (
)}
{selectedModelData?.name && (
{selectedModelData.name}
)}
No models found.
{["OpenAI", "Anthropic", "Google"].map((chef) => (
{models
.filter((m) => m.chef === chef)
.map((m) => (
))}
))}
);
};
export default Example;