Skip to content

Commit

Permalink
Show videos in search result
Browse files Browse the repository at this point in the history
  • Loading branch information
ahaapple committed Sep 10, 2024
1 parent 2b7fb15 commit f475d87
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 108 deletions.
9 changes: 1 addition & 8 deletions frontend/app/[locale]/(search)/search/[id]/search-result.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export default function SearchResult({ id, user }: SearchPageProps) {

useEffect(() => {
if (!search) {
console.error('fetching search:', id, searches.length);
const fetchSearch = async () => {
const search = await getSearch(id, user.id);
if (search) {
Expand All @@ -28,11 +27,5 @@ export default function SearchResult({ id, user }: SearchPageProps) {
}
}, [id, user, search]);

return (
<SearchWindow
id={id}
initialMessages={search?.messages ?? []}
user={user}
></SearchWindow>
);
return <SearchWindow id={id} initialMessages={search?.messages ?? []} user={user}></SearchWindow>;
}
38 changes: 38 additions & 0 deletions frontend/components/search/expandable-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import { Minus, Plus } from 'lucide-react';
import React, { useEffect, useState } from 'react';

const ExpandableSection = ({ title, icon: Icon, children }) => {
const [isOpen, setIsOpen] = useState(true);

useEffect(() => {
const checkScreenSize = () => setIsOpen(window.innerWidth >= 768);
checkScreenSize();
window.addEventListener('resize', checkScreenSize);
return () => window.removeEventListener('resize', checkScreenSize);
}, []);

return (
<div className="w-full">
<details open={isOpen} className="w-full">
<summary
className="flex w-full cursor-pointer items-center justify-between mb-2"
onClick={(e) => {
e.preventDefault();
setIsOpen(!isOpen);
}}
>
<div className="flex items-center space-x-2">
<Icon className="text-primary size-22" />
<h3 className="py-2 text-lg font-bold text-primary">{title}</h3>
</div>
<div className="ml-auto">{isOpen ? <Minus className="text-primary size-22" /> : <Plus className="text-primary size-22" />}</div>
</summary>
{isOpen && children}
</details>
</div>
);
};

export default ExpandableSection;
31 changes: 16 additions & 15 deletions frontend/components/search/search-message-bubble.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import SourceBubble from '@/components/search/source-bubble';
import { FileTextIcon, Images, ListPlusIcon, PlusIcon, TextSearchIcon } from 'lucide-react';
import { FileTextIcon, Film, Images, ListPlusIcon, PlusIcon, TextSearchIcon, Youtube } from 'lucide-react';
import ImageGallery from '@/components/search/image-gallery';
import { Message } from '@/lib/types';

Expand All @@ -8,6 +8,8 @@ import AnswerSection from '@/components/search/answer-section';
import QuestionSection from '@/components/search/question-section';
import ActionButtons from '@/components/search/action-buttons';
import { extractAllImageUrls } from '@/lib/shared-utils';
import VideoGallery from '@/components/search/video-gallery';
import ExpandableSection from '@/components/search/expandable-section';

const SearchMessageBubble = memo(
(props: { searchId: string; message: Message; onSelect: (question: string) => void; reload: (msgId: string) => void; isLoading: boolean }) => {
Expand All @@ -20,6 +22,7 @@ const SearchMessageBubble = memo(
const message = props.message;
const sources = message.sources ?? [];
const images = message.images ?? [];
const videos = message.videos ?? [];
const searchId = props.searchId;

const attachments = useMemo(() => {
Expand All @@ -37,30 +40,28 @@ const SearchMessageBubble = memo(
<div className="flex flex-col w-full items-start space-y-6 pb-10">
{!isUser && content && <AnswerSection content={content} sources={sources} />}
{(images.length > 0 || !isLoading) && !isUser && <ActionButtons content={content} searchId={searchId} msgId={id} reload={reload} />}
{!isUser && sources.length > 0 && (
<div className="flex w-full flex-col items-start space-y-2.5 py-4">
<div className="flex items-center space-x-2">
<TextSearchIcon className="text-primary size-22"></TextSearchIcon>
<h3 className="text-lg font-bold text-primary">Sources</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-full overflow-auto ">
{sources.length > 0 && (
<ExpandableSection title="Sources" icon={TextSearchIcon}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{sources.map((source, index) => (
<div key={index}>
<SourceBubble source={source} onSelect={onSelect} />
</div>
))}
</div>
</div>
</ExpandableSection>
)}

{images.length > 0 && (
<div className="flex w-full flex-col items-start space-y-2.5 py-4">
<div className="flex items-center space-x-2">
<Images className="text-primary size-22"></Images>
<h3 className="py-2 text-lg font-bold text-primary">Images</h3>
</div>
<ExpandableSection title="Images" icon={Images}>
<ImageGallery initialImages={images}></ImageGallery>
</div>
</ExpandableSection>
)}

{videos.length > 0 && (
<ExpandableSection title="Videos" icon={Film}>
<VideoGallery videos={videos} />
</ExpandableSection>
)}

{related && (
Expand Down
17 changes: 13 additions & 4 deletions frontend/components/search/search-window.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useSigninModal } from '@/hooks/use-signin-modal';
import SearchBar from '@/components/search-bar';
import { configStore, useProfileStore } from '@/lib/store';

import { ImageSource, Message, TextSource, User } from '@/lib/types';
import { ImageSource, Message, TextSource, User, VideoSource } from '@/lib/types';
import { generateId } from 'ai';
import { LoaderCircle } from 'lucide-react';
import { useScrollAnchor } from '@/hooks/use-scroll-anchor';
Expand Down Expand Up @@ -99,7 +99,13 @@ export function SearchWindow({ id, initialMessages, user, isReadOnly = false }:
let accumulatedRelated = '';
let messageIndex: number | null = null;

const updateMessages = (parsedResult?: string, newSources?: TextSource[], newImages?: ImageSource[], newRelated?: string) => {
const updateMessages = (
parsedResult?: string,
newSources?: TextSource[],
newImages?: ImageSource[],
newRelated?: string,
newVideos?: VideoSource[],
) => {
const activeSearch = useSearchStore.getState().activeSearch;
if (messageIndex === null || !activeSearch.messages[messageIndex]) {
messageIndex = activeSearch.messages.length;
Expand All @@ -112,6 +118,7 @@ export function SearchWindow({ id, initialMessages, user, isReadOnly = false }:
sources: newSources || [],
images: newImages || [],
related: newRelated || '',
videos: newVideos || [],
role: 'assistant',
},
],
Expand All @@ -127,6 +134,7 @@ export function SearchWindow({ id, initialMessages, user, isReadOnly = false }:
content: parsedResult ? parsedResult.trim() : msg.content,
sources: newSources || msg.sources,
images: newImages || msg.images,
videos: newVideos || msg.videos,
related: newRelated || msg.related,
};
}
Expand Down Expand Up @@ -200,7 +208,7 @@ export function SearchWindow({ id, initialMessages, user, isReadOnly = false }:
setIsLoading(false);
},
onmessage(msg) {
const { clear, answer, status, sources, images, related } = JSON.parse(msg.data);
const { clear, answer, status, sources, images, related, videos } = JSON.parse(msg.data);
if (clear) {
accumulatedMessage = '';
updateMessages(accumulatedMessage);
Expand All @@ -213,6 +221,7 @@ export function SearchWindow({ id, initialMessages, user, isReadOnly = false }:
sources,
images,
related ? (accumulatedRelated += related) : undefined,
videos,
);
},
});
Expand All @@ -222,7 +231,7 @@ export function SearchWindow({ id, initialMessages, user, isReadOnly = false }:
toast.error('An error occurred while searching, please refresh your page and try again');
}
},
[input, isReadOnly, isLoading, signInModal, addSearch, updateActiveSearch, user?.id],
[input, isReadOnly, isLoading, signInModal, addSearch, updateActiveSearch, upgradeModal, user],
);

const sendSelectedQuestion = useCallback(
Expand Down
28 changes: 28 additions & 0 deletions frontend/components/search/video-gallery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { VideoSource } from '@/lib/types';
import React, { memo } from 'react';

type VideoGalleryProps = {
videos: VideoSource[];
};

const VideoGallery: React.FC<VideoGalleryProps> = memo(({ videos }) => {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{videos.map((video, index) => (
<div key={index} className="aspect-video w-full">
<iframe
className="size-full rounded-xl"
src={`https://www.youtube.com/embed/${video.id}`}
title={video.title}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen"
referrerPolicy="strict-origin-when-cross-origin"
allowFullScreen
></iframe>
</div>
))}
</div>
);
});

VideoGallery.displayName = 'VideoGallery';
export default VideoGallery;
52 changes: 10 additions & 42 deletions frontend/components/sidebar/sidebar-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,17 @@ import {
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Button } from '@/components/ui/button';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { LoaderCircle, Share2, Trash2 } from 'lucide-react';
import { SearchShareDialog } from '@/components/search/search-share-dialog';
import { useSearchStore } from '@/lib/store/local-history';

interface SidebarActionsProps {
search: Search;
removeSearch: (args: {
id: string;
path: string;
}) => ServerActionResult<void>;
removeSearch: (args: { id: string; path: string }) => ServerActionResult<void>;
}

export function SidebarActions({
search: search,
removeSearch: removeSearch,
}: SidebarActionsProps) {
export function SidebarActions({ search: search, removeSearch: removeSearch }: SidebarActionsProps) {
const router = useRouter();
const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
const [shareDialogOpen, setShareDialogOpen] = React.useState(false);
Expand All @@ -47,11 +37,7 @@ export function SidebarActions({
<div>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
className="leading-none p-2 h-auto hover:bg-background"
onClick={() => setShareDialogOpen(true)}
>
<Button variant="ghost" className="leading-none p-2 h-auto hover:bg-background" onClick={() => setShareDialogOpen(true)}>
<Share2 className="size-4" />
<span className="sr-only">Share</span>
</Button>
Expand All @@ -73,30 +59,15 @@ export function SidebarActions({
<TooltipContent>Delete It</TooltipContent>
</Tooltip>
</div>
<SearchShareDialog
search={search}
open={shareDialogOpen}
onOpenChange={setShareDialogOpen}
onCopy={() => setShareDialogOpen(false)}
/>
<AlertDialog
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
>
<SearchShareDialog search={search} open={shareDialogOpen} onOpenChange={setShareDialogOpen} onCopy={() => setShareDialogOpen(false)} />
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you absolutely sure?
</AlertDialogTitle>
<AlertDialogDescription>
This will permanently delete your search message and
remove your data from our servers.
</AlertDialogDescription>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>This will permanently delete your search message and remove your data from our servers.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isRemovePending}>
Cancel
</AlertDialogCancel>
<AlertDialogCancel disabled={isRemovePending}>Cancel</AlertDialogCancel>
<AlertDialogAction
disabled={isRemovePending}
onClick={(event) => {
Expand All @@ -115,15 +86,12 @@ export function SidebarActions({
}

setDeleteDialogOpen(false);
// router.refresh();
router.push('/');
toast.success('Search deleted');
});
}}
>
{isRemovePending && (
<LoaderCircle className="mr-2 animate-spin" />
)}
{isRemovePending && <LoaderCircle className="mr-2 animate-spin" />}
Delete
</AlertDialogAction>
</AlertDialogFooter>
Expand Down
3 changes: 2 additions & 1 deletion frontend/lib/search/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'server-only';

import { SerperSearch } from '@/lib/search/serper';
import { VectorSearch } from '@/lib/search/vector';
import { ImageSource, SearchCategory, TextSource } from '@/lib/types';
import { ImageSource, SearchCategory, TextSource, VideoSource } from '@/lib/types';
import { EXASearch } from '@/lib/search/exa';

export interface SearchOptions {
Expand All @@ -22,6 +22,7 @@ export interface AnySource {
export interface SearchResult {
texts: TextSource[];
images: ImageSource[];
videos?: VideoSource[];
}

export interface SearchSource {
Expand Down
Loading

0 comments on commit f475d87

Please sign in to comment.