mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-16 03:14:45 +08:00
feat: implement basic web app
This commit is contained in:
@@ -23,14 +23,14 @@ type ChainOfThoughtContextValue = {
|
||||
};
|
||||
|
||||
const ChainOfThoughtContext = createContext<ChainOfThoughtContextValue | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
|
||||
const useChainOfThought = () => {
|
||||
const context = useContext(ChainOfThoughtContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"ChainOfThought components must be used within ChainOfThought"
|
||||
"ChainOfThought components must be used within ChainOfThought",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
@@ -59,7 +59,7 @@ export const ChainOfThought = memo(
|
||||
|
||||
const chainOfThoughtContext = useMemo(
|
||||
() => ({ isOpen, setIsOpen }),
|
||||
[isOpen, setIsOpen]
|
||||
[isOpen, setIsOpen],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -72,40 +72,42 @@ export const ChainOfThought = memo(
|
||||
</div>
|
||||
</ChainOfThoughtContext.Provider>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export type ChainOfThoughtHeaderProps = ComponentProps<
|
||||
typeof CollapsibleTrigger
|
||||
>;
|
||||
> & {
|
||||
icon?: React.ReactElement;
|
||||
};
|
||||
|
||||
export const ChainOfThoughtHeader = memo(
|
||||
({ className, children, ...props }: ChainOfThoughtHeaderProps) => {
|
||||
({ className, children, icon, ...props }: ChainOfThoughtHeaderProps) => {
|
||||
const { isOpen, setIsOpen } = useChainOfThought();
|
||||
|
||||
return (
|
||||
<Collapsible onOpenChange={setIsOpen} open={isOpen}>
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",
|
||||
className
|
||||
"text-muted-foreground hover:text-foreground flex w-full items-center gap-2 text-sm transition-colors",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<BrainIcon className="size-4" />
|
||||
{icon ?? <BrainIcon className="size-4" />}
|
||||
<span className="flex-1 text-left">
|
||||
{children ?? "Chain of Thought"}
|
||||
</span>
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"size-4 transition-transform",
|
||||
isOpen ? "rotate-180" : "rotate-0"
|
||||
isOpen ? "rotate-180" : "rotate-0",
|
||||
)}
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export type ChainOfThoughtStepProps = ComponentProps<"div"> & {
|
||||
@@ -137,13 +139,13 @@ export const ChainOfThoughtStep = memo(
|
||||
"flex gap-2 text-sm",
|
||||
statusStyles[status],
|
||||
"fade-in-0 slide-in-from-top-2 animate-in",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative mt-0.5">
|
||||
<Icon className="size-4" />
|
||||
<div className="-mx-px absolute top-7 bottom-0 left-1/2 w-px bg-border" />
|
||||
<div className="bg-border absolute top-7 bottom-0 left-1/2 -mx-px w-px" />
|
||||
</div>
|
||||
<div className="flex-1 space-y-2 overflow-hidden">
|
||||
<div>{label}</div>
|
||||
@@ -154,7 +156,7 @@ export const ChainOfThoughtStep = memo(
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export type ChainOfThoughtSearchResultsProps = ComponentProps<"div">;
|
||||
@@ -165,7 +167,7 @@ export const ChainOfThoughtSearchResults = memo(
|
||||
className={cn("flex flex-wrap items-center gap-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
export type ChainOfThoughtSearchResultProps = ComponentProps<typeof Badge>;
|
||||
@@ -173,13 +175,13 @@ export type ChainOfThoughtSearchResultProps = ComponentProps<typeof Badge>;
|
||||
export const ChainOfThoughtSearchResult = memo(
|
||||
({ className, children, ...props }: ChainOfThoughtSearchResultProps) => (
|
||||
<Badge
|
||||
className={cn("gap-1 px-2 py-0.5 font-normal text-xs", className)}
|
||||
className={cn("gap-1 px-2 py-0.5 text-xs font-normal", className)}
|
||||
variant="secondary"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Badge>
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
export type ChainOfThoughtContentProps = ComponentProps<
|
||||
@@ -195,8 +197,8 @@ export const ChainOfThoughtContent = memo(
|
||||
<CollapsibleContent
|
||||
className={cn(
|
||||
"mt-2 space-y-3",
|
||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
className
|
||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -204,7 +206,7 @@ export const ChainOfThoughtContent = memo(
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export type ChainOfThoughtImageProps = ComponentProps<"div"> & {
|
||||
@@ -214,12 +216,12 @@ export type ChainOfThoughtImageProps = ComponentProps<"div"> & {
|
||||
export const ChainOfThoughtImage = memo(
|
||||
({ className, children, caption, ...props }: ChainOfThoughtImageProps) => (
|
||||
<div className={cn("mt-2 space-y-2", className)} {...props}>
|
||||
<div className="relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-lg bg-muted p-3">
|
||||
<div className="bg-muted relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-lg p-3">
|
||||
{children}
|
||||
</div>
|
||||
{caption && <p className="text-muted-foreground text-xs">{caption}</p>}
|
||||
</div>
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
ChainOfThought.displayName = "ChainOfThought";
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonGroupText,
|
||||
} from "@/components/ui/button-group";
|
||||
import { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -30,9 +27,9 @@ export type MessageProps = HTMLAttributes<HTMLDivElement> & {
|
||||
export const Message = ({ className, from, ...props }: MessageProps) => (
|
||||
<div
|
||||
className={cn(
|
||||
"group flex w-full max-w-[95%] flex-col gap-2",
|
||||
"group flex w-full flex-col gap-2",
|
||||
from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -47,10 +44,10 @@ export const MessageContent = ({
|
||||
}: MessageContentProps) => (
|
||||
<div
|
||||
className={cn(
|
||||
"is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm",
|
||||
"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
|
||||
"is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-hidden",
|
||||
"group-[.is-user]:bg-secondary group-[.is-user]:text-foreground group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:px-4 group-[.is-user]:py-3",
|
||||
"group-[.is-assistant]:text-foreground",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -116,7 +113,7 @@ type MessageBranchContextType = {
|
||||
};
|
||||
|
||||
const MessageBranchContext = createContext<MessageBranchContextType | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
|
||||
const useMessageBranch = () => {
|
||||
@@ -124,7 +121,7 @@ const useMessageBranch = () => {
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"MessageBranch components must be used within MessageBranch"
|
||||
"MessageBranch components must be used within MessageBranch",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -201,7 +198,7 @@ export const MessageBranchContent = ({
|
||||
<div
|
||||
className={cn(
|
||||
"grid gap-2 overflow-hidden [&>div]:pb-0",
|
||||
index === currentBranch ? "block" : "hidden"
|
||||
index === currentBranch ? "block" : "hidden",
|
||||
)}
|
||||
key={branch.key}
|
||||
{...props}
|
||||
@@ -294,8 +291,8 @@ export const MessageBranchPage = ({
|
||||
return (
|
||||
<ButtonGroupText
|
||||
className={cn(
|
||||
"border-none bg-transparent text-muted-foreground shadow-none",
|
||||
className
|
||||
"text-muted-foreground border-none bg-transparent shadow-none",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -311,12 +308,12 @@ export const MessageResponse = memo(
|
||||
<Streamdown
|
||||
className={cn(
|
||||
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
(prevProps, nextProps) => prevProps.children === nextProps.children
|
||||
(prevProps, nextProps) => prevProps.children === nextProps.children,
|
||||
);
|
||||
|
||||
MessageResponse.displayName = "MessageResponse";
|
||||
@@ -343,7 +340,7 @@ export function MessageAttachment({
|
||||
<div
|
||||
className={cn(
|
||||
"group relative size-24 overflow-hidden rounded-lg",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -359,7 +356,7 @@ export function MessageAttachment({
|
||||
{onRemove && (
|
||||
<Button
|
||||
aria-label="Remove attachment"
|
||||
className="absolute top-2 right-2 size-6 rounded-full bg-background/80 p-0 opacity-0 backdrop-blur-sm transition-opacity hover:bg-background group-hover:opacity-100 [&>svg]:size-3"
|
||||
className="bg-background/80 hover:bg-background absolute top-2 right-2 size-6 rounded-full p-0 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100 [&>svg]:size-3"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
@@ -376,7 +373,7 @@ export function MessageAttachment({
|
||||
<>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex size-full shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
||||
<div className="bg-muted text-muted-foreground flex size-full shrink-0 items-center justify-center rounded-lg">
|
||||
<PaperclipIcon className="size-4" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
@@ -387,7 +384,7 @@ export function MessageAttachment({
|
||||
{onRemove && (
|
||||
<Button
|
||||
aria-label="Remove attachment"
|
||||
className="size-6 shrink-0 rounded-full p-0 opacity-0 transition-opacity hover:bg-accent group-hover:opacity-100 [&>svg]:size-3"
|
||||
className="hover:bg-accent size-6 shrink-0 rounded-full p-0 opacity-0 transition-opacity group-hover:opacity-100 [&>svg]:size-3"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRemove();
|
||||
@@ -420,7 +417,7 @@ export function MessageAttachments({
|
||||
<div
|
||||
className={cn(
|
||||
"ml-auto flex w-fit flex-wrap items-start gap-2",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -439,7 +436,7 @@ export const MessageToolbar = ({
|
||||
<div
|
||||
className={cn(
|
||||
"mt-4 flex w-full items-center justify-between gap-4",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { ChatStatus, FileUIPart } from "ai";
|
||||
import {
|
||||
CornerDownLeftIcon,
|
||||
ArrowUpIcon,
|
||||
ImageIcon,
|
||||
Loader2Icon,
|
||||
MicIcon,
|
||||
@@ -95,22 +95,22 @@ export type PromptInputControllerProps = {
|
||||
/** INTERNAL: Allows PromptInput to register its file textInput + "open" callback */
|
||||
__registerFileInput: (
|
||||
ref: RefObject<HTMLInputElement | null>,
|
||||
open: () => void
|
||||
open: () => void,
|
||||
) => void;
|
||||
};
|
||||
|
||||
const PromptInputController = createContext<PromptInputControllerProps | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const ProviderAttachmentsContext = createContext<AttachmentsContext | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
|
||||
export const usePromptInputController = () => {
|
||||
const ctx = useContext(PromptInputController);
|
||||
if (!ctx) {
|
||||
throw new Error(
|
||||
"Wrap your component inside <PromptInputProvider> to use usePromptInputController()."
|
||||
"Wrap your component inside <PromptInputProvider> to use usePromptInputController().",
|
||||
);
|
||||
}
|
||||
return ctx;
|
||||
@@ -124,7 +124,7 @@ export const useProviderAttachments = () => {
|
||||
const ctx = useContext(ProviderAttachmentsContext);
|
||||
if (!ctx) {
|
||||
throw new Error(
|
||||
"Wrap your component inside <PromptInputProvider> to use useProviderAttachments()."
|
||||
"Wrap your component inside <PromptInputProvider> to use useProviderAttachments().",
|
||||
);
|
||||
}
|
||||
return ctx;
|
||||
@@ -170,8 +170,8 @@ export function PromptInputProvider({
|
||||
url: URL.createObjectURL(file),
|
||||
mediaType: file.type,
|
||||
filename: file.name,
|
||||
}))
|
||||
)
|
||||
})),
|
||||
),
|
||||
);
|
||||
}, []);
|
||||
|
||||
@@ -224,7 +224,7 @@ export function PromptInputProvider({
|
||||
openFileDialog,
|
||||
fileInputRef,
|
||||
}),
|
||||
[attachmentFiles, add, remove, clear, openFileDialog]
|
||||
[attachmentFiles, add, remove, clear, openFileDialog],
|
||||
);
|
||||
|
||||
const __registerFileInput = useCallback(
|
||||
@@ -232,7 +232,7 @@ export function PromptInputProvider({
|
||||
fileInputRef.current = ref.current;
|
||||
openRef.current = open;
|
||||
},
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const controller = useMemo<PromptInputControllerProps>(
|
||||
@@ -245,7 +245,7 @@ export function PromptInputProvider({
|
||||
attachments,
|
||||
__registerFileInput,
|
||||
}),
|
||||
[textInput, clearInput, attachments, __registerFileInput]
|
||||
[textInput, clearInput, attachments, __registerFileInput],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -270,7 +270,7 @@ export const usePromptInputAttachments = () => {
|
||||
const context = provider ?? local;
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"usePromptInputAttachments must be used within a PromptInput or PromptInputProvider"
|
||||
"usePromptInputAttachments must be used within a PromptInput or PromptInputProvider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
@@ -301,14 +301,14 @@ export function PromptInputAttachment({
|
||||
<HoverCardTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
"group relative flex h-8 cursor-pointer select-none items-center gap-1.5 rounded-md border border-border px-1.5 font-medium text-sm transition-all hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||
className
|
||||
"group border-border hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 relative flex h-8 cursor-pointer items-center gap-1.5 rounded-md border px-1.5 text-sm font-medium transition-all select-none",
|
||||
className,
|
||||
)}
|
||||
key={data.id}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative size-5 shrink-0">
|
||||
<div className="absolute inset-0 flex size-5 items-center justify-center overflow-hidden rounded bg-background transition-opacity group-hover:opacity-0">
|
||||
<div className="bg-background absolute inset-0 flex size-5 items-center justify-center overflow-hidden rounded transition-opacity group-hover:opacity-0">
|
||||
{isImage ? (
|
||||
<img
|
||||
alt={filename || "attachment"}
|
||||
@@ -318,7 +318,7 @@ export function PromptInputAttachment({
|
||||
width={20}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex size-5 items-center justify-center text-muted-foreground">
|
||||
<div className="text-muted-foreground flex size-5 items-center justify-center">
|
||||
<PaperclipIcon className="size-3" />
|
||||
</div>
|
||||
)}
|
||||
@@ -356,11 +356,11 @@ export function PromptInputAttachment({
|
||||
)}
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="min-w-0 flex-1 space-y-1 px-0.5">
|
||||
<h4 className="truncate font-semibold text-sm leading-none">
|
||||
<h4 className="truncate text-sm leading-none font-semibold">
|
||||
{filename || (isImage ? "Image" : "Attachment")}
|
||||
</h4>
|
||||
{data.mediaType && (
|
||||
<p className="truncate font-mono text-muted-foreground text-xs">
|
||||
<p className="text-muted-foreground truncate font-mono text-xs">
|
||||
{data.mediaType}
|
||||
</p>
|
||||
)}
|
||||
@@ -392,7 +392,7 @@ export function PromptInputAttachments({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("flex flex-wrap items-center gap-2 p-3 w-full", className)}
|
||||
className={cn("flex w-full flex-wrap items-center gap-2 p-3", className)}
|
||||
{...props}
|
||||
>
|
||||
{attachments.files.map((file) => (
|
||||
@@ -451,7 +451,7 @@ export type PromptInputProps = Omit<
|
||||
}) => void;
|
||||
onSubmit: (
|
||||
message: PromptInputMessage,
|
||||
event: FormEvent<HTMLFormElement>
|
||||
event: FormEvent<HTMLFormElement>,
|
||||
) => void | Promise<void>;
|
||||
};
|
||||
|
||||
@@ -507,7 +507,7 @@ export const PromptInput = ({
|
||||
return f.type === pattern;
|
||||
});
|
||||
},
|
||||
[accept]
|
||||
[accept],
|
||||
);
|
||||
|
||||
const addLocal = useCallback(
|
||||
@@ -558,7 +558,7 @@ export const PromptInput = ({
|
||||
return prev.concat(next);
|
||||
});
|
||||
},
|
||||
[matchesAccept, maxFiles, maxFileSize, onError]
|
||||
[matchesAccept, maxFiles, maxFileSize, onError],
|
||||
);
|
||||
|
||||
const removeLocal = useCallback(
|
||||
@@ -570,7 +570,7 @@ export const PromptInput = ({
|
||||
}
|
||||
return prev.filter((file) => file.id !== id);
|
||||
}),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const clearLocal = useCallback(
|
||||
@@ -583,7 +583,7 @@ export const PromptInput = ({
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const add = usingProvider ? controller.attachments.add : addLocal;
|
||||
@@ -611,7 +611,7 @@ export const PromptInput = ({
|
||||
useEffect(() => {
|
||||
const form = formRef.current;
|
||||
if (!form) return;
|
||||
if (globalDrop) return // when global drop is on, let the document-level handler own drops
|
||||
if (globalDrop) return; // when global drop is on, let the document-level handler own drops
|
||||
|
||||
const onDragOver = (e: DragEvent) => {
|
||||
if (e.dataTransfer?.types?.includes("Files")) {
|
||||
@@ -667,7 +667,7 @@ export const PromptInput = ({
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- cleanup only on unmount; filesRef always current
|
||||
[usingProvider]
|
||||
[usingProvider],
|
||||
);
|
||||
|
||||
const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
@@ -679,7 +679,7 @@ export const PromptInput = ({
|
||||
};
|
||||
|
||||
const convertBlobUrlToDataUrl = async (
|
||||
url: string
|
||||
url: string,
|
||||
): Promise<string | null> => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
@@ -704,7 +704,7 @@ export const PromptInput = ({
|
||||
openFileDialog,
|
||||
fileInputRef: inputRef,
|
||||
}),
|
||||
[files, add, remove, clear, openFileDialog]
|
||||
[files, add, remove, clear, openFileDialog],
|
||||
);
|
||||
|
||||
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
|
||||
@@ -736,7 +736,7 @@ export const PromptInput = ({
|
||||
};
|
||||
}
|
||||
return item;
|
||||
})
|
||||
}),
|
||||
)
|
||||
.then((convertedFiles: FileUIPart[]) => {
|
||||
try {
|
||||
@@ -839,7 +839,7 @@ export const PromptInputTextarea = ({
|
||||
// Check if the submit button is disabled before submitting
|
||||
const form = e.currentTarget.form;
|
||||
const submitButton = form?.querySelector(
|
||||
'button[type="submit"]'
|
||||
'button[type="submit"]',
|
||||
) as HTMLButtonElement | null;
|
||||
if (submitButton?.disabled) {
|
||||
return;
|
||||
@@ -1030,7 +1030,7 @@ export const PromptInputSubmit = ({
|
||||
children,
|
||||
...props
|
||||
}: PromptInputSubmitProps) => {
|
||||
let Icon = <CornerDownLeftIcon className="size-4" />;
|
||||
let Icon = <ArrowUpIcon className="size-4" />;
|
||||
|
||||
if (status === "submitted") {
|
||||
Icon = <Loader2Icon className="size-4 animate-spin" />;
|
||||
@@ -1123,7 +1123,7 @@ export const PromptInputSpeechButton = ({
|
||||
}: PromptInputSpeechButtonProps) => {
|
||||
const [isListening, setIsListening] = useState(false);
|
||||
const [recognition, setRecognition] = useState<SpeechRecognition | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const recognitionRef = useRef<SpeechRecognition | null>(null);
|
||||
|
||||
@@ -1202,8 +1202,8 @@ export const PromptInputSpeechButton = ({
|
||||
<PromptInputButton
|
||||
className={cn(
|
||||
"relative transition-all duration-200",
|
||||
isListening && "animate-pulse bg-accent text-accent-foreground",
|
||||
className
|
||||
isListening && "bg-accent text-accent-foreground animate-pulse",
|
||||
className,
|
||||
)}
|
||||
disabled={!recognition}
|
||||
onClick={toggleListening}
|
||||
@@ -1230,9 +1230,9 @@ export const PromptInputSelectTrigger = ({
|
||||
}: PromptInputSelectTriggerProps) => (
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
"border-none bg-transparent font-medium text-muted-foreground shadow-none transition-colors",
|
||||
"text-muted-foreground border-none bg-transparent font-medium shadow-none transition-colors",
|
||||
"hover:bg-accent hover:text-foreground aria-expanded:bg-accent aria-expanded:text-foreground",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -1282,7 +1282,7 @@ export type PromptInputHoverCardTriggerProps = ComponentProps<
|
||||
>;
|
||||
|
||||
export const PromptInputHoverCardTrigger = (
|
||||
props: PromptInputHoverCardTriggerProps
|
||||
props: PromptInputHoverCardTriggerProps,
|
||||
) => <HoverCardTrigger {...props} />;
|
||||
|
||||
export type PromptInputHoverCardContentProps = ComponentProps<
|
||||
@@ -1318,8 +1318,8 @@ export const PromptInputTabLabel = ({
|
||||
}: PromptInputTabLabelProps) => (
|
||||
<h3
|
||||
className={cn(
|
||||
"mb-2 px-3 font-medium text-muted-foreground text-xs",
|
||||
className
|
||||
"text-muted-foreground mb-2 px-3 text-xs font-medium",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -1342,8 +1342,8 @@ export const PromptInputTabItem = ({
|
||||
}: PromptInputTabItemProps) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-3 py-2 text-xs hover:bg-accent",
|
||||
className
|
||||
"hover:bg-accent flex items-center gap-2 px-3 py-2 text-xs",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user