Skip to content

Commit

Permalink
Merge pull request #930 from ElishaKay/display-images
Browse files Browse the repository at this point in the history
✅ showing images in report
  • Loading branch information
assafelovic authored Oct 20, 2024
2 parents 46e3193 + 02ca9ba commit 55a1197
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 2 deletions.
1 change: 1 addition & 0 deletions frontend/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ export default function Home() {
const logs = data.items.map((item:any, subIndex:any) => ({
header: item.content,
text: item.output,
metadata: item.metadata,
key: `${item.type}-${item.content}-${subIndex}`,
}));

Expand Down
45 changes: 45 additions & 0 deletions frontend/nextjs/components/Task/ImageModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';

export default function ImageModal({ imageSrc, isOpen, onClose }) {
if (!isOpen) return null;

const handleClose = (e) => {
if (e.target === e.currentTarget) {
onClose();
}
};

return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60 overflow-auto"
onClick={handleClose}
>
<div className="relative m-4 max-w-full">
<button
className="absolute top-0 right-0 p-2 text-white"
onClick={onClose}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={2}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
<img
src={imageSrc}
alt="Full view"
className="max-h-[80vh] max-w-full object-contain"
/>
</div>
</div>
);
}
136 changes: 136 additions & 0 deletions frontend/nextjs/components/Task/ImagesCarousel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { useState, useEffect } from 'react';
import ImageModal from './ImageModal'; // Import the ImageModal component

export default function ImagesCarousel({ images }) {
const [activeIndex, setActiveIndex] = useState(0);
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedImage, setSelectedImage] = useState(null);
const [validImages, setValidImages] = useState(images); // Keep track of valid images

const nextSlide = () => {
setActiveIndex((prevIndex) => (prevIndex + 1) % validImages.length);
};

const prevSlide = () => {
setActiveIndex((prevIndex) => (prevIndex - 1 + validImages.length) % validImages.length);
};

const openModal = (image) => {
setSelectedImage(image);
setIsModalOpen(true);
};

const closeModal = () => {
setIsModalOpen(false);
setSelectedImage(null);
};

// Handle broken images by filtering them out
const handleImageError = (brokenImage) => {
setValidImages((prevImages) => prevImages.filter((img) => img !== brokenImage));
};

useEffect(() => {
const imagesToHide = ['https://gptr.dev/_ipx/w_3840,q_75/%2F_next%2Fstatic%2Fmedia%2Fbg-pattern.5aa07776.webp?url=%2F_next%2Fstatic%2Fmedia%2Fbg-pattern.5aa07776.webp&w=3840&q=75',"https://tavily.com/_ipx/w_3840,q_75/%2F_next%2Fstatic%2Fmedia%2Fbg-pattern.5aa07776.webp?url=%2F_next%2Fstatic%2Fmedia%2Fbg-pattern.5aa07776.webp&w=3840&q=75"]
const filteredImages = images.filter((img) => !imagesToHide.includes(img));
setValidImages(filteredImages);
}, [images]);

// Hide the entire component if there are no valid images
if (validImages.length === 0) return null;

return (
<div id="default-carousel" className="relative w-full" data-carousel="slide">
<div className="relative h-56 overflow-hidden rounded-lg md:h-96">
{validImages.map((image, index) => (
<div
key={index}
className={`duration-700 ease-in-out ${index === activeIndex ? '' : 'hidden'}`}
data-carousel-item
>
<img
src={image}
className="absolute block w-full -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 cursor-pointer"
alt={`Slide ${index + 1}`}
onClick={() => openModal(image)} // Open modal on image click
onError={() => handleImageError(image)} // Handle broken images
/>
</div>
))}
</div>

{/* Only show indicators and buttons if there is more than 1 valid image */}
{validImages.length > 1 && (
<>
<div className="absolute z-30 flex -translate-x-1/2 bottom-5 left-1/2 space-x-3 rtl:space-x-reverse">
{validImages.map((_, index) => (
<button
key={index}
type="button"
className={`w-3 h-3 rounded-full ${index === activeIndex ? 'bg-gray-800' : 'bg-gray-400'}`}
aria-current={index === activeIndex ? 'true' : 'false'}
aria-label={`Slide ${index + 1}`}
onClick={() => setActiveIndex(index)}
data-carousel-slide-to={index}
></button>
))}
</div>

<button
type="button"
className="absolute top-0 start-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
onClick={prevSlide}
data-carousel-prev
>
<span className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-white/30 dark:bg-gray-800/30 group-hover:bg-white/50 dark:group-hover:bg-gray-800/60 group-focus:ring-4 group-focus:ring-white dark:group-focus:ring-gray-800/70 group-focus:outline-none">
<svg
className="w-4 h-4 text-white dark:text-gray-800 rtl:rotate-180"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 1 1 5l4 4"
/>
</svg>
<span className="sr-only">Previous</span>
</span>
</button>
<button
type="button"
className="absolute top-0 end-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
onClick={nextSlide}
data-carousel-next
>
<span className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-white/30 dark:bg-gray-800/30 group-hover:bg-white/50 dark:group-hover:bg-gray-800/60 group-focus:ring-4 group-focus:ring-white dark:group-focus:ring-gray-800/70 group-focus:outline-none">
<svg
className="w-4 h-4 text-white dark:text-gray-800 rtl:rotate-180"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m1 9 4-4-4-4"
/>
</svg>
<span className="sr-only">Next</span>
</span>
</button>
</>
)}

{/* Render the modal when it's open */}
{selectedImage && (
<ImageModal imageSrc={selectedImage} isOpen={isModalOpen} onClose={closeModal} />
)}
</div>
);
}
9 changes: 8 additions & 1 deletion frontend/nextjs/components/Task/LogMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Accordion from './Accordion';
import { useEffect, useState } from 'react';
import { remark } from 'remark';
import html from 'remark-html';
import ImagesCarousel from './ImagesCarousel';

type ProcessedData = {
field: string;
Expand Down Expand Up @@ -58,7 +59,13 @@ const LogMessage: React.FC<LogMessageProps> = ({ logs }) => {
{processedLogs.map((log, index) => {
if (log.header === 'subquery_context_window' || log.header === 'differences') {
return <Accordion key={index} logs={[log]} />;
} else {
} else if(log.header === 'scraping_images') {
return (
<ImagesCarousel
images={log.metadata}
/>
)
} else if(log.header !== "selected_images") {
return (
<div
key={index}
Expand Down
2 changes: 1 addition & 1 deletion frontend/nextjs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "components/Task/ImagesCarousel.js"],
"exclude": ["node_modules"]
}
2 changes: 2 additions & 0 deletions gpt_researcher/skills/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ async def browse_urls(self, urls: List[str]) -> List[Dict]:
"scraping_images",
f"🖼️ Selected {len(new_images)} new images from {len(images)} total images",
self.researcher.websocket,
True,
new_images
)
await stream_output(
"logs",
Expand Down

0 comments on commit 55a1197

Please sign in to comment.