From db2c6678e40c645e0fc0fa1e07d98f90963cc7c1 Mon Sep 17 00:00:00 2001 From: yessenia Date: Wed, 29 Oct 2025 14:35:12 +0800 Subject: [PATCH] fix(trigger): show subscription url & add readme in trigger plugin node --- .../components/base/markdown-blocks/img.tsx | 43 +----------- .../components/base/markdown-blocks/index.ts | 4 +- .../base/markdown-blocks/paragraph.tsx | 68 ++++-------------- .../base/markdown-blocks/plugin-img.tsx | 48 +++++++++++++ .../base/markdown-blocks/plugin-paragraph.tsx | 69 +++++++++++++++++++ .../base/markdown/react-markdown-wrapper.tsx | 34 ++++----- .../subscription-list/subscription-card.tsx | 17 ++++- .../components/plugins/readme-panel/index.tsx | 56 +++++++-------- .../_base/components/workflow-panel/index.tsx | 24 ++++++- web/service/demo/index.tsx | 4 +- 10 files changed, 208 insertions(+), 159 deletions(-) create mode 100644 web/app/components/base/markdown-blocks/plugin-img.tsx create mode 100644 web/app/components/base/markdown-blocks/plugin-paragraph.tsx diff --git a/web/app/components/base/markdown-blocks/img.tsx b/web/app/components/base/markdown-blocks/img.tsx index fe20bad6b1..33fce13f0b 100644 --- a/web/app/components/base/markdown-blocks/img.tsx +++ b/web/app/components/base/markdown-blocks/img.tsx @@ -3,48 +3,11 @@ * Extracted from the main markdown renderer for modularity. * Uses the ImageGallery component to display images. */ -import React, { useEffect, useMemo, useState } from 'react' +import React from 'react' import ImageGallery from '@/app/components/base/image-gallery' -import { getMarkdownImageURL } from './utils' -import { usePluginReadmeAsset } from '@/service/use-plugins' -import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper' -type ImgProps = { - src: string - pluginInfo?: SimplePluginInfo -} - -const Img: React.FC = ({ src, pluginInfo }) => { - const { plugin_unique_identifier, plugin_id } = pluginInfo || {} - const { data: assetData } = usePluginReadmeAsset({ plugin_unique_identifier, file_name: src }) - const [blobUrl, setBlobUrl] = useState() - - useEffect(() => { - if (!assetData) { - setBlobUrl(undefined) - return - } - - const objectUrl = URL.createObjectURL(assetData) - setBlobUrl(objectUrl) - - return () => { - URL.revokeObjectURL(objectUrl) - } - }, [assetData]) - - const imageUrl = useMemo(() => { - if (blobUrl) - return blobUrl - - return getMarkdownImageURL(src, plugin_id) - }, [blobUrl, plugin_id, src]) - - return ( -
- -
- ) +const Img = ({ src }: any) => { + return
} export default Img diff --git a/web/app/components/base/markdown-blocks/index.ts b/web/app/components/base/markdown-blocks/index.ts index ba68b4e8b1..ab6be2e9e7 100644 --- a/web/app/components/base/markdown-blocks/index.ts +++ b/web/app/components/base/markdown-blocks/index.ts @@ -5,9 +5,11 @@ export { default as AudioBlock } from './audio-block' export { default as CodeBlock } from './code-block' +export * from './plugin-img' +export * from './plugin-paragraph' export { default as Img } from './img' -export { default as Link } from './link' export { default as Paragraph } from './paragraph' +export { default as Link } from './link' export { default as PreCode } from './pre-code' export { default as ScriptBlock } from './script-block' export { default as VideoBlock } from './video-block' diff --git a/web/app/components/base/markdown-blocks/paragraph.tsx b/web/app/components/base/markdown-blocks/paragraph.tsx index cb654118fd..fb1612477a 100644 --- a/web/app/components/base/markdown-blocks/paragraph.tsx +++ b/web/app/components/base/markdown-blocks/paragraph.tsx @@ -3,69 +3,25 @@ * Extracted from the main markdown renderer for modularity. * Handles special rendering for paragraphs that directly contain an image. */ -import React, { useEffect, useMemo, useState } from 'react' +import React from 'react' import ImageGallery from '@/app/components/base/image-gallery' -import { getMarkdownImageURL } from './utils' -import { usePluginReadmeAsset } from '@/service/use-plugins' -import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper' - -type ParagraphProps = { - pluginInfo?: SimplePluginInfo - node?: any - children?: React.ReactNode -} - -const Paragraph: React.FC = ({ pluginInfo, node, children }) => { - const { plugin_unique_identifier, plugin_id } = pluginInfo || {} - const childrenNode = node?.children as Array | undefined - const firstChild = childrenNode?.[0] - const isImageParagraph = firstChild?.tagName === 'img' - const imageSrc = isImageParagraph ? firstChild?.properties?.src : undefined - - const { data: assetData } = usePluginReadmeAsset({ - plugin_unique_identifier, - file_name: isImageParagraph && imageSrc ? imageSrc : '', - }) - - const [blobUrl, setBlobUrl] = useState() - - useEffect(() => { - if (!assetData) { - setBlobUrl(undefined) - return - } - - const objectUrl = URL.createObjectURL(assetData) - setBlobUrl(objectUrl) - - return () => { - URL.revokeObjectURL(objectUrl) - } - }, [assetData]) - - const imageUrl = useMemo(() => { - if (blobUrl) - return blobUrl - - if (isImageParagraph && imageSrc) - return getMarkdownImageURL(imageSrc, plugin_id) - - return '' - }, [blobUrl, imageSrc, isImageParagraph, plugin_id]) - - if (isImageParagraph) { - const remainingChildren = Array.isArray(children) && children.length > 1 ? children.slice(1) : undefined +const Paragraph = (paragraph: any) => { + const { node }: any = paragraph + const children_node = node.children + if (children_node && children_node[0] && 'tagName' in children_node[0] && children_node[0].tagName === 'img') { return (
- - {remainingChildren && ( -
{remainingChildren}
- )} + + { + Array.isArray(paragraph.children) && paragraph.children.length > 1 && ( +
{paragraph.children.slice(1)}
+ ) + }
) } - return

