Skip to content

Commit

Permalink
AI-Powered Search History part 3
Browse files Browse the repository at this point in the history
  • Loading branch information
ahaapple committed Jan 6, 2025
1 parent 7cc92c6 commit dbe35ad
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 53 deletions.
2 changes: 1 addition & 1 deletion frontend/app/api/history-index/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function POST(req: Request) {
}

const result = await response.json();
return Response.json(result);
return Response.json('Success');
} catch (error) {
console.error('Full index error:', error);
return Response.json({ error: 'Failed to process full index' }, { status: 500 });
Expand Down
29 changes: 21 additions & 8 deletions frontend/components/modal/search-model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Loader2, MessageCircle } from 'lucide-react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { isUserFullIndexed } from '@/lib/store/search';
import { User } from '@/lib/types';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { resolveTime } from '@/lib/utils';
import { toast } from 'sonner';

interface SearchResult {
id: string;
Expand All @@ -29,6 +30,7 @@ interface SearchResult {
title: string;
url: string;
text: string;
create_time: Date;
}

export function SearchDialog({ openSearch: open, onOpenModelChange: onOpenChange, user: user }: SearchDialogProps) {
Expand All @@ -50,6 +52,7 @@ export function SearchDialog({ openSearch: open, onOpenModelChange: onOpenChange

const [isIndexing, setIsIndexing] = useState(false);
const handleFullIndex = async () => {
if (isIndexing) return;
setIsIndexing(true);
try {
const response = await fetch('/api/history-index', {
Expand All @@ -62,7 +65,12 @@ export function SearchDialog({ openSearch: open, onOpenModelChange: onOpenChange

const result = await response.json();
if (result === 'Success') {
setIsIndexed(true);
toast.success('Historical messages have started to index', {
description:
'MemFre is building your search index in the background. It will take several minutes to complete. You can use the search function after it is completed.',
duration: 5000,
});
onOpenChange(false);
}
} catch (error) {
console.error('Failed to trigger full index:', error);
Expand Down Expand Up @@ -137,31 +145,36 @@ export function SearchDialog({ openSearch: open, onOpenModelChange: onOpenChange
</Button>
</div>

<ScrollArea className="h-[400px] rounded-md border">
<div className="h-[400px] overflow-y-auto">
{isLoading ? (
<div className="flex items-center justify-center h-full">
<div className="text-sm text-muted-foreground">Searching ...</div>
</div>
) : (
<div className="divide-y">
<div>
{results.map((result) => (
<div
key={result.id}
className="flex items-start gap-3 p-4 hover:bg-muted/50 cursor-pointer transition-colors"
className="flex items-center gap-3 p-4 hover:bg-primary/20 cursor-pointer rounded-md relative group"
onClick={() => handleResultClick(result.url)}
>
<div className="mt-1">
<div className="flex items-center justify-center">
<MessageCircle className="h-5 w-5 text-muted-foreground" />
</div>
<div className="flex-1 min-w-0">
<h4 className="text-sm font-medium leading-none mb-1 truncate">{result.title}</h4>
<p className="text-sm text-muted-foreground line-clamp-2">{result.text}</p>
<p className="text-sm text-muted-foreground line-clamp-2 relative">
<span className="group-hover:mr-[100px] transition-all duration-200 block">{result.text}</span>
</p>
</div>
<div className="absolute right-2 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity">
<span className="text-sm text-muted-foreground px-2 py-1 rounded">{resolveTime(result.create_time)}</span>
</div>
</div>
))}
</div>
)}
</ScrollArea>
</div>
</div>
)}
</DialogContent>
Expand Down
6 changes: 3 additions & 3 deletions frontend/components/sidebar/sidebar-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ export function SidebarHeader({ user }: UserProps) {
<span className=" mx-2 font-urban text-xl font-bold">{siteConfig.name}</span>
</Link>
<div className="flex ml-auto space-x-2">
{/* <Button
<Button
variant="ghost"
className="hidden border-solid shadow-sm border-gray-200 dark:text-white dark:hover:bg-gray-700 rounded-full size-9 p-0 lg:flex"
onClick={() => setOpen(true)}
>
<Search className="size-4" />
</Button> */}
</Button>
<SidebarClose />
</div>
{/* <SearchDialog openSearch={open} onOpenModelChange={setOpen} user={user} /> */}
<SearchDialog openSearch={open} onOpenModelChange={setOpen} user={user} />
</div>
);
}
2 changes: 1 addition & 1 deletion frontend/components/sidebar/sidebar-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function SidebarItem({ search: search, children }: SidebarItemProps) {
<span>{search.messages.length}</span>
<span className="ml-1">{search.messages.length > 1 ? 'messages' : 'message'}</span>
</div>
{!isActive && <span>{resolveTime(search)}</span>}
{!isActive && <span>{resolveTime(search.createdAt)}</span>}
</div>
</div>
{isActive && <div className="absolute right-2 top-1/2 transform -translate-y-1/2">{children}</div>}
Expand Down
6 changes: 3 additions & 3 deletions frontend/lib/tools/auto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,9 @@ export async function autoAnswer(
// });

await saveMessages(userId, messages, fullAnswer, texts, images, videos, fullRelated, SearchCategory.ALL);
// indexMessage(userId, messages[0].title, messages[0].id, query + '\n\n' + fullAnswer).catch((error) => {
// console.error(`Failed to index message for user ${userId}:`, error);
// });
indexMessage(userId, messages[0].title, messages[0].id, query + '\n\n' + fullAnswer).catch((error) => {
console.error(`Failed to index message for user ${userId}:`, error);
});
onStream?.(null, true);
} catch (error) {
console.error('Error:', error);
Expand Down
6 changes: 3 additions & 3 deletions frontend/lib/tools/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ export async function chat(
// console.error(`Failed to increment search count for user ${userId}:`, error);
// });
await saveMessages(userId, messages, fullAnswer, [], [], [], '', SearchCategory.ALL);
// indexMessage(userId, messages[0].title, messages[0].id, query + '\n\n' + fullAnswer).catch((error) => {
// console.error(`Failed to index message for user ${userId}:`, error);
// });
indexMessage(userId, messages[0].title, messages[0].id, query + '\n\n' + fullAnswer).catch((error) => {
console.error(`Failed to index message for user ${userId}:`, error);
});

onStream?.(null, true);
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions frontend/lib/tools/index-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export async function indexMessage(userId: string, title: string, url: string, t
title: title,
text: text,
url: url,
timestamp: new Date(),
}),
});

Expand Down
6 changes: 3 additions & 3 deletions frontend/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export function formatDate(input: string | number): string {
});
}

export function resolveTime(search) {
const createdAt = new Date(search.createdAt);
export function resolveTime(timestamp: Date | number): string {
const createdAt = new Date(timestamp);
const now = new Date();

const timeDiff = now.getTime() - createdAt.getTime();
Expand All @@ -33,7 +33,7 @@ export function resolveTime(search) {
if (diffInHours <= 24) return `${diffInHours} hours ago`;
if (diffInDays <= 7) return `${diffInDays} days ago`;

return format(new Date(search.createdAt), 'MMM d, yyyy h:mm a');
return format(new Date(timestamp), 'MMM d, yyyy');
}

export function formatDateTime(input: string | number): string {
Expand Down
35 changes: 13 additions & 22 deletions vector/history-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,6 @@ async function handleRequest(req: Request): Promise<Response> {
const path = new URL(req.url).pathname;
const { method } = req;

// if (method === "OPTIONS") {
// const origin = req.headers.get("Origin");
// if (origin && allowedOrigins.includes(origin)) {
// return new Response("OK", {
// headers: {
// "Access-Control-Allow-Origin": origin,
// "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
// "Access-Control-Allow-Headers": "Authorization, Content-Type, Token",
// },
// });
// } else {
// return new Response("Forbidden", { status: 403 });
// }
// }

let authResponse = checkAuth(req, path);
if (authResponse) {
return authResponse;
Expand All @@ -46,7 +31,7 @@ async function handleRequest(req: Request): Promise<Response> {
const { query, userId } = await req.json();
try {
const result = await db.search(userId, query, {
selectFields: ["title", "text", "url", "image"],
selectFields: ["title", "text", "url", "image", "create_time"],
});
return Response.json(result);
} catch (unknownError) {
Expand Down Expand Up @@ -129,12 +114,18 @@ async function handleRequest(req: Request): Promise<Response> {

await markUserIndexing(userId);

const success = await processAllUserSearchMessages(userId);
if (!success) {
return Response.json("Failed to index all users", { status: 500 });
}
await markUserFullIndexed(userId);
await clearUserIndexing(userId);
Promise.resolve().then(async () => {
try {
const success = await processAllUserSearchMessages(userId);
if (success) {
await markUserFullIndexed(userId);
}
} catch (error) {
console.error("Background indexing error:", error);
} finally {
await clearUserIndexing(userId);
}
});
return Response.json("Success");
} catch (error) {
log({
Expand Down
9 changes: 6 additions & 3 deletions vector/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ export async function addVectors(
image: string,
title: string,
url: string,
documents: Document[]
documents: Document[],
timestamp?: number
): Promise<Array<Record<string, unknown>>> {
const texts = documents.map(({ pageContent }) => pageContent);
if (texts.length === 0) {
Expand All @@ -121,17 +122,19 @@ export async function addVectors(

const embeddings = await getEmbedding().embedDocuments(texts);
const data: Array<Record<string, unknown>> = [];

for (let i = 0; i < documents.length; i += 1) {
if (documents[i].pageContent.length < 10) {
continue;
}

const newImage = image ? extractImage(documents[i].pageContent) : null;

const record = {
create_time: Date.now(),
create_time: timestamp || Date.now(),
title: title,
url: url,
image: newImage ? newImage : image,
image: newImage || image,
text: documents[i].pageContent,
vector: embeddings[i] as number[],
};
Expand Down
15 changes: 10 additions & 5 deletions vector/memfree_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export async function processAllUserSearchMessages(
try {
let lastIndexedTime =
Number(await redis.get(LAST_INDEXED_TIME_KEY + userId)) || 0;
let hasError = false;

console.time("processSearchMessages");
while (true) {
Expand Down Expand Up @@ -111,22 +112,26 @@ export async function processAllUserSearchMessages(
"",
search.title,
search.id,
documents
documents,
new Date(search.createdAt).getTime()
);
console.log("data length", data.length);
await appendData(userId, data);

console.log("search title ", search.title, "search id ", search.id);
} catch (error) {
console.error(`Failed to process search ${search.id}:`, error);
hasError = true;
}
})
);

lastIndexedTime = lastTimestamp;
await redis.set(LAST_INDEXED_TIME_KEY + userId, lastIndexedTime);
if (!hasError) {
lastIndexedTime = lastTimestamp;
await redis.set(LAST_INDEXED_TIME_KEY + userId, lastIndexedTime);
}

if (!hasMore) {
if (!hasMore || hasError) {
break;
}
}
Expand All @@ -135,7 +140,7 @@ export async function processAllUserSearchMessages(

await compact(userId);

return true;
return !hasError;
} catch (error) {
console.error("Failed to process search messages:", error);
return false;
Expand Down
6 changes: 5 additions & 1 deletion vector/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export const TOTAL_SEARCH_COUNT_KEY = "t_s_count:";

export const SEARCH_KEY = "search:";
export const USER_SEARCH_KEY = "user:search:";
export const LAST_INDEXED_TIME_KEY = "user:last_indexed_time:";

export const LAST_INDEXED_TIME_KEY = "user:last_indexed_time:";
export const USER_FULL_INDEXED = "user:f-indexed:";
export const USER_INDEXING = "user:indexing:";

Expand All @@ -44,6 +44,10 @@ export async function clearUserIndexing(userId: string): Promise<void> {
await redis.del(USER_INDEXING + userId);
}

export async function clearUserIndexTime(userId: string): Promise<void> {
await redis.del(LAST_INDEXED_TIME_KEY + userId);
}

export async function markUserFullIndexed(userId: string): Promise<void> {
await redis.set(USER_FULL_INDEXED + userId, "1");
}
Expand Down

0 comments on commit dbe35ad

Please sign in to comment.