search: slightly better results appearance
This commit is contained in:
@@ -1,10 +1,25 @@
|
||||
import { Search } from "lucide-preact";
|
||||
import type { SearchDetail, SearchResultItem } from "@/lib/api";
|
||||
import { MarkdownContent } from "@/components/markdown/markdown-content";
|
||||
|
||||
function cleanResultText(input: string) {
|
||||
return input
|
||||
.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, "$1")
|
||||
.replace(/\[\s*\]/g, " ")
|
||||
.replace(/(^|\s)#{1,6}\s*/g, "$1")
|
||||
.replace(/^\s*[-*+]\s+/gm, "")
|
||||
.replace(/(\*\*|__|\*|_|`{1,3}|~~)/g, "")
|
||||
.replace(/\r?\n+/g, " ")
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function summarizeResult(result: SearchResultItem) {
|
||||
const highlights = Array.isArray(result.highlights) ? result.highlights.filter(Boolean) : [];
|
||||
if (highlights.length) return highlights.join(" ").slice(0, 420);
|
||||
return (result.text ?? "").slice(0, 420);
|
||||
const raw = highlights.length ? highlights.join(" ") : result.text ?? "";
|
||||
const cleaned = cleanResultText(raw);
|
||||
if (cleaned.length <= 680) return cleaned;
|
||||
return `${cleaned.slice(0, 679).trimEnd()}…`;
|
||||
}
|
||||
|
||||
function formatHost(url: string) {
|
||||
@@ -15,6 +30,17 @@ function formatHost(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHref(href: string) {
|
||||
try {
|
||||
const parsed = new URL(href);
|
||||
parsed.hash = "";
|
||||
const normalized = parsed.toString();
|
||||
return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
||||
} catch {
|
||||
return href.trim().replace(/\/$/, "");
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
search: SearchDetail | null;
|
||||
isLoading: boolean;
|
||||
@@ -24,6 +50,24 @@ type Props = {
|
||||
};
|
||||
|
||||
export function SearchResultsPanel({ search, isLoading, isRunning, showPrompt = true, className }: Props) {
|
||||
const citationEntries = (search?.answerCitations ?? [])
|
||||
.map((citation, index) => {
|
||||
const href = citation.url || citation.id || "";
|
||||
if (!href) return null;
|
||||
return {
|
||||
href,
|
||||
normalizedHref: normalizeHref(href),
|
||||
index: index + 1,
|
||||
label: citation.title?.trim() || formatHost(href),
|
||||
};
|
||||
})
|
||||
.filter((entry): entry is { href: string; normalizedHref: string; index: number; label: string } => !!entry);
|
||||
|
||||
const resolveCitationIndex = (href: string) => {
|
||||
const normalized = normalizeHref(href);
|
||||
return citationEntries.find((entry) => entry.normalizedHref === normalized)?.index;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className ?? "mx-auto w-full max-w-4xl"}>
|
||||
{search?.query ? (
|
||||
@@ -41,22 +85,28 @@ export function SearchResultsPanel({ search, isLoading, isRunning, showPrompt =
|
||||
<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>
|
||||
{isRunning && !search?.answerText ? <p className="mt-2 text-sm text-muted-foreground">Generating answer...</p> : null}
|
||||
{search?.answerText ? <p className="mt-2 whitespace-pre-wrap text-sm leading-6 text-slate-100">{search.answerText}</p> : null}
|
||||
{search?.answerText ? (
|
||||
<MarkdownContent
|
||||
markdown={search.answerText}
|
||||
mode="citationTokens"
|
||||
resolveCitationIndex={resolveCitationIndex}
|
||||
className="mt-2 text-sm leading-6 text-slate-100"
|
||||
/>
|
||||
) : null}
|
||||
{search?.answerError ? <p className="mt-2 text-sm text-red-500">{search.answerError}</p> : null}
|
||||
{!!search?.answerCitations?.length && (
|
||||
{!!citationEntries.length && (
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{search.answerCitations.slice(0, 6).map((citation, index) => {
|
||||
const href = citation.url || citation.id || "";
|
||||
if (!href) return null;
|
||||
{citationEntries.slice(0, 8).map((citation) => {
|
||||
return (
|
||||
<a
|
||||
key={`${href}-${index}`}
|
||||
href={href}
|
||||
key={`${citation.href}-${citation.index}`}
|
||||
href={citation.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="rounded-md border border-slate-500/60 px-2 py-1 text-xs text-sky-200 hover:bg-slate-700/40"
|
||||
>
|
||||
{citation.title?.trim() || formatHost(href)}
|
||||
<span className="mr-1 rounded bg-slate-700/80 px-1 py-0.5 text-[10px] text-slate-100">{citation.index}</span>
|
||||
{citation.label}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
@@ -93,7 +143,7 @@ export function SearchResultsPanel({ search, isLoading, isRunning, showPrompt =
|
||||
{(result.publishedDate || result.author) && (
|
||||
<p className="mt-1 text-xs text-muted-foreground">{[result.publishedDate, result.author].filter(Boolean).join(" • ")}</p>
|
||||
)}
|
||||
{summary ? <p className="mt-2 whitespace-pre-wrap text-sm leading-6 text-slate-200">{summary}</p> : null}
|
||||
{summary ? <p className="mt-2 text-sm leading-6 text-slate-200">{summary}</p> : null}
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user