Make chat thumbnails attach to composer on click

This commit is contained in:
Josh Hawkins 2026-04-08 16:57:06 -05:00
parent 16e98918c5
commit 2b538ae54b

View File

@ -1,5 +1,6 @@
import { useApiHost } from "@/api"; import { useApiHost } from "@/api";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { LuExternalLink } from "react-icons/lu";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
type ChatEvent = { id: string; score?: number }; type ChatEvent = { id: string; score?: number };
@ -7,17 +8,21 @@ type ChatEvent = { id: string; score?: number };
type ChatEventThumbnailsRowProps = { type ChatEventThumbnailsRowProps = {
events: ChatEvent[]; events: ChatEvent[];
anchor?: { id: string } | null; anchor?: { id: string } | null;
onAttach?: (eventId: string) => void;
}; };
/** /**
* Horizontal scroll row of event thumbnail images for chat. * Horizontal scroll row of event thumbnail images for chat.
* Optionally renders an anchor thumbnail with a "reference" badge above the * Optionally renders an anchor thumbnail with a "reference" badge above the
* results, and per-event similarity scores when provided. * results, and per-event similarity scores when provided.
* Clicking a thumbnail calls onAttach; a small external-link overlay opens
* the event in Explore.
* Renders nothing when there is nothing to show. * Renders nothing when there is nothing to show.
*/ */
export function ChatEventThumbnailsRow({ export function ChatEventThumbnailsRow({
events, events,
anchor = null, anchor = null,
onAttach,
}: ChatEventThumbnailsRowProps) { }: ChatEventThumbnailsRowProps) {
const apiHost = useApiHost(); const apiHost = useApiHost();
const { t } = useTranslation(["views/chat"]); const { t } = useTranslation(["views/chat"]);
@ -25,15 +30,18 @@ export function ChatEventThumbnailsRow({
if (events.length === 0 && !anchor) return null; if (events.length === 0 && !anchor) return null;
const renderThumb = (event: ChatEvent, isAnchor = false) => ( const renderThumb = (event: ChatEvent, isAnchor = false) => (
<a <div
key={event.id} key={event.id}
href={`/explore?event_id=${event.id}`}
target="_blank"
rel="noopener noreferrer"
className={cn( className={cn(
"relative aspect-square size-32 shrink-0 overflow-hidden rounded-lg", "relative aspect-square size-32 shrink-0 overflow-hidden rounded-lg",
isAnchor && "ring-2 ring-primary", isAnchor && "ring-2 ring-primary",
)} )}
>
<button
type="button"
className="block size-full"
onClick={() => onAttach?.(event.id)}
aria-label={t("attach_event_aria", { eventId: event.id })}
> >
<img <img
className="size-full object-cover" className="size-full object-cover"
@ -41,17 +49,28 @@ export function ChatEventThumbnailsRow({
alt="" alt=""
loading="lazy" loading="lazy"
/> />
</button>
<a
href={`/explore?event_id=${event.id}`}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="absolute right-1 top-1 flex size-6 items-center justify-center rounded bg-black/60 text-white hover:bg-black/80"
aria-label={t("open_in_explore")}
>
<LuExternalLink className="size-3" />
</a>
{typeof event.score === "number" && !isAnchor && ( {typeof event.score === "number" && !isAnchor && (
<span className="absolute bottom-1 right-1 rounded bg-black/60 px-1 text-[10px] text-white"> <span className="pointer-events-none absolute bottom-1 right-1 rounded bg-black/60 px-1 text-[10px] text-white">
{Math.round(event.score * 100)}% {Math.round(event.score * 100)}%
</span> </span>
)} )}
{isAnchor && ( {isAnchor && (
<span className="absolute left-1 top-1 rounded bg-primary px-1 text-[10px] text-primary-foreground"> <span className="pointer-events-none absolute left-1 top-1 rounded bg-primary px-1 text-[10px] text-primary-foreground">
{t("anchor")} {t("anchor")}
</span> </span>
)} )}
</a> </div>
); );
return ( return (