Skip to content

Commit

Permalink
Merge pull request #551 from refly-ai/feat/code-artifact-readonly
Browse files Browse the repository at this point in the history
feat(canvas): respect readonly mode in code artifact preview
  • Loading branch information
CH1111 authored Mar 5, 2025
2 parents 70eba7a + 63e88dc commit 97d0523
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 33 deletions.
11 changes: 11 additions & 0 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
<meta
name="description"
content="Refly is a free-form canvas creation platform powered by multi-threaded dialogue, knowledge integration, context memory, intelligent search and WYSIWYG AI editor, easily transforms ideas into quality content." />
<!-- Open Graph -->
<meta property="og:title" content="The AI Native Creation Engine · Refly" />
<meta property="og:description" content="Refly is a free-form canvas creation platform powered by multi-threaded dialogue, knowledge integration, context memory, intelligent search and WYSIWYG AI editor, easily transforms ideas into quality content." />
<meta property="og:image" content="https://static.refly.ai/landing/product-og-min.png" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://staging.refly.ai/" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="The AI Native Creation Engine · Refly" />
<meta name="twitter:description" content="Refly is a free-form canvas creation platform powered by multi-threaded dialogue, knowledge integration, context memory, intelligent search and WYSIWYG AI editor, easily transforms ideas into quality content." />
<meta name="twitter:image" content="https://static.refly.ai/landing/product-og-min.png" />
<style>
@font-face {
font-family: "Alibaba PuHuiTi";
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/pages/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function Home() {
<meta name="description" content={t('landingPage.description')} />
<meta property="og:title" content={`${t('landingPage.slogan')} · Refly`} />
<meta property="og:description" content={t('landingPage.description')} />
<meta property="og:image" content="https://static.refly.ai/landing/product.webp" />
<meta property="og:image" content="https://static.refly.ai/landing/product-og-min.png" />
<meta property="og:type" content="website" />
<meta property="og:url" content={window.location.href} />
</Helmet>
Expand Down
11 changes: 2 additions & 9 deletions packages/ai-workspace-common/src/components/canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -294,6 +293,8 @@ const Flow = memo(({ canvasId }: { canvasId: string }) => {
setSelectedEdgeId(null);
}

if (readonly) return;

const currentTime = new Date().getTime();
const timeDiff = currentTime - lastClickTime;

Expand Down Expand Up @@ -381,8 +382,6 @@ const Flow = memo(({ canvasId }: { canvasId: string }) => {
};
}, [provider?.status]);

const { handleUpdateCanvasMiniMap } = useUploadMinimap(canvasId);

useEffect(() => {
const unsubscribe = locateToNodePreviewEmitter.on(
'locateToNodePreview',
Expand All @@ -404,12 +403,6 @@ const Flow = memo(({ canvasId }: { canvasId: string }) => {
},
);

setTimeout(() => {
if (!readonly) {
handleUpdateCanvasMiniMap();
}
}, 3000);

return unsubscribe;
}, [canvasId]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CodeArtifactNodeMeta>;
Expand All @@ -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');
Expand Down Expand Up @@ -177,7 +178,7 @@ const CodeArtifactNodePreviewComponent = ({ node, artifactId }: CodeArtifactNode
onClose={handleClose}
onRequestFix={handleRequestFix}
onChange={handleCodeChange}
readOnly={false}
readOnly={readonly}
type={type as CodeArtifactType}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<WebsiteNodeMeta>;
Expand All @@ -16,6 +17,7 @@ const WebsiteNodePreviewComponent = ({ node }: WebsiteNodePreviewProps) => {
const [isEditing, setIsEditing] = useState(viewMode === 'form' || !url);
const formRef = useRef<any>(null);
const setNodeDataByEntity = useSetNodeDataByEntity();
const { readonly } = useCanvasContext();

// Initialize form with current URL when entering edit mode
useEffect(() => {
Expand Down Expand Up @@ -124,10 +126,10 @@ const WebsiteNodePreviewComponent = ({ node }: WebsiteNodePreviewProps) => {
},
]}
>
<Input placeholder="https://example.com" className="w-full" />
<Input placeholder="https://example.com" className="w-full" disabled={readonly} />
</Form.Item>
<Form.Item className="mt-4">
<Button type="primary" htmlType="submit" className="w-full">
<Button type="primary" htmlType="submit" className="w-full" disabled={readonly}>
{t('canvas.nodes.website.save', 'Save and View Website')}
</Button>
</Form.Item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<WebsiteNodeMeta> }) => {
({ data, readonly }: { data: CanvasNodeData<WebsiteNodeMeta>; readonly: boolean }) => {
const { url = '', viewMode = 'form', sizeMode = 'adaptive' } = data?.metadata ?? {};
const [isEditing, setIsEditing] = useState(viewMode === 'form' || !url);
const { t } = useTranslation();
Expand Down Expand Up @@ -167,10 +167,10 @@ const NodeContent = memo(
},
]}
>
<Input placeholder="https://example.com" className="w-full" />
<Input placeholder="https://example.com" className="w-full" disabled={readonly} />
</Form.Item>
<Form.Item className="mt-4">
<Button type="primary" htmlType="submit" className="w-full">
<Button type="primary" htmlType="submit" className="w-full" disabled={readonly}>
{t('canvas.nodes.website.save', 'Save and View Website')}
</Button>
</Form.Item>
Expand Down Expand Up @@ -529,7 +529,9 @@ export const WebsiteNode = memo(
/>

<div className="relative flex-grow min-h-0">
<div className="h-full overflow-y-auto">{data && <NodeContent data={data} />}</div>
<div className="h-full overflow-y-auto">
{data && <NodeContent data={data} readonly={readonly} />}
</div>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ const CanvasItem = (props: {
>
<div className="h-36 overflow-hidden">
{canvas?.minimapUrl ? (
<img src={canvas?.minimapUrl} alt="minimap" className="w-full h-full p-3 object-cover" />
<img
src={`${canvas?.minimapUrl}${canvas?.minimapUrl.includes('?') ? '&' : '?'}_t=${canvas?.updatedAt ?? Date.now()}`}
alt="minimap"
className="w-full h-full p-3 object-cover"
key={`minimap-${canvas?.canvasId}-${canvas?.updatedAt ?? Date.now()}`}
/>
) : (
<div className="flex items-center justify-center w-full h-full bg-gray-100" />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -309,5 +312,56 @@ export const useExportCanvasAsImage = () => {
}
}, [reactFlowInstance, isMinimapLoading, imageToBase64]);

return { exportCanvasAsImage, isLoading, getMinimap, isMinimapLoading };
const svgBlobToPngBlob = useCallback(
async (svgBlob: Blob, svgElement: SVGSVGElement): Promise<Blob | null> => {
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 };
};
21 changes: 11 additions & 10 deletions packages/ai-workspace-common/src/hooks/use-upload-minimap.ts
Original file line number Diff line number Diff line change
@@ -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<string | null>(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,
Expand All @@ -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);
}
}
};
Expand Down

0 comments on commit 97d0523

Please sign in to comment.