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:
@@ -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">
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
Reference in New Issue
Block a user