feat: enhance person merging functionality with name filtering

- Updated the `listSimilarFaces` API to accept a name parameter for filtering results.
- Modified the `PersonMergeDropdown` component to include a new select input for choosing between "nameless", "tagged", and "all" options.
- Adjusted the API handler to filter similar faces based on the selected name criteria.
- Improved the UI layout for the threshold and name selection inputs.
This commit is contained in:
Varun Raj
2025-10-29 22:35:38 +05:30
parent 2dba053b86
commit 6259481d3e
3 changed files with 45 additions and 11 deletions
+26 -7
View File
@@ -33,6 +33,8 @@ import { CaretDownIcon } from "@radix-ui/react-icons";
import FaceThumbnail from "./merge/FaceThumbnail";
import ErrorBlock from "../shared/ErrorBlock";
import { cn } from "@/lib/utils";
import { Label } from "../ui/label";
import { Select, SelectItem, SelectContent, SelectTrigger, SelectValue } from "../ui/select";
interface PersonMergeDropdownProps {
person: IPerson;
@@ -57,6 +59,7 @@ export function PersonMergeDropdown({
const [primaryPerson, setPrimaryPerson] = useState<IPerson>(person);
const [merging, setMerging] = useState(false);
const [threshold, setThreshold] = useState(0.5);
const [name, setName] = useState<"nameless" | "tagged">("tagged");
const { toast } = useToast();
const selectedIds = useMemo(
@@ -90,7 +93,7 @@ export function PersonMergeDropdown({
};
const fetchSuggestions = () => {
return listSimilarFaces(person.id, threshold)
return listSimilarFaces(person.id, { threshold, name: name })
.then(setSimilarPeople)
.catch(() => {
toast({
@@ -168,11 +171,11 @@ export function PersonMergeDropdown({
setSimilarLoading(true);
fetchSuggestions();
}
}, [threshold, open]);
}, [threshold, name, open]);
useEffect(() => {
if (open && !similarPeople.length) fetchSuggestions();
}, [open, person.id, similarPeople]);
}, [open, person.id, similarPeople, threshold, name]);
useEffect(() => {
setPrimaryPerson(person);
@@ -291,10 +294,11 @@ export function PersonMergeDropdown({
handleSearch(e.target.value);
}}
/>
<div>
<label htmlFor="threshold" className="block text-sm font-medium text-gray-300">
<div className="flex gap-2">
<div className="flex flex-col gap-2 flex-1">
<Label htmlFor="threshold">
Threshold
</label>
</Label>
<Input
type="number"
step="0.01"
@@ -303,9 +307,24 @@ export function PersonMergeDropdown({
value={threshold}
onChange={(e) => setThreshold(parseFloat(e.target.value))}
placeholder="Threshold (0 to 1)"
className="mt-2"
/>
</div>
<div className="flex flex-col gap-2 flex-1">
<Label htmlFor="threshold">
Name
</Label>
<Select value={name} onValueChange={(value) => setName(value as "nameless" | "tagged")}>
<SelectTrigger>
<SelectValue placeholder="Select a name" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="tagged">Tagged</SelectItem>
<SelectItem value="nameless">Nameless</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{selectedPeople.length > 0 ? (
<div className="flex flex-nowrap overflow-x-auto gap-2 py-2">
+7 -2
View File
@@ -41,7 +41,12 @@ export const mergePerson = (id: string, targetIds: string[]) => {
return API.post(MERGE_PERSON_PATH(id), { ids: targetIds })
}
export const listSimilarFaces = (id: string, threshold: number) => {
return API.get(SIMILAR_FACES_PATH(id), { threshold: threshold })
interface IListSimilarFacesParams {
threshold: number;
name: "nameless" | "tagged";
}
export const listSimilarFaces = (id: string, params: IListSimilarFacesParams) => {
const { threshold, name } = params;
return API.get(SIMILAR_FACES_PATH(id), { threshold: threshold, name: name })
.then((response) => response.map((person: any) => cleanUpPerson(person, true)));
}
+12 -2
View File
@@ -12,7 +12,7 @@ interface IQuery {
id: string;
page: number;
perPage: number;
nameLessOnly: boolean | string;
name: "nameless" | "tagged" | "all";
minimumAssetCount: number;
sort: ISortField;
sortOrder: "asc" | "desc";
@@ -26,6 +26,7 @@ export default async function handler(
const {
id,
threshold = 0.5,
name,
} = req.query as any as IQuery;
const currentUser = await getCurrentUser(req);
@@ -97,7 +98,16 @@ export default async function handler(
)
.limit(12);
return res.status(200).json(people);
const filteredPeople = people.filter((personRecord) => {
if (name === "nameless") {
return !personRecord.name || personRecord.name === "";
} else if (name === "tagged") {
return personRecord.name && personRecord.name !== "";
}
return true;
});
return res.status(200).json(filteredPeople);
} catch (error: any) {
res.status(500).json({
error: error?.message,