From 56a6f105b616e2e1e67dd5653233cacb29f134be Mon Sep 17 00:00:00 2001 From: CH Date: Wed, 5 Mar 2025 14:33:06 +0800 Subject: [PATCH 1/6] feat(canvas): respect readonly mode in code artifact preview - Import `useCanvasContext` to access readonly state - Update code viewer component to use readonly flag from canvas context - Prevent code editing in read-only canvas view --- .../src/components/canvas/node-preview/code-artifact.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ai-workspace-common/src/components/canvas/node-preview/code-artifact.tsx b/packages/ai-workspace-common/src/components/canvas/node-preview/code-artifact.tsx index f6804fa78..5a039e03d 100644 --- a/packages/ai-workspace-common/src/components/canvas/node-preview/code-artifact.tsx +++ b/packages/ai-workspace-common/src/components/canvas/node-preview/code-artifact.tsx @@ -13,6 +13,7 @@ import { IContextItem } from '@refly-packages/ai-workspace-common/stores/context import { useChatStore } from '@refly-packages/ai-workspace-common/stores/chat'; import { ConfigScope, Skill } from '@refly/openapi-schema'; import { CodeArtifactType } from '@refly-packages/ai-workspace-common/modules/artifacts/code-runner/types'; +import { useCanvasContext } from '@refly-packages/ai-workspace-common/context/canvas'; interface CodeArtifactNodePreviewProps { node: CanvasNode; @@ -24,7 +25,7 @@ const CodeArtifactNodePreviewComponent = ({ node, artifactId }: CodeArtifactNode const [isShowingCodeViewer, setIsShowingCodeViewer] = useState(true); const setNodeDataByEntity = useSetNodeDataByEntity(); const { addNode } = useAddNode(); - + const { readonly } = useCanvasContext(); // Use activeTab from node metadata with fallback to 'code' const { activeTab = 'code', type = 'text/html', language = 'html' } = node.data?.metadata || {}; const [currentTab, setCurrentTab] = useState<'code' | 'preview'>(activeTab as 'code' | 'preview'); @@ -177,7 +178,7 @@ const CodeArtifactNodePreviewComponent = ({ node, artifactId }: CodeArtifactNode onClose={handleClose} onRequestFix={handleRequestFix} onChange={handleCodeChange} - readOnly={false} + readOnly={readonly} type={type as CodeArtifactType} /> )} From 7c5b16491eab9b386486ab79e94f8cac89e10157 Mon Sep 17 00:00:00 2001 From: CH Date: Wed, 5 Mar 2025 15:38:47 +0800 Subject: [PATCH 2/6] feat(canvas): improve minimap handling and readonly mode interactions - Remove unnecessary minimap upload on component mount - Add timestamp to minimap image to ensure cache invalidation - Refactor minimap upload hook to fetch storage key dynamically - Prevent minimap updates in readonly mode --- .../src/components/canvas/index.tsx | 11 ++-------- .../workspace/canvas-list-modal/index.tsx | 7 ++++++- .../src/hooks/use-upload-minimap.ts | 21 ++++++++++--------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/ai-workspace-common/src/components/canvas/index.tsx b/packages/ai-workspace-common/src/components/canvas/index.tsx index fe015c81c..f7b08c0ed 100644 --- a/packages/ai-workspace-common/src/components/canvas/index.tsx +++ b/packages/ai-workspace-common/src/components/canvas/index.tsx @@ -59,7 +59,6 @@ import { useUpdateSettings } from '@refly-packages/ai-workspace-common/queries'; import { useUploadImage } from '@refly-packages/ai-workspace-common/hooks/use-upload-image'; import { useCanvasSync } from '@refly-packages/ai-workspace-common/hooks/canvas/use-canvas-sync'; import { EmptyGuide } from './empty-guide'; -import { useUploadMinimap } from '@refly-packages/ai-workspace-common/hooks/use-upload-minimap'; const selectionStyles = ` .react-flow__selection { @@ -294,6 +293,8 @@ const Flow = memo(({ canvasId }: { canvasId: string }) => { setSelectedEdgeId(null); } + if (readonly) return; + const currentTime = new Date().getTime(); const timeDiff = currentTime - lastClickTime; @@ -381,8 +382,6 @@ const Flow = memo(({ canvasId }: { canvasId: string }) => { }; }, [provider?.status]); - const { handleUpdateCanvasMiniMap } = useUploadMinimap(canvasId); - useEffect(() => { const unsubscribe = locateToNodePreviewEmitter.on( 'locateToNodePreview', @@ -404,12 +403,6 @@ const Flow = memo(({ canvasId }: { canvasId: string }) => { }, ); - setTimeout(() => { - if (!readonly) { - handleUpdateCanvasMiniMap(); - } - }, 3000); - return unsubscribe; }, [canvasId]); diff --git a/packages/ai-workspace-common/src/components/workspace/canvas-list-modal/index.tsx b/packages/ai-workspace-common/src/components/workspace/canvas-list-modal/index.tsx index 077b68732..88906a2d4 100644 --- a/packages/ai-workspace-common/src/components/workspace/canvas-list-modal/index.tsx +++ b/packages/ai-workspace-common/src/components/workspace/canvas-list-modal/index.tsx @@ -36,7 +36,12 @@ const CanvasItem = (props: { >
{canvas?.minimapUrl ? ( - minimap + minimap ) : (
)} diff --git a/packages/ai-workspace-common/src/hooks/use-upload-minimap.ts b/packages/ai-workspace-common/src/hooks/use-upload-minimap.ts index 3e5bd8ea2..2f6a7299b 100644 --- a/packages/ai-workspace-common/src/hooks/use-upload-minimap.ts +++ b/packages/ai-workspace-common/src/hooks/use-upload-minimap.ts @@ -1,20 +1,18 @@ -import { useState } from 'react'; import getClient from '@refly-packages/ai-workspace-common/requests/proxiedRequest'; import { useExportCanvasAsImage } from '@refly-packages/ai-workspace-common/hooks/use-export-canvas-as-image'; import { useDebouncedCallback } from 'use-debounce'; -import { useGetCanvasDetail } from '@refly-packages/ai-workspace-common/queries'; import { useUserStoreShallow } from '@refly-packages/ai-workspace-common/stores/user'; export const useUploadMinimap = (canvasId: string) => { const { getMinimap } = useExportCanvasAsImage(); const isLogin = useUserStoreShallow((state) => state.isLogin); - const { data, isFetched } = useGetCanvasDetail({ query: { canvasId } }, null, { - enabled: isLogin, - }); - const [storageKey, setStorageKey] = useState(data?.data?.minimapStorageKey); - const uploadMinimap = async (image: Blob) => { - if (!isFetched) return; + const getCanvasMinimapStorageKey = async () => { + const { data } = await getClient().getCanvasDetail({ query: { canvasId } }); + return data?.data?.minimapStorageKey; + }; + + const uploadMinimap = async (image: Blob, storageKey: string) => { const { data } = await getClient().upload({ body: { file: image, @@ -25,14 +23,17 @@ export const useUploadMinimap = (canvasId: string) => { }; const handleUpdateCanvasMiniMap = async () => { + if (!isLogin) return; const minimap = await getMinimap(); if (minimap) { - const { data, success } = await uploadMinimap(minimap); + const storageKey = await getCanvasMinimapStorageKey(); + const result = await uploadMinimap(minimap, storageKey); + const { data, success } = result ?? {}; + if (success && data?.storageKey && data.storageKey !== storageKey) { await getClient().updateCanvas({ body: { canvasId, minimapStorageKey: data.storageKey }, }); - setStorageKey(data.storageKey); } } }; From b36b800d3343db0a4e5518aff25db441132456c4 Mon Sep 17 00:00:00 2001 From: CH Date: Wed, 5 Mar 2025 15:38:47 +0800 Subject: [PATCH 3/6] feat(canvas): improve minimap handling and readonly mode interactions - Remove unnecessary minimap upload on component mount - Add timestamp to minimap image to ensure cache invalidation - Refactor minimap upload hook to fetch storage key dynamically - Prevent minimap updates in readonly mode --- .../src/components/canvas/top-toolbar/share-settings.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ai-workspace-common/src/components/canvas/top-toolbar/share-settings.tsx b/packages/ai-workspace-common/src/components/canvas/top-toolbar/share-settings.tsx index 7a9ed33cc..2a92e8702 100644 --- a/packages/ai-workspace-common/src/components/canvas/top-toolbar/share-settings.tsx +++ b/packages/ai-workspace-common/src/components/canvas/top-toolbar/share-settings.tsx @@ -9,7 +9,6 @@ import { RiUserForbidLine } from 'react-icons/ri'; import { GrLanguage } from 'react-icons/gr'; import { useTranslation } from 'react-i18next'; import getClient from '@refly-packages/ai-workspace-common/requests/proxiedRequest'; -import { getClientOrigin } from '@refly/utils/url'; import { CreateTemplateModal } from '@refly-packages/ai-workspace-common/components/canvas-template/create-template-modal'; import { useListShares } from '@refly-packages/ai-workspace-common/queries'; @@ -88,7 +87,7 @@ const ShareSettings = React.memo(({ canvasId }: ShareSettingsProps) => { }); const shareRecord = useMemo(() => data?.data?.[0], [data]); const shareLink = useMemo( - () => `${getClientOrigin()}/share/canvas/${shareRecord?.shareId}`, + () => `${window.location.origin}/share/canvas/${shareRecord?.shareId}`, [shareRecord], ); From 149f07e1a339ec7b258d43f26d3ce10f0a16f8e4 Mon Sep 17 00:00:00 2001 From: CH Date: Wed, 5 Mar 2025 15:38:47 +0800 Subject: [PATCH 4/6] feat(canvas): improve minimap handling and readonly mode interactions - Remove unnecessary minimap upload on component mount - Add timestamp to minimap image to ensure cache invalidation - Refactor minimap upload hook to fetch storage key dynamically - Prevent minimap updates in readonly mode --- .../src/components/canvas/node-preview/website.tsx | 6 ++++-- .../src/components/canvas/nodes/website.tsx | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/ai-workspace-common/src/components/canvas/node-preview/website.tsx b/packages/ai-workspace-common/src/components/canvas/node-preview/website.tsx index b933f24ac..ce2a34c89 100644 --- a/packages/ai-workspace-common/src/components/canvas/node-preview/website.tsx +++ b/packages/ai-workspace-common/src/components/canvas/node-preview/website.tsx @@ -5,6 +5,7 @@ import { WebsiteNodeMeta } from '../nodes/shared/types'; import { Button, Form, Input, message, Tooltip } from 'antd'; import { FiCode, FiEye, FiExternalLink, FiCopy } from 'react-icons/fi'; import { useSetNodeDataByEntity } from '@refly-packages/ai-workspace-common/hooks/canvas/use-set-node-data-by-entity'; +import { useCanvasContext } from '@refly-packages/ai-workspace-common/context/canvas'; interface WebsiteNodePreviewProps { node: CanvasNode; @@ -16,6 +17,7 @@ const WebsiteNodePreviewComponent = ({ node }: WebsiteNodePreviewProps) => { const [isEditing, setIsEditing] = useState(viewMode === 'form' || !url); const formRef = useRef(null); const setNodeDataByEntity = useSetNodeDataByEntity(); + const { readonly } = useCanvasContext(); // Initialize form with current URL when entering edit mode useEffect(() => { @@ -124,10 +126,10 @@ const WebsiteNodePreviewComponent = ({ node }: WebsiteNodePreviewProps) => { }, ]} > - + - diff --git a/packages/ai-workspace-common/src/components/canvas/nodes/website.tsx b/packages/ai-workspace-common/src/components/canvas/nodes/website.tsx index 45e098712..b5784149c 100644 --- a/packages/ai-workspace-common/src/components/canvas/nodes/website.tsx +++ b/packages/ai-workspace-common/src/components/canvas/nodes/website.tsx @@ -35,7 +35,7 @@ const DEFAULT_HEIGHT = 400; * Website node content component that displays either a form for URL input or an iframe preview */ const NodeContent = memo( - ({ data }: { data: CanvasNodeData }) => { + ({ data, readonly }: { data: CanvasNodeData; readonly: boolean }) => { const { url = '', viewMode = 'form', sizeMode = 'adaptive' } = data?.metadata ?? {}; const [isEditing, setIsEditing] = useState(viewMode === 'form' || !url); const { t } = useTranslation(); @@ -167,10 +167,10 @@ const NodeContent = memo( }, ]} > - + - @@ -529,7 +529,9 @@ export const WebsiteNode = memo( />
-
{data && }
+
+ {data && } +
From be0189c1f7e09bf856080b89a2f9d573aa4d72d3 Mon Sep 17 00:00:00 2001 From: CH Date: Wed, 5 Mar 2025 16:11:21 +0800 Subject: [PATCH 5/6] feat(canvas): add SVG to PNG conversion for minimap export - Implement `svgBlobToPngBlob` method to convert SVG blobs to PNG - Enhance `exportCanvasAsImage` to return PNG blob when possible - Fallback to SVG blob if PNG conversion fails - Add default dimensions for SVG to PNG conversion --- .../src/hooks/use-export-canvas-as-image.ts | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/ai-workspace-common/src/hooks/use-export-canvas-as-image.ts b/packages/ai-workspace-common/src/hooks/use-export-canvas-as-image.ts index c7ed9f803..8c2e4fabc 100644 --- a/packages/ai-workspace-common/src/hooks/use-export-canvas-as-image.ts +++ b/packages/ai-workspace-common/src/hooks/use-export-canvas-as-image.ts @@ -300,7 +300,10 @@ export const useExportCanvasAsImage = () => { // create an svg blob const svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' }); - return svgBlob; + + // Convert SVG Blob to PNG + const pngBlob = await svgBlobToPngBlob(svgBlob, svgClone); + return pngBlob ?? svgBlob; } catch (error) { console.error('Error exporting minimap as image:', error); return null; @@ -309,5 +312,56 @@ export const useExportCanvasAsImage = () => { } }, [reactFlowInstance, isMinimapLoading, imageToBase64]); - return { exportCanvasAsImage, isLoading, getMinimap, isMinimapLoading }; + const svgBlobToPngBlob = useCallback( + async (svgBlob: Blob, svgElement: SVGSVGElement): Promise => { + try { + const svgUrl = URL.createObjectURL(svgBlob); + + const width = svgElement?.width?.baseVal?.value ?? 800; + const height = svgElement?.height?.baseVal?.value ?? 600; + + const img = new Image(); + img.width = width; + img.height = height; + + await new Promise((resolve, reject) => { + img.onload = resolve; + img.onerror = reject; + img.src = svgUrl; + }); + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d'); + if (!ctx) { + throw new Error('canvas context not found'); + } + + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, width, height); + + ctx.drawImage(img, 0, 0, width, height); + + URL.revokeObjectURL(svgUrl); + + return new Promise((resolve) => { + canvas.toBlob( + (blob) => { + resolve(blob); + }, + 'image/png', + 1.0, + ); + }); + } catch (error) { + console.error('Error converting SVG to PNG:', error); + return null; + } + }, + [], + ); + + return { exportCanvasAsImage, isLoading, getMinimap, isMinimapLoading, svgBlobToPngBlob }; }; From 63e88dcf93da0a7f61ceb848e284f61a812aefff Mon Sep 17 00:00:00 2001 From: CH Date: Wed, 5 Mar 2025 16:34:04 +0800 Subject: [PATCH 6/6] feat(seo): add Open Graph and Twitter card metadata - Update index.html with comprehensive Open Graph meta tags - Modify home page component to use optimized OG image - Ensure consistent metadata for improved social media sharing --- apps/web/index.html | 11 +++++++++++ apps/web/src/pages/home/index.tsx | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/web/index.html b/apps/web/index.html index 7c8708e87..ccf3bdb78 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -8,6 +8,17 @@ + + + + + + + + + + +