fix: gemini response and query fixes

Signed-off-by: Varun Raj <varun@skcript.com>
This commit is contained in:
Varun Raj committed 2025-01-05 15:26:46 +05:30
1 parent bb6ffeb456
commit 1b986a831f
4 files changed
+94 -29

No files matched your search

+2 -1
View File
@@ -57,7 +57,8 @@ const responseSchema = {
export const parseFindQuery = async (query: string): Promise<FindQuery> => {
const prompt = `
Parse the following query and return the query and tags: ${query}
Parse the following query and return the query and tags: ${query}.
Dont include any information that are not intentionally provided in the query.
Additional Information For Parsing:
today's date is ${new Date().toISOString().split('T')[0]}
`;
+13 -1
View File
@@ -3,6 +3,7 @@ import { ENV } from '@/config/environment';
import { getCurrentUser } from '@/handlers/serverUtils/user.utils';
import { parseFindQuery } from '@/helpers/gemini.helper';
import { assetFaces, assets, person } from '@/schema';
import { Person } from '@/schema/person.schema';
import { and, eq, inArray } from 'drizzle-orm';
import { NextApiRequest, NextApiResponse } from 'next';
@@ -10,8 +11,16 @@ export default async function search(req: NextApiRequest, res: NextApiResponse)
const { query } = req.body;
const currentUser = await getCurrentUser(req);
const parsedQuery = await parseFindQuery(query as string);
const { personIds } = parsedQuery;
const url = ENV.IMMICH_URL + "/api/search/smart";
let dbPeople: Person[] = [];
if (personIds) {
dbPeople = await db.select().from(person).where(
inArray(person.id, personIds)
);
}
return fetch(url, {
method: 'POST',
body: JSON.stringify(parsedQuery),
@@ -33,7 +42,10 @@ export default async function search(req: NextApiRequest, res: NextApiResponse)
.then(data => {
res.status(200).json({
assets: data.assets.items,
filters: parsedQuery
filters: {
...parsedQuery,
personIds: dbPeople.map((person) => person.name)
},
});
}).catch(err => {
res.status(500).json({ error: err.message });
+76 -26
View File
@@ -7,14 +7,27 @@ import { ASSET_PREVIEW_PATH, ASSET_THUMBNAIL_PATH } from '@/config/routes';
import { useConfig } from '@/contexts/ConfigContext';
import { findAssets } from '@/handlers/api/asset.handler';
import { IAsset } from '@/types/asset';
import { Captions, Megaphone, Search, Speaker, TriangleAlert, WandSparkles } from 'lucide-react';
import { ArrowUpRight, Captions, Megaphone, Search, Speaker, TriangleAlert, WandSparkles } from 'lucide-react';
import Image from 'next/image';
import React, { useMemo, useState } from 'react'
import Lightbox from 'yet-another-react-lightbox';
import { Button } from "@/components/ui/button";
import Link from "next/link";
interface IFindFilters {
[key: string]: string;
}
const FILTER_KEY_MAP = {
"city": "City",
"state": "State",
"country": "Country",
"takenAfter": "Taken After",
"takenBefore": "Taken Before",
"size": "Size",
"model": "Model",
"personIds": "People",
}
export default function FindPage() {
const [index, setIndex] = useState(-1);
@@ -22,6 +35,7 @@ export default function FindPage() {
const [query, setQuery] = useState('');
const [assets, setAssets] = useState<IAsset[]>([]);
const [loading, setLoading] = useState(false);
const [filters, setFilters] = useState<IFindFilters>({});
const slides = useMemo(
() =>
@@ -33,19 +47,46 @@ export default function FindPage() {
[assets]
);
const appliedFilters: {
label: string;
value: string;
}[] = useMemo(() => {
return Object.entries(filters)
.filter(([_key, value]) => value !== undefined)
.map(([key, value]) => ({
label: key,
value: Array.isArray(value) ? value.join(', ') : value,
}))
.filter((filter) => filter.label !== "query");
}, [filters]);
const handleSearch = (query: string) => {
setQuery(query);
setLoading(true);
findAssets(query)
.then(({ assets }) => {
.then(({ assets, filters: _filters }) => {
setAssets(assets);
setFilters(_filters);
})
.finally(() => {
setLoading(false);
});
}
const renderFilters = () => {
if (appliedFilters.length === 0) return null;
return (
<div className="flex gap-2 flex-wrap px-2">
{appliedFilters.map((filter) => (
<div key={filter.label} className="flex gap-2 items-center divide-x divide-gray-400 dark:divide-zinc-800 bg-zinc-100 dark:bg-zinc-800 border border-gray-400 dark:border-zinc-800 rounded-md px-2">
<p className="text-sm text-gray-500 dark:text-zinc-400">{FILTER_KEY_MAP[filter.label as keyof typeof FILTER_KEY_MAP] || filter.label}</p>
<p className="text-sm text-gray-500 dark:text-zinc-400 pl-1.5">{filter.value}</p>
</div>
))}
</div>
)
}
const renderContent = () => {
if (loading) {
return <div className="flex justify-center items-center h-full">
@@ -53,37 +94,46 @@ export default function FindPage() {
</div>
}
else if (query.length === 0) {
return <div className="flex justify-center flex-col gap-2 items-center h-full">
<Search className='w-10 h-10 text-muted-foreground' />
<p className='text-lg font-bold'>Search for photos in natural language</p>
<p className='text-sm text-muted-foreground'>
Example: Sunset photos from last week. Use <kbd className='bg-zinc-200 text-black dark:text-white px-1 py-0.5 rounded-md dark:bg-zinc-500'>@</kbd> to search for photos of a specific person.
</p>
<p className='text-xs text-muted-foreground text-center flex gap-1 items-center'>
<span>Power tools uses Google Gemini only for parsing your query. None of your data is sent to Gemini.</span>
</p>
</div>
return (
<div className="flex justify-center flex-col gap-2 items-center h-full">
<Search className='w-10 h-10 text-muted-foreground' />
<p className='text-lg font-bold'>Search for photos in natural language</p>
<p className='text-sm text-muted-foreground'>
Example: Sunset photos from last week. Use <kbd className='bg-zinc-200 text-black dark:text-white px-1 py-0.5 rounded-md dark:bg-zinc-500'>@</kbd> to search for photos of a specific person.
</p>
<p className='text-xs text-muted-foreground text-center flex gap-1 items-center'>
<span>Power tools uses Google Gemini only for parsing your query. None of your data is sent to Gemini.</span>
</p>
</div>
)
}
else if (assets.length === 0) {
return <div className="flex justify-center items-center h-full">
No results found
return <div className="flex justify-center items-center h-full flex-col gap-2">
<TriangleAlert className='w-10 h-10 text-muted-foreground' />
<p className='text-lg font-bold'>No results found for the below filters</p>
<p className='text-sm text-muted-foreground'>
{renderFilters()}
</p>
</div>
}
return (
<>
<Lightbox
slides={slides}
open={index >= 0}
index={index}
close={() => setIndex(-1)}
/>
<Lightbox
slides={slides}
open={index >= 0}
index={index}
close={() => setIndex(-1)}
/>
{renderFilters()}
<div className="grid grid-cols-5 gap-4 p-2">
{assets.map((asset, idx) => (
<div key={asset.id} className='w-full h-[200px] rounded-md overflow-hidden relative'>
<Link href={`${exImmichUrl}/photos/${asset.id}`}
target="_blank"
className='absolute top-2 right-2 text-xs'>
Open in Immich
<div key={asset.id} className='group w-full h-[200px] rounded-md overflow-hidden relative'>
<Link href={`${exImmichUrl}/photos/${asset.id}`}
target="_blank"
className='absolute top-2 right-2 text-xs bg-white dark:bg-zinc-800 px-2 py-1 rounded-md flex gap-1 items-center opacity-0 group-hover:opacity-100 transition-opacity duration-300'>
Open
<ArrowUpRight className='w-4 h-4' />
</Link>
<Image
src={ASSET_THUMBNAIL_PATH(asset.id)}
+3 -1
View File
@@ -11,4 +11,6 @@ export const person = pgTable("person", {
isHidden: boolean("isHidden").notNull().default(false),
birthDate: date("birthDate", { mode: "date" }),
faceAssetId: uuid("faceAssetId"),
});
});
export type Person = typeof person.$inferSelect;