"use client";
import { cn } from "@/lib/utils";
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import {
ActionBarPrimitive,
AuiIf,
AttachmentPrimitive,
BranchPickerPrimitive,
ComposerPrimitive,
MessagePrimitive,
ThreadPrimitive,
useAuiState,
} from "@assistant-ui/react";
import {
ArrowRight,
AudioLines,
CheckIcon,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
CopyIcon,
FileIcon,
PencilIcon,
Plus,
RefreshCwIcon,
Search,
Sparkles,
Square,
Telescope,
XIcon,
} from "lucide-react";
import { type FC, useEffect, useState } from "react";
import { useShallow } from "zustand/shallow";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
const composerPrimaryActionClassName =
"absolute inset-0 flex items-center justify-center rounded-full transition-all duration-200 ease-out";
const composerPrimaryActionColorsClassName =
"bg-[#25211c] text-[#f8f5f0] hover:bg-[#171411] dark:bg-[#f5f2ed] dark:text-[#1b1713] dark:hover:bg-white";
const messageActionClassName =
"flex size-8 items-center justify-center rounded-full text-[#7a7268] transition-colors hover:bg-[#f1ece5] hover:text-[#1f1b17] dark:text-[#9d968d] dark:hover:bg-[#2a2724] dark:hover:text-[#f5f2ed]";
export const Perplexity: FC = () => {
return (
s.thread.isEmpty}>
!s.thread.isEmpty}>
{() => }
);
};
const EmptyState: FC = () => {
return (
);
};
const Composer: FC<{ placeholder: string }> = ({ placeholder }) => {
return (
s.composer.attachments.length > 0}>
);
};
const ComposerPrimaryAction: FC = () => {
return (
s.thread.isRunning}>
!s.thread.isRunning && s.composer.dictation != null}
>
!s.thread.isRunning &&
s.composer.dictation == null &&
!s.composer.isEmpty
}
>
!s.thread.isRunning &&
s.composer.dictation == null &&
s.composer.isEmpty
}
>
);
};
const SEARCH_MODES = [
{
id: "search",
name: "Search",
description: "Fast answers to everyday questions",
Icon: Search,
},
{
id: "research",
name: "Research",
description: "In-depth reports on complex topics",
Icon: Telescope,
},
{
id: "labs",
name: "Labs",
description: "Apps, slides, and dashboards",
Icon: Sparkles,
},
];
const SearchModePicker: FC = () => {
const [mode, setMode] = useState(SEARCH_MODES[0]!.id);
const current = SEARCH_MODES.find((m) => m.id === mode) ?? SEARCH_MODES[0]!;
const CurrentIcon = current.Icon;
return (
{current.name}
{SEARCH_MODES.map(({ id, name, description, Icon }) => (
setMode(id)}
className="flex items-start gap-3"
>
{id === mode ? : }
{name}
{description}
))}
);
};
const PERPLEXITY_MODELS = [
{ id: "best", name: "Best", description: "Auto-pick the best model" },
{ id: "sonar", name: "Sonar", description: "Perplexity's fast model" },
{ id: "claude", name: "Claude 4.5 Sonnet", description: "Anthropic" },
{ id: "gpt-5", name: "GPT-5", description: "OpenAI" },
{ id: "gemini", name: "Gemini 3 Pro", description: "Google" },
];
const ModelPicker: FC = () => {
const [model, setModel] = useState(PERPLEXITY_MODELS[0]!.id);
const current =
PERPLEXITY_MODELS.find((m) => m.id === model) ?? PERPLEXITY_MODELS[0]!;
return (
{current.name}
{PERPLEXITY_MODELS.map((m) => (
setModel(m.id)}
className="flex items-start gap-3"
>
{m.id === model ? : null}
{m.name}
{m.description}
))}
);
};
const ChatMessage: FC = () => {
return (
s.message.role === "user"}>
s.message.role === "assistant"}>
s.message.isCopied}>
!s.message.isCopied}>
);
};
const BranchPicker: FC = ({
className,
...props
}) => {
return (
/
);
};
const useFileSrc = (file: File | undefined) => {
const [src, setSrc] = useState(undefined);
useEffect(() => {
if (!file) {
setSrc(undefined);
return;
}
const objectUrl = URL.createObjectURL(file);
setSrc(objectUrl);
return () => {
URL.revokeObjectURL(objectUrl);
};
}, [file]);
return src;
};
const useAttachmentSrc = () => {
const { file, src } = useAuiState(
useShallow((s): { file?: File; src?: string } => {
if (s.attachment.type !== "image") return {};
if (s.attachment.file) return { file: s.attachment.file };
const src = s.attachment.content?.filter((c) => c.type === "image")[0]
?.image;
if (!src) return {};
return { src };
}),
);
return useFileSrc(file) ?? src;
};
const AttachmentTypeLabel: FC = () => {
const typeLabel = useAuiState((s) => {
switch (s.attachment.type) {
case "image":
return "Image";
case "document":
return "Document";
case "file":
return "File";
default:
return s.attachment.type;
}
});
return {typeLabel};
};
const AttachmentPreview: FC<{ removable: boolean }> = ({ removable }) => {
const src = useAttachmentSrc();
return (
{src ? (

) : (
)}
{removable ? (
) : null}
);
};