From d94e0720f364ec2d85e72dfa252b249c3524939e Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Fri, 28 Nov 2025 10:09:09 -0800 Subject: [PATCH] Handle HTML fallbacks for node help and skip blueprint docs (#7021) explicitly prevents subgraphs from making an api call since they don't have docs, this was previously reliant on a non-ok resolution also doesn't try returning anything that has contenttype of text/html to prevent the markdown renderer from crashing ## Summary - short-circuit blueprint/subgraph nodes in help: skip doc fetch and return the node description, avoiding SPA fallback responses - guard node help fetch against HTML/SPA fallbacks using content-type checks; treat them as missing and trigger the existing description fallback - keep base URL logic unchanged for non-blueprint nodes ## Testing - pnpm typecheck - pnpm lint:fix - pnpm test:unit --- src/services/nodeHelpService.ts | 57 +++++++++++++++++++++-------- src/workbench/utils/nodeHelpUtil.ts | 3 ++ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/services/nodeHelpService.ts b/src/services/nodeHelpService.ts index 88bb48d29..3bbebfdda 100644 --- a/src/services/nodeHelpService.ts +++ b/src/services/nodeHelpService.ts @@ -7,6 +7,10 @@ class NodeHelpService { async fetchNodeHelp(node: ComfyNodeDefImpl, locale: string): Promise { const nodeSource = getNodeSource(node.python_module) + if (nodeSource.type === NodeSourceType.Blueprint) { + return node.description || '' + } + if (nodeSource.type === NodeSourceType.CustomNodes) { return this.fetchCustomNodeHelp(node, locale) } else { @@ -19,25 +23,24 @@ class NodeHelpService { locale: string ): Promise { const customNodeName = extractCustomNodeName(node.python_module) + let lastError: string | undefined if (!customNodeName) { throw new Error('Invalid custom node module') } // Try locale-specific path first const localePath = `/extensions/${customNodeName}/docs/${node.name}/${locale}.md` - let res = await fetch(api.fileURL(localePath)) + const localeDoc = await this.tryFetchMarkdown(localePath) + if (localeDoc.text) return localeDoc.text + lastError = localeDoc.errorText - if (!res.ok) { - // Fall back to non-locale path - const fallbackPath = `/extensions/${customNodeName}/docs/${node.name}.md` - res = await fetch(api.fileURL(fallbackPath)) - } + // Fall back to non-locale path + const fallbackPath = `/extensions/${customNodeName}/docs/${node.name}.md` + const fallbackDoc = await this.tryFetchMarkdown(fallbackPath) + if (fallbackDoc.text) return fallbackDoc.text + lastError = fallbackDoc.errorText ?? lastError - if (!res.ok) { - throw new Error(res.statusText) - } - - return res.text() + throw new Error(lastError ?? 'Help not found') } private async fetchCoreNodeHelp( @@ -45,13 +48,35 @@ class NodeHelpService { locale: string ): Promise { const mdUrl = `/docs/${node.name}/${locale}.md` - const res = await fetch(api.fileURL(mdUrl)) - - if (!res.ok) { - throw new Error(res.statusText) + const doc = await this.tryFetchMarkdown(mdUrl) + if (!doc.text) { + throw new Error(doc.errorText ?? 'Help not found') } - return res.text() + return doc.text + } + + /** + * Fetch a markdown file and return its text, guarding against HTML/SPA fallbacks. + * Returns null when not OK or when the content type indicates HTML. + */ + private async tryFetchMarkdown( + path: string + ): Promise<{ text: string | null; errorText?: string }> { + const res = await fetch(api.fileURL(path)) + + if (!res.ok) { + return { text: null, errorText: res.statusText } + } + + const contentType = res.headers?.get?.('content-type') ?? '' + const text = await res.text() + + const isHtmlContentType = contentType.includes('text/html') + + if (isHtmlContentType) return { text: null, errorText: res.statusText } + + return { text } } } diff --git a/src/workbench/utils/nodeHelpUtil.ts b/src/workbench/utils/nodeHelpUtil.ts index 2bcd89961..2efec34a0 100644 --- a/src/workbench/utils/nodeHelpUtil.ts +++ b/src/workbench/utils/nodeHelpUtil.ts @@ -15,6 +15,9 @@ export function extractCustomNodeName( export function getNodeHelpBaseUrl(node: ComfyNodeDefImpl): string { const nodeSource = getNodeSource(node.python_module) + if (nodeSource.type === NodeSourceType.Blueprint) { + return '' + } if (nodeSource.type === NodeSourceType.CustomNodes) { const customNodeName = extractCustomNodeName(node.python_module) if (customNodeName) {