From 38d2693cafc4700d870ac4fb0c6d8453f9a12c74 Mon Sep 17 00:00:00 2001 From: buzzCraft <67235613+buzzCraft@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:23:49 +0200 Subject: [PATCH 1/9] Update Image.tsx --- .../src/components/atoms/elements/Image.tsx | 105 +++++++----------- 1 file changed, 41 insertions(+), 64 deletions(-) diff --git a/frontend/src/components/atoms/elements/Image.tsx b/frontend/src/components/atoms/elements/Image.tsx index 201e450567..484d12ce18 100644 --- a/frontend/src/components/atoms/elements/Image.tsx +++ b/frontend/src/components/atoms/elements/Image.tsx @@ -1,8 +1,11 @@ import { useState } from 'react'; - import Skeleton from '@mui/material/Skeleton'; +import Lightbox from 'yet-another-react-lightbox'; +import 'yet-another-react-lightbox/styles.css'; +import Zoom from 'yet-another-react-lightbox/plugins/zoom'; +import Download from 'yet-another-react-lightbox/plugins/download'; -import { type IImageElement } from 'client-types/'; +import { type IImageElement, useConfig } from '@chainlit/react-client'; import { FrameElement } from './Frame'; @@ -10,68 +13,23 @@ interface Props { element: IImageElement; } -const handleImageClick = (name: string, src: string) => { - const width = window.innerWidth / 2; - const height = window.innerHeight / 2; - const left = window.innerWidth / 4; - const top = window.innerHeight / 4; - - const newWindow = window.open( - '', - '_blank', - `width=${width},height=${height},left=${left},top=${top}` - ); - if (newWindow) { - newWindow.document.write(` - - - ${name} - - - - - ${name} - Download - - - `); - newWindow.document.close(); - } -}; - const ImageElement = ({ element }: Props) => { const [loading, setLoading] = useState(true); + const [lightboxOpen, setLightboxOpen] = useState(false); + const { config } = useConfig(); if (!element.url) { return null; } + const enableLightbox = typedConfig?.features?.image_lightbox && element.display === 'inline'; + + const handleImageClick = () => { + if (enableLightbox) { + setLightboxOpen(true); + } + }; + return ( {loading && } @@ -79,23 +37,42 @@ const ImageElement = ({ element }: Props) => { className={`${element.display}-image`} src={element.url} onLoad={() => setLoading(false)} - onClick={() => { - if (element.display === 'inline') { - const name = `${element.name}.png`; - handleImageClick(name, element.url!); - } - }} + onClick={handleImageClick} style={{ objectFit: 'cover', maxWidth: '100%', margin: 'auto', height: 'auto', display: 'block', - cursor: element.display === 'inline' ? 'pointer' : 'default' + cursor: enableLightbox ? 'pointer' : 'default' }} alt={element.name} loading="lazy" /> + {enableLightbox && ( + setLightboxOpen(false)} + slides={[{ src: element.url }]} + carousel={{ finite: true }} + render={{ buttonPrev: () => null, buttonNext: () => null }} + plugins={[Zoom, Download]} + zoom={{ + maxZoomPixelRatio: 5, + zoomInMultiplier: 2, + }} + download={{ + download: ({ slide }) => { + const link = document.createElement('a'); + link.href = slide.src; + link.download = element.name || 'image'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }, + }} + /> + )} ); }; From 4df08700d5b70faa74b577b26007e0222656012c Mon Sep 17 00:00:00 2001 From: buzzCraft <67235613+buzzCraft@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:26:09 +0200 Subject: [PATCH 2/9] Added lightbox toggle to config --- backend/chainlit/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index ad9c1aaf88..02183002ba 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -241,6 +241,7 @@ class FeaturesSettings(DataClassJsonMixin): unsafe_allow_html: bool = False auto_tag_thread: bool = True edit_message: bool = True + image_lightbox: bool = True @dataclass() From dbcd077d567a4309942044d8459dbaf323100845 Mon Sep 17 00:00:00 2001 From: buzzCraft <67235613+buzzCraft@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:33:42 +0200 Subject: [PATCH 3/9] added "yet-another-react-lightbox": "^3.21.6" to dependencies --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 24a3288f98..7a8125c616 100644 --- a/package.json +++ b/package.json @@ -34,5 +34,8 @@ "braces@<3.0.3": ">=3.0.3", "micromatch@<4.0.8": ">=4.0.8" } + }, + "dependencies": { + "yet-another-react-lightbox": "^3.21.6" } } From 06962e31ca83ff304b6b20aaaa7c4f3f2ff349c0 Mon Sep 17 00:00:00 2001 From: buzzCraft <67235613+buzzCraft@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:05:22 +0200 Subject: [PATCH 4/9] Implementing lightbox --- .gitignore | 2 ++ frontend/package.json | 1 + frontend/pnpm-lock.yaml | 15 +++++++++++++++ frontend/src/components/atoms/elements/Image.tsx | 2 +- package.json | 5 +---- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 1b0f2b0171..adad6aa713 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,5 @@ dist-ssr .coverage backend/README.md + +myenv diff --git a/frontend/package.json b/frontend/package.json index 635dbed1ce..5c98ab934a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,6 +50,7 @@ "unist-util-visit": "^5.0.0", "usehooks-ts": "^2.9.1", "uuid": "^9.0.0", + "yet-another-react-lightbox": "^3.21.6", "yup": "^1.2.0" }, "devDependencies": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 06dfed2047..c03cda4032 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -131,6 +131,9 @@ importers: uuid: specifier: ^9.0.0 version: 9.0.0 + yet-another-react-lightbox: + specifier: ^3.21.6 + version: 3.21.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0) yup: specifier: ^1.2.0 version: 1.2.0 @@ -3918,6 +3921,13 @@ packages: engines: {node: '>= 14'} hasBin: true + yet-another-react-lightbox@3.21.6: + resolution: {integrity: sha512-uKcRmmezsj1Fbj38B6hFOGwbAu94fPr8d5H6I0+1FmcToX56freEGXXXtdA1oRo6036ug+UgrKZzzvsw/MIM/w==} + engines: {node: '>=14'} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} @@ -8156,6 +8166,11 @@ snapshots: yaml@2.4.1: {} + yet-another-react-lightbox@3.21.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + yocto-queue@1.0.0: {} yup@1.2.0: diff --git a/frontend/src/components/atoms/elements/Image.tsx b/frontend/src/components/atoms/elements/Image.tsx index 484d12ce18..d2b93edf7e 100644 --- a/frontend/src/components/atoms/elements/Image.tsx +++ b/frontend/src/components/atoms/elements/Image.tsx @@ -22,7 +22,7 @@ const ImageElement = ({ element }: Props) => { return null; } - const enableLightbox = typedConfig?.features?.image_lightbox && element.display === 'inline'; + const enableLightbox = config?.features?.image_lightbox && element.display === 'inline'; const handleImageClick = () => { if (enableLightbox) { diff --git a/package.json b/package.json index 7a8125c616..044f439085 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,5 @@ "braces@<3.0.3": ">=3.0.3", "micromatch@<4.0.8": ">=4.0.8" } - }, - "dependencies": { - "yet-another-react-lightbox": "^3.21.6" } -} +} \ No newline at end of file From 6beb298d2d2146feb6188c206ad85f3188836d8f Mon Sep 17 00:00:00 2001 From: buzzCraft <67235613+buzzCraft@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:54:41 +0200 Subject: [PATCH 5/9] Fixing configs --- backend/chainlit/config.py | 3 +++ frontend/src/components/atoms/elements/Image.tsx | 5 +++-- libs/react-client/src/types/config.ts | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index 02183002ba..fbe3af60b2 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -87,6 +87,9 @@ # Allow users to edit their own messages edit_message = true +# Enable lightbox for images - making it possible to view images in full screen +image_lightbox = true + # Authorize users to spontaneously upload files with messages [features.spontaneous_file_upload] enabled = true diff --git a/frontend/src/components/atoms/elements/Image.tsx b/frontend/src/components/atoms/elements/Image.tsx index d2b93edf7e..43276a51ee 100644 --- a/frontend/src/components/atoms/elements/Image.tsx +++ b/frontend/src/components/atoms/elements/Image.tsx @@ -16,13 +16,13 @@ interface Props { const ImageElement = ({ element }: Props) => { const [loading, setLoading] = useState(true); const [lightboxOpen, setLightboxOpen] = useState(false); - const { config } = useConfig(); + const config = useConfig(); if (!element.url) { return null; } - const enableLightbox = config?.features?.image_lightbox && element.display === 'inline'; + const enableLightbox = config.config?.features.image_lightbox && element.display === 'inline'; const handleImageClick = () => { if (enableLightbox) { @@ -30,6 +30,7 @@ const ImageElement = ({ element }: Props) => { } }; + return ( {loading && } diff --git a/libs/react-client/src/types/config.ts b/libs/react-client/src/types/config.ts index 0184e35c4e..0dae4df4ba 100644 --- a/libs/react-client/src/types/config.ts +++ b/libs/react-client/src/types/config.ts @@ -49,6 +49,7 @@ export interface IChainlitConfig { unsafe_allow_html?: boolean; latex?: boolean; edit_message?: boolean; + image_lightbox?: boolean; }; debugUrl?: string; userEnv: string[]; From 3e50101acabab7a1f3de56567092a53d83555224 Mon Sep 17 00:00:00 2001 From: buzzCraft <67235613+buzzCraft@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:04:50 +0200 Subject: [PATCH 6/9] Fixing image download --- .../src/components/atoms/elements/Image.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/atoms/elements/Image.tsx b/frontend/src/components/atoms/elements/Image.tsx index 43276a51ee..49709ef79e 100644 --- a/frontend/src/components/atoms/elements/Image.tsx +++ b/frontend/src/components/atoms/elements/Image.tsx @@ -63,13 +63,19 @@ const ImageElement = ({ element }: Props) => { zoomInMultiplier: 2, }} download={{ - download: ({ slide }) => { - const link = document.createElement('a'); - link.href = slide.src; - link.download = element.name || 'image'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); + download: async ({ slide }) => { + try { + const response = await fetch(slide.src, { mode: 'cors' }); + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = element.name || 'image'; + link.click(); + window.URL.revokeObjectURL(url); + } catch (error) { + console.error('Failed to download image:', error); + } }, }} /> From cb86d5a0a1ef6b6082fe9e8f2eb9b92acbe452bf Mon Sep 17 00:00:00 2001 From: Aleksander Theo Strand/Users/aleksandertheostrand/repos/tetgpt-chat/venv/bin/python Date: Wed, 6 Nov 2024 14:18:05 +0100 Subject: [PATCH 7/9] Reimplementing original imagebox --- .gitignore | 4 +- .../src/components/atoms/elements/Image.tsx | 106 ++++++++++++------ .../atoms/elements/LightboxWrapper.tsx | 52 +++++++++ 3 files changed, 123 insertions(+), 39 deletions(-) create mode 100644 frontend/src/components/atoms/elements/LightboxWrapper.tsx diff --git a/.gitignore b/.gitignore index adad6aa713..97b1efa248 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,4 @@ dist-ssr .aider* .coverage -backend/README.md - -myenv +backend/README.md \ No newline at end of file diff --git a/frontend/src/components/atoms/elements/Image.tsx b/frontend/src/components/atoms/elements/Image.tsx index 49709ef79e..dfc1a0df9a 100644 --- a/frontend/src/components/atoms/elements/Image.tsx +++ b/frontend/src/components/atoms/elements/Image.tsx @@ -1,14 +1,14 @@ -import { useState } from 'react'; +import { Suspense, lazy, useState } from 'react'; + import Skeleton from '@mui/material/Skeleton'; -import Lightbox from 'yet-another-react-lightbox'; -import 'yet-another-react-lightbox/styles.css'; -import Zoom from 'yet-another-react-lightbox/plugins/zoom'; -import Download from 'yet-another-react-lightbox/plugins/download'; import { type IImageElement, useConfig } from '@chainlit/react-client'; import { FrameElement } from './Frame'; +// Lazy load the Lightbox component and its dependencies +const LightboxWrapper = lazy(() => import('./LightboxWrapper')); + interface Props { element: IImageElement; } @@ -22,15 +22,69 @@ const ImageElement = ({ element }: Props) => { return null; } - const enableLightbox = config.config?.features.image_lightbox && element.display === 'inline'; + const enableLightbox = + config.config?.features.image_lightbox && element.display === 'inline'; const handleImageClick = () => { if (enableLightbox) { setLightboxOpen(true); + } else { + // Fall back to popup window behavior + const width = window.innerWidth / 2; + const height = window.innerHeight / 2; + const left = window.innerWidth / 4; + const top = window.innerHeight / 4; + + const newWindow = window.open( + '', + '_blank', + `width=${width},height=${height},left=${left},top=${top}` + ); + if (newWindow) { + newWindow.document.write(` + + + ${element.name} + + + + + ${element.name} + Download + + + `); + newWindow.document.close(); + } } }; - return ( {loading && } @@ -50,35 +104,15 @@ const ImageElement = ({ element }: Props) => { alt={element.name} loading="lazy" /> - {enableLightbox && ( - setLightboxOpen(false)} - slides={[{ src: element.url }]} - carousel={{ finite: true }} - render={{ buttonPrev: () => null, buttonNext: () => null }} - plugins={[Zoom, Download]} - zoom={{ - maxZoomPixelRatio: 5, - zoomInMultiplier: 2, - }} - download={{ - download: async ({ slide }) => { - try { - const response = await fetch(slide.src, { mode: 'cors' }); - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = element.name || 'image'; - link.click(); - window.URL.revokeObjectURL(url); - } catch (error) { - console.error('Failed to download image:', error); - } - }, - }} - /> + {enableLightbox && lightboxOpen && ( + Loading...}> + setLightboxOpen(false)} + imageUrl={element.url} + imageName={element.name} + /> + )} ); diff --git a/frontend/src/components/atoms/elements/LightboxWrapper.tsx b/frontend/src/components/atoms/elements/LightboxWrapper.tsx new file mode 100644 index 0000000000..3d125b1f74 --- /dev/null +++ b/frontend/src/components/atoms/elements/LightboxWrapper.tsx @@ -0,0 +1,52 @@ +import Lightbox from 'yet-another-react-lightbox'; +import Download from 'yet-another-react-lightbox/plugins/download'; +import Zoom from 'yet-another-react-lightbox/plugins/zoom'; + +import 'yet-another-react-lightbox/styles.css'; + +interface LightboxWrapperProps { + isOpen: boolean; + onClose: () => void; + imageUrl: string; + imageName: string; +} + +const LightboxWrapper = ({ + isOpen, + onClose, + imageUrl, + imageName +}: LightboxWrapperProps) => { + return ( + null, buttonNext: () => null }} + plugins={[Zoom, Download]} + zoom={{ + maxZoomPixelRatio: 5, + zoomInMultiplier: 2 + }} + download={{ + download: async ({ slide }) => { + try { + const response = await fetch(slide.src, { mode: 'cors' }); + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = imageName || 'image'; + link.click(); + window.URL.revokeObjectURL(url); + } catch (error) { + console.error('Failed to download image:', error); + } + } + }} + /> + ); +}; + +export default LightboxWrapper; From 52a3817bda283d81899e408b91fff320d0d3d220 Mon Sep 17 00:00:00 2001 From: buzzCraft <67235613+buzzCraft@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:18:05 +0100 Subject: [PATCH 8/9] Reimplementing original imagebox --- .gitignore | 4 +- .../src/components/atoms/elements/Image.tsx | 106 ++++++++++++------ .../atoms/elements/LightboxWrapper.tsx | 52 +++++++++ 3 files changed, 123 insertions(+), 39 deletions(-) create mode 100644 frontend/src/components/atoms/elements/LightboxWrapper.tsx diff --git a/.gitignore b/.gitignore index adad6aa713..97b1efa248 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,4 @@ dist-ssr .aider* .coverage -backend/README.md - -myenv +backend/README.md \ No newline at end of file diff --git a/frontend/src/components/atoms/elements/Image.tsx b/frontend/src/components/atoms/elements/Image.tsx index 49709ef79e..dfc1a0df9a 100644 --- a/frontend/src/components/atoms/elements/Image.tsx +++ b/frontend/src/components/atoms/elements/Image.tsx @@ -1,14 +1,14 @@ -import { useState } from 'react'; +import { Suspense, lazy, useState } from 'react'; + import Skeleton from '@mui/material/Skeleton'; -import Lightbox from 'yet-another-react-lightbox'; -import 'yet-another-react-lightbox/styles.css'; -import Zoom from 'yet-another-react-lightbox/plugins/zoom'; -import Download from 'yet-another-react-lightbox/plugins/download'; import { type IImageElement, useConfig } from '@chainlit/react-client'; import { FrameElement } from './Frame'; +// Lazy load the Lightbox component and its dependencies +const LightboxWrapper = lazy(() => import('./LightboxWrapper')); + interface Props { element: IImageElement; } @@ -22,15 +22,69 @@ const ImageElement = ({ element }: Props) => { return null; } - const enableLightbox = config.config?.features.image_lightbox && element.display === 'inline'; + const enableLightbox = + config.config?.features.image_lightbox && element.display === 'inline'; const handleImageClick = () => { if (enableLightbox) { setLightboxOpen(true); + } else { + // Fall back to popup window behavior + const width = window.innerWidth / 2; + const height = window.innerHeight / 2; + const left = window.innerWidth / 4; + const top = window.innerHeight / 4; + + const newWindow = window.open( + '', + '_blank', + `width=${width},height=${height},left=${left},top=${top}` + ); + if (newWindow) { + newWindow.document.write(` + + + ${element.name} + + + + + ${element.name} + Download + + + `); + newWindow.document.close(); + } } }; - return ( {loading && } @@ -50,35 +104,15 @@ const ImageElement = ({ element }: Props) => { alt={element.name} loading="lazy" /> - {enableLightbox && ( - setLightboxOpen(false)} - slides={[{ src: element.url }]} - carousel={{ finite: true }} - render={{ buttonPrev: () => null, buttonNext: () => null }} - plugins={[Zoom, Download]} - zoom={{ - maxZoomPixelRatio: 5, - zoomInMultiplier: 2, - }} - download={{ - download: async ({ slide }) => { - try { - const response = await fetch(slide.src, { mode: 'cors' }); - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = url; - link.download = element.name || 'image'; - link.click(); - window.URL.revokeObjectURL(url); - } catch (error) { - console.error('Failed to download image:', error); - } - }, - }} - /> + {enableLightbox && lightboxOpen && ( + Loading...}> + setLightboxOpen(false)} + imageUrl={element.url} + imageName={element.name} + /> + )} ); diff --git a/frontend/src/components/atoms/elements/LightboxWrapper.tsx b/frontend/src/components/atoms/elements/LightboxWrapper.tsx new file mode 100644 index 0000000000..3d125b1f74 --- /dev/null +++ b/frontend/src/components/atoms/elements/LightboxWrapper.tsx @@ -0,0 +1,52 @@ +import Lightbox from 'yet-another-react-lightbox'; +import Download from 'yet-another-react-lightbox/plugins/download'; +import Zoom from 'yet-another-react-lightbox/plugins/zoom'; + +import 'yet-another-react-lightbox/styles.css'; + +interface LightboxWrapperProps { + isOpen: boolean; + onClose: () => void; + imageUrl: string; + imageName: string; +} + +const LightboxWrapper = ({ + isOpen, + onClose, + imageUrl, + imageName +}: LightboxWrapperProps) => { + return ( + null, buttonNext: () => null }} + plugins={[Zoom, Download]} + zoom={{ + maxZoomPixelRatio: 5, + zoomInMultiplier: 2 + }} + download={{ + download: async ({ slide }) => { + try { + const response = await fetch(slide.src, { mode: 'cors' }); + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = imageName || 'image'; + link.click(); + window.URL.revokeObjectURL(url); + } catch (error) { + console.error('Failed to download image:', error); + } + } + }} + /> + ); +}; + +export default LightboxWrapper; From 01afa187fe8c7ec524c76608047cc784745225f7 Mon Sep 17 00:00:00 2001 From: buzzCraft <67235613+buzzCraft@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:46:56 +0100 Subject: [PATCH 9/9] Added trailing new line --- frontend/src/components/atoms/elements/Image.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/atoms/elements/Image.tsx b/frontend/src/components/atoms/elements/Image.tsx index dfc1a0df9a..e336c94c07 100644 --- a/frontend/src/components/atoms/elements/Image.tsx +++ b/frontend/src/components/atoms/elements/Image.tsx @@ -119,3 +119,4 @@ const ImageElement = ({ element }: Props) => { }; export { ImageElement }; +