dishui 0.0.45 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/chart.js +46 -0
  2. package/dist/components/Chart/Chart.d.ts +17 -0
  3. package/dist/components/Chart/Chart.js +29 -0
  4. package/dist/components/Chart/Chart.js.map +1 -0
  5. package/dist/components/Chart/index.d.ts +15 -0
  6. package/dist/components/Input/index.js +2 -2
  7. package/dist/components/Input/index.js.map +1 -1
  8. package/dist/components/Select/index.js +1 -1
  9. package/dist/components/Select/index.js.map +1 -1
  10. package/dist/components/pro/Flow/Flow.js +492 -392
  11. package/dist/components/pro/Flow/Flow.js.map +1 -1
  12. package/dist/components/pro/Flow/FlowCanvas.js +895 -864
  13. package/dist/components/pro/Flow/FlowCanvas.js.map +1 -1
  14. package/dist/components/pro/Flow/context/FlowUIContext.d.ts +2 -0
  15. package/dist/components/pro/Flow/context/FlowUIContext.js +68 -65
  16. package/dist/components/pro/Flow/context/FlowUIContext.js.map +1 -1
  17. package/dist/components/pro/Flow/edges/edgePaths.js +234 -216
  18. package/dist/components/pro/Flow/edges/edgePaths.js.map +1 -1
  19. package/dist/components/pro/Flow/hooks/useKeyboardShortcuts.js +21 -10
  20. package/dist/components/pro/Flow/hooks/useKeyboardShortcuts.js.map +1 -1
  21. package/dist/components/pro/Flow/hooks/useMindMap.js +138 -134
  22. package/dist/components/pro/Flow/hooks/useMindMap.js.map +1 -1
  23. package/dist/components/pro/Flow/index.d.ts +1 -1
  24. package/dist/components/pro/Flow/nodes/MediaContent.js +47 -25
  25. package/dist/components/pro/Flow/nodes/MediaContent.js.map +1 -1
  26. package/dist/components/pro/Flow/nodes/Model3DView.d.ts +7 -0
  27. package/dist/components/pro/Flow/nodes/Model3DView.js +118 -0
  28. package/dist/components/pro/Flow/nodes/Model3DView.js.map +1 -0
  29. package/dist/components/pro/Flow/nodes/NodeHandles.d.ts +2 -1
  30. package/dist/components/pro/Flow/nodes/NodeHandles.js +4 -2
  31. package/dist/components/pro/Flow/nodes/NodeHandles.js.map +1 -1
  32. package/dist/components/pro/Flow/nodes/NodeRenderer.d.ts +2 -0
  33. package/dist/components/pro/Flow/nodes/NodeRenderer.js +115 -113
  34. package/dist/components/pro/Flow/nodes/NodeRenderer.js.map +1 -1
  35. package/dist/components/pro/Flow/nodes/NodeText.d.ts +3 -0
  36. package/dist/components/pro/Flow/nodes/NodeText.js +26 -17
  37. package/dist/components/pro/Flow/nodes/NodeText.js.map +1 -1
  38. package/dist/components/pro/Flow/panels/FlowToolbar.js +85 -79
  39. package/dist/components/pro/Flow/panels/FlowToolbar.js.map +1 -1
  40. package/dist/components/pro/Flow/panels/InspectorPanel.js +618 -488
  41. package/dist/components/pro/Flow/panels/InspectorPanel.js.map +1 -1
  42. package/dist/components/pro/Flow/panels/OutlinePanel.js +15 -15
  43. package/dist/components/pro/Flow/panels/OutlinePanel.js.map +1 -1
  44. package/dist/components/pro/Flow/panels/ShapePalette.js +71 -55
  45. package/dist/components/pro/Flow/panels/ShapePalette.js.map +1 -1
  46. package/dist/components/pro/Flow/panels/toolbarIcons.d.ts +1 -0
  47. package/dist/components/pro/Flow/panels/toolbarIcons.js +59 -41
  48. package/dist/components/pro/Flow/panels/toolbarIcons.js.map +1 -1
  49. package/dist/components/pro/Flow/types.d.ts +13 -4
  50. package/dist/components/pro/Flow/types.js.map +1 -1
  51. package/dist/dishui.css +1 -1
  52. package/dist/index-nostyled.d.ts +3 -1
  53. package/dist/index.d.ts +3 -1
  54. package/dist/index.js +145 -143
  55. package/dist/utils/i18n.d.ts +1 -0
  56. package/dist/utils/i18n.js +200 -6
  57. package/dist/utils/i18n.js.map +1 -1
  58. package/dist/utils/menu.js +26 -10
  59. package/dist/utils/menu.js.map +1 -1
  60. package/package.json +14 -3
