answers: collapsing behavior fixed height

This commit is contained in:
2026-02-14 02:02:18 -08:00
parent 769cd6966a
commit 444b5b33f0

View File

@@ -1,6 +1,8 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { Search } from "lucide-preact"; import { Search } from "lucide-preact";
import type { SearchDetail, SearchResultItem } from "@/lib/api"; import type { SearchDetail, SearchResultItem } from "@/lib/api";
import { MarkdownContent } from "@/components/markdown/markdown-content"; import { MarkdownContent } from "@/components/markdown/markdown-content";
import { cn } from "@/lib/utils";
function cleanResultText(input: string) { function cleanResultText(input: string) {
return input return input
@@ -50,6 +52,29 @@ type Props = {
}; };
export function SearchResultsPanel({ search, isLoading, isRunning, showPrompt = true, className }: Props) { export function SearchResultsPanel({ search, isLoading, isRunning, showPrompt = true, className }: Props) {
const ANSWER_COLLAPSED_HEIGHT_CLASS = "h-[3rem]";
const [isAnswerExpanded, setIsAnswerExpanded] = useState(false);
const [canExpandAnswer, setCanExpandAnswer] = useState(false);
const answerBodyRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
setIsAnswerExpanded(false);
setCanExpandAnswer(false);
}, [search?.id, search?.answerText]);
useEffect(() => {
const el = answerBodyRef.current;
if (!el || !search?.answerText) {
setCanExpandAnswer(false);
return;
}
if (isAnswerExpanded) {
setCanExpandAnswer(true);
return;
}
setCanExpandAnswer(el.scrollHeight - el.clientHeight > 1);
}, [search?.answerText, isAnswerExpanded]);
const citationEntries = (search?.answerCitations ?? []) const citationEntries = (search?.answerCitations ?? [])
.map((citation, index) => { .map((citation, index) => {
const href = citation.url || citation.id || ""; const href = citation.url || citation.id || "";
@@ -68,6 +93,11 @@ export function SearchResultsPanel({ search, isLoading, isRunning, showPrompt =
return citationEntries.find((entry) => entry.normalizedHref === normalized)?.index; return citationEntries.find((entry) => entry.normalizedHref === normalized)?.index;
}; };
const hasAnswerText = !!search?.answerText;
const isAnswerLoading = isRunning && !hasAnswerText;
const hasCitations = citationEntries.length > 0;
const isExpandable = hasAnswerText && (canExpandAnswer || hasCitations);
return ( return (
<div className={className ?? "mx-auto w-full max-w-4xl"}> <div className={className ?? "mx-auto w-full max-w-4xl"}>
{search?.query ? ( {search?.query ? (
@@ -84,17 +114,47 @@ export function SearchResultsPanel({ search, isLoading, isRunning, showPrompt =
{(isRunning || !!search?.answerText || !!search?.answerError) && ( {(isRunning || !!search?.answerText || !!search?.answerError) && (
<section className="mb-6 rounded-xl border border-slate-600/60 bg-[#121a2e] p-4"> <section className="mb-6 rounded-xl border border-slate-600/60 bg-[#121a2e] p-4">
<p className="text-xs font-semibold uppercase tracking-wide text-sky-300/90">Answer</p> <p className="text-xs font-semibold uppercase tracking-wide text-sky-300/90">Answer</p>
{isRunning && !search?.answerText ? <p className="mt-2 text-sm text-muted-foreground">Generating answer...</p> : null} {(isAnswerLoading || hasAnswerText) ? (
{search?.answerText ? ( <div className="mt-2">
<div className="relative">
<div
ref={answerBodyRef}
className={cn(
"overflow-hidden",
!isAnswerExpanded && ANSWER_COLLAPSED_HEIGHT_CLASS
)}
>
{isAnswerLoading ? (
<div className="text-sm text-muted-foreground">Generating answer...</div>
) : (
<MarkdownContent <MarkdownContent
markdown={search.answerText} markdown={search?.answerText ?? ""}
mode="citationTokens" mode="citationTokens"
resolveCitationIndex={resolveCitationIndex} resolveCitationIndex={resolveCitationIndex}
className="mt-2 text-sm leading-6 text-slate-100" className="text-sm leading-6 text-slate-100"
/> />
)}
</div>
{!isAnswerExpanded && (isExpandable || isAnswerLoading) ? (
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-7 bg-gradient-to-t from-[#121a2e] to-transparent" />
) : null}
</div>
<div className="mt-2 h-5">
{isExpandable ? (
<button
type="button"
className="inline-flex items-center gap-1 text-xs font-medium text-sky-200 hover:text-sky-100"
onClick={() => setIsAnswerExpanded((current) => !current)}
>
<span className={cn("inline-block text-[11px] transition-transform", isAnswerExpanded && "rotate-180")}></span>
{isAnswerExpanded ? "Show less" : "Show more"}
</button>
) : null}
</div>
</div>
) : null} ) : null}
{search?.answerError ? <p className="mt-2 text-sm text-red-500">{search.answerError}</p> : null} {search?.answerError ? <p className="mt-2 text-sm text-red-500">{search.answerError}</p> : null}
{!!citationEntries.length && ( {isAnswerExpanded && !!citationEntries.length && (
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
{citationEntries.slice(0, 8).map((citation) => { {citationEntries.slice(0, 8).map((citation) => {
return ( return (