From 7a50eed545dce4aa0a14d3bba3e02a0113c16ed3 Mon Sep 17 00:00:00 2001 From: JEN BOEN Date: Thu, 12 Jun 2025 14:28:23 +0800 Subject: [PATCH 1/2] burger menu - version checker --- .../components/artifacts/ArtifactRenderer.tsx | 3 + .../header/artifact-versions-menu.tsx | 150 ++++++++++++++++++ .../src/components/artifacts/header/index.tsx | 15 +- 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/components/artifacts/header/artifact-versions-menu.tsx diff --git a/apps/web/src/components/artifacts/ArtifactRenderer.tsx b/apps/web/src/components/artifacts/ArtifactRenderer.tsx index 7a2caf4a..993f8c9b 100644 --- a/apps/web/src/components/artifacts/ArtifactRenderer.tsx +++ b/apps/web/src/components/artifacts/ArtifactRenderer.tsx @@ -318,6 +318,9 @@ function ArtifactRendererComponent(props: ArtifactRendererProps) { artifactUpdateFailed={artifactUpdateFailed} chatCollapsed={props.chatCollapsed} setChatCollapsed={props.setChatCollapsed} + artifact={artifact} + setSelectedBlocks={setSelectedBlocks} + isStreaming={isStreaming} />
void; + setSelectedBlocks: (blocks: TextHighlight | undefined) => void; + isStreaming?: boolean; +} + +export function ArtifactVersionsMenu({ + artifact, + currentArtifactContent, + setSelectedArtifact, + setSelectedBlocks, + isStreaming = false, +}: ArtifactVersionsMenuProps) { + const [isOpen, setIsOpen] = useState(false); + + const handleVersionSelect = (index: number) => { + if (!isStreaming && index !== currentArtifactContent.index) { + // 清理文本選中狀態以避免版本間的狀態不同步 + setSelectedBlocks(undefined); + setSelectedArtifact(index); + } + setIsOpen(false); + }; + + const getVersionTypeIcon = (content: ArtifactCodeV3 | ArtifactMarkdownV3) => { + return content.type === "code" ? ( + + ) : ( + + ); + }; + + const getVersionLabel = (content: ArtifactCodeV3 | ArtifactMarkdownV3) => { + const baseTitle = content.title || `${content.type === "code" ? "Code" : "Text"} v${content.index}`; + if (content.type === "code" && content.language && content.language !== "other") { + return `${baseTitle} (${content.language})`; + } + return baseTitle; + }; + + const getVersionPreview = (content: ArtifactCodeV3 | ArtifactMarkdownV3) => { + if (content.type === "code") { + const preview = content.code.slice(0, 60).replace(/\n/g, " "); + return preview.length < content.code.length ? `${preview}...` : preview; + } else { + const preview = content.fullMarkdown.slice(0, 60).replace(/\n/g, " "); + return preview.length < content.fullMarkdown.length ? `${preview}...` : preview; + } + }; + + // 按照 index 排序版本 + const sortedVersions = [...artifact.contents].sort((a, b) => a.index - b.index); + + return ( + + + + {isOpen ? ( + + ) : ( + + )} + + + + + + Artifact Versions ({artifact.contents.length}) + + + + {sortedVersions.map((version) => { + const isCurrentVersion = version.index === currentArtifactContent.index; + + return ( + handleVersionSelect(version.index)} + disabled={isStreaming} + > +
+ {getVersionTypeIcon(version)} + + {getVersionLabel(version)} + + {isCurrentVersion && ( + + Current + + )} +
+ + {/* 版本預覽 */} +
+ {getVersionPreview(version)} +
+ + {/* 版本號 */} +
+ Version {version.index} +
+
+ ); + })} + + {artifact.contents.length === 0 && ( + + No versions available + + )} +
+ + ); +} diff --git a/apps/web/src/components/artifacts/header/index.tsx b/apps/web/src/components/artifacts/header/index.tsx index 2fae7e1e..2c1aaaf0 100644 --- a/apps/web/src/components/artifacts/header/index.tsx +++ b/apps/web/src/components/artifacts/header/index.tsx @@ -1,7 +1,8 @@ import { ReflectionsDialog } from "../../reflections-dialog/ReflectionsDialog"; import { ArtifactTitle } from "./artifact-title"; import { NavigateArtifactHistory } from "./navigate-artifact-history"; -import { ArtifactCodeV3, ArtifactMarkdownV3 } from "@opencanvas/shared/types"; +import { ArtifactVersionsMenu } from "./artifact-versions-menu"; +import { ArtifactCodeV3, ArtifactMarkdownV3, ArtifactV3, TextHighlight } from "@opencanvas/shared/types"; import { Assistant } from "@langchain/langgraph-sdk"; import { PanelRightClose } from "lucide-react"; import { TooltipIconButton } from "@/components/ui/assistant-ui/tooltip-icon-button"; @@ -17,12 +18,24 @@ interface ArtifactHeaderProps { artifactUpdateFailed: boolean; chatCollapsed: boolean; setChatCollapsed: (c: boolean) => void; + artifact: ArtifactV3; + setSelectedBlocks: (blocks: TextHighlight | undefined) => void; + isStreaming?: boolean; } export function ArtifactHeader(props: ArtifactHeaderProps) { return (
+ {/* Burger Menu for Artifact Versions */} + + {props.chatCollapsed && ( Date: Thu, 12 Jun 2025 15:30:20 +0800 Subject: [PATCH 2/2] burger menu - version checker --- .../artifacts/header/artifact-versions-menu.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/artifacts/header/artifact-versions-menu.tsx b/apps/web/src/components/artifacts/header/artifact-versions-menu.tsx index 240d92d5..cc893b41 100644 --- a/apps/web/src/components/artifacts/header/artifact-versions-menu.tsx +++ b/apps/web/src/components/artifacts/header/artifact-versions-menu.tsx @@ -31,7 +31,7 @@ export function ArtifactVersionsMenu({ const handleVersionSelect = (index: number) => { if (!isStreaming && index !== currentArtifactContent.index) { - // 清理文本選中狀態以避免版本間的狀態不同步 + setSelectedBlocks(undefined); setSelectedArtifact(index); } @@ -64,7 +64,7 @@ export function ArtifactVersionsMenu({ } }; - // 按照 index 排序版本 + // Sort versions by index const sortedVersions = [...artifact.contents].sort((a, b) => a.index - b.index); return ( @@ -122,7 +122,7 @@ export function ArtifactVersionsMenu({ )}
- {/* 版本預覽 */} + {/* version preview */}
- {/* 版本號 */} + {/* version number */}
Version {version.index}