@@ -1 +1 @@
1
- {"version":3,"file":"useMindMap.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useMindMap.ts"],"sourcesContent":["import { useCallback, useEffect } from 'react';\nimport { useFlowContext } from '../context/FlowContext';\nimport { useFlowUI } from '../context/FlowUIContext';\nimport { DEFAULT_EDGE_DATA, DEFAULT_NODE_DATA } from '../constants';\nimport { generateId } from '../nodes/nodeUtils';\nimport type { FlowCanvasData, FlowNode } from '../types';\n\nconst NODE_WIDTH = 150;\nconst NODE_HEIGHT = 52;\nconst LEVEL_GAP = 100;\nconst ROW_GAP = 26;\n\nfunction layoutMindMap(\n canvas: FlowCanvasData,\n rootId: string,\n fixed?: { id: string; position: { x: number; y: number } },\n): Map<string, { x: number; y: number }> {\n const nodes = canvas.nodes.filter(node => node.data.isMindMapNode);\n const nodeMap = new Map(nodes.map(node => [node.id, node]));\n const children = new Map<string, FlowNode[]>();\n for (const node of nodes) {\n const parentId = node.data.mindMapParentId;\n if (!parentId || !nodeMap.has(parentId)) continue;\n const list = children.get(parentId) ?? [];\n list.push(node);\n children.set(parentId, list);\n }\n\n const root = nodeMap.get(rootId);\n if (!root) return new Map();\n const positions = new Map<string, { x: number; y: number }>();\n let nextY = root.position.y;\n\n const place = (node: FlowNode, depth: number): number => {\n const kids = children.get(node.id) ?? [];\n let centerY: number;\n if (kids.length === 0) {\n centerY = nextY;\n nextY += NODE_HEIGHT + ROW_GAP;\n } else {\n const childYs = kids.map(child => place(child, depth + 1));\n centerY = (childYs[0] + childYs[childYs.length - 1]) / 2;\n }\n positions.set(node.id, {\n x: root.position.x + depth * (NODE_WIDTH + LEVEL_GAP),\n y: centerY,\n });\n return centerY;\n };\n\n place(root, 0);\n if (fixed) {\n const computed = positions.get(fixed.id);\n if (computed) {\n const dx = fixed.position.x - computed.x;\n const dy = fixed.position.y - computed.y;\n for (const [id, position] of positions) {\n positions.set(id, { x: position.x + dx, y: position.y + dy });\n }\n }\n }\n return positions;\n}\n\nexport function useMindMap() {\n const { setEditingNodeId, focusOnRect } = useFlowUI();\n const { currentCanvas, currentCanvasId, selection, setSelection, setDoc } = useFlowContext();\n\n const focusNode = useCallback((node: FlowNode | undefined) => {\n if (!node) return;\n window.setTimeout(() => {\n focusOnRect(node.position.x, node.position.y, node.width, node.height, {\n padding: 80,\n preserveZoom: true,\n duration: 280,\n });\n }, 0);\n }, [focusOnRect]);\n\n const createNode = useCallback((kind: 'child' | 'sibling-after' | 'sibling-before') => {\n const selectedId = selection.nodeIds.size === 1 ? [...selection.nodeIds][0] : null;\n const selected = selectedId ? currentCanvas.nodes.find(node => node.id === selectedId) : undefined;\n if (!selected) return;\n\n // A regular Flow node can become the root of a mind map on first use.\n const parentId = kind === 'child' ? selected.id : selected.data.mindMapParentId;\n if (kind !== 'child' && !parentId) return;\n const id = generateId('mind');\n const node: FlowNode = {\n id,\n type: 'shapeRoundRect',\n position: { x: selected.position.x + NODE_WIDTH + LEVEL_GAP, y: selected.position.y + NODE_HEIGHT + ROW_GAP },\n width: NODE_WIDTH,\n height: NODE_HEIGHT,\n zIndex: Math.max(0, ...currentCanvas.nodes.map(item => item.zIndex ?? 0)) + 1,\n data: {\n ...DEFAULT_NODE_DATA,\n label: kind === 'child' ? '子主题' : '主题',\n fill: '#ffffff',\n stroke: '#6366f1',\n strokeWidth: 2,\n isMindMapNode: true,\n mindMapParentId: parentId,\n },\n };\n\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n const rootId = (() => {\n let cursor = selected;\n const map = new Map(canvas.nodes.map(item => [item.id, item]));\n while (cursor.data.mindMapParentId && map.has(cursor.data.mindMapParentId)) {\n cursor = map.get(cursor.data.mindMapParentId)!;\n }\n return cursor.id;\n })();\n const normalizedNodes = canvas.nodes.map(item => item.id === selected.id && !item.data.isMindMapNode\n ? { ...item, data: { ...item.data, isMindMapNode: true } }\n : item);\n const selectedIndex = normalizedNodes.findIndex(item => item.id === selected.id);\n const insertIndex = kind === 'sibling-before'\n ? selectedIndex\n : selectedIndex + 1;\n normalizedNodes.splice(Math.max(0, insertIndex), 0, node);\n const nextCanvas: FlowCanvasData = {\n ...canvas,\n nodes: normalizedNodes,\n edges: [...canvas.edges, {\n id: generateId('edge'),\n source: parentId!,\n target: id,\n type: 'simplebezier',\n data: { ...DEFAULT_EDGE_DATA, markerEnd: 'none', strokeColor: '#94a3b8', strokeWidth: 1.5 },\n }],\n };\n // Child insertion keeps the current node fixed. Sibling insertion keeps\n // their shared parent fixed, so adding nodes never makes the parent jump.\n const fixedNode = kind === 'child'\n ? selected\n : canvas.nodes.find(item => item.id === parentId) ?? selected;\n const positions = layoutMindMap(nextCanvas, rootId, {\n id: fixedNode.id,\n position: fixedNode.position,\n });\n nextCanvas.nodes = nextCanvas.nodes.map(item => {\n const pos = positions.get(item.id);\n return pos ? { ...item, position: pos } : item;\n });\n return { ...prev, canvases: { ...prev.canvases, [currentCanvasId]: nextCanvas } };\n });\n setSelection({ nodeIds: new Set([id]), edgeIds: new Set(), lineIds: new Set() });\n }, [selection, currentCanvas, currentCanvasId, setDoc, setSelection]);\n\n const createRoot = useCallback(() => {\n if (selection.nodeIds.size !== 0) return false;\n const id = generateId('mind');\n const highestZ = Math.max(0, ...currentCanvas.nodes.map(node => node.zIndex ?? 0));\n const node: FlowNode = {\n id,\n type: 'shapeRoundRect',\n position: { x: 120, y: 120 },\n width: NODE_WIDTH,\n height: NODE_HEIGHT,\n zIndex: highestZ + 1,\n data: { ...DEFAULT_NODE_DATA, label: '中心主题', fill: '#eef2ff', stroke: '#4f46e5', strokeWidth: 2, isMindMapNode: true },\n };\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return { ...prev, canvases: { ...prev.canvases, [currentCanvasId]: { ...canvas, nodes: [...canvas.nodes, node] } } };\n });\n setSelection({ nodeIds: new Set([id]), edgeIds: new Set(), lineIds: new Set() });\n return true;\n }, [selection, currentCanvas, currentCanvasId, setDoc, setSelection]);\n\n const focusedNodeId = selection.nodeIds.size === 1 ? [...selection.nodeIds][0] : null;\n\n // Center only when keyboard focus/selection moves to another node. Node drag\n // updates currentCanvas continuously; depending on currentCanvas here would\n // make the viewport chase the mouse while an object is being repositioned.\n useEffect(() => {\n if (!focusedNodeId) return;\n const node = currentCanvas.nodes.find(item => item.id === focusedNodeId);\n if (!node) return;\n const timer = window.setTimeout(() => focusNode(node), 20);\n return () => window.clearTimeout(timer);\n // currentCanvas is intentionally excluded: position-only updates must not\n // retrigger viewport focus while dragging.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [focusedNodeId, focusNode]);\n\n useEffect(() => {\n const onKey = (event: KeyboardEvent) => {\n const target = event.target as HTMLElement;\n // Never intercept native editing keys from form controls. In particular,\n // inspector textareas must keep Enter/Tab for multiline media source.\n if (target.matches('input, textarea, select, .flow-label-editor') || target.isContentEditable) return;\n const selectedId = selection.nodeIds.size === 1 ? [...selection.nodeIds][0] : null;\n const selected = selectedId ? currentCanvas.nodes.find(node => node.id === selectedId) : undefined;\n\n const consume = () => {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n };\n\n if (event.key === 'Tab') {\n consume();\n if (!selected) createRoot();\n else createNode('child');\n } else if (event.key === 'Enter' && selected) {\n consume();\n createNode(event.shiftKey ? 'sibling-before' : 'sibling-after');\n } else if ((event.key === ' ' || event.code === 'Space') && selected) {\n consume();\n setEditingNodeId(selected.id);\n } else if (selected && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {\n const nodeMap = new Map(currentCanvas.nodes.map(node => [node.id, node]));\n const siblings = currentCanvas.nodes.filter(node =>\n node.data.isMindMapNode && node.data.mindMapParentId === selected.data.mindMapParentId,\n );\n const siblingIndex = siblings.findIndex(node => node.id === selected.id);\n const children = currentCanvas.nodes.filter(node => node.data.mindMapParentId === selected.id);\n let targetNode: FlowNode | undefined;\n if (event.key === 'ArrowLeft' && selected.data.mindMapParentId) {\n targetNode = nodeMap.get(selected.data.mindMapParentId);\n } else if (event.key === 'ArrowRight') {\n targetNode = children[0];\n } else if (event.key === 'ArrowUp' && siblingIndex > 0) {\n targetNode = siblings[siblingIndex - 1];\n } else if (event.key === 'ArrowDown' && siblingIndex >= 0 && siblingIndex < siblings.length - 1) {\n targetNode = siblings[siblingIndex + 1];\n }\n\n // Regular Flow nodes have no mind-map relationship metadata. Fall back\n // to spatial navigation, choosing the nearest node in the requested\n // direction. The perpendicular penalty favors visually aligned nodes.\n if (!targetNode) {\n const sx = selected.position.x + selected.width / 2;\n const sy = selected.position.y + selected.height / 2;\n const candidates = currentCanvas.nodes\n .filter(node => node.id !== selected.id && node.type !== 'animAnchor')\n .map(node => {\n const dx = node.position.x + node.width / 2 - sx;\n const dy = node.position.y + node.height / 2 - sy;\n const inDirection = event.key === 'ArrowLeft' ? dx < 0\n : event.key === 'ArrowRight' ? dx > 0\n : event.key === 'ArrowUp' ? dy < 0\n : dy > 0;\n const primary = event.key === 'ArrowLeft' || event.key === 'ArrowRight'\n ? Math.abs(dx)\n : Math.abs(dy);\n const perpendicular = event.key === 'ArrowLeft' || event.key === 'ArrowRight'\n ? Math.abs(dy)\n : Math.abs(dx);\n return { node, inDirection, score: primary + perpendicular * 1.75 };\n })\n .filter(candidate => candidate.inDirection)\n .sort((a, b) => a.score - b.score);\n targetNode = candidates[0]?.node;\n }\n\n if (targetNode) {\n consume();\n setSelection({ nodeIds: new Set([targetNode.id]), edgeIds: new Set(), lineIds: new Set() });\n }\n }\n };\n window.addEventListener('keydown', onKey, true);\n return () => window.removeEventListener('keydown', onKey, true);\n }, [selection, currentCanvas, createNode, createRoot, setEditingNodeId, setSelection]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useMindMap.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useMindMap.ts"],"sourcesContent":["import { useCallback, useEffect } from 'react';\nimport { useFlowContext } from '../context/FlowContext';\nimport { useFlowUI } from '../context/FlowUIContext';\nimport { DEFAULT_EDGE_DATA, DEFAULT_NODE_DATA } from '../constants';\nimport { generateId } from '../nodes/nodeUtils';\nimport type { FlowCanvasData, FlowNode } from '../types';\n\nconst NODE_WIDTH = 150;\nconst NODE_HEIGHT = 52;\nconst LEVEL_GAP = 100;\nconst ROW_GAP = 26;\n\nfunction layoutMindMap(\n canvas: FlowCanvasData,\n rootId: string,\n fixed?: { id: string; position: { x: number; y: number } },\n): Map<string, { x: number; y: number }> {\n const nodes = canvas.nodes.filter(node => node.data.isMindMapNode);\n const nodeMap = new Map(nodes.map(node => [node.id, node]));\n const children = new Map<string, FlowNode[]>();\n for (const node of nodes) {\n const parentId = node.data.mindMapParentId;\n if (!parentId || !nodeMap.has(parentId)) continue;\n const list = children.get(parentId) ?? [];\n list.push(node);\n children.set(parentId, list);\n }\n\n const root = nodeMap.get(rootId);\n if (!root) return new Map();\n const positions = new Map<string, { x: number; y: number }>();\n let nextY = root.position.y;\n\n const place = (node: FlowNode, depth: number): number => {\n const kids = children.get(node.id) ?? [];\n let centerY: number;\n if (kids.length === 0) {\n centerY = nextY;\n nextY += NODE_HEIGHT + ROW_GAP;\n } else {\n const childYs = kids.map(child => place(child, depth + 1));\n centerY = (childYs[0] + childYs[childYs.length - 1]) / 2;\n }\n positions.set(node.id, {\n x: root.position.x + depth * (NODE_WIDTH + LEVEL_GAP),\n y: centerY,\n });\n return centerY;\n };\n\n place(root, 0);\n if (fixed) {\n const computed = positions.get(fixed.id);\n if (computed) {\n const dx = fixed.position.x - computed.x;\n const dy = fixed.position.y - computed.y;\n for (const [id, position] of positions) {\n positions.set(id, { x: position.x + dx, y: position.y + dy });\n }\n }\n }\n return positions;\n}\n\nexport function useMindMap() {\n const { setEditingNodeId, focusOnRect, autoFocus } = useFlowUI();\n const { currentCanvas, currentCanvasId, selection, setSelection, setDoc } = useFlowContext();\n\n const focusNode = useCallback((node: FlowNode | undefined) => {\n if (!node) return;\n window.setTimeout(() => {\n focusOnRect(node.position.x, node.position.y, node.width, node.height, {\n padding: 80,\n preserveZoom: true,\n duration: 280,\n });\n }, 0);\n }, [focusOnRect]);\n\n const createNode = useCallback((kind: 'child' | 'sibling-after' | 'sibling-before') => {\n const selectedId = selection.nodeIds.size === 1 ? [...selection.nodeIds][0] : null;\n const selected = selectedId ? currentCanvas.nodes.find(node => node.id === selectedId) : undefined;\n if (!selected) return;\n\n // A regular Flow node can become the root of a mind map on first use.\n const parentId = kind === 'child' ? selected.id : selected.data.mindMapParentId;\n if (kind !== 'child' && !parentId) return;\n const id = generateId('mind');\n const node: FlowNode = {\n id,\n type: 'shapeRoundRect',\n position: { x: selected.position.x + NODE_WIDTH + LEVEL_GAP, y: selected.position.y + NODE_HEIGHT + ROW_GAP },\n width: NODE_WIDTH,\n height: NODE_HEIGHT,\n zIndex: Math.max(0, ...currentCanvas.nodes.map(item => item.zIndex ?? 0)) + 1,\n data: {\n ...DEFAULT_NODE_DATA,\n label: kind === 'child' ? '子主题' : '主题',\n fill: '#ffffff',\n stroke: '#6366f1',\n strokeWidth: 2,\n isMindMapNode: true,\n mindMapParentId: parentId,\n },\n };\n\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n const rootId = (() => {\n let cursor = selected;\n const map = new Map(canvas.nodes.map(item => [item.id, item]));\n while (cursor.data.mindMapParentId && map.has(cursor.data.mindMapParentId)) {\n cursor = map.get(cursor.data.mindMapParentId)!;\n }\n return cursor.id;\n })();\n const normalizedNodes = canvas.nodes.map(item => item.id === selected.id && !item.data.isMindMapNode\n ? { ...item, data: { ...item.data, isMindMapNode: true } }\n : item);\n const selectedIndex = normalizedNodes.findIndex(item => item.id === selected.id);\n const insertIndex = kind === 'sibling-before'\n ? selectedIndex\n : selectedIndex + 1;\n normalizedNodes.splice(Math.max(0, insertIndex), 0, node);\n const nextCanvas: FlowCanvasData = {\n ...canvas,\n nodes: normalizedNodes,\n edges: [...canvas.edges, {\n id: generateId('edge'),\n source: parentId!,\n target: id,\n type: 'simplebezier',\n data: { ...DEFAULT_EDGE_DATA, markerEnd: 'none', strokeColor: '#94a3b8', strokeWidth: 1.5 },\n }],\n };\n // Child insertion keeps the current node fixed. Sibling insertion keeps\n // their shared parent fixed, so adding nodes never makes the parent jump.\n const fixedNode = kind === 'child'\n ? selected\n : canvas.nodes.find(item => item.id === parentId) ?? selected;\n const positions = layoutMindMap(nextCanvas, rootId, {\n id: fixedNode.id,\n position: fixedNode.position,\n });\n nextCanvas.nodes = nextCanvas.nodes.map(item => {\n const pos = positions.get(item.id);\n return pos ? { ...item, position: pos } : item;\n });\n return { ...prev, canvases: { ...prev.canvases, [currentCanvasId]: nextCanvas } };\n });\n setSelection({ nodeIds: new Set([id]), edgeIds: new Set(), lineIds: new Set() });\n }, [selection, currentCanvas, currentCanvasId, setDoc, setSelection]);\n\n const createRoot = useCallback(() => {\n if (selection.nodeIds.size !== 0) return false;\n const id = generateId('mind');\n const highestZ = Math.max(0, ...currentCanvas.nodes.map(node => node.zIndex ?? 0));\n const node: FlowNode = {\n id,\n type: 'shapeRoundRect',\n position: { x: 120, y: 120 },\n width: NODE_WIDTH,\n height: NODE_HEIGHT,\n zIndex: highestZ + 1,\n data: { ...DEFAULT_NODE_DATA, label: '中心主题', fill: '#eef2ff', stroke: '#4f46e5', strokeWidth: 2, isMindMapNode: true },\n };\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return { ...prev, canvases: { ...prev.canvases, [currentCanvasId]: { ...canvas, nodes: [...canvas.nodes, node] } } };\n });\n setSelection({ nodeIds: new Set([id]), edgeIds: new Set(), lineIds: new Set() });\n return true;\n }, [selection, currentCanvas, currentCanvasId, setDoc, setSelection]);\n\n const focusedNodeId = selection.nodeIds.size === 1 ? [...selection.nodeIds][0] : null;\n\n // Center only when keyboard focus/selection moves to another node. Node drag\n // updates currentCanvas continuously; depending on currentCanvas here would\n // make the viewport chase the mouse while an object is being repositioned.\n useEffect(() => {\n if (!autoFocus || !focusedNodeId) return;\n const node = currentCanvas.nodes.find(item => item.id === focusedNodeId);\n if (!node) return;\n const timer = window.setTimeout(() => focusNode(node), 20);\n return () => window.clearTimeout(timer);\n // currentCanvas is intentionally excluded: position-only updates must not\n // retrigger viewport focus while dragging.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [autoFocus, focusedNodeId, focusNode]);\n\n useEffect(() => {\n const onKey = (event: KeyboardEvent) => {\n const target = event.target as HTMLElement;\n // Never intercept native editing keys from form controls. In particular,\n // inspector textareas must keep Enter/Tab for multiline media source.\n if (target.matches('input, textarea, select, .flow-label-editor') || target.isContentEditable) return;\n const selectedId = selection.nodeIds.size === 1 ? [...selection.nodeIds][0] : null;\n const selected = selectedId ? currentCanvas.nodes.find(node => node.id === selectedId) : undefined;\n\n const consume = () => {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n };\n\n if (event.key === 'Tab') {\n consume();\n if (!selected) createRoot();\n else createNode('child');\n } else if (event.key === 'Enter' && selected) {\n consume();\n createNode(event.shiftKey ? 'sibling-before' : 'sibling-after');\n } else if ((event.key === ' ' || event.code === 'Space') && selected) {\n consume();\n setEditingNodeId(selected.id);\n } else if (selected && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {\n const nodeMap = new Map(currentCanvas.nodes.map(node => [node.id, node]));\n const siblings = currentCanvas.nodes.filter(node =>\n node.data.isMindMapNode && node.data.mindMapParentId === selected.data.mindMapParentId,\n );\n const siblingIndex = siblings.findIndex(node => node.id === selected.id);\n const children = currentCanvas.nodes.filter(node => node.data.mindMapParentId === selected.id);\n let targetNode: FlowNode | undefined;\n if (event.key === 'ArrowLeft' && selected.data.mindMapParentId) {\n targetNode = nodeMap.get(selected.data.mindMapParentId);\n } else if (event.key === 'ArrowRight') {\n targetNode = children[0];\n } else if (event.key === 'ArrowUp' && siblingIndex > 0) {\n targetNode = siblings[siblingIndex - 1];\n } else if (event.key === 'ArrowDown' && siblingIndex >= 0 && siblingIndex < siblings.length - 1) {\n targetNode = siblings[siblingIndex + 1];\n }\n\n // Regular Flow nodes have no mind-map relationship metadata. Fall back\n // to spatial navigation, choosing the nearest node in the requested\n // direction. The perpendicular penalty favors visually aligned nodes.\n if (!targetNode) {\n const sx = selected.position.x + selected.width / 2;\n const sy = selected.position.y + selected.height / 2;\n const candidates = currentCanvas.nodes\n .filter(node => node.id !== selected.id && node.type !== 'animAnchor')\n .map(node => {\n const dx = node.position.x + node.width / 2 - sx;\n const dy = node.position.y + node.height / 2 - sy;\n const inDirection = event.key === 'ArrowLeft' ? dx < 0\n : event.key === 'ArrowRight' ? dx > 0\n : event.key === 'ArrowUp' ? dy < 0\n : dy > 0;\n const primary = event.key === 'ArrowLeft' || event.key === 'ArrowRight'\n ? Math.abs(dx)\n : Math.abs(dy);\n const perpendicular = event.key === 'ArrowLeft' || event.key === 'ArrowRight'\n ? Math.abs(dy)\n : Math.abs(dx);\n return { node, inDirection, score: primary + perpendicular * 1.75 };\n })\n .filter(candidate => candidate.inDirection)\n .sort((a, b) => a.score - b.score);\n targetNode = candidates[0]?.node;\n }\n\n if (targetNode) {\n consume();\n setSelection({ nodeIds: new Set([targetNode.id]), edgeIds: new Set(), lineIds: new Set() });\n }\n }\n };\n window.addEventListener('keydown', onKey, true);\n return () => window.removeEventListener('keydown', onKey, true);\n }, [selection, currentCanvas, createNode, createRoot, setEditingNodeId, setSelection]);\n}\n"],"mappings":";;;;;AAOA,IAAM,IAAa,KACb,IAAc,IACd,IAAY,KACZ,IAAU;AAEhB,SAAS,EACP,GACA,GACA,GACuC;AACvC,QAAM,IAAQ,EAAO,MAAM,OAAA,CAAO,MAAQ,EAAK,KAAK,aAAa,GAC3D,IAAU,IAAI,IAAI,EAAM,IAAA,CAAI,MAAQ,CAAC,EAAK,IAAI,CAAI,CAAC,CAAC,GACpD,IAAW,oBAAI,IAAwB;AAC7C,aAAW,KAAQ,GAAO;AACxB,UAAM,IAAW,EAAK,KAAK;AAC3B,QAAI,CAAC,KAAY,CAAC,EAAQ,IAAI,CAAQ,EAAG;AACzC,UAAM,IAAO,EAAS,IAAI,CAAQ,KAAK,CAAC;AACxC,IAAA,EAAK,KAAK,CAAI,GACd,EAAS,IAAI,GAAU,CAAI;AAAA,EAC7B;AAEA,QAAM,IAAO,EAAQ,IAAI,CAAM;AAC/B,MAAI,CAAC,EAAM,QAAO,oBAAI,IAAI;AAC1B,QAAM,IAAY,oBAAI,IAAsC;AAC5D,MAAI,IAAQ,EAAK,SAAS;AAE1B,QAAM,IAAA,CAAS,GAAgB,MAA0B;AACvD,UAAM,IAAO,EAAS,IAAI,EAAK,EAAE,KAAK,CAAC;AACvC,QAAI;AACJ,QAAI,EAAK,WAAW;AAClB,MAAA,IAAU,GACV,KAAS;AAAA,SACJ;AACL,YAAM,IAAU,EAAK,IAAA,CAAI,MAAS,EAAM,GAAO,IAAQ,CAAC,CAAC;AACzD,MAAA,KAAW,EAAQ,CAAA,IAAK,EAAQ,EAAQ,SAAS,CAAA,KAAM;AAAA,IACzD;AACA,WAAA,EAAU,IAAI,EAAK,IAAI;AAAA,MACrB,GAAG,EAAK,SAAS,IAAI,IAAS;AAAA,MAC9B,GAAG;AAAA,IACL,CAAC,GACM;AAAA,EACT;AAGA,MADA,EAAM,GAAM,CAAC,GACT,GAAO;AACT,UAAM,IAAW,EAAU,IAAI,EAAM,EAAE;AACvC,QAAI,GAAU;AACZ,YAAM,IAAK,EAAM,SAAS,IAAI,EAAS,GACjC,IAAK,EAAM,SAAS,IAAI,EAAS;AACvC,iBAAW,CAAC,GAAI,CAAA,KAAa,EAC3B,CAAA,EAAU,IAAI,GAAI;AAAA,QAAE,GAAG,EAAS,IAAI;AAAA,QAAI,GAAG,EAAS,IAAI;AAAA,MAAG,CAAC;AAAA,IAEhE;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAgB,IAAa;AAC3B,QAAM,EAAE,kBAAA,GAAkB,aAAA,GAAa,WAAA,EAAA,IAAc,EAAU,GACzD,EAAE,eAAA,GAAe,iBAAA,GAAiB,WAAA,GAAW,cAAA,GAAc,QAAA,EAAA,IAAW,EAAe,GAErF,IAAY,EAAA,CAAa,MAA+B;AAC5D,IAAK,KACL,OAAO,WAAA,MAAiB;AACtB,MAAA,EAAY,EAAK,SAAS,GAAG,EAAK,SAAS,GAAG,EAAK,OAAO,EAAK,QAAQ;AAAA,QACrE,SAAS;AAAA,QACT,cAAc;AAAA,QACd,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,GAAG,CAAC;AAAA,EACN,GAAG,CAAC,CAAW,CAAC,GAEV,IAAa,EAAA,CAAa,MAAuD;AACrF,UAAM,IAAa,EAAU,QAAQ,SAAS,IAAI,CAAC,GAAG,EAAU,OAAO,EAAE,CAAA,IAAK,MACxE,IAAW,IAAa,EAAc,MAAM,KAAA,CAAK,MAAQ,EAAK,OAAO,CAAU,IAAI;AACzF,QAAI,CAAC,EAAU;AAGf,UAAM,IAAW,MAAS,UAAU,EAAS,KAAK,EAAS,KAAK;AAChE,QAAI,MAAS,WAAW,CAAC,EAAU;AACnC,UAAM,IAAK,EAAW,MAAM,GACtB,IAAiB;AAAA,MACrB,IAAA;AAAA,MACA,MAAM;AAAA,MACN,UAAU;AAAA,QAAE,GAAG,EAAS,SAAS,IAAI,IAAa;AAAA,QAAW,GAAG,EAAS,SAAS,IAAI,IAAc;AAAA,MAAQ;AAAA,MAC5G,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ,KAAK,IAAI,GAAG,GAAG,EAAc,MAAM,IAAA,CAAI,MAAQ,EAAK,UAAU,CAAC,CAAC,IAAI;AAAA,MAC5E,MAAM;AAAA,QACJ,GAAG;AAAA,QACH,OAAO,MAAS,UAAU,QAAQ;AAAA,QAClC,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,eAAe;AAAA,QACf,iBAAiB;AAAA,MACnB;AAAA,IACF;AAEA,IAAA,EAAA,CAAO,MAAQ;AACb,YAAM,IAAS,EAAK,SAAS,CAAA;AAC7B,UAAI,CAAC,EAAQ,QAAO;AACpB,YAAM,KAAA,MAAgB;AACpB,YAAI,IAAS;AACb,cAAM,IAAM,IAAI,IAAI,EAAO,MAAM,IAAA,CAAI,MAAQ,CAAC,EAAK,IAAI,CAAI,CAAC,CAAC;AAC7D,eAAO,EAAO,KAAK,mBAAmB,EAAI,IAAI,EAAO,KAAK,eAAe,IACvE,CAAA,IAAS,EAAI,IAAI,EAAO,KAAK,eAAe;AAE9C,eAAO,EAAO;AAAA,MAChB,GAAG,GACG,IAAkB,EAAO,MAAM,IAAA,CAAI,MAAQ,EAAK,OAAO,EAAS,MAAM,CAAC,EAAK,KAAK,gBACnF;AAAA,QAAE,GAAG;AAAA,QAAM,MAAM;AAAA,UAAE,GAAG,EAAK;AAAA,UAAM,eAAe;AAAA,QAAK;AAAA,MAAE,IACvD,CAAI,GACF,IAAgB,EAAgB,UAAA,CAAU,MAAQ,EAAK,OAAO,EAAS,EAAE,GACzE,IAAc,MAAS,mBACzB,IACA,IAAgB;AACpB,MAAA,EAAgB,OAAO,KAAK,IAAI,GAAG,CAAW,GAAG,GAAG,CAAI;AACxD,YAAM,IAA6B;AAAA,QACjC,GAAG;AAAA,QACH,OAAO;AAAA,QACP,OAAO,CAAC,GAAG,EAAO,OAAO;AAAA,UACvB,IAAI,EAAW,MAAM;AAAA,UACrB,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,MAAM;AAAA,YAAE,GAAG;AAAA,YAAmB,WAAW;AAAA,YAAQ,aAAa;AAAA,YAAW,aAAa;AAAA,UAAI;AAAA,QAC5F,CAAC;AAAA,MACH,GAGM,IAAY,MAAS,UACvB,IACA,EAAO,MAAM,KAAA,CAAK,MAAQ,EAAK,OAAO,CAAQ,KAAK,GACjD,IAAY,EAAc,GAAY,GAAQ;AAAA,QAClD,IAAI,EAAU;AAAA,QACd,UAAU,EAAU;AAAA,MACtB,CAAC;AACD,aAAA,EAAW,QAAQ,EAAW,MAAM,IAAA,CAAI,MAAQ;AAC9C,cAAM,IAAM,EAAU,IAAI,EAAK,EAAE;AACjC,eAAO,IAAM;AAAA,UAAE,GAAG;AAAA,UAAM,UAAU;AAAA,QAAI,IAAI;AAAA,MAC5C,CAAC,GACM;AAAA,QAAE,GAAG;AAAA,QAAM,UAAU;AAAA,UAAE,GAAG,EAAK;AAAA,WAAW,CAAA,GAAkB;AAAA,QAAW;AAAA,MAAE;AAAA,IAClF,CAAC,GACD,EAAa;AAAA,MAAE,SAAS,oBAAI,IAAI,CAAC,CAAE,CAAC;AAAA,MAAG,SAAS,oBAAI,IAAI;AAAA,MAAG,SAAS,oBAAI,IAAI;AAAA,IAAE,CAAC;AAAA,EACjF,GAAG;AAAA,IAAC;AAAA,IAAW;AAAA,IAAe;AAAA,IAAiB;AAAA,IAAQ;AAAA,EAAY,CAAC,GAE9D,IAAa,EAAA,MAAkB;AACnC,QAAI,EAAU,QAAQ,SAAS,EAAG,QAAO;AACzC,UAAM,IAAK,EAAW,MAAM,GACtB,IAAW,KAAK,IAAI,GAAG,GAAG,EAAc,MAAM,IAAA,CAAI,MAAQ,EAAK,UAAU,CAAC,CAAC,GAC3E,IAAiB;AAAA,MACrB,IAAA;AAAA,MACA,MAAM;AAAA,MACN,UAAU;AAAA,QAAE,GAAG;AAAA,QAAK,GAAG;AAAA,MAAI;AAAA,MAC3B,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ,IAAW;AAAA,MACnB,MAAM;AAAA,QAAE,GAAG;AAAA,QAAmB,OAAO;AAAA,QAAQ,MAAM;AAAA,QAAW,QAAQ;AAAA,QAAW,aAAa;AAAA,QAAG,eAAe;AAAA,MAAK;AAAA,IACvH;AACA,WAAA,EAAA,CAAO,MAAQ;AACb,YAAM,IAAS,EAAK,SAAS,CAAA;AAC7B,aAAK,IACE;AAAA,QAAE,GAAG;AAAA,QAAM,UAAU;AAAA,UAAE,GAAG,EAAK;AAAA,WAAW,CAAA,GAAkB;AAAA,YAAE,GAAG;AAAA,YAAQ,OAAO,CAAC,GAAG,EAAO,OAAO,CAAI;AAAA,UAAE;AAAA,QAAE;AAAA,MAAE,IAD/F;AAAA,IAEtB,CAAC,GACD,EAAa;AAAA,MAAE,SAAS,oBAAI,IAAI,CAAC,CAAE,CAAC;AAAA,MAAG,SAAS,oBAAI,IAAI;AAAA,MAAG,SAAS,oBAAI,IAAI;AAAA,IAAE,CAAC,GACxE;AAAA,EACT,GAAG;AAAA,IAAC;AAAA,IAAW;AAAA,IAAe;AAAA,IAAiB;AAAA,IAAQ;AAAA,EAAY,CAAC,GAE9D,IAAgB,EAAU,QAAQ,SAAS,IAAI,CAAC,GAAG,EAAU,OAAO,EAAE,CAAA,IAAK;AAKjF,EAAA,EAAA,MAAgB;AACd,QAAI,CAAC,KAAa,CAAC,EAAe;AAClC,UAAM,IAAO,EAAc,MAAM,KAAA,CAAK,MAAQ,EAAK,OAAO,CAAa;AACvE,QAAI,CAAC,EAAM;AACX,UAAM,IAAQ,OAAO,WAAA,MAAiB,EAAU,CAAI,GAAG,EAAE;AACzD,WAAA,MAAa,OAAO,aAAa,CAAK;AAAA,EAIxC,GAAG;AAAA,IAAC;AAAA,IAAW;AAAA,IAAe;AAAA,EAAS,CAAC,GAExC,EAAA,MAAgB;AACd,UAAM,IAAA,CAAS,MAAyB;AACtC,YAAM,IAAS,EAAM;AAGrB,UAAI,EAAO,QAAQ,6CAA6C,KAAK,EAAO,kBAAmB;AAC/F,YAAM,IAAa,EAAU,QAAQ,SAAS,IAAI,CAAC,GAAG,EAAU,OAAO,EAAE,CAAA,IAAK,MACxE,IAAW,IAAa,EAAc,MAAM,KAAA,CAAK,MAAQ,EAAK,OAAO,CAAU,IAAI,QAEnF,IAAA,MAAgB;AACpB,QAAA,EAAM,eAAe,GACrB,EAAM,gBAAgB,GACtB,EAAM,yBAAyB;AAAA,MACjC;AAEA,UAAI,EAAM,QAAQ;AAChB,QAAA,EAAQ,GACH,IACA,EAAW,OAAO,IADR,EAAW;AAAA,eAEjB,EAAM,QAAQ,WAAW;AAClC,QAAA,EAAQ,GACR,EAAW,EAAM,WAAW,mBAAmB,eAAe;AAAA,gBACpD,EAAM,QAAQ,OAAO,EAAM,SAAS,YAAY;AAC1D,QAAA,EAAQ,GACR,EAAiB,EAAS,EAAE;AAAA,eACnB,KAAY;AAAA,QAAC;AAAA,QAAW;AAAA,QAAa;AAAA,QAAa;AAAA,MAAY,EAAE,SAAS,EAAM,GAAG,GAAG;AAC9F,cAAM,IAAU,IAAI,IAAI,EAAc,MAAM,IAAA,CAAI,MAAQ,CAAC,EAAK,IAAI,CAAI,CAAC,CAAC,GAClE,IAAW,EAAc,MAAM,OAAA,CAAO,MAC1C,EAAK,KAAK,iBAAiB,EAAK,KAAK,oBAAoB,EAAS,KAAK,eACzE,GACM,IAAe,EAAS,UAAA,CAAU,MAAQ,EAAK,OAAO,EAAS,EAAE,GACjE,IAAW,EAAc,MAAM,OAAA,CAAO,MAAQ,EAAK,KAAK,oBAAoB,EAAS,EAAE;AAC7F,YAAI;AAcJ,YAbI,EAAM,QAAQ,eAAe,EAAS,KAAK,kBAC7C,IAAa,EAAQ,IAAI,EAAS,KAAK,eAAe,IAC7C,EAAM,QAAQ,eACvB,IAAa,EAAS,CAAA,IACb,EAAM,QAAQ,aAAa,IAAe,IACnD,IAAa,EAAS,IAAe,CAAA,IAC5B,EAAM,QAAQ,eAAe,KAAgB,KAAK,IAAe,EAAS,SAAS,MAC5F,IAAa,EAAS,IAAe,CAAA,IAMnC,CAAC,GAAY;AACf,gBAAM,IAAK,EAAS,SAAS,IAAI,EAAS,QAAQ,GAC5C,IAAK,EAAS,SAAS,IAAI,EAAS,SAAS;AAoBnD,UAAA,IAnBmB,EAAc,MAC9B,OAAA,CAAO,MAAQ,EAAK,OAAO,EAAS,MAAM,EAAK,SAAS,YAAY,EACpE,IAAA,CAAI,MAAQ;AACX,kBAAM,IAAK,EAAK,SAAS,IAAI,EAAK,QAAQ,IAAI,GACxC,IAAK,EAAK,SAAS,IAAI,EAAK,SAAS,IAAI;AAW/C,mBAAO;AAAA,cAAE,MAAA;AAAA,cAAM,aAVK,EAAM,QAAQ,cAAc,IAAK,IACjD,EAAM,QAAQ,eAAe,IAAK,IAChC,EAAM,QAAQ,YAAY,IAAK,IAC7B,IAAK;AAAA,cAOe,QANZ,EAAM,QAAQ,eAAe,EAAM,QAAQ,eACvD,KAAK,IAAI,CAAE,IACX,KAAK,IAAI,CAAE,MACO,EAAM,QAAQ,eAAe,EAAM,QAAQ,eAC7D,KAAK,IAAI,CAAE,IACX,KAAK,IAAI,CAAE,KAC8C;AAAA,YAAK;AAAA,UACpE,CAAC,EACA,OAAA,CAAO,MAAa,EAAU,WAAW,EACzC,KAAA,CAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KACjB,EAAW,CAAA,GAAI;AAAA,QAC9B;AAEA,QAAI,MACF,EAAQ,GACR,EAAa;AAAA,UAAE,SAAS,oBAAI,IAAI,CAAC,EAAW,EAAE,CAAC;AAAA,UAAG,SAAS,oBAAI,IAAI;AAAA,UAAG,SAAS,oBAAI,IAAI;AAAA,QAAE,CAAC;AAAA,MAE9F;AAAA,IACF;AACA,kBAAO,iBAAiB,WAAW,GAAO,EAAI,GAC9C,MAAa,OAAO,oBAAoB,WAAW,GAAO,EAAI;AAAA,EAChE,GAAG;AAAA,IAAC;AAAA,IAAW;AAAA,IAAe;AAAA,IAAY;AAAA,IAAY;AAAA,IAAkB;AAAA,EAAY,CAAC;AACvF"}
@@ -15,7 +15,7 @@ export { default as InspectorPanel } from './panels/InspectorPanel';
15
15
  export { default as OutlinePanel } from './panels/OutlinePanel';
