diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index 3722556931..a0a9323729 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -234,4 +234,6 @@ const Answer: FC = ({ ) } -export default memo(Answer) +export default memo(Answer, (prevProps, nextProps) => + prevProps.responding === false && nextProps.responding === false, +) diff --git a/web/app/components/base/markdown-blocks/music.tsx b/web/app/components/base/markdown-blocks/music.tsx new file mode 100644 index 0000000000..7edd1713c9 --- /dev/null +++ b/web/app/components/base/markdown-blocks/music.tsx @@ -0,0 +1,37 @@ +import abcjs from 'abcjs' +import { useEffect, useRef } from 'react' +import 'abcjs/abcjs-audio.css' + +const MarkdownMusic = ({ children }: { children: React.ReactNode }) => { + const containerRef = useRef(null) + const controlsRef = useRef(null) + + useEffect(() => { + if (containerRef.current && controlsRef.current) { + if (typeof children === 'string') { + const visualObjs = abcjs.renderAbc(containerRef.current, children, { + add_classes: true, // Add classes to SVG elements for cursor tracking + responsive: 'resize', // Make notation responsive + }) + const synthControl = new abcjs.synth.SynthController() + synthControl.load(controlsRef.current, {}, { displayPlay: true }) + const synth = new abcjs.synth.CreateSynth() + const visualObj = visualObjs[0] + synth.init({ visualObj }).then(() => { + synthControl.setTune(visualObj, false) + }) + containerRef.current.style.overflow = 'auto' + } + } + }, [children]) + + return ( +
+
+
+
+ ) +} +MarkdownMusic.displayName = 'MarkdownMusic' + +export default MarkdownMusic diff --git a/web/app/components/base/markdown.tsx b/web/app/components/base/markdown.tsx index d50c397177..52b880affa 100644 --- a/web/app/components/base/markdown.tsx +++ b/web/app/components/base/markdown.tsx @@ -23,6 +23,7 @@ import VideoGallery from '@/app/components/base/video-gallery' import AudioGallery from '@/app/components/base/audio-gallery' import MarkdownButton from '@/app/components/base/markdown-blocks/button' import MarkdownForm from '@/app/components/base/markdown-blocks/form' +import MarkdownMusic from '@/app/components/base/markdown-blocks/music' import ThinkBlock from '@/app/components/base/markdown-blocks/think-block' import { Theme } from '@/types/app' import useTheme from '@/hooks/use-theme' @@ -51,6 +52,7 @@ const capitalizationLanguageNameMap: Record = { json: 'JSON', latex: 'Latex', svg: 'SVG', + abc: 'ABC', } const getCorrectCapitalizationLanguageName = (language: string) => { if (!language) @@ -137,45 +139,54 @@ const CodeBlock: any = memo(({ inline, className, children, ...props }: any) => const renderCodeContent = useMemo(() => { const content = String(children).replace(/\n$/, '') - if (language === 'mermaid' && isSVG) { - return - } - else if (language === 'echarts') { - return ( -
+ switch (language) { + case 'mermaid': + if (isSVG) + return + break + case 'echarts': + return ( +
+ + + +
+ ) + case 'svg': + if (isSVG) { + return ( + + + + ) + } + break + case 'abc': + return ( - + -
- ) - } - else if (language === 'svg' && isSVG) { - return ( - - - - ) - } - else { - return ( - - {content} - - ) + ) + default: + return ( + + {content} + + ) } - }, [language, match, props, children, chartData, isSVG]) + }, [children, language, isSVG, chartData, props, theme, match]) if (inline || !match) return {children} diff --git a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx index 025cb87dc1..9019051989 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-icon/index.tsx @@ -23,9 +23,9 @@ const ModelIcon: FC = ({ isDeprecated = false, }) => { const language = useLanguage() - if (provider?.provider.includes('openai') && modelName?.includes('gpt-4o')) + if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.includes('gpt-4o')) return
- if (provider?.provider.includes('openai') && modelName?.startsWith('gpt-4')) + if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.startsWith('gpt-4')) return
if (provider?.icon_small) { diff --git a/web/app/components/workflow/panel/debug-and-preview/index.tsx b/web/app/components/workflow/panel/debug-and-preview/index.tsx index c33a6355f2..b63b7af16c 100644 --- a/web/app/components/workflow/panel/debug-and-preview/index.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/index.tsx @@ -10,10 +10,10 @@ import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useNodes } from 'reactflow' import { - useEdgesInteractions, - useNodesInteractions, useWorkflowInteractions, } from '../../hooks' +import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' +import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' import { BlockEnum } from '../../types' import type { StartNodeType } from '../../nodes/start/types' import ChatWrapper from './chat-wrapper' @@ -32,8 +32,8 @@ const DebugAndPreview = () => { const { t } = useTranslation() const chatRef = useRef({ handleRestart: noop }) const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions() - const { handleNodeCancelRunningStatus } = useNodesInteractions() - const { handleEdgeCancelRunningStatus } = useEdgesInteractions() + const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync() + const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync() const varList = useStore(s => s.conversationVariables) const [expanded, setExpanded] = useState(true) const nodes = useNodes() @@ -116,7 +116,7 @@ const DebugAndPreview = () => { - {expanded &&
} + {expanded &&
}
)}
diff --git a/web/app/styles/markdown.scss b/web/app/styles/markdown.scss index f1f2a7d670..bd9c7343f3 100644 --- a/web/app/styles/markdown.scss +++ b/web/app/styles/markdown.scss @@ -1039,3 +1039,6 @@ .markdown-body .react-syntax-highlighter-line-number { color: var(--color-text-quaternary); } +.markdown-body .abcjs-inline-audio .abcjs-btn { + display: flex !important; +} diff --git a/web/package.json b/web/package.json index b63617f47b..0dd0b72bc2 100644 --- a/web/package.json +++ b/web/package.json @@ -57,6 +57,7 @@ "@tanstack/react-form": "^1.3.3", "@tanstack/react-query": "^5.60.5", "@tanstack/react-query-devtools": "^5.60.5", + "abcjs": "^6.4.4", "ahooks": "^3.8.4", "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 28822be807..bf39194eaa 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -103,6 +103,9 @@ importers: '@tanstack/react-query-devtools': specifier: ^5.60.5 version: 5.72.2(@tanstack/react-query@5.72.2(react@19.0.0))(react@19.0.0) + abcjs: + specifier: ^6.4.4 + version: 6.4.4 ahooks: specifier: ^3.8.4 version: 3.8.4(react@19.0.0) @@ -3416,6 +3419,9 @@ packages: abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + abcjs@6.4.4: + resolution: {integrity: sha512-dT3Z2vb8yihbiPMzSoup0JOcvO2je4qpFNlTD+kS5VBelE3AASAs18dS5qeMWkZeqCz7kI/hz62B2lpMDugWLA==} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -12127,6 +12133,8 @@ snapshots: abbrev@1.1.1: optional: true + abcjs@6.4.4: {} + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1