feat(web): implement recent mention management in document mention picker and enhance composer component

This commit is contained in:
Anish Sarkar 2026-05-27 00:26:44 +05:30
parent 79f5e8f88c
commit 17293125ef
4 changed files with 243 additions and 73 deletions

View file

@ -70,6 +70,7 @@ import { UserMessage } from "@/components/assistant-ui/user-message";
import { ComposerSuggestionPopoverContent } from "@/components/new-chat/composer-suggestion-popup"; import { ComposerSuggestionPopoverContent } from "@/components/new-chat/composer-suggestion-popup";
import { import {
DocumentMentionPicker, DocumentMentionPicker,
promoteRecentMention,
type DocumentMentionPickerRef, type DocumentMentionPickerRef,
} from "../new-chat/document-mention-picker"; } from "../new-chat/document-mention-picker";
import { PromptPicker, type PromptPickerRef } from "@/components/new-chat/prompt-picker"; import { PromptPicker, type PromptPickerRef } from "@/components/new-chat/prompt-picker";
@ -768,6 +769,7 @@ const Composer: FC = () => {
); );
const handleDocumentsMention = useCallback((mentions: MentionedDocumentInfo[]) => { const handleDocumentsMention = useCallback((mentions: MentionedDocumentInfo[]) => {
const parsedSearchSpaceId = Number(search_space_id);
const editorMentionedDocs = editorRef.current?.getMentionedDocuments() ?? []; const editorMentionedDocs = editorRef.current?.getMentionedDocuments() ?? [];
const editorDocKeys = new Set(editorMentionedDocs.map((doc) => getMentionDocKey(doc))); const editorDocKeys = new Set(editorMentionedDocs.map((doc) => getMentionDocKey(doc)));
@ -775,6 +777,9 @@ const Composer: FC = () => {
const key = getMentionDocKey(mention); const key = getMentionDocKey(mention);
if (editorDocKeys.has(key)) continue; if (editorDocKeys.has(key)) continue;
editorRef.current?.insertMentionChip(mention); editorRef.current?.insertMentionChip(mention);
if (Number.isFinite(parsedSearchSpaceId)) {
promoteRecentMention(parsedSearchSpaceId, mention);
}
// Track within the loop so a duplicate-in-batch can't double-insert. // Track within the loop so a duplicate-in-batch can't double-insert.
editorDocKeys.add(key); editorDocKeys.add(key);
} }
@ -783,7 +788,7 @@ const Composer: FC = () => {
// onChange — no second write path here. // onChange — no second write path here.
setMentionQuery(""); setMentionQuery("");
setSuggestionAnchorPoint(null); setSuggestionAnchorPoint(null);
}, []); }, [search_space_id]);
useEffect(() => { useEffect(() => {
const editor = editorRef.current; const editor = editorRef.current;

View file

@ -31,7 +31,7 @@ function ComposerSuggestionPopoverContent({
onCloseAutoFocus?.(event); onCloseAutoFocus?.(event);
}} }}
className={cn( className={cn(
"w-[256px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[288px]", "w-[232px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[264px]",
"data-[state=open]:!animate-none data-[state=closed]:!animate-none data-[state=open]:!duration-0 data-[state=closed]:!duration-0", "data-[state=open]:!animate-none data-[state=closed]:!animate-none data-[state=open]:!duration-0 data-[state=closed]:!duration-0",
className className
)} )}
@ -47,14 +47,14 @@ const ComposerSuggestionList = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn("max-h-[160px] overflow-y-auto sm:max-h-[232px]", className)} className={cn("max-h-[144px] overflow-y-auto sm:max-h-[200px]", className)}
{...props} {...props}
/> />
)); ));
ComposerSuggestionList.displayName = "ComposerSuggestionList"; ComposerSuggestionList.displayName = "ComposerSuggestionList";
function ComposerSuggestionGroup({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) { function ComposerSuggestionGroup({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn("px-1.5 py-2", className)} {...props} />; return <div className={cn("px-1.5 py-1.5", className)} {...props} />;
} }
function ComposerSuggestionGroupHeading({ function ComposerSuggestionGroupHeading({
@ -63,7 +63,7 @@ function ComposerSuggestionGroupHeading({
}: React.HTMLAttributes<HTMLDivElement>) { }: React.HTMLAttributes<HTMLDivElement>) {
return ( return (
<div <div
className={cn("px-2.5 py-1.5 text-xs font-bold text-muted-foreground/55", className)} className={cn("px-2 py-1 text-xs font-semibold text-muted-foreground", className)}
{...props} {...props}
/> />
); );
@ -78,12 +78,12 @@ function ComposerSuggestionHeader({
return ( return (
<div <div
className={cn( className={cn(
"flex items-center gap-2 px-2 py-1.5 text-sm font-semibold text-muted-foreground", "flex items-center gap-1.5 px-2 py-1 text-xs font-semibold text-muted-foreground",
className className
)} )}
{...props} {...props}
> >
{icon ? <span className="shrink-0 text-muted-foreground">{icon}</span> : null} {icon ? <span className="shrink-0 text-current [&_svg]:size-3.5">{icon}</span> : null}
{children} {children}
</div> </div>
); );
@ -103,7 +103,7 @@ const ComposerSuggestionItem = React.forwardRef<
variant="ghost" variant="ghost"
disabled={disabled} disabled={disabled}
className={cn( className={cn(
"h-auto w-full justify-start gap-2 rounded-md px-2.5 py-1.5 text-left text-sm font-normal transition-colors", "h-auto w-full justify-start gap-1.5 rounded-md px-2 py-1 text-left text-xs font-normal transition-colors",
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
muted && !selected && "text-muted-foreground hover:bg-accent hover:text-accent-foreground", muted && !selected && "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
selected && "bg-accent text-accent-foreground", selected && "bg-accent text-accent-foreground",
@ -111,7 +111,7 @@ const ComposerSuggestionItem = React.forwardRef<
)} )}
{...props} {...props}
> >
{icon ? <span className="shrink-0 text-muted-foreground">{icon}</span> : null} {icon ? <span className="shrink-0 text-current [&_svg]:size-3.5">{icon}</span> : null}
{children} {children}
</Button> </Button>
)); ));
@ -119,7 +119,7 @@ ComposerSuggestionItem.displayName = "ComposerSuggestionItem";
function ComposerSuggestionSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) { function ComposerSuggestionSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {
return ( return (
<div className={cn("my-1 px-3", className)}> <div className={cn("my-0.5 px-2.5", className)}>
<Separator className="bg-popover-border" {...props} /> <Separator className="bg-popover-border" {...props} />
</div> </div>
); );
@ -134,7 +134,7 @@ function ComposerSuggestionMessage({
<div className="px-1.5 py-1"> <div className="px-1.5 py-1">
<p <p
className={cn( className={cn(
"px-2.5 py-1.5 text-xs", "px-2 py-1 text-xs",
variant === "destructive" ? "text-destructive" : "text-muted-foreground", variant === "destructive" ? "text-destructive" : "text-muted-foreground",
className className
)} )}
@ -148,22 +148,22 @@ function ComposerSuggestionMessage({
function ComposerSuggestionSkeleton() { function ComposerSuggestionSkeleton() {
return ( return (
<div className="px-1.5 py-1"> <div className="px-1.5 py-1">
<div className="px-2.5 py-1.5"> <div className="px-2 py-1">
<Skeleton className="h-[16px] w-24" /> <Skeleton className="h-3.5 w-20" />
</div> </div>
{["a", "b", "c", "d", "e"].map((id, index) => ( {["a", "b", "c", "d", "e"].map((id, index) => (
<div <div
key={id} key={id}
className={cn( className={cn(
"flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left", "flex w-full items-center gap-1.5 rounded-md px-2 py-1 text-left",
index >= 3 && "hidden sm:flex" index >= 3 && "hidden sm:flex"
)} )}
> >
<span className="shrink-0"> <span className="shrink-0">
<Skeleton className="size-4" /> <Skeleton className="size-3.5" />
</span> </span>
<span className="flex-1 text-sm"> <span className="flex-1 text-xs">
<Skeleton className="h-[20px]" style={{ width: `${60 + ((index * 7) % 30)}%` }} /> <Skeleton className="h-4" style={{ width: `${60 + ((index * 7) % 30)}%` }} />
</span> </span>
</div> </div>
))} ))}

View file

@ -11,7 +11,9 @@ import {
Unplug, Unplug,
} from "lucide-react"; } from "lucide-react";
import { import {
Fragment,
forwardRef, forwardRef,
type UIEvent,
useCallback, useCallback,
useDeferredValue, useDeferredValue,
useEffect, useEffect,
@ -19,7 +21,6 @@ import {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import type * as React from "react";
import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
@ -61,6 +62,8 @@ interface DocumentMentionPickerProps {
const PAGE_SIZE = 20; const PAGE_SIZE = 20;
const MIN_SEARCH_LENGTH = 2; const MIN_SEARCH_LENGTH = 2;
const DEBOUNCE_MS = 100; const DEBOUNCE_MS = 100;
const RECENTS_LIMIT = 3;
const RECENTS_STORAGE_PREFIX = "surfsense:composer-mention-recents:v1:";
type BrowseView = type BrowseView =
| { kind: "root" } | { kind: "root" }
@ -77,6 +80,89 @@ function isConnectorActive(connector: SearchSourceConnector) {
return connector.is_active !== false; return connector.is_active !== false;
} }
function isMentionedContextItem(value: unknown): value is MentionedDocumentInfo {
if (!value || typeof value !== "object") return false;
const item = value as Partial<MentionedDocumentInfo>;
if (typeof item.id !== "number" || typeof item.title !== "string") return false;
if (item.kind === "doc") return typeof item.document_type === "string";
if (item.kind === "folder") return true;
if (item.kind === "connector") {
return typeof item.connector_type === "string" && typeof item.account_name === "string";
}
return false;
}
function getRecentsStorageKey(searchSpaceId: number) {
return `${RECENTS_STORAGE_PREFIX}${searchSpaceId}`;
}
function readRecentMentions(searchSpaceId: number): MentionedDocumentInfo[] {
if (typeof window === "undefined") return [];
try {
const raw = window.localStorage.getItem(getRecentsStorageKey(searchSpaceId));
if (!raw) return [];
const parsed: unknown = JSON.parse(raw);
if (!Array.isArray(parsed)) return [];
return parsed.filter(isMentionedContextItem).slice(0, RECENTS_LIMIT);
} catch {
return [];
}
}
function writeRecentMentions(searchSpaceId: number, mentions: MentionedDocumentInfo[]) {
if (typeof window === "undefined") return;
try {
window.localStorage.setItem(
getRecentsStorageKey(searchSpaceId),
JSON.stringify(mentions.slice(0, RECENTS_LIMIT))
);
} catch {
// Recents are optional UI state; storage failures should not block mention insertion.
}
}
export function promoteRecentMention(searchSpaceId: number, mention: MentionedDocumentInfo) {
const mentionKey = getMentionDocKey(mention);
const next = [
mention,
...readRecentMentions(searchSpaceId).filter((item) => getMentionDocKey(item) !== mentionKey),
].slice(0, RECENTS_LIMIT);
writeRecentMentions(searchSpaceId, next);
return next;
}
function getMentionIcon(mention: MentionedDocumentInfo) {
if (mention.kind === "folder") return <FolderIcon className="size-4" />;
if (mention.kind === "connector") {
return getConnectorIcon(mention.connector_type, "size-4") ?? <Unplug className="size-4" />;
}
return getConnectorIcon(mention.document_type, "size-4");
}
function refreshRecentMention(
mention: MentionedDocumentInfo,
documents: Pick<Document, "id" | "title" | "document_type">[],
folders: { id: number; name: string }[],
connectors: SearchSourceConnector[],
hasHydratedRecentDocs: boolean
): MentionedDocumentInfo | null {
if (mention.kind === "doc") {
const doc = documents.find(
(item) => item.id === mention.id && item.document_type === mention.document_type
);
if (doc) return makeDocMention(doc);
return hasHydratedRecentDocs ? null : mention;
}
if (mention.kind === "folder") {
const folder = folders.find((item) => item.id === mention.id);
return folder ? makeFolderMention({ id: folder.id, title: folder.name }) : null;
}
const connector = connectors.find(
(item) => item.id === mention.id && item.connector_type === mention.connector_type
);
return connector ? makeConnectorMention(connector) : null;
}
function useDebounced<T>(value: T, delay = DEBOUNCE_MS) { function useDebounced<T>(value: T, delay = DEBOUNCE_MS) {
const [debounced, setDebounced] = useState(value); const [debounced, setDebounced] = useState(value);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined); const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
@ -156,6 +242,9 @@ export const DocumentMentionPicker = forwardRef<
const [currentPage, setCurrentPage] = useState(0); const [currentPage, setCurrentPage] = useState(0);
const [hasMore, setHasMore] = useState(false); const [hasMore, setHasMore] = useState(false);
const [isLoadingMore, setIsLoadingMore] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false);
const [recentMentions, setRecentMentions] = useState<MentionedDocumentInfo[]>(() =>
readRecentMentions(searchSpaceId)
);
const [zeroFolders] = useZeroQuery(queries.folders.bySpace({ searchSpaceId })); const [zeroFolders] = useZeroQuery(queries.folders.bySpace({ searchSpaceId }));
const { data: connectors = [], isLoading: isConnectorsLoading } = useAtomValue(connectorsAtom); const { data: connectors = [], isLoading: isConnectorsLoading } = useAtomValue(connectorsAtom);
@ -178,6 +267,10 @@ export const DocumentMentionPicker = forwardRef<
if (hasSearch) setView({ kind: "root" }); if (hasSearch) setView({ kind: "root" });
}, [hasSearch]); }, [hasSearch]);
useEffect(() => {
setRecentMentions(readRecentMentions(searchSpaceId));
}, [searchSpaceId]);
const titleSearchParams = useMemo( const titleSearchParams = useMemo(
() => ({ () => ({
search_space_id: searchSpaceId, search_space_id: searchSpaceId,
@ -299,6 +392,47 @@ export const DocumentMentionPicker = forwardRef<
() => activeConnectors.map(makeConnectorMention), () => activeConnectors.map(makeConnectorMention),
[activeConnectors] [activeConnectors]
); );
const recentDocMentions = useMemo(
() => recentMentions.filter((mention) => mention.kind === "doc"),
[recentMentions]
);
const recentDocIdsKey = useMemo(
() => recentDocMentions.map((mention) => mention.id).join(","),
[recentDocMentions]
);
const { data: hydratedRecentDocs = [], isFetched: hasHydratedRecentDocs } = useQuery({
queryKey: ["composer-mention-recent-docs", searchSpaceId, recentDocIdsKey],
queryFn: async () => {
const results = await Promise.allSettled(
recentDocMentions.map((mention) => documentsApiService.getDocument({ id: mention.id }))
);
return results
.map((result) => (result.status === "fulfilled" ? result.value : null))
.filter((doc): doc is Document => doc !== null);
},
enabled: recentDocMentions.length > 0,
staleTime: 60 * 1000,
});
const recentValidationDocuments = useMemo(
() => [...actualDocuments, ...hydratedRecentDocs],
[actualDocuments, hydratedRecentDocs]
);
const visibleRecentMentions = useMemo(
() =>
recentMentions
.map((mention) =>
refreshRecentMention(
mention,
recentValidationDocuments,
zeroFolders ?? [],
activeConnectors,
hasHydratedRecentDocs
)
)
.filter((mention): mention is MentionedDocumentInfo => mention !== null)
.slice(0, RECENTS_LIMIT),
[activeConnectors, hasHydratedRecentDocs, recentMentions, recentValidationDocuments, zeroFolders]
);
const selectedKeys = useMemo( const selectedKeys = useMemo(
() => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))),
@ -313,10 +447,22 @@ export const DocumentMentionPicker = forwardRef<
}, },
[initialSelectedDocuments, onSelectionChange, onDone] [initialSelectedDocuments, onSelectionChange, onDone]
); );
const recentRootNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>(
() =>
visibleRecentMentions.map((mention) => ({
id: `recent:${getMentionDocKey(mention)}`,
label: mention.title,
icon: getMentionIcon(mention),
type: "item" as const,
disabled: selectedKeys.has(getMentionDocKey(mention)),
value: { kind: "mention" as const, mention },
})),
[visibleRecentMentions, selectedKeys]
);
const rootNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>( const rootNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>(
() => { () => {
const nodes: ComposerSuggestionNode<ResourceNodeValue>[] = []; const nodes: ComposerSuggestionNode<ResourceNodeValue>[] = [...recentRootNodes];
if (showSurfsenseDocsRootRef.current) { if (showSurfsenseDocsRootRef.current) {
nodes.push({ nodes.push({
id: "surfsense-docs", id: "surfsense-docs",
@ -350,7 +496,7 @@ export const DocumentMentionPicker = forwardRef<
); );
return nodes; return nodes;
}, },
[activeConnectors.length] [activeConnectors.length, recentRootNodes]
); );
const searchNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>(() => { const searchNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>(() => {
@ -519,10 +665,11 @@ export const DocumentMentionPicker = forwardRef<
onBack: handleBack, onBack: handleBack,
ref, ref,
}); });
const canLoadMoreDocuments = hasSearch || view.kind === "files-folders";
const handleScroll = useCallback( const handleScroll = useCallback(
(e: React.UIEvent<HTMLDivElement>) => { (e: UIEvent<HTMLDivElement>) => {
if (view.kind === "connectors" || view.kind === "connector-type") return; if (!canLoadMoreDocuments) return;
const target = e.currentTarget; const target = e.currentTarget;
const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight; const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
@ -530,7 +677,7 @@ export const DocumentMentionPicker = forwardRef<
loadNextPage(); loadNextPage();
} }
}, },
[hasMore, isLoadingMore, loadNextPage, view.kind] [canLoadMoreDocuments, hasMore, isLoadingMore, loadNextPage]
); );
const actualLoading = const actualLoading =
@ -564,15 +711,21 @@ export const DocumentMentionPicker = forwardRef<
{title ? ( {title ? (
<> <>
<ComposerSuggestionHeader <ComposerSuggestionHeader
icon={ role="button"
<button tabIndex={0}
type="button" aria-label={`Back from ${title}`}
onClick={handleBack} onClick={handleBack}
aria-label="Back" onKeyDown={(event) => {
className="-ml-0.5 flex size-5 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" if (event.key === "Enter" || event.key === " ") {
> event.preventDefault();
<ChevronLeft className="size-4" /> handleBack();
</button> }
}}
className="cursor-pointer rounded-sm transition-colors hover:text-foreground focus-visible:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
icon={
<span className="-ml-0.5 flex size-4.5 items-center justify-center">
<ChevronLeft className="size-3.5" />
</span>
} }
> >
<span className="flex-1 truncate">{title}</span> <span className="flex-1 truncate">{title}</span>
@ -586,9 +739,19 @@ export const DocumentMentionPicker = forwardRef<
{hasSearch ? ( {hasSearch ? (
<ComposerSuggestionGroupHeading>Suggested Context</ComposerSuggestionGroupHeading> <ComposerSuggestionGroupHeading>Suggested Context</ComposerSuggestionGroupHeading>
) : null} ) : null}
{visibleNodes.map((node, index) => ( {!hasSearch && view.kind === "root" && recentRootNodes.length > 0 ? (
<ComposerSuggestionGroupHeading>Recents</ComposerSuggestionGroupHeading>
) : null}
{visibleNodes.map((node, index) => {
const showRecentsSeparator =
!hasSearch &&
view.kind === "root" &&
recentRootNodes.length > 0 &&
index === recentRootNodes.length;
return (
<Fragment key={node.id}>
{showRecentsSeparator ? <ComposerSuggestionSeparator /> : null}
<ComposerSuggestionItem <ComposerSuggestionItem
key={node.id}
ref={navigator.getItemRef(index)} ref={navigator.getItemRef(index)}
icon={node.icon} icon={node.icon}
selected={index === navigator.highlightedIndex} selected={index === navigator.highlightedIndex}
@ -597,20 +760,22 @@ export const DocumentMentionPicker = forwardRef<
onMouseEnter={() => navigator.setHighlightedIndex(index)} onMouseEnter={() => navigator.setHighlightedIndex(index)}
> >
<span className="min-w-0 flex-1"> <span className="min-w-0 flex-1">
<span className="block truncate text-sm" title={node.label}> <span className="block truncate text-xs" title={node.label}>
{node.label} {node.label}
</span> </span>
{node.subtitle ? ( {node.subtitle ? (
<span className="block truncate text-[11px] text-muted-foreground"> <span className="block truncate text-[10px] text-muted-foreground">
{node.subtitle} {node.subtitle}
</span> </span>
) : null} ) : null}
</span> </span>
{node.type === "branch" ? ( {node.type === "branch" ? (
<ChevronRight className="size-4 shrink-0 text-muted-foreground" /> <ChevronRight className="size-3.5 shrink-0 text-muted-foreground" />
) : null} ) : null}
</ComposerSuggestionItem> </ComposerSuggestionItem>
))} </Fragment>
);
})}
</> </>
) : ( ) : (
<ComposerSuggestionMessage> <ComposerSuggestionMessage>
@ -618,7 +783,7 @@ export const DocumentMentionPicker = forwardRef<
</ComposerSuggestionMessage> </ComposerSuggestionMessage>
)} )}
{isLoadingMore && ( {canLoadMoreDocuments && isLoadingMore && (
<div className="flex items-center justify-center py-2 text-primary"> <div className="flex items-center justify-center py-2 text-primary">
<Spinner size="sm" /> <Spinner size="sm" />
</div> </div>

View file

@ -142,12 +142,12 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct
if (el) itemRefs.current.set(index, el); if (el) itemRefs.current.set(index, el);
else itemRefs.current.delete(index); else itemRefs.current.delete(index);
}} }}
icon={<WandSparkles className="size-4" />} icon={<WandSparkles className="size-3.5" />}
selected={index === highlightedIndex} selected={index === highlightedIndex}
onClick={() => handleSelect(index)} onClick={() => handleSelect(index)}
onMouseEnter={() => setHighlightedIndex(index)} onMouseEnter={() => setHighlightedIndex(index)}
> >
<span className="flex-1 truncate text-sm">{action.name}</span> <span className="flex-1 truncate text-xs">{action.name}</span>
</ComposerSuggestionItem> </ComposerSuggestionItem>
))} ))}
@ -157,7 +157,7 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct
if (el) itemRefs.current.set(createPromptIndex, el); if (el) itemRefs.current.set(createPromptIndex, el);
else itemRefs.current.delete(createPromptIndex); else itemRefs.current.delete(createPromptIndex);
}} }}
icon={<Plus className="size-4" />} icon={<Plus className="size-3.5" />}
muted muted
selected={highlightedIndex === createPromptIndex} selected={highlightedIndex === createPromptIndex}
onClick={() => handleSelect(createPromptIndex)} onClick={() => handleSelect(createPromptIndex)}