16
16
  export { default as ContextMenu } from './panels/ContextMenu';
17
17
  export { default as Breadcrumb } from './panels/Breadcrumb';
18
- export type { FlowProps, FlowDocument, FlowCanvasData, FlowNode, FlowNodeData, FlowEdge, FlowEdgeData, FlowLine, FlowLineType, FlowShapeType, FlowEdgeTypeName, FlowEdgeMarkerKind, FlowEdgeLineStyle, FlowEdgeDashDensity, FlowBezierControl, FlowBorderStyle, FlowToolMode, ViewportState, SelectionState, FlowNodeAttribute, GradientDirection, FlowGroupLabelPlacement, FlowGroupLabelAlign, } from './types';
18
+ export type { FlowProps, FlowDocument, FlowCanvasData, FlowNode, FlowNodeData, FlowEdge, FlowEdgeData, FlowLine, FlowLineType, FlowShapeType, FlowEdgeTypeName, FlowEdgeMarkerKind, FlowEdgeLineStyle, FlowEdgeDashDensity, FlowBezierControl, FlowBorderStyle, FlowToolMode, ViewportState, SelectionState, FlowNodeAttribute, GradientDirection, FlowGroupLabelPlacement, FlowGroupLabelAlign, FlowIconLabelPlacement, FlowMediaKind, FlowModelFormat, } from './types';
19
19
  export { FLOW_NODE_TYPES } from './types';
