import { DeleteOutlined, HeartFilled, HeartOutlined, ImportOutlined, LeftOutlined, PictureOutlined, PlusOutlined, RightOutlined, } from "@ant-design/icons"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { communityClient, type ServerCommunityCase } from "../../api/communityClient"; import WorkspacePageShell from "../../components/WorkspacePageShell"; import OptimizedImage from "../../components/OptimizedImage"; import { EmptyState } from "../../components/EmptyState"; import { cloneWorkflow, createBlankWorkflow } from "../../data/workflows"; import type { WebCanvasWorkflow, WebProjectSummary } from "../../types"; import { getCommunityCaseCover, getWorkflowFromCase, shouldShowInCanvasCommunity } from "./communityCaseUtils"; import { ossThumb } from "../../utils/ossImageOptimize"; const OSS_MUBAN = "https://stringtest.oss-cn-hangzhou.aliyuncs.com/muban"; interface CommunityPageProps { projects: WebProjectSummary[]; isAuthenticated: boolean; onStartCreate: () => void; onOpenProject: (project: WebProjectSummary) => void; onDeleteProject?: (project: WebProjectSummary) => void; onImportWorkflow: (workflow: WebCanvasWorkflow) => void; onRequireLogin?: (action: string) => boolean | void; } const communityCardImages = [ `${OSS_MUBAN}/dianshang1.png`, `${OSS_MUBAN}/dianshang2.png`, `${OSS_MUBAN}/dianshang3.png`, `${OSS_MUBAN}/wechat-7.png`, `${OSS_MUBAN}/wechat-8.png`, `${OSS_MUBAN}/wechat-9.png`, ]; const SLIDE_INTERVAL = 3000; const CAROUSEL_VISIBLE_COUNT = 3; const MANUAL_PAUSE_DURATION = 2000; const COMMUNITY_CAROUSEL_VIDEOS = [ "https://stringtest.oss-cn-hangzhou.aliyuncs.com/test3.mp4", "https://stringtest.oss-cn-hangzhou.aliyuncs.com/test4.mp4", "https://stringtest.oss-cn-hangzhou.aliyuncs.com/test6.mp4", ]; function buildWorkflowFromServerCase(item: ServerCommunityCase, fallback: WebCanvasWorkflow): WebCanvasWorkflow { const workflow = getWorkflowFromCase(item); if (workflow) { return cloneWorkflow(workflow); } const coverUrl = getCommunityCaseCover(item); return { ...cloneWorkflow(fallback), id: `community-server-${item.id}`, title: item.title, description: item.description || fallback.description, author: item.username || fallback.author, source: "community", nodes: fallback.nodes.map((node) => node.id === "reference" && coverUrl ? { ...node, previewUrl: coverUrl } : node, ), }; } function CommunityPage({ projects, isAuthenticated, onStartCreate, onOpenProject, onDeleteProject, onImportWorkflow, onRequireLogin }: CommunityPageProps) { const [serverCases, setServerCases] = useState([]); const [serverNotice, setServerNotice] = useState(null); const [favoriteIds, setFavoriteIds] = useState([]); const canUseProtectedAction = (action: string) => onRequireLogin?.(action) !== false; const handleStartCreate = () => { if (!canUseProtectedAction("start-community-project")) return; onStartCreate(); }; const handleImportWorkflow = (workflow: WebCanvasWorkflow) => { if (!canUseProtectedAction("import-community-workflow")) return; onImportWorkflow(workflow); }; useEffect(() => { let cancelled = false; communityClient .listApprovedCases() .then((items) => { if (!cancelled) { const canvasItems = items.filter(shouldShowInCanvasCommunity); setServerCases(canvasItems); setServerNotice( canvasItems.length ? "已连接服务器画布社区" : items.length ? "服务器暂无已审核画布案例" : "社区暂无模板", ); } }) .catch((error) => { if (!cancelled) { setServerCases([]); setServerNotice(error instanceof Error ? error.message : "社区服务暂时不可用"); } }); return () => { cancelled = true; }; }, []); const getProjectTitle = (project: WebProjectSummary, index: number) => project.source === "server" ? project.name : `预览项目 ${index + 1}`; const getProjectSummary = (project: WebProjectSummary) => { if (project.source === "server") { return project.description || "最近更新的项目"; } return "预览数据"; }; /* Carousel state */ const [currentIndex, setCurrentIndex] = useState(0); const [carouselTransitionEnabled, setCarouselTransitionEnabled] = useState(true); const [isHoverPaused, setIsHoverPaused] = useState(false); const [isManualPaused, setIsManualPaused] = useState(false); const timerRef = useRef | null>(null); const manualPauseTimerRef = useRef(null); const carouselCases = useMemo( () => COMMUNITY_CAROUSEL_VIDEOS.map((videoUrl, index) => ({ title: `社区视频 ${index + 1}`, videoUrl, })), [], ); const totalSlides = carouselCases.length; const visibleCarouselCases = useMemo( () => [...carouselCases, ...carouselCases.slice(0, CAROUSEL_VISIBLE_COUNT)], [carouselCases], ); const activeCarouselIndex = (currentIndex + 1) % totalSlides; const centerCarouselItemIndex = currentIndex + Math.floor(CAROUSEL_VISIBLE_COUNT / 2); const isAutoPaused = isHoverPaused || isManualPaused; const pauseAutoCarousel = useCallback(() => { setIsManualPaused(true); if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } if (manualPauseTimerRef.current) { window.clearTimeout(manualPauseTimerRef.current); } manualPauseTimerRef.current = window.setTimeout(() => { setIsManualPaused(false); manualPauseTimerRef.current = null; }, MANUAL_PAUSE_DURATION); }, []); const goNext = useCallback((pause = true) => { if (pause) pauseAutoCarousel(); setCarouselTransitionEnabled(true); setCurrentIndex((prev) => prev + 1); }, [pauseAutoCarousel]); const goPrev = useCallback(() => { pauseAutoCarousel(); setCarouselTransitionEnabled(true); setCurrentIndex((prev) => { if (prev > 0) return prev - 1; setCarouselTransitionEnabled(false); window.requestAnimationFrame(() => { setCurrentIndex(totalSlides); window.requestAnimationFrame(() => { setCarouselTransitionEnabled(true); setCurrentIndex(totalSlides - 1); }); }); return prev; }); }, [pauseAutoCarousel, totalSlides]); const handlePrevClick = useCallback(() => { goPrev(); }, [goPrev]); const handleNextClick = useCallback(() => { pauseAutoCarousel(); goNext(false); }, [goNext, pauseAutoCarousel]); const goTo = useCallback((index: number) => { setCarouselTransitionEnabled(true); setCurrentIndex((index - 1 + totalSlides) % totalSlides); }, [totalSlides]); const handleCarouselTransitionEnd = () => { if (currentIndex < totalSlides) return; setCarouselTransitionEnabled(false); setCurrentIndex(0); window.requestAnimationFrame(() => { setCarouselTransitionEnabled(true); }); }; useEffect(() => { if (isAutoPaused) return; timerRef.current = setInterval(() => goNext(false), SLIDE_INTERVAL); return () => { if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } }; }, [isAutoPaused, goNext]); useEffect(() => { return () => { if (manualPauseTimerRef.current) { window.clearTimeout(manualPauseTimerRef.current); } if (timerRef.current) { clearInterval(timerRef.current); } }; }, []); useEffect(() => { let cancelled = false; const timeoutId = window.setTimeout(() => { communityClient .listApprovedCases({ limit: 30, sort: "latest", }) .then((items) => { if (!cancelled) { const canvasItems = items.filter(shouldShowInCanvasCommunity); setServerCases(canvasItems); setServerNotice( canvasItems.length ? "已连接服务器画布社区" : items.length ? "服务器暂无匹配画布案例" : "社区暂无模板", ); } }) .catch((error) => { if (!cancelled) { setServerNotice(error instanceof Error ? error.message : "社区服务暂时不可用"); } }); }, 280); return () => { cancelled = true; window.clearTimeout(timeoutId); }; }, []); const handleToggleFavorite = async (item: ServerCommunityCase, cardId: string) => { const nextActive = !(item.isFavorited || favoriteIds.includes(cardId)); setFavoriteIds((current) => nextActive ? Array.from(new Set([...current, cardId])) : current.filter((id) => id !== cardId), ); setServerCases((current) => current.map((caseItem) => caseItem.id === item.id ? { ...caseItem, isFavorited: nextActive, favoriteCount: Math.max(0, caseItem.favoriteCount + (nextActive ? 1 : -1)), } : caseItem, ), ); if (!isAuthenticated || !serverCases.some((caseItem) => caseItem.id === item.id)) return; try { const stats = await communityClient.setReaction(item.id, "favorite", nextActive); setServerCases((current) => current.map((caseItem) => (caseItem.id === item.id ? { ...caseItem, ...stats } : caseItem)), ); } catch (error) { setServerNotice(error instanceof Error ? error.message : "收藏状态同步失败"); } }; const liveCases: ServerCommunityCase[] = serverCases.slice(0, 12); return (
setIsHoverPaused(true)} onMouseLeave={() => setIsHoverPaused(false)} role="region" aria-label="示例预览轮播" >
{visibleCarouselCases.map((card, index) => (
))}
{carouselCases.map((_, index) => (

最近项目

{isAuthenticated && {projects.length} 个项目}
{isAuthenticated && projects.map((project, index) => (
{onDeleteProject ? ( ) : null}
))}

社区精选

{serverNotice ? {serverNotice} : null}
{liveCases.length ? (
{liveCases.map((item, index) => { const fallbackWorkflow = createBlankWorkflow(item.title); const workflow = buildWorkflowFromServerCase(item, fallbackWorkflow); const cardId = `case-${item.id}`; const isFavorite = item.isFavorited || favoriteIds.includes(cardId); const imageUrl = getCommunityCaseCover(item) || communityCardImages[index % communityCardImages.length] || fallbackWorkflow.nodes.find((node) => node.previewUrl)?.previewUrl || ""; return (
handleImportWorkflow(workflow)} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); handleImportWorkflow(workflow); } }} tabIndex={0} >

{item.description || "可一键导入工作流继续创作。"}

handleImportWorkflow(workflow)} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); handleImportWorkflow(workflow); } }} >
{item.username || "OmniAI"} {item.tags[0] || item.status}
{item.title}

{item.description || "Import this workflow and continue creating."}

); })}
) : ( } title="社区暂无模板" description="管理员审核通过后,画布社区案例会显示在这里。" /> )}
); } export default CommunityPage;