Skip to content

Commit aed97d2

Browse files
committed
feat(search): show what the query search understood
1 parent 4168ea4 commit aed97d2

5 files changed

Lines changed: 73 additions & 2 deletions

File tree

‎src/api/generated/index.ts‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎src/api/generated/types.gen.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,15 @@ export type ListingPage = {
934934
understood: string;
935935
};
936936

937+
/**
938+
* The moderation queue's page: the same cards, without the two search fields. That worklist takes no query, so there is nothing it could have understood, and a key that is always empty reads as a search a moderator could have run.
939+
*
940+
*/
941+
export type ListingQueuePage = {
942+
data: Array<Listing>;
943+
meta: PageMeta;
944+
};
945+
937946
/**
938947
* The slug a link carries: the listing's name, slugified and frozen at creation, with the listing's id on the end. The text in front is for the person reading the link and is not matched — renaming a listing does not rewrite links already shared, and a stale one still resolves because the id behind the last hyphen is what addresses the listing.
939948
*
@@ -3217,7 +3226,7 @@ export type GetAdminListingsResponses = {
32173226
/**
32183227
* OK
32193228
*/
3220-
200: ListingPage;
3229+
200: ListingQueuePage;
32213230
};
32223231

32233232
export type GetAdminListingsResponse = GetAdminListingsResponses[keyof GetAdminListingsResponses];
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"use client";
2+
3+
import type { SearchState } from "../_hooks/useSearchFilters";
4+
5+
/**
6+
* What the search made of a typed query, and the phrases that actually widened the
7+
* ranking.
8+
*
9+
* `understood` is model-written prose, rendered as plain text and never as HTML — it
10+
* stays hidden whenever the model had nothing usable to add: a browse with no query
11+
* answers both fields empty, and a query the model could not read still gets its base
12+
* retrieval rather than a claimed understanding it never reached. `probes` always
13+
* includes the shopper's own words alongside whatever was corrected or expanded, so only
14+
* the phrases that differ from what they typed are shown as chips — echoing their own
15+
* words back as a "correction" would be exactly the coincidence this feature exists to
16+
* explain away.
17+
*/
18+
export default function SearchUnderstanding({ search }: { search: SearchState }) {
19+
const { understood, probes } = search.feed;
20+
const own = search.query.trim().toLowerCase();
21+
const corrected = probes.filter((probe) => probe.trim().toLowerCase() !== own);
22+
23+
if (!understood && corrected.length === 0) return null;
24+
25+
return (
26+
<div className="mb-6 flex flex-col gap-2">
27+
{understood && (
28+
<p className="flex items-start gap-2 text-body-sm text-on-surface-variant">
29+
<span
30+
className="material-symbols-outlined text-base text-primary shrink-0"
31+
aria-hidden="true"
32+
>
33+
auto_awesome
34+
</span>
35+
<span>{understood}</span>
36+
</p>
37+
)}
38+
39+
{corrected.length > 0 && (
40+
<div className="flex flex-wrap items-center gap-2">
41+
<span className="text-label-sm text-on-surface-variant">Cũng tìm theo:</span>
42+
{corrected.map((probe) => (
43+
<span
44+
key={probe}
45+
className="rounded-full border border-outline-variant/30 bg-surface-container-low px-3 py-1 text-label-sm text-on-surface-variant"
46+
>
47+
{probe}
48+
</span>
49+
))}
50+
</div>
51+
)}
52+
</div>
53+
);
54+
}

‎src/app/search/page.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ActiveFilterChips from "./_components/ActiveFilterChips";
55
import SearchFilterPanel from "./_components/SearchFilterPanel";
66
import SearchResults from "./_components/SearchResults";
77
import SearchToolbar from "./_components/SearchToolbar";
8+
import SearchUnderstanding from "./_components/SearchUnderstanding";
89
import { useSearchFilters } from "./_hooks/useSearchFilters";
910

1011
function SearchPageContent(): React.ReactElement {
@@ -17,6 +18,7 @@ function SearchPageContent(): React.ReactElement {
1718

1819
<section className="md:col-span-9">
1920
<SearchToolbar search={search} />
21+
<SearchUnderstanding search={search} />
2022
<ActiveFilterChips search={search} />
2123
<SearchResults search={search} />
2224
</section>

‎src/hooks/api/useCatalog.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,17 @@ export function useListingsFeed(filters: ListingFilters = {}, enabled = true) {
8686
})
8787

8888
const listings = useMemo(() => flattenPages(query.data), [query.data])
89+
// `understood` and `probes` describe the query, not any one page of it, so every page
90+
// repeats them and the first is enough. Both read as empty before the first page lands,
91+
// same as an unset query — there is nothing yet to explain either way.
92+
const firstPage = query.data?.pages[0]
8993

9094
return {
9195
...query,
9296
listings,
9397
totalCount: totalCountOf(query.data),
98+
understood: firstPage?.understood ?? "",
99+
probes: firstPage?.probes ?? [],
94100
}
95101
}
96102

0 commit comments

Comments
 (0)