20
20
  export { SVG_ICON_CATEGORIES, getSvgIcon, getSvgIconsByCategory, getAllSvgIcons, buildSvgMarkup, } from './nodes/svgIcons';
21
21
  export type { SvgIconDef, SvgIconCategory } from './nodes/svgIcons';
@@ -1,10 +1,11 @@
1
1
  import A from "../../../Markdown/Markdown.js";
2
- import { streamAIChat as z } from "./aiClient.js";
3
- import { useEffect as b, useMemo as T, useRef as M, useState as u } from "react";
2
+ import { streamAIChat as V } from "./aiClient.js";
3
+ import { Model3DView as z } from "./Model3DView.js";
4
+ import { useEffect as b, useMemo as D, useRef as M, useState as u } from "react";
4
5
  import { jsx as e, jsxs as v } from "react/jsx-runtime";
5
6
  import R from "mermaid";
6
7
  import "katex/dist/katex.min.css";
7
- import D from "katex";
8
+ import T from "katex";
8
9
  var I = !1;
9
10
  function H() {
10
11
  I || (R.initialize({
@@ -41,9 +42,9 @@ function K({ content: t, color: r }) {
41
42
  });
42
43
  }
43
44
  function L({ content: t, color: r, fontSize: i }) {
44
- const l = T(() => {
45
+ const l = D(() => {
45
46
  try {
46
- return D.renderToString(t.trim() || "\\;", {
47
+ return T.renderToString(t.trim() || "\\;", {
47
48
  displayMode: !0,
48
49
  throwOnError: !1
49
50
  });
@@ -60,7 +61,7 @@ function L({ content: t, color: r, fontSize: i }) {
60
61
  dangerouslySetInnerHTML: { __html: l }
61
62
  });
62
63
  }
63
- function V({ content: t, interactive: r }) {
64
+ function _({ content: t, interactive: r }) {
64
65
  return /* @__PURE__ */ e("iframe", {
65
66
  title: "html-media",
66
67
  className: "flow-media-iframe",
@@ -69,11 +70,24 @@ function V({ content: t, interactive: r }) {
69
70
  srcDoc: t
70
71
  });
71
72
  }
72
- function _({ node: t, config: r, interactive: i, color: l }) {
73
- const [n, s] = u([]), [f, c] = u(""), [m, d] = u(!1), [N, S] = u(null), w = M(null), y = M(null);
73
+ function $({ content: t }) {
74
+ const r = t.trim();
75
+ return r ? /* @__PURE__ */ e("img", {
76
+ className: "flow-media-image",
77
+ src: r,
78
+ alt: "",
79
+ draggable: !1,
80
+ referrerPolicy: "no-referrer"
81
+ }) : /* @__PURE__ */ e("div", {
82
+ className: "flow-media-image-empty",
83
+ children: "输入图片链接,或直接粘贴图片"
84
+ });
85
+ }
86
+ function P({ node: t, config: r, interactive: i, color: l }) {
87
+ const [n, s] = u([]), [f, c] = u(""), [m, d] = u(!1), [N, S] = u(null), h = M(null), g = M(null);
74
88
  b(() => {
75
- y.current?.scrollTo({ top: y.current.scrollHeight });
76
- }, [n, m]), b(() => () => w.current?.abort(), []);
89
+ g.current?.scrollTo({ top: g.current.scrollHeight });
90
+ }, [n, m]), b(() => () => h.current?.abort(), []);
77
91
  const k = !!r?.gateway && !!r?.model && !m, C = async () => {
78
92
  const a = f.trim();
79
93
  if (!a || !r?.gateway) return;
@@ -84,28 +98,28 @@ function _({ node: t, config: r, interactive: i, color: l }) {
84
98
  }];
85
99
  s(p), c(""), d(!0);
86
100
  const x = new AbortController();
87
- w.current = x;
101
+ h.current = x;
88
102
  let E = "";
89
103
  s((o) => [...o, {
90
104
  role: "assistant",
91
105
  content: ""
92
106
  }]);
93
107
  try {
94
- for await (const o of z(r, p, x.signal))
95
- E += o, s((h) => {
96
- const g = h.slice();
97
- return g[g.length - 1] = {
108
+ for await (const o of V(r, p, x.signal))
109
+ E += o, s((w) => {
110
+ const y = w.slice();
111
+ return y[y.length - 1] = {
98
112
  role: "assistant",
99
113
  content: E
100
- }, g;
114
+ }, y;
101
115
  });
102
116
  } catch (o) {
103
117
  o?.name !== "AbortError" && S(o?.message ?? "Request failed");
104
118
  } finally {
105
119
  s((o) => {
106
- const h = o[o.length - 1];
107
- return h && h.role === "assistant" && h.content.trim() === "" ? o.slice(0, -1) : o;
108
- }), d(!1), w.current = null;
120
+ const w = o[o.length - 1];
121
+ return w && w.role === "assistant" && w.content.trim() === "" ? o.slice(0, -1) : o;
122
+ }), d(!1), h.current = null;
109
123
  }
110
124
  };
111
125
  return /* @__PURE__ */ v("div", {
@@ -118,7 +132,7 @@ function _({ node: t, config: r, interactive: i, color: l }) {
118
132
  }),
119
133
  /* @__PURE__ */ v("div", {
120
134
  className: "flow-media-ai-log",
121
- ref: y,
135
+ ref: g,
122
136
  children: [
123
137
  n.length === 0 && /* @__PURE__ */ e("div", {
124
138
  className: "flow-media-ai-empty",
@@ -151,7 +165,7 @@ function _({ node: t, config: r, interactive: i, color: l }) {
151
165
  onPointerDown: (a) => a.stopPropagation()
152
166
  }), m ? /* @__PURE__ */ e("button", {
153
167
  type: "button",
154
- onClick: () => w.current?.abort(),
168
+ onClick: () => h.current?.abort(),
155
169
  children: "停止"
156
170
  }) : /* @__PURE__ */ e("button", {
157
171
  type: "button",
@@ -163,7 +177,7 @@ function _({ node: t, config: r, interactive: i, color: l }) {
163
177
  ]
164
178
  });
165
179
  }
166
- function G({ node: t, interactive: r }) {
180
+ function U({ node: t, interactive: r }) {
167
181
  const i = t.data.mediaKind ?? "markdown", l = t.data.mediaContent ?? "", n = t.data.labelColor ?? t.data.color;
168
182
  switch (i) {
169
183
  case "markdown":
@@ -176,7 +190,7 @@ function G({ node: t, interactive: r }) {
176
190
  children: /* @__PURE__ */ e(A, { content: l })
177
191
  });
178
192
  case "html":
179
- return /* @__PURE__ */ e(V, {
193
+ return /* @__PURE__ */ e(_, {
180
194
  content: l,
181
195
  interactive: r
182
196
  });
@@ -191,8 +205,16 @@ function G({ node: t, interactive: r }) {
191
205
  color: n,
192
206
  fontSize: t.data.fontSize
193
207
  });
208
+ case "image":
209
+ return /* @__PURE__ */ e($, { content: l });
210
+ case "model3d":
211
+ return /* @__PURE__ */ e(z, {
212
+ content: l,
213
+ format: t.data.modelFormat ?? "stl",
214
+ interactive: r
215
+ });
194
216
  case "ai":
195
- return /* @__PURE__ */ e(_, {
217
+ return /* @__PURE__ */ e(P, {
196
218
  node: t,
197
219
  config: t.data.aiConfig,
198
220
  interactive: r,
@@ -203,7 +225,7 @@ function G({ node: t, interactive: r }) {
203
225
  }
204
226
  }
205
227
  export {
206
- G as MediaContent
228
+ U as MediaContent
207
229
  };
208
230
 
209
231
  //# sourceMappingURL=MediaContent.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MediaContent.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/MediaContent.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useRef, useState } from 'react';\nimport katex from 'katex';\nimport mermaid from 'mermaid';\nimport Markdown from '../../../Markdown';\nimport type { FlowNode, FlowAIConfig } from '../types';\nimport { streamAIChat, type AIChatMessage } from './aiClient';\nimport 'katex/dist/katex.min.css';\n\nlet mermaidReady = false;\nfunction ensureMermaid() {\n if (mermaidReady) return;\n mermaid.initialize({ startOnLoad: false, theme: 'default', securityLevel: 'loose', fontFamily: 'inherit' });\n mermaidReady = true;\n}\n\n/** Renders a Mermaid diagram from source into an inline SVG. */\nfunction MermaidView({ content, color }: { content: string; color?: string }) {\n const [svg, setSvg] = useState('');\n const [error, setError] = useState<string | null>(null);\n const idRef = useRef(`flow-mermaid-${Math.random().toString(36).slice(2)}`);\n\n useEffect(() => {\n let cancelled = false;\n ensureMermaid();\n const src = content.trim();\n if (!src) { setSvg(''); setError(null); return; }\n mermaid\n .render(idRef.current, src)\n .then(({ svg }) => { if (!cancelled) { setSvg(svg); setError(null); } })\n .catch((e) => { if (!cancelled) setError(e?.message ?? 'Mermaid error'); });\n return () => { cancelled = true; };\n }, [content]);\n\n if (error) return <div className=\"flow-media-error\">{error}</div>;\n return <div className=\"flow-media-mermaid\" style={{ color }} dangerouslySetInnerHTML={{ __html: svg }} />;\n}\n\n/** Renders a LaTeX formula (display mode) via KaTeX. */\nfunction MathView({ content, color, fontSize }: { content: string; color?: string; fontSize?: number }) {\n const html = useMemo(() => {\n try {\n return katex.renderToString(content.trim() || '\\\\;', { displayMode: true, throwOnError: false });\n } catch (e: any) {\n return `<span class=\"flow-media-error\">${e?.message ?? 'KaTeX error'}</span>`;\n }\n }, [content]);\n return <div className=\"flow-media-math\" style={{ color, fontSize }} dangerouslySetInnerHTML={{ __html: html }} />;\n}\n\n/** Renders raw HTML in a sandboxed iframe (isolated from the host document). */\nfunction HtmlView({ content, interactive }: { content: string; interactive: boolean }) {\n return (\n <iframe\n title=\"html-media\"\n className=\"flow-media-iframe\"\n style={{ pointerEvents: interactive ? 'auto' : 'none' }}\n sandbox=\"allow-scripts allow-same-origin allow-popups allow-forms\"\n srcDoc={content}\n />\n );\n}\n\n/** Interactive AI chat backed by an OpenAI/Anthropic-compatible endpoint. */\nfunction AIView({ node, config, interactive, color }: { node: FlowNode; config?: FlowAIConfig; interactive: boolean; color?: string }) {\n const [messages, setMessages] = useState<AIChatMessage[]>([]);\n const [input, setInput] = useState('');\n const [streaming, setStreaming] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight });\n }, [messages, streaming]);\n\n useEffect(() => () => abortRef.current?.abort(), []);\n\n const canSend = !!config?.gateway && !!config?.model && !streaming;\n\n const send = async () => {\n const text = input.trim();\n if (!text || !config?.gateway) return;\n setError(null);\n // Only keep non-empty assistant messages in history; an aborted/errored\n // request can leave an empty assistant placeholder behind, which the API\n // then rejects (\"assistant message must not be empty\").\n const history = messages.filter(\n (m) => !(m.role === 'assistant' && m.content.trim() === ''),\n );\n const next: AIChatMessage[] = [...history, { role: 'user', content: text }];\n setMessages(next);\n setInput('');\n setStreaming(true);\n const ac = new AbortController();\n abortRef.current = ac;\n let acc = '';\n setMessages((m) => [...m, { role: 'assistant', content: '' }]);\n try {\n for await (const delta of streamAIChat(config, next, ac.signal)) {\n acc += delta;\n setMessages((m) => {\n const copy = m.slice();\n copy[copy.length - 1] = { role: 'assistant', content: acc };\n return copy;\n });\n }\n } catch (e: any) {\n if (e?.name !== 'AbortError') setError(e?.message ?? 'Request failed');\n } finally {\n // Drop the trailing assistant placeholder if nothing streamed in.\n setMessages((m) => {\n const last = m[m.length - 1];\n if (last && last.role === 'assistant' && last.content.trim() === '') {\n return m.slice(0, -1);\n }\n return m;\n });\n setStreaming(false);\n abortRef.current = null;\n }\n };\n\n return (\n <div className=\"flow-media-ai\" style={{ color }}>\n <div className=\"flow-media-ai-header\">\n {config?.model ? `AI · ${config.model}` : (node.data.label || 'AI')}\n </div>\n <div className=\"flow-media-ai-log\" ref={scrollRef}>\n {messages.length === 0 && (\n <div className=\"flow-media-ai-empty\">\n {config?.gateway ? '开始对话…' : '请在属性面板配置 AI 接口'}\n </div>\n )}\n {messages.map((m, i) => (\n <div key={i} className={`flow-media-ai-msg flow-media-ai-msg--${m.role}`}>\n {m.role === 'assistant'\n ? <Markdown content={m.content || '…'} enableMermaid={false} enableMarkmap={false} />\n : <span>{m.content}</span>}\n </div>\n ))}\n {error && <div className=\"flow-media-error\">{error}</div>}\n </div>\n <div className=\"flow-media-ai-input\" style={{ pointerEvents: interactive ? 'auto' : 'none' }}>\n <textarea\n value={input}\n placeholder=\"输入消息,Enter 发送\"\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if (canSend) send(); }\n }}\n onPointerDown={(e) => e.stopPropagation()}\n />\n {streaming ? (\n <button type=\"button\" onClick={() => abortRef.current?.abort()}>停止</button>\n ) : (\n <button type=\"button\" disabled={!canSend || !input.trim()} onClick={send}>发送</button>\n )}\n </div>\n </div>\n );\n}\n\n/** Dispatches to the correct renderer for the node's media kind. */\nexport function MediaContent({ node, interactive }: { node: FlowNode; interactive: boolean }) {\n const kind = node.data.mediaKind ?? 'markdown';\n const content = node.data.mediaContent ?? '';\n const textColor = node.data.labelColor ?? node.data.color;\n\n switch (kind) {\n case 'markdown':\n return (\n <div className=\"flow-media-markdown\" style={{ color: textColor, fontSize: node.data.fontSize }}>\n <Markdown content={content} />\n </div>\n );\n case 'html':\n return <HtmlView content={content} interactive={interactive} />;\n case 'mermaid':\n return <MermaidView content={content} color={textColor} />;\n case 'math':\n return <MathView content={content} color={textColor} fontSize={node.data.fontSize} />;\n case 'ai':\n return <AIView node={node} config={node.data.aiConfig} interactive={interactive} color={textColor} />;\n default:\n return null;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MediaContent.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/MediaContent.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useRef, useState } from 'react';\nimport katex from 'katex';\nimport mermaid from 'mermaid';\nimport Markdown from '../../../Markdown';\nimport type { FlowNode, FlowAIConfig } from '../types';\nimport { streamAIChat, type AIChatMessage } from './aiClient';\nimport 'katex/dist/katex.min.css';\nimport { Model3DView } from './Model3DView';\n\nlet mermaidReady = false;\nfunction ensureMermaid() {\n if (mermaidReady) return;\n mermaid.initialize({ startOnLoad: false, theme: 'default', securityLevel: 'loose', fontFamily: 'inherit' });\n mermaidReady = true;\n}\n\n/** Renders a Mermaid diagram from source into an inline SVG. */\nfunction MermaidView({ content, color }: { content: string; color?: string }) {\n const [svg, setSvg] = useState('');\n const [error, setError] = useState<string | null>(null);\n const idRef = useRef(`flow-mermaid-${Math.random().toString(36).slice(2)}`);\n\n useEffect(() => {\n let cancelled = false;\n ensureMermaid();\n const src = content.trim();\n if (!src) { setSvg(''); setError(null); return; }\n mermaid\n .render(idRef.current, src)\n .then(({ svg }) => { if (!cancelled) { setSvg(svg); setError(null); } })\n .catch((e) => { if (!cancelled) setError(e?.message ?? 'Mermaid error'); });\n return () => { cancelled = true; };\n }, [content]);\n\n if (error) return <div className=\"flow-media-error\">{error}</div>;\n return <div className=\"flow-media-mermaid\" style={{ color }} dangerouslySetInnerHTML={{ __html: svg }} />;\n}\n\n/** Renders a LaTeX formula (display mode) via KaTeX. */\nfunction MathView({ content, color, fontSize }: { content: string; color?: string; fontSize?: number }) {\n const html = useMemo(() => {\n try {\n return katex.renderToString(content.trim() || '\\\\;', { displayMode: true, throwOnError: false });\n } catch (e: any) {\n return `<span class=\"flow-media-error\">${e?.message ?? 'KaTeX error'}</span>`;\n }\n }, [content]);\n return <div className=\"flow-media-math\" style={{ color, fontSize }} dangerouslySetInnerHTML={{ __html: html }} />;\n}\n\n/** Renders raw HTML in a sandboxed iframe (isolated from the host document). */\nfunction HtmlView({ content, interactive }: { content: string; interactive: boolean }) {\n return (\n <iframe\n title=\"html-media\"\n className=\"flow-media-iframe\"\n style={{ pointerEvents: interactive ? 'auto' : 'none' }}\n sandbox=\"allow-scripts allow-same-origin allow-popups allow-forms\"\n srcDoc={content}\n />\n );\n}\n\nfunction ImageView({ content }: { content: string }) {\n const src = content.trim();\n if (!src) {\n return <div className=\"flow-media-image-empty\">输入图片链接,或直接粘贴图片</div>;\n }\n return (\n <img\n className=\"flow-media-image\"\n src={src}\n alt=\"\"\n draggable={false}\n referrerPolicy=\"no-referrer\"\n />\n );\n}\n\n/** Interactive AI chat backed by an OpenAI/Anthropic-compatible endpoint. */\nfunction AIView({ node, config, interactive, color }: { node: FlowNode; config?: FlowAIConfig; interactive: boolean; color?: string }) {\n const [messages, setMessages] = useState<AIChatMessage[]>([]);\n const [input, setInput] = useState('');\n const [streaming, setStreaming] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight });\n }, [messages, streaming]);\n\n useEffect(() => () => abortRef.current?.abort(), []);\n\n const canSend = !!config?.gateway && !!config?.model && !streaming;\n\n const send = async () => {\n const text = input.trim();\n if (!text || !config?.gateway) return;\n setError(null);\n // Only keep non-empty assistant messages in history; an aborted/errored\n // request can leave an empty assistant placeholder behind, which the API\n // then rejects (\"assistant message must not be empty\").\n const history = messages.filter(\n (m) => !(m.role === 'assistant' && m.content.trim() === ''),\n );\n const next: AIChatMessage[] = [...history, { role: 'user', content: text }];\n setMessages(next);\n setInput('');\n setStreaming(true);\n const ac = new AbortController();\n abortRef.current = ac;\n let acc = '';\n setMessages((m) => [...m, { role: 'assistant', content: '' }]);\n try {\n for await (const delta of streamAIChat(config, next, ac.signal)) {\n acc += delta;\n setMessages((m) => {\n const copy = m.slice();\n copy[copy.length - 1] = { role: 'assistant', content: acc };\n return copy;\n });\n }\n } catch (e: any) {\n if (e?.name !== 'AbortError') setError(e?.message ?? 'Request failed');\n } finally {\n // Drop the trailing assistant placeholder if nothing streamed in.\n setMessages((m) => {\n const last = m[m.length - 1];\n if (last && last.role === 'assistant' && last.content.trim() === '') {\n return m.slice(0, -1);\n }\n return m;\n });\n setStreaming(false);\n abortRef.current = null;\n }\n };\n\n return (\n <div className=\"flow-media-ai\" style={{ color }}>\n <div className=\"flow-media-ai-header\">\n {config?.model ? `AI · ${config.model}` : (node.data.label || 'AI')}\n </div>\n <div className=\"flow-media-ai-log\" ref={scrollRef}>\n {messages.length === 0 && (\n <div className=\"flow-media-ai-empty\">\n {config?.gateway ? '开始对话…' : '请在属性面板配置 AI 接口'}\n </div>\n )}\n {messages.map((m, i) => (\n <div key={i} className={`flow-media-ai-msg flow-media-ai-msg--${m.role}`}>\n {m.role === 'assistant'\n ? <Markdown content={m.content || '…'} enableMermaid={false} enableMarkmap={false} />\n : <span>{m.content}</span>}\n </div>\n ))}\n {error && <div className=\"flow-media-error\">{error}</div>}\n </div>\n <div className=\"flow-media-ai-input\" style={{ pointerEvents: interactive ? 'auto' : 'none' }}>\n <textarea\n value={input}\n placeholder=\"输入消息,Enter 发送\"\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if (canSend) send(); }\n }}\n onPointerDown={(e) => e.stopPropagation()}\n />\n {streaming ? (\n <button type=\"button\" onClick={() => abortRef.current?.abort()}>停止</button>\n ) : (\n <button type=\"button\" disabled={!canSend || !input.trim()} onClick={send}>发送</button>\n )}\n </div>\n </div>\n );\n}\n\n/** Dispatches to the correct renderer for the node's media kind. */\nexport function MediaContent({ node, interactive }: { node: FlowNode; interactive: boolean }) {\n const kind = node.data.mediaKind ?? 'markdown';\n const content = node.data.mediaContent ?? '';\n const textColor = node.data.labelColor ?? node.data.color;\n\n switch (kind) {\n case 'markdown':\n return (\n <div className=\"flow-media-markdown\" style={{ color: textColor, fontSize: node.data.fontSize }}>\n <Markdown content={content} />\n </div>\n );\n case 'html':\n return <HtmlView content={content} interactive={interactive} />;\n case 'mermaid':\n return <MermaidView content={content} color={textColor} />;\n case 'math':\n return <MathView content={content} color={textColor} fontSize={node.data.fontSize} />;\n case 'image':\n return <ImageView content={content} />;\n case 'model3d':\n return (\n <Model3DView\n content={content}\n format={node.data.modelFormat ?? 'stl'}\n interactive={interactive}\n />\n );\n case 'ai':\n return <AIView node={node} config={node.data.aiConfig} interactive={interactive} color={textColor} />;\n default:\n return null;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { FlowModelFormat } from '../types';
3
+ export declare function Model3DView({ content, format, interactive, }: {
4
+ content: string;
5
+ format: FlowModelFormat;
6
+ interactive: boolean;
7
+ }): React.JSX.Element;
@@ -0,0 +1,118 @@
1
+ import { useEffect as T, useRef as A, useState as B } from "react";
2
+ import { jsx as M, jsxs as W } from "react/jsx-runtime";
3
+ import * as e from "three";
4
+ import { OrbitControls as z } from "three/addons/controls/OrbitControls.js";
5
+ import { ThreeMFLoader as H } from "three/addons/loaders/3MFLoader.js";
6
+ import { OBJLoader as V } from "three/addons/loaders/OBJLoader.js";
7
+ import { STLLoader as G } from "three/addons/loaders/STLLoader.js";
8
+ function D(d) {
9
+ const s = /* @__PURE__ */ new Set(), t = /* @__PURE__ */ new Set();
10
+ d.traverse((r) => {
11
+ if (!(r instanceof e.Mesh)) return;
12
+ r.geometry?.dispose();
13
+ const f = Array.isArray(r.material) ? r.material : [r.material];
14
+ for (const u of f)
15
+ if (!(!u || s.has(u))) {
16
+ s.add(u);
17
+ for (const p of Object.values(u)) p instanceof e.Texture && t.add(p);
18
+ }
19
+ });
20
+ for (const r of t) r.dispose();
21
+ for (const r of s) r.dispose();
22
+ }
23
+ function J(d, s) {
24
+ if (d === "stl") {
25
+ const t = new G().parse(s);
26
+ return t.getAttribute("normal") || t.computeVertexNormals(), new e.Mesh(t, new e.MeshStandardMaterial({
27
+ color: 12108492,
28
+ metalness: 0.05,
29
+ roughness: 0.72,
30
+ side: e.DoubleSide
31
+ }));
32
+ }
33
+ return d === "obj" ? new V().parse(new TextDecoder().decode(s)) : new H().parse(s);
34
+ }
35
+ function K({ content: d, format: s, interactive: t }) {
36
+ const r = A(null), f = A(null), [u, p] = B(null), [O, x] = B(!1);
37
+ return T(() => {
38
+ f.current && (f.current.enabled = t);
39
+ }, [t]), T(() => {
40
+ const l = r.current, b = d.trim();
41
+ if (!l || !b) return;
42
+ let w = !1, S = 0, g = null;
43
+ const E = new AbortController(), h = new e.Scene(), i = new e.PerspectiveCamera(45, 1, 0.01, 1e4), n = new e.WebGLRenderer({
44
+ antialias: !0,
45
+ alpha: !0
46
+ });
47
+ n.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2)), n.outputColorSpace = e.SRGBColorSpace, n.setClearColor(0, 0), l.appendChild(n.domElement), h.add(new e.HemisphereLight(16777215, 4477030, 2));
48
+ const y = new e.DirectionalLight(16777215, 2.4);
49
+ y.position.set(3, 5, 4), h.add(y);
50
+ const c = new z(i, n.domElement);
51
+ c.enableDamping = !0, c.enabled = t, f.current = c;
52
+ const L = () => {
53
+ const o = Math.max(1, l.clientWidth), a = Math.max(1, l.clientHeight);
54
+ n.setSize(o, a, !1), i.aspect = o / a, i.updateProjectionMatrix();
55
+ };
56
+ L();
57
+ const v = new ResizeObserver(L);
58
+ v.observe(l);
59
+ const R = () => {
60
+ c.update(), n.render(h, i), S = requestAnimationFrame(R);
61
+ };
62
+ return R(), x(!0), p(null), fetch(b, { signal: E.signal }).then((o) => {
63
+ if (!o.ok) throw new Error(`模型加载失败:HTTP ${o.status}`);
64
+ return o.arrayBuffer();
65
+ }).then((o) => {
66
+ if (o.byteLength > 50 * 1024 * 1024) throw new Error("模型超过 50 MiB,无法加载");
67
+ const a = J(s, o);
68
+ if (w) {
69
+ D(a);
70
+ return;
71
+ }
72
+ g = a, a.updateMatrixWorld(!0);
73
+ const C = new e.Box3().setFromObject(a);
74
+ if (C.isEmpty()) throw new Error("模型不包含可显示的几何体");
75
+ const N = C.getCenter(new e.Vector3());
76
+ a.position.sub(N), a.updateMatrixWorld(!0);
77
+ const m = new e.Box3().setFromObject(a).getBoundingSphere(new e.Sphere());
78
+ if (!Number.isFinite(m.radius) || m.radius <= 0) throw new Error("模型尺寸无效");
79
+ h.add(a);
80
+ const F = e.MathUtils.degToRad(i.fov / 2), j = Math.atan(Math.tan(F) * i.aspect), P = m.radius / Math.sin(Math.min(j, F)) * 1.15;
81
+ i.position.copy(new e.Vector3(1, 0.75, 1).normalize().multiplyScalar(P)), i.near = Math.max(m.radius / 1e3, 1e-3), i.far = Math.max(P + m.radius * 10, m.radius * 1e3), i.updateProjectionMatrix(), c.target.set(0, 0, 0), c.minDistance = m.radius * 0.05, c.maxDistance = m.radius * 100, c.update();
82
+ }).catch((o) => {
83
+ !w && o?.name !== "AbortError" && p(o?.message ?? "模型加载失败");
84
+ }).finally(() => {
85
+ w || x(!1);
86
+ }), () => {
87
+ w = !0, E.abort(), cancelAnimationFrame(S), v.disconnect(), c.dispose(), f.current = null, g && D(g), h.clear(), n.renderLists.dispose(), n.dispose(), n.forceContextLoss(), n.domElement.remove();
88
+ };
89
+ }, [d, s]), d.trim() ? /* @__PURE__ */ W("div", {
90
+ ref: r,
91
+ className: "flow-media-model3d",
92
+ style: {
93
+ pointerEvents: t ? "auto" : "none",
94
+ touchAction: t ? "none" : "auto"
95
+ },
96
+ onPointerDown: (l) => {
97
+ t && l.stopPropagation();
98
+ },
99
+ onWheel: (l) => {
100
+ t && l.stopPropagation();
101
+ },
102
+ children: [O && /* @__PURE__ */ M("div", {
103
+ className: "flow-media-loading flow-media-model3d-status",
104
+ children: "加载模型…"
105
+ }), u && /* @__PURE__ */ M("div", {
106
+ className: "flow-media-error flow-media-model3d-status",
107
+ children: u
108
+ })]
109
+ }) : /* @__PURE__ */ M("div", {
110
+ className: "flow-media-image-empty",
111
+ children: "输入 STL、OBJ 或 3MF 模型链接"
112
+ });
113
+ }
114
+ export {
115
+ K as Model3DView
116
+ };
117
+
118
+ //# sourceMappingURL=Model3DView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Model3DView.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/Model3DView.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react';\nimport * as THREE from 'three';\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\nimport { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js';\nimport { OBJLoader } from 'three/addons/loaders/OBJLoader.js';\nimport { STLLoader } from 'three/addons/loaders/STLLoader.js';\nimport type { FlowModelFormat } from '../types';\n\nfunction disposeObject(root: any) {\n const materials = new Set<any>();\n const textures = new Set<any>();\n root.traverse((object: any) => {\n if (!(object instanceof THREE.Mesh)) return;\n object.geometry?.dispose();\n const list = Array.isArray(object.material) ? object.material : [object.material];\n for (const material of list) {\n if (!material || materials.has(material)) continue;\n materials.add(material);\n for (const value of Object.values(material)) {\n if (value instanceof THREE.Texture) textures.add(value);\n }\n }\n });\n for (const texture of textures) texture.dispose();\n for (const material of materials) material.dispose();\n}\n\nfunction parseModel(format: FlowModelFormat, data: ArrayBuffer): any {\n if (format === 'stl') {\n const geometry = new STLLoader().parse(data);\n if (!geometry.getAttribute('normal')) geometry.computeVertexNormals();\n return new THREE.Mesh(\n geometry,\n new THREE.MeshStandardMaterial({\n color: 0xb8c2cc,\n metalness: 0.05,\n roughness: 0.72,\n side: THREE.DoubleSide,\n }),\n );\n }\n if (format === 'obj') {\n return new OBJLoader().parse(new TextDecoder().decode(data));\n }\n return new ThreeMFLoader().parse(data);\n}\n\nexport function Model3DView({\n content,\n format,\n interactive,\n}: {\n content: string;\n format: FlowModelFormat;\n interactive: boolean;\n}) {\n const hostRef = useRef<HTMLDivElement>(null);\n const controlsRef = useRef<any>(null);\n const [error, setError] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (controlsRef.current) controlsRef.current.enabled = interactive;\n }, [interactive]);\n\n useEffect(() => {\n const host = hostRef.current;\n const source = content.trim();\n if (!host || !source) return;\n\n let cancelled = false;\n let frame = 0;\n let model: any = null;\n const abort = new AbortController();\n const scene = new THREE.Scene();\n const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 10000);\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));\n renderer.outputColorSpace = THREE.SRGBColorSpace;\n renderer.setClearColor(0x000000, 0);\n host.appendChild(renderer.domElement);\n\n scene.add(new THREE.HemisphereLight(0xffffff, 0x445066, 2));\n const directional = new THREE.DirectionalLight(0xffffff, 2.4);\n directional.position.set(3, 5, 4);\n scene.add(directional);\n\n const controls = new OrbitControls(camera, renderer.domElement);\n controls.enableDamping = true;\n controls.enabled = interactive;\n controlsRef.current = controls;\n\n const resize = () => {\n const width = Math.max(1, host.clientWidth);\n const height = Math.max(1, host.clientHeight);\n renderer.setSize(width, height, false);\n camera.aspect = width / height;\n camera.updateProjectionMatrix();\n };\n resize();\n const observer = new ResizeObserver(resize);\n observer.observe(host);\n\n const animate = () => {\n controls.update();\n renderer.render(scene, camera);\n frame = requestAnimationFrame(animate);\n };\n animate();\n\n setLoading(true);\n setError(null);\n fetch(source, { signal: abort.signal })\n .then(response => {\n if (!response.ok) throw new Error(`模型加载失败:HTTP ${response.status}`);\n return response.arrayBuffer();\n })\n .then(data => {\n if (data.byteLength > 50 * 1024 * 1024) throw new Error('模型超过 50 MiB,无法加载');\n const root = parseModel(format, data);\n if (cancelled) {\n disposeObject(root);\n return;\n }\n model = root;\n root.updateMatrixWorld(true);\n const box = new THREE.Box3().setFromObject(root);\n if (box.isEmpty()) throw new Error('模型不包含可显示的几何体');\n const center = box.getCenter(new THREE.Vector3());\n root.position.sub(center);\n root.updateMatrixWorld(true);\n const sphere = new THREE.Box3().setFromObject(root).getBoundingSphere(new THREE.Sphere());\n if (!Number.isFinite(sphere.radius) || sphere.radius <= 0) throw new Error('模型尺寸无效');\n scene.add(root);\n\n const halfFovY = THREE.MathUtils.degToRad(camera.fov / 2);\n const halfFovX = Math.atan(Math.tan(halfFovY) * camera.aspect);\n const distance = (sphere.radius / Math.sin(Math.min(halfFovX, halfFovY))) * 1.15;\n camera.position.copy(new THREE.Vector3(1, 0.75, 1).normalize().multiplyScalar(distance));\n camera.near = Math.max(sphere.radius / 1000, 0.001);\n camera.far = Math.max(distance + sphere.radius * 10, sphere.radius * 1000);\n camera.updateProjectionMatrix();\n controls.target.set(0, 0, 0);\n controls.minDistance = sphere.radius * 0.05;\n controls.maxDistance = sphere.radius * 100;\n controls.update();\n })\n .catch(nextError => {\n if (!cancelled && nextError?.name !== 'AbortError') setError(nextError?.message ?? '模型加载失败');\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n abort.abort();\n cancelAnimationFrame(frame);\n observer.disconnect();\n controls.dispose();\n controlsRef.current = null;\n if (model) disposeObject(model);\n scene.clear();\n renderer.renderLists.dispose();\n renderer.dispose();\n renderer.forceContextLoss();\n renderer.domElement.remove();\n };\n }, [content, format]);\n\n if (!content.trim()) {\n return <div className=\"flow-media-image-empty\">输入 STL、OBJ 或 3MF 模型链接</div>;\n }\n\n return (\n <div\n ref={hostRef}\n className=\"flow-media-model3d\"\n style={{ pointerEvents: interactive ? 'auto' : 'none', touchAction: interactive ? 'none' : 'auto' }}\n onPointerDown={event => { if (interactive) event.stopPropagation(); }}\n onWheel={event => { if (interactive) event.stopPropagation(); }}\n >\n {loading && <div className=\"flow-media-loading flow-media-model3d-status\">加载模型…</div>}\n {error && <div className=\"flow-media-error flow-media-model3d-status\">{error}</div>}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -7,8 +7,9 @@ export interface HandleDef {
7
7
  side: 'top' | 'right' | 'bottom' | 'left';
8
8
  }
9
9
  /**
10
- * Creates 12 connection handle positions per node (3 per side).
10
+ * Creates 20 connection handle positions per node (5 per side).
11
11
  * Handles are invisible hit areas; shown on hover as small circles.
12
+ * The positions divide each edge into six equal segments.
12
13
  */
13
14
  export declare function getHandlePositions(width: number, height: number): HandleDef[];
14
15
  export declare function createHandleGraphics(parent: Container, handles: HandleDef[], visible?: boolean): Graphics;
@@ -1,9 +1,11 @@
1
1
  import { Graphics as u } from "pixi.js";
2
2
  function h(n, t) {
3
3
  const i = [
4
- 0.25,
4
+ 0.16666666666666666,
5
+ 0.3333333333333333,
5
6
  0.5,
6
- 0.75
7
+ 0.6666666666666666,
8
+ 0.8333333333333334
7
9
  ], s = [];
8
10
  for (const o of i)
9
11
  s.push({
@@ -1 +1 @@
1
- {"version":3,"file":"NodeHandles.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/NodeHandles.ts"],"sourcesContent":["import { Graphics, Container } from 'pixi.js';\nimport { HANDLE_SIZE } from '../constants';\nimport type { FlowNode } from '../types';\n\nexport interface HandleDef {\n id: string;\n x: number;\n y: number;\n side: 'top' | 'right' | 'bottom' | 'left';\n}\n\n/**\n * Creates 12 connection handle positions per node (3 per side).\n * Handles are invisible hit areas; shown on hover as small circles.\n */\nexport function getHandlePositions(width: number, height: number): HandleDef[] {\n const offsets = [0.25, 0.5, 0.75];\n const handles: HandleDef[] = [];\n\n for (const pct of offsets) {\n handles.push({ id: `t_${pct}`, x: width * pct, y: 0, side: 'top' });\n handles.push({ id: `b_${pct}`, x: width * pct, y: height, side: 'bottom' });\n handles.push({ id: `l_${pct}`, x: 0, y: height * pct, side: 'left' });\n handles.push({ id: `r_${pct}`, x: width, y: height * pct, side: 'right' });\n }\n return handles;\n}\n\nexport function createHandleGraphics(\n parent: Container,\n handles: HandleDef[],\n visible = false,\n): Graphics {\n const g = new Graphics();\n g.label = 'handles';\n g.visible = visible;\n g.eventMode = 'static';\n\n for (const h of handles) {\n g.circle(h.x, h.y, HANDLE_SIZE / 2)\n .fill({ color: 0x3b82f6, alpha: 0.9 });\n }\n\n parent.addChild(g);\n return g;\n}\n\nexport function updateHandleGraphics(\n g: Graphics,\n handles: HandleDef[],\n): void {\n g.clear();\n for (const h of handles) {\n g.circle(h.x, h.y, HANDLE_SIZE / 2)\n .fill({ color: 0x3b82f6, alpha: 0.9 });\n }\n}\n\n/**\n * Compute a handle's world-space position considering node rotation.\n */\nexport function handleWorldPos(\n h: HandleDef,\n node: FlowNode,\n): { x: number; y: number } {\n const deg = node.data.rotation ?? 0;\n const nx = node.position.x, ny = node.position.y;\n if (deg === 0) return { x: nx + h.x, y: ny + h.y };\n const rad = (deg * Math.PI) / 180;\n const cx = node.width / 2, cy = node.height / 2;\n const dx = h.x - cx, dy = h.y - cy;\n const cos = Math.cos(rad), sin = Math.sin(rad);\n return {\n x: nx + cx + dx * cos - dy * sin,\n y: ny + cy + dx * sin + dy * cos,\n };\n}\n\n/** Find the closest handle to a world-space point */\nexport function findClosestHandle(\n handles: HandleDef[],\n nodeX: number,\n nodeY: number,\n worldX: number,\n worldY: number,\n threshold = 20,\n): HandleDef | null {\n let best: HandleDef | null = null;\n let bestDist = threshold;\n for (const h of handles) {\n const dx = (nodeX + h.x) - worldX;\n const dy = (nodeY + h.y) - worldY;\n const dist = Math.sqrt(dx * dx + dy * dy);\n if (dist < bestDist) {\n bestDist = dist;\n best = h;\n }\n }\n return best;\n}\n\n/**\n * Like findClosestHandle but accounts for node rotation.\n */\nexport function findClosestHandleWorld(\n handles: HandleDef[],\n node: FlowNode,\n worldX: number,\n worldY: number,\n threshold = 20,\n): HandleDef | null {\n const deg = node.data.rotation ?? 0;\n if (deg === 0) {\n return findClosestHandle(handles, node.position.x, node.position.y, worldX, worldY, threshold);\n }\n let best: HandleDef | null = null;\n let bestDist = threshold;\n for (const h of handles) {\n const wp = handleWorldPos(h, node);\n const dx = wp.x - worldX, dy = wp.y - worldY;\n const dist = Math.sqrt(dx * dx + dy * dy);\n if (dist < bestDist) { bestDist = dist; best = h; }\n }\n return best;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"NodeHandles.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/NodeHandles.ts"],"sourcesContent":["import { Graphics, Container } from 'pixi.js';\nimport { HANDLE_SIZE } from '../constants';\nimport type { FlowNode } from '../types';\n\nexport interface HandleDef {\n id: string;\n x: number;\n y: number;\n side: 'top' | 'right' | 'bottom' | 'left';\n}\n\n/**\n * Creates 20 connection handle positions per node (5 per side).\n * Handles are invisible hit areas; shown on hover as small circles.\n * The positions divide each edge into six equal segments.\n */\nexport function getHandlePositions(width: number, height: number): HandleDef[] {\n const offsets = [1 / 6, 2 / 6, 3 / 6, 4 / 6, 5 / 6];\n const handles: HandleDef[] = [];\n\n for (const pct of offsets) {\n handles.push({ id: `t_${pct}`, x: width * pct, y: 0, side: 'top' });\n handles.push({ id: `b_${pct}`, x: width * pct, y: height, side: 'bottom' });\n handles.push({ id: `l_${pct}`, x: 0, y: height * pct, side: 'left' });\n handles.push({ id: `r_${pct}`, x: width, y: height * pct, side: 'right' });\n }\n return handles;\n}\n\nexport function createHandleGraphics(\n parent: Container,\n handles: HandleDef[],\n visible = false,\n): Graphics {\n const g = new Graphics();\n g.label = 'handles';\n g.visible = visible;\n g.eventMode = 'static';\n\n for (const h of handles) {\n g.circle(h.x, h.y, HANDLE_SIZE / 2)\n .fill({ color: 0x3b82f6, alpha: 0.9 });\n }\n\n parent.addChild(g);\n return g;\n}\n\nexport function updateHandleGraphics(\n g: Graphics,\n handles: HandleDef[],\n): void {\n g.clear();\n for (const h of handles) {\n g.circle(h.x, h.y, HANDLE_SIZE / 2)\n .fill({ color: 0x3b82f6, alpha: 0.9 });\n }\n}\n\n/**\n * Compute a handle's world-space position considering node rotation.\n */\nexport function handleWorldPos(\n h: HandleDef,\n node: FlowNode,\n): { x: number; y: number } {\n const deg = node.data.rotation ?? 0;\n const nx = node.position.x, ny = node.position.y;\n if (deg === 0) return { x: nx + h.x, y: ny + h.y };\n const rad = (deg * Math.PI) / 180;\n const cx = node.width / 2, cy = node.height / 2;\n const dx = h.x - cx, dy = h.y - cy;\n const cos = Math.cos(rad), sin = Math.sin(rad);\n return {\n x: nx + cx + dx * cos - dy * sin,\n y: ny + cy + dx * sin + dy * cos,\n };\n}\n\n/** Find the closest handle to a world-space point */\nexport function findClosestHandle(\n handles: HandleDef[],\n nodeX: number,\n nodeY: number,\n worldX: number,\n worldY: number,\n threshold = 20,\n): HandleDef | null {\n let best: HandleDef | null = null;\n let bestDist = threshold;\n for (const h of handles) {\n const dx = (nodeX + h.x) - worldX;\n const dy = (nodeY + h.y) - worldY;\n const dist = Math.sqrt(dx * dx + dy * dy);\n if (dist < bestDist) {\n bestDist = dist;\n best = h;\n }\n }\n return best;\n}\n\n/**\n * Like findClosestHandle but accounts for node rotation.\n */\nexport function findClosestHandleWorld(\n handles: HandleDef[],\n node: FlowNode,\n worldX: number,\n worldY: number,\n threshold = 20,\n): HandleDef | null {\n const deg = node.data.rotation ?? 0;\n if (deg === 0) {\n return findClosestHandle(handles, node.position.x, node.position.y, worldX, worldY, threshold);\n }\n let best: HandleDef | null = null;\n let bestDist = threshold;\n for (const h of handles) {\n const wp = handleWorldPos(h, node);\n const dx = wp.x - worldX, dy = wp.y - worldY;\n const dist = Math.sqrt(dx * dx + dy * dy);\n if (dist < bestDist) { bestDist = dist; best = h; }\n }\n return best;\n}\n"],"mappings":"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"}
@@ -4,6 +4,8 @@ import { type HandleDef } from './NodeHandles';
4
4
  export interface NodeContainer {
5
5
  container: Container;
6
6
  shape: Graphics;
7
+ /** Rotated/flipped visual content for SVG icon stamps. */
8
+ svgTransform: Container;
7
9
  icon: Sprite;
8
10
  text: Text;
9
11
  /** Sequence badge shown on `animAnchor` nodes. */