From b0fdeab99568cd4cfee11d51ab7206b887c836cc Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Fri, 5 Jun 2026 18:58:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/docs/embed-iframe-chat.md | 6 +- frontend-web/src/components/page-sdebar.css | 2 +- .../artifacts/artifact-file-detail.tsx | 243 +++++++++++++----- frontend-web/src/core/auth/yuser-login.ts | 2 +- frontend-web/src/core/i18n/locales/zh-CN.ts | 2 +- .../src/core/page-layout/sidebar-menu.ts | 16 +- frontend-web/src/core/utils/files.tsx | 9 +- frontend-web/src/core/utils/mermaid.ts | 75 ++++++ frontend-web/src/pages/NewAgentPage.tsx | 2 +- frontend-web/src/pages/StrategyRoutes.tsx | 10 + .../components/ArtifactPreviewModal.tsx | 54 +++- .../components/Step3ReportMode.tsx | 2 +- .../app/gateway/roundtable_job_executor.py | 14 +- .../agents/roundtable_orchestrator/engine.py | 6 +- .../scripts/inspect_latest_roundtable_run.py | 114 ++++++++ 15 files changed, 467 insertions(+), 90 deletions(-) create mode 100644 frontend-web/src/core/utils/mermaid.ts create mode 100644 offline-backend-20260512/backend/scripts/inspect_latest_roundtable_run.py diff --git a/frontend-web/docs/embed-iframe-chat.md b/frontend-web/docs/embed-iframe-chat.md index 45ed5de2..5c80a46d 100644 --- a/frontend-web/docs/embed-iframe-chat.md +++ b/frontend-web/docs/embed-iframe-chat.md @@ -107,7 +107,7 @@ http://localhost:5173/#/embed/chats/new?sessionId=...&message=...&username=guest ## 4. 公开 HTTP API(无需鉴权) -前缀:`/api/public/embed/`(走 Gateway,与 `VITE_BACKEND_BASE_URL` 一致,默认 `http://127.0.0.1:8001`)。 +前缀:`/api/public/embed/`(走 Gateway,与 `VITE_BACKEND_BASE_URL` 一致,默认 `http://47.88.25.99:7001/`)。 CSRF / Auth 中间件对 `/api/public/` 放行;请求 **不需要** Cookie。 @@ -315,7 +315,7 @@ document.getElementById("deerflow-embed").src = ### 5.2 轮询直到结束 ```javascript -const API = "http://127.0.0.1:8001"; // VITE_BACKEND_BASE_URL +const API = "http://47.88.25.99:7001/"; // VITE_BACKEND_BASE_URL async function pollUntilFinished(sessionId) { const res = await fetch( @@ -348,7 +348,7 @@ const timer = setInterval(async () => { ### 5.3 跨域注意 -父页面从浏览器 `fetch` Gateway(如 `http://127.0.0.1:8001`)时,**Origin 必须是后端 CORS 白名单里的完整地址**(含协议、主机、端口),与 API 地址是否写 `localhost` / `127.0.0.1` 无关。 +父页面从浏览器 `fetch` Gateway(如 `http://47.88.25.99:7001/`)时,**Origin 必须是后端 CORS 白名单里的完整地址**(含协议、主机、端口),与 API 地址是否写 `localhost` / `127.0.0.1` 无关。 **本地开发**(前端默认 `5174`)请在 Gateway 环境变量中配置: diff --git a/frontend-web/src/components/page-sdebar.css b/frontend-web/src/components/page-sdebar.css index f4710c40..6aff6410 100644 --- a/frontend-web/src/components/page-sdebar.css +++ b/frontend-web/src/components/page-sdebar.css @@ -1,5 +1,5 @@ .sidebar-contnet { - position: fixed; + position: absolute; left: 0; top: 4rem; z-index: 40; diff --git a/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx b/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx index 98013290..f9786b76 100644 --- a/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx +++ b/frontend-web/src/components/workspace/artifacts/artifact-file-detail.tsx @@ -48,11 +48,17 @@ import { useI18n } from "@/core/i18n/hooks"; import { downloadMarkdownAsDocx } from "@/core/scheduled-tasks/markdown-docx"; import { installSkill } from "@/core/skills/api"; import { streamdownPlugins } from "@/core/streamdown"; -import { - checkCodeFile, - getFileName, - isImageFile, -} from "@/core/utils/files"; +import { + checkCodeFile, + getFileName, + isImageFile, +} from "@/core/utils/files"; +import { + downloadMermaidSvgFile, + isMermaidFile, + openMermaidSvgInNewTab, + renderMermaidToSvg, +} from "@/core/utils/mermaid"; import { getBackendBaseURL } from "@/core/config"; import { resolveArtifactURL } from "@/core/artifacts/utils"; import { env } from "@/env"; @@ -129,10 +135,14 @@ export function ArtifactFileDetail({ return { isCodeFile: true, language: "markdown" }; } return checkCodeFile(filepath); - }, [filepath, useWriteFileStreaming, isSkillFile]); - const isSupportPreview = useMemo(() => { - return language === "html" || language === "markdown"; - }, [language]); + }, [filepath, useWriteFileStreaming, isSkillFile]); + const isSupportPreview = useMemo(() => { + return ( + language === "html" || + language === "markdown" || + language === "mermaid" + ); + }, [language]); const { content, isApplyingEdit } = useArtifactContent({ threadId, filepath: filepathFromProps, @@ -150,11 +160,12 @@ export function ArtifactFileDetail({ const displayContent = canEditArtifact ? draftContent : (content ?? ""); const isHtmlStreaming = language === "html" && thread.isLoading; const hasMultipleArtifacts = (artifacts?.length ?? 0) > 1; - const fileTypeLabel = useMemo(() => { - if (isImageFile(filepath)) return "IMG"; - if (language === "html") return "HTML"; - if (language === "markdown") return "MD"; - if (isSkillFile) return "SKILL"; + const fileTypeLabel = useMemo(() => { + if (isImageFile(filepath)) return "IMG"; + if (isMermaidFile(filepath) || language === "mermaid") return "MMD"; + if (language === "html") return "HTML"; + if (language === "markdown") return "MD"; + if (isSkillFile) return "SKILL"; if (language) return language.toUpperCase(); return "FILE"; }, [filepath, isSkillFile, language]); @@ -423,9 +434,16 @@ export function ArtifactFileDetail({ toast.error("文件内容为空,暂时无法打开"); return; } - const blobType = - language === "html" - ? "text/html;charset=utf-8" + if (language === "mermaid") { + void openMermaidSvgInNewTab(source).catch((error) => { + console.error("Failed to open mermaid artifact:", error); + toast.error(error instanceof Error ? error.message : "Failed to open artifact"); + }); + return; + } + const blobType = + language === "html" + ? "text/html;charset=utf-8" : language === "markdown" ? "text/markdown;charset=utf-8" : "text/plain;charset=utf-8"; @@ -435,15 +453,22 @@ export function ArtifactFileDetail({ window.setTimeout(() => URL.revokeObjectURL(url), 60_000); }, [displayContent, language]); - const handleDownloadWriteFile = useCallback(() => { - const source = displayContent ?? ""; + const handleDownloadWriteFile = useCallback(() => { + const source = displayContent ?? ""; if (!source.trim()) { toast.error("文件内容为空,暂时无法下载"); return; } - const blobType = - language === "html" - ? "text/html;charset=utf-8" + if (language === "mermaid") { + void downloadMermaidSvgFile(source, filepath).catch((error) => { + console.error("Failed to download mermaid artifact:", error); + toast.error(error instanceof Error ? error.message : "Download failed"); + }); + return; + } + const blobType = + language === "html" + ? "text/html;charset=utf-8" : language === "markdown" ? "text/markdown;charset=utf-8" : "text/plain;charset=utf-8"; @@ -562,14 +587,27 @@ export function ArtifactFileDetail({ icon={SquareArrowOutUpRightIcon} label={t.common.openInNewWindow} tooltip={t.common.openInNewWindow} - onClick={async () => { - if (useWriteFileStreaming) { - handleOpenWriteFileInNewTab(); - return; - } - try { - await openArtifactInNewTab({ - filepath, + onClick={async () => { + if (useWriteFileStreaming) { + handleOpenWriteFileInNewTab(); + return; + } + if (language === "mermaid") { + try { + await openMermaidSvgInNewTab(displayContent ?? ""); + } catch (error) { + console.error(error); + toast.error( + error instanceof Error + ? error.message + : "Failed to open artifact", + ); + } + return; + } + try { + await openArtifactInNewTab({ + filepath, threadId, isMock, publicShareCode, @@ -614,14 +652,27 @@ export function ArtifactFileDetail({ icon={DownloadIcon} label={t.common.download} tooltip={t.common.download} - onClick={async () => { - if (useWriteFileStreaming) { - handleDownloadWriteFile(); - return; - } - try { - await downloadArtifactFile({ - filepath, + onClick={async () => { + if (useWriteFileStreaming) { + handleDownloadWriteFile(); + return; + } + if (language === "mermaid") { + try { + await downloadMermaidSvgFile(displayContent ?? "", filepath); + } catch (error) { + console.error(error); + toast.error( + error instanceof Error + ? error.message + : "Download failed", + ); + } + return; + } + try { + await downloadArtifactFile({ + filepath, threadId, isMock, publicShareCode, @@ -666,13 +717,15 @@ export function ArtifactFileDetail({ 滑动到底部并继续自动下滑 - )} - {isSupportPreview && - viewMode === "preview" && - (language === "markdown" || language === "html") && ( - (); +}) { + const [htmlPreviewUrl, setHtmlPreviewUrl] = useState(); + const [mermaidSvg, setMermaidSvg] = useState(null); + const [mermaidError, setMermaidError] = useState(null); - useEffect(() => { - if (language !== "html") { - setHtmlPreviewUrl(undefined); - return; + useEffect(() => { + if (language !== "html") { + setHtmlPreviewUrl(undefined); + return; } // 必须带 charset=utf-8:blob: URL 的 Content-Type 不含 charset 时,iframe 会按 @@ -785,9 +840,40 @@ export function ArtifactFilePreview({ setHtmlPreviewUrl(url); return () => { - URL.revokeObjectURL(url); - }; - }, [content, language]); + URL.revokeObjectURL(url); + }; + }, [content, language]); + + useEffect(() => { + if (language !== "mermaid") { + setMermaidSvg(null); + setMermaidError(null); + return; + } + + let cancelled = false; + setMermaidSvg(null); + setMermaidError(null); + + void renderMermaidToSvg(content ?? "") + .then((svg) => { + if (!cancelled) { + setMermaidSvg(svg); + } + }) + .catch((error) => { + console.error("Failed to render mermaid preview:", error); + if (!cancelled) { + setMermaidError( + error instanceof Error ? error.message : "Mermaid 渲染失败", + ); + } + }); + + return () => { + cancelled = true; + }; + }, [content, language]); if (language === "markdown") { return ( @@ -821,9 +907,9 @@ export function ArtifactFilePreview({ ); } - if (language === "html") { - return ( -