{children}

+ return

{paragraph.children}

} export default Paragraph diff --git a/web/app/components/base/markdown-blocks/plugin-img.tsx b/web/app/components/base/markdown-blocks/plugin-img.tsx new file mode 100644 index 0000000000..ed1ee8fa0b --- /dev/null +++ b/web/app/components/base/markdown-blocks/plugin-img.tsx @@ -0,0 +1,48 @@ +/** + * @fileoverview Img component for rendering tags in Markdown. + * Extracted from the main markdown renderer for modularity. + * Uses the ImageGallery component to display images. + */ +import React, { useEffect, useMemo, useState } from 'react' +import ImageGallery from '@/app/components/base/image-gallery' +import { getMarkdownImageURL } from './utils' +import { usePluginReadmeAsset } from '@/service/use-plugins' +import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper' + +type ImgProps = { + src: string + pluginInfo?: SimplePluginInfo +} + +export const PluginImg: React.FC = ({ src, pluginInfo }) => { + const { pluginUniqueIdentifier, pluginId } = pluginInfo || {} + const { data: assetData } = usePluginReadmeAsset({ plugin_unique_identifier: pluginUniqueIdentifier, file_name: src }) + const [blobUrl, setBlobUrl] = useState() + + useEffect(() => { + if (!assetData) { + setBlobUrl(undefined) + return + } + + const objectUrl = URL.createObjectURL(assetData) + setBlobUrl(objectUrl) + + return () => { + URL.revokeObjectURL(objectUrl) + } + }, [assetData]) + + const imageUrl = useMemo(() => { + if (blobUrl) + return blobUrl + + return getMarkdownImageURL(src, pluginId) + }, [blobUrl, pluginId, src]) + + return ( +
+ +
+ ) +} diff --git a/web/app/components/base/markdown-blocks/plugin-paragraph.tsx b/web/app/components/base/markdown-blocks/plugin-paragraph.tsx new file mode 100644 index 0000000000..ae1e2d7101 --- /dev/null +++ b/web/app/components/base/markdown-blocks/plugin-paragraph.tsx @@ -0,0 +1,69 @@ +/** + * @fileoverview Paragraph component for rendering

tags in Markdown. + * Extracted from the main markdown renderer for modularity. + * Handles special rendering for paragraphs that directly contain an image. + */ +import ImageGallery from '@/app/components/base/image-gallery' +import { usePluginReadmeAsset } from '@/service/use-plugins' +import React, { useEffect, useMemo, useState } from 'react' +import type { SimplePluginInfo } from '../markdown/react-markdown-wrapper' +import { getMarkdownImageURL } from './utils' + +type PluginParagraphProps = { + pluginInfo?: SimplePluginInfo + node?: any + children?: React.ReactNode +} + +export const PluginParagraph: React.FC = ({ pluginInfo, node, children }) => { + const { pluginUniqueIdentifier, pluginId } = pluginInfo || {} + const childrenNode = node?.children as Array | undefined + const firstChild = childrenNode?.[0] + const isImageParagraph = firstChild?.tagName === 'img' + const imageSrc = isImageParagraph ? firstChild?.properties?.src : undefined + + const { data: assetData } = usePluginReadmeAsset({ + plugin_unique_identifier: pluginUniqueIdentifier, + file_name: isImageParagraph && imageSrc ? imageSrc : '', + }) + + const [blobUrl, setBlobUrl] = useState() + + useEffect(() => { + if (!assetData) { + setBlobUrl(undefined) + return + } + + const objectUrl = URL.createObjectURL(assetData) + setBlobUrl(objectUrl) + + return () => { + URL.revokeObjectURL(objectUrl) + } + }, [assetData]) + + const imageUrl = useMemo(() => { + if (blobUrl) + return blobUrl + + if (isImageParagraph && imageSrc) + return getMarkdownImageURL(imageSrc, pluginId) + + return '' + }, [blobUrl, imageSrc, isImageParagraph, pluginId]) + + if (isImageParagraph) { + const remainingChildren = Array.isArray(children) && children.length > 1 ? children.slice(1) : undefined + + return ( +

+ + {remainingChildren && ( +
{remainingChildren}
+ )} +
+ ) + } + return

{children}

+} diff --git a/web/app/components/base/markdown/react-markdown-wrapper.tsx b/web/app/components/base/markdown/react-markdown-wrapper.tsx index 83b76d97cc..22964ec04f 100644 --- a/web/app/components/base/markdown/react-markdown-wrapper.tsx +++ b/web/app/components/base/markdown/react-markdown-wrapper.tsx @@ -1,30 +1,20 @@ -import ReactMarkdown from 'react-markdown' -import RemarkMath from 'remark-math' -import RemarkBreaks from 'remark-breaks' -import RehypeKatex from 'rehype-katex' -import RemarkGfm from 'remark-gfm' -import RehypeRaw from 'rehype-raw' +import { AudioBlock, Img, Link, MarkdownButton, MarkdownForm, Paragraph, PluginImg, PluginParagraph, ScriptBlock, ThinkBlock, VideoBlock } from '@/app/components/base/markdown-blocks' import { ENABLE_SINGLE_DOLLAR_LATEX } from '@/config' -import AudioBlock from '@/app/components/base/markdown-blocks/audio-block' -import Img from '@/app/components/base/markdown-blocks/img' -import Link from '@/app/components/base/markdown-blocks/link' -import MarkdownButton from '@/app/components/base/markdown-blocks/button' -import MarkdownForm from '@/app/components/base/markdown-blocks/form' -import Paragraph from '@/app/components/base/markdown-blocks/paragraph' -import ScriptBlock from '@/app/components/base/markdown-blocks/script-block' -import ThinkBlock from '@/app/components/base/markdown-blocks/think-block' -import VideoBlock from '@/app/components/base/markdown-blocks/video-block' -import { customUrlTransform } from './markdown-utils' - -import type { FC } from 'react' - import dynamic from 'next/dynamic' +import type { FC } from 'react' +import ReactMarkdown from 'react-markdown' +import RehypeKatex from 'rehype-katex' +import RehypeRaw from 'rehype-raw' +import RemarkBreaks from 'remark-breaks' +import RemarkGfm from 'remark-gfm' +import RemarkMath from 'remark-math' +import { customUrlTransform } from './markdown-utils' const CodeBlock = dynamic(() => import('@/app/components/base/markdown-blocks/code-block'), { ssr: false }) export type SimplePluginInfo = { pluginUniqueIdentifier: string - plugin_id: string + pluginId: string } export type ReactMarkdownWrapperProps = { @@ -70,11 +60,11 @@ export const ReactMarkdownWrapper: FC = (props) => { disallowedElements={['iframe', 'head', 'html', 'meta', 'link', 'style', 'body', ...(props.customDisallowedElements || [])]} components={{ code: CodeBlock, - img: (props: any) => , + img: (props: any) => pluginInfo ? : , video: VideoBlock, audio: AudioBlock, a: Link, - p: (props: any) => , + p: (props: any) => pluginInfo ? : , button: MarkdownButton, form: MarkdownForm, script: ScriptBlock as any, diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/subscription-card.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/subscription-card.tsx index f4766803a4..b2a86b5c76 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/subscription-card.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/subscription-card.tsx @@ -1,5 +1,6 @@ 'use client' import ActionButton from '@/app/components/base/action-button' +import Tooltip from '@/app/components/base/tooltip' import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types' import cn from '@/utils/classnames' import { @@ -48,9 +49,19 @@ const SubscriptionCard = ({ data }: Props) => {
-
- {data.endpoint} -
+ + {data.endpoint} +
+ )} + position='left' + > +
+ {data.endpoint} +
+
ยท
{data.workflows_in_use > 0 ? t('pluginTrigger.subscription.list.item.usedByNum', { num: data.workflows_in_use }) : t('pluginTrigger.subscription.list.item.noUsed')} diff --git a/web/app/components/plugins/readme-panel/index.tsx b/web/app/components/plugins/readme-panel/index.tsx index 369f6538e4..b77d59fb0b 100644 --- a/web/app/components/plugins/readme-panel/index.tsx +++ b/web/app/components/plugins/readme-panel/index.tsx @@ -3,13 +3,12 @@ import ActionButton from '@/app/components/base/action-button' import Loading from '@/app/components/base/loading' import { Markdown } from '@/app/components/base/markdown' import Modal from '@/app/components/base/modal' -import Drawer from '@/app/components/base/drawer' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import { usePluginReadme } from '@/service/use-plugins' import cn from '@/utils/classnames' import { RiBookReadLine, RiCloseLine } from '@remixicon/react' import type { FC } from 'react' -import React from 'react' +import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import DetailHeader from '../plugin-detail-panel/detail-header' import { ReadmeShowType, useReadmePanelStore } from './store' @@ -34,7 +33,7 @@ const ReadmePanel: FC = () => { const children = (
-
+
@@ -71,7 +70,7 @@ const ReadmePanel: FC = () => { return ( ) } @@ -86,38 +85,29 @@ const ReadmePanel: FC = () => {
) - return ( - showType === ReadmeShowType.drawer ? ( - +
{children} - - ) : ( - - {children} - - ) +
+
, + document.body, + ) : ( + + {children} + ) } diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index ca91021885..32b9cb2671 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -342,6 +342,25 @@ const BasePanel: FC = ({ ) }, [handleNodeDataUpdateWithSyncDraft, id]) + const readmeEntranceComponent = useMemo(() => { + let pluginDetail + switch (data.type) { + case BlockEnum.Tool: + pluginDetail = currToolCollection + break + case BlockEnum.DataSource: + pluginDetail = currentDataSource + break + case BlockEnum.TriggerPlugin: + pluginDetail = currentTriggerProvider + break + + default: + break + } + return !pluginDetail ? null : + }, [data.type, currToolCollection, currentDataSource, currentTriggerProvider]) + if (logParams.showSpecialResultPanel) { return (
= ({
{tabType === TabType.settings && ( -
+
{cloneElement(children as any, { id, @@ -609,6 +628,7 @@ const BasePanel: FC = ({
) } + {readmeEntranceComponent}
)} @@ -628,8 +648,6 @@ const BasePanel: FC = ({ /> )} - {data.type === BlockEnum.Tool && } - {data.type === BlockEnum.DataSource && }
) diff --git a/web/service/demo/index.tsx b/web/service/demo/index.tsx index aa02968549..5cbfa7c52a 100644 --- a/web/service/demo/index.tsx +++ b/web/service/demo/index.tsx @@ -4,6 +4,8 @@ import React from 'react' import useSWR, { useSWRConfig } from 'swr' import { createApp, fetchAppDetail, fetchAppList, getAppDailyConversations, getAppDailyEndUsers, updateAppApiStatus, updateAppModelConfig, updateAppRateLimit, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '../apps' import Loading from '@/app/components/base/loading' +import { AppModeEnum } from '@/types/app' + const Service: FC = () => { const { data: appList, error: appListError } = useSWR({ url: '/apps', params: { page: 1 } }, fetchAppList) const { data: firstApp, error: appDetailError } = useSWR({ url: '/apps', id: '1' }, fetchAppDetail) @@ -21,7 +23,7 @@ const Service: FC = () => { const handleCreateApp = async () => { await createApp({ name: `new app${Math.round(Math.random() * 100)}`, - mode: 'chat', + mode: AppModeEnum.CHAT, }) // reload app list mutate({ url: '/apps', params: { page: 1 } })