feat: refactor rendering I
This commit is contained in:
@@ -24,6 +24,20 @@ export type RenderOptions = { width?: number; height?: number }
|
||||
/** How the renderer should display this type: HTML in a div, or image (SVG/PNG) in a viewport. */
|
||||
export type ConfigOutputType = 'html' | 'image'
|
||||
|
||||
/** Data-only descriptor for output menu items on the Rendering node (e.g. Export SVG/PNG). Provided by config/source types. */
|
||||
export type OutputMenuAction = 'downloadSvg' | 'downloadPng' | 'copySvg' | 'copyPng'
|
||||
export type OutputMenuItemDescriptor = { id: string; label: string; action: OutputMenuAction }
|
||||
export type OutputMenuDescriptor = {
|
||||
/** Submenu label (e.g. "Export"). Omit for inline items. */
|
||||
submenuLabel?: string
|
||||
items: OutputMenuItemDescriptor[]
|
||||
}
|
||||
|
||||
/** Whether this output menu action requires SVG content (disabled when no SVG). */
|
||||
export function outputMenuActionRequiresSvg(action: OutputMenuAction): boolean {
|
||||
return action === 'downloadSvg' || action === 'downloadPng' || action === 'copySvg' || action === 'copyPng'
|
||||
}
|
||||
|
||||
export type ConfigType = {
|
||||
id: ConfigTypeId
|
||||
label: string
|
||||
@@ -35,6 +49,8 @@ export type ConfigType = {
|
||||
insertBlocks: InsertBlockOrGroup[]
|
||||
/** Render resolved content (after Nunjucks) to HTML/SVG string for the renderer. */
|
||||
render: (content: string, options?: RenderOptions) => Promise<string>
|
||||
/** Optional. Output menu items for the Rendering node when this type is shown (e.g. Export submenu for image types). */
|
||||
outputMenuDescriptor?: OutputMenuDescriptor
|
||||
}
|
||||
|
||||
const KROKI_PLANTUML_SVG = '/api/kroki/plantuml/svg'
|
||||
@@ -169,6 +185,15 @@ export const CONFIG_TYPES: ConfigType[] = [
|
||||
throw err
|
||||
}
|
||||
},
|
||||
outputMenuDescriptor: {
|
||||
submenuLabel: 'Export',
|
||||
items: [
|
||||
{ id: 'downloadSvg', label: 'Download SVG', action: 'downloadSvg' },
|
||||
{ id: 'downloadPng', label: 'Download PNG', action: 'downloadPng' },
|
||||
{ id: 'copySvg', label: 'Copy SVG', action: 'copySvg' },
|
||||
{ id: 'copyPng', label: 'Copy image', action: 'copyPng' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'markdown',
|
||||
@@ -185,6 +210,15 @@ export const CONFIG_TYPES: ConfigType[] = [
|
||||
language: 'markdown',
|
||||
insertBlocks: WIREFRAME_INSERT_BLOCKS,
|
||||
render: renderWireframe,
|
||||
outputMenuDescriptor: {
|
||||
submenuLabel: 'Export',
|
||||
items: [
|
||||
{ id: 'downloadSvg', label: 'Download SVG', action: 'downloadSvg' },
|
||||
{ id: 'downloadPng', label: 'Download PNG', action: 'downloadPng' },
|
||||
{ id: 'copySvg', label: 'Copy SVG', action: 'copySvg' },
|
||||
{ id: 'copyPng', label: 'Copy image', action: 'copyPng' },
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
39
frontend/src/lib/graph/renderingUtils.ts
Normal file
39
frontend/src/lib/graph/renderingUtils.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Shared rendering utilities used by the Rendering node and output views.
|
||||
* Pure functions only; no React or node-specific logic.
|
||||
*/
|
||||
|
||||
/** Extract <think>...</think> blocks from HTML/markdown; return main (with blocks removed) and think for collapsible. */
|
||||
export function parseThinkSections(html: string): { main: string; think: string } {
|
||||
const thinkRegex = /<think>([\s\S]*?)<\/think>/gi
|
||||
const thinkParts: string[] = []
|
||||
let match
|
||||
while ((match = thinkRegex.exec(html)) !== null) thinkParts.push(match[1].trim())
|
||||
const think = thinkParts.join('\n\n')
|
||||
const main = html.replace(thinkRegex, '').replace(/\n{3,}/g, '\n\n').trim()
|
||||
return { main, think }
|
||||
}
|
||||
|
||||
/** Process SVG HTML for viewport display (aspect ratio, fill container). */
|
||||
export function processSvgDisplay(html: string): string {
|
||||
let out = html
|
||||
out = out.replace(/\bpreserveAspectRatio\s*=\s*["']none["']/gi, 'preserveAspectRatio="xMidYMid meet"')
|
||||
out = out.replace(/\bwidth\s*=\s*["'][^"']*["']/i, 'width="100%"')
|
||||
out = out.replace(/\bheight\s*=\s*["'][^"']*["']/i, 'height="100%"')
|
||||
out = out.replace(/\bstyle\s*=\s*["']([^"']*)["']/i, (_, style) => {
|
||||
const overridden = style.replace(/\b(width|height):[^;]+/gi, '$1:100%')
|
||||
return `style="${overridden}"`
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
/** Strip Nunjucks/template syntax for raw view. */
|
||||
export function stripTemplateSyntax(text: string): string {
|
||||
return text
|
||||
.replace(/\{%[\s\S]*?%\}/g, '')
|
||||
.replace(/\{\{[\s\S]*?\}\}/g, '')
|
||||
.replace(/\{#[\s\S]*?#\}/g, '')
|
||||
.replace(/(\r?\n)\s*(\r?\n)/g, '$1$2')
|
||||
.replace(/^\s*\n|\n\s*$/g, (m) => (m === '\n' ? '\n' : ''))
|
||||
.trim()
|
||||
}
|
||||
@@ -7,7 +7,9 @@
|
||||
* Node types that are allowed sources for the Renderer should register here (see nodeRegistry NodeTypeDescriptor).
|
||||
*/
|
||||
|
||||
import type { ConfigTypeId } from './configTypes'
|
||||
import type { ConfigTypeId, OutputMenuDescriptor } from './configTypes'
|
||||
|
||||
export type { OutputMenuDescriptor, OutputMenuItemDescriptor, OutputMenuAction } from './configTypes'
|
||||
|
||||
export type SourceRenderingLogicContext = {
|
||||
nodes: { id: string; type?: string; data?: unknown }[]
|
||||
@@ -41,10 +43,13 @@ export type ResolvedContentResult = {
|
||||
* Rendering logic provided by a source node type (e.g. config, agent).
|
||||
* - defaultUpdateMode: 'auto' = re-render on upstream changes; 'manual' = only on Run
|
||||
* - getResolvedContent: async resolve step; returns resolved string and which renderer (ConfigTypeId) to use
|
||||
* - getOutputMenuDescriptor: optional; returns output menu items for the Rendering node (e.g. Export submenu for image types)
|
||||
*/
|
||||
export type SourceRenderingLogic = {
|
||||
defaultUpdateMode: 'auto' | 'manual'
|
||||
getResolvedContent: (context: SourceRenderingLogicContext) => Promise<ResolvedContentResult>
|
||||
/** Optional. When the renderer displays this source's output, which extra output menu items to show (e.g. Export SVG/PNG). */
|
||||
getOutputMenuDescriptor?: (outputTypeId: ConfigTypeId) => OutputMenuDescriptor | null
|
||||
}
|
||||
|
||||
const registry = new Map<string, SourceRenderingLogic>()
|
||||
|
||||
Reference in New Issue
Block a user