generateReactComponent
Generate React components from Spline 3D scenes with configurable interactivity levels, responsive design options, and TypeScript support for web integration.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sceneId | Yes | Scene ID | |
| componentName | No | React component name | SplineScene |
| interactivity | No | Level of interactivity | basic |
| responsive | No | Whether to make the component responsive | |
| typescript | No | Whether to generate TypeScript code |
Implementation Reference
- src/tools/runtime-tools.js:479-655 (registration)Registration of the 'generateReactComponent' tool, including Zod input schema and the full async handler function that generates React components embedding Spline scenes with configurable interactivity levels.server.tool( 'generateReactComponent', { sceneId: z.string().min(1).describe('Scene ID'), componentName: z.string().min(1).default('SplineScene').describe('React component name'), interactivity: z.enum(['none', 'basic', 'advanced']).default('basic') .describe('Level of interactivity'), responsive: z.boolean().default(true).describe('Whether to make the component responsive'), typescript: z.boolean().default(false).describe('Whether to generate TypeScript code'), }, async ({ sceneId, componentName, interactivity, responsive, typescript }) => { try { const sceneUrl = `https://prod.spline.design/${sceneId}/scene.splinecode`; // Base component with no interactivity let componentCode = ` import React${typescript ? ', { FC }' : ''} from 'react'; import Spline from '@splinetool/react-spline'; ${typescript ? `interface ${componentName}Props { width?: string | number; height?: string | number; className?: string; } const ${componentName}: FC<${componentName}Props> = ({ width = '100%', height = '100%', className = '' }) => {` : `const ${componentName} = ({ width = '100%', height = '100%', className = '' }) => {`} return ( <div style={{ width: ${responsive ? 'width' : "'100%'"}, height: ${responsive ? 'height' : "'100%'"}, position: 'relative' }} className={className} > <Spline scene="${sceneUrl}"${interactivity === 'none' ? '' : ` onLoad={handleOnLoad}`} /> </div> ); }; export default ${componentName};`; // Add interactivity if requested if (interactivity !== 'none') { // Insert before the return statement const basicCode = ` ${typescript ? 'const handleOnLoad = (splineApp: any) => {' : 'const handleOnLoad = (splineApp) => {'} console.log('Spline scene loaded'); // Get objects by name const cube = splineApp.findObjectByName('Cube'); if (cube) { console.log('Found cube:', cube); } }; `; const advancedCode = ` ${typescript ? 'const [activeObject, setActiveObject] = React.useState<any | null>(null);' : 'const [activeObject, setActiveObject] = React.useState(null);'} ${typescript ? 'const splineRef = React.useRef<any | null>(null);' : 'const splineRef = React.useRef(null);'} ${typescript ? 'const handleOnLoad = (splineApp: any) => {' : 'const handleOnLoad = (splineApp) => {'} console.log('Spline scene loaded'); splineRef.current = splineApp; // Get all interactive objects const objects = splineApp.getObjects(); console.log('Scene objects:', objects.length); // Set up event listeners splineApp.addEventListener('mouseDown', handleMouseDown); splineApp.addEventListener('mouseUp', handleMouseUp); splineApp.addEventListener('mouseHover', handleMouseHover); }; ${typescript ? 'const handleMouseDown = (e: any) => {' : 'const handleMouseDown = (e) => {'} console.log('Mouse down on:', e.target.name); setActiveObject(e.target); // Example: Scale up on click e.target.scale.multiplyScalar(1.1); }; ${typescript ? 'const handleMouseUp = (e: any) => {' : 'const handleMouseUp = (e) => {'} console.log('Mouse up on:', e.target.name); // Example: Scale back down if (e.target === activeObject) { e.target.scale.divideScalar(1.1); } }; ${typescript ? 'const handleMouseHover = (e: any) => {' : 'const handleMouseHover = (e) => {'} console.log('Mouse hover on:', e.target.name); // Example: Change cursor document.body.style.cursor = 'pointer'; }; // Example function to animate an object ${typescript ? 'const animateObject = (objectName: string) => {' : 'const animateObject = (objectName) => {'} if (!splineRef.current) return; const obj = splineRef.current.findObjectByName(objectName); if (!obj) return; let startTime = null; const duration = 1000; // ms function animate(timestamp${typescript ? ': number' : ''}) { if (!startTime) startTime = timestamp; const elapsed = timestamp - startTime; const progress = Math.min(elapsed / duration, 1); // Rotate the object obj.rotation.y = progress * Math.PI * 2; if (progress < 1) { requestAnimationFrame(animate); } } requestAnimationFrame(animate); }; // Effect to set up keyboard controls React.useEffect(() => { const handleKeyDown = (e${typescript ? ': KeyboardEvent' : ''}) => { if (!splineRef.current) return; if (e.key === 'r' && activeObject) { // Rotate the active object animateObject(activeObject.name); } }; window.addEventListener('keydown', handleKeyDown); return () => { window.removeEventListener('keydown', handleKeyDown); }; }, [activeObject]); `; // Insert the interactivity code based on level if (interactivity === 'basic') { componentCode = componentCode.replace(`const ${componentName} = (`, basicCode + `const ${componentName} = (`); } else if (interactivity === 'advanced') { componentCode = componentCode.replace(`import React`, `import React, { useState, useEffect, useRef }`); componentCode = componentCode.replace(`const ${componentName} = (`, advancedCode + `const ${componentName} = (`); } } return { content: [ { type: 'text', text: componentCode } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error generating React component: ${error.message}` } ], isError: true }; } } );
- src/tools/runtime-tools.js:489-655 (handler)The handler function executes the tool logic: generates a complete React component using @splinetool/react-spline, embedding the specified Spline scene. Supports configurable interactivity (none/basic/advanced), responsiveness, and TypeScript. Returns the generated code as text content.async ({ sceneId, componentName, interactivity, responsive, typescript }) => { try { const sceneUrl = `https://prod.spline.design/${sceneId}/scene.splinecode`; // Base component with no interactivity let componentCode = ` import React${typescript ? ', { FC }' : ''} from 'react'; import Spline from '@splinetool/react-spline'; ${typescript ? `interface ${componentName}Props { width?: string | number; height?: string | number; className?: string; } const ${componentName}: FC<${componentName}Props> = ({ width = '100%', height = '100%', className = '' }) => {` : `const ${componentName} = ({ width = '100%', height = '100%', className = '' }) => {`} return ( <div style={{ width: ${responsive ? 'width' : "'100%'"}, height: ${responsive ? 'height' : "'100%'"}, position: 'relative' }} className={className} > <Spline scene="${sceneUrl}"${interactivity === 'none' ? '' : ` onLoad={handleOnLoad}`} /> </div> ); }; export default ${componentName};`; // Add interactivity if requested if (interactivity !== 'none') { // Insert before the return statement const basicCode = ` ${typescript ? 'const handleOnLoad = (splineApp: any) => {' : 'const handleOnLoad = (splineApp) => {'} console.log('Spline scene loaded'); // Get objects by name const cube = splineApp.findObjectByName('Cube'); if (cube) { console.log('Found cube:', cube); } }; `; const advancedCode = ` ${typescript ? 'const [activeObject, setActiveObject] = React.useState<any | null>(null);' : 'const [activeObject, setActiveObject] = React.useState(null);'} ${typescript ? 'const splineRef = React.useRef<any | null>(null);' : 'const splineRef = React.useRef(null);'} ${typescript ? 'const handleOnLoad = (splineApp: any) => {' : 'const handleOnLoad = (splineApp) => {'} console.log('Spline scene loaded'); splineRef.current = splineApp; // Get all interactive objects const objects = splineApp.getObjects(); console.log('Scene objects:', objects.length); // Set up event listeners splineApp.addEventListener('mouseDown', handleMouseDown); splineApp.addEventListener('mouseUp', handleMouseUp); splineApp.addEventListener('mouseHover', handleMouseHover); }; ${typescript ? 'const handleMouseDown = (e: any) => {' : 'const handleMouseDown = (e) => {'} console.log('Mouse down on:', e.target.name); setActiveObject(e.target); // Example: Scale up on click e.target.scale.multiplyScalar(1.1); }; ${typescript ? 'const handleMouseUp = (e: any) => {' : 'const handleMouseUp = (e) => {'} console.log('Mouse up on:', e.target.name); // Example: Scale back down if (e.target === activeObject) { e.target.scale.divideScalar(1.1); } }; ${typescript ? 'const handleMouseHover = (e: any) => {' : 'const handleMouseHover = (e) => {'} console.log('Mouse hover on:', e.target.name); // Example: Change cursor document.body.style.cursor = 'pointer'; }; // Example function to animate an object ${typescript ? 'const animateObject = (objectName: string) => {' : 'const animateObject = (objectName) => {'} if (!splineRef.current) return; const obj = splineRef.current.findObjectByName(objectName); if (!obj) return; let startTime = null; const duration = 1000; // ms function animate(timestamp${typescript ? ': number' : ''}) { if (!startTime) startTime = timestamp; const elapsed = timestamp - startTime; const progress = Math.min(elapsed / duration, 1); // Rotate the object obj.rotation.y = progress * Math.PI * 2; if (progress < 1) { requestAnimationFrame(animate); } } requestAnimationFrame(animate); }; // Effect to set up keyboard controls React.useEffect(() => { const handleKeyDown = (e${typescript ? ': KeyboardEvent' : ''}) => { if (!splineRef.current) return; if (e.key === 'r' && activeObject) { // Rotate the active object animateObject(activeObject.name); } }; window.addEventListener('keydown', handleKeyDown); return () => { window.removeEventListener('keydown', handleKeyDown); }; }, [activeObject]); `; // Insert the interactivity code based on level if (interactivity === 'basic') { componentCode = componentCode.replace(`const ${componentName} = (`, basicCode + `const ${componentName} = (`); } else if (interactivity === 'advanced') { componentCode = componentCode.replace(`import React`, `import React, { useState, useEffect, useRef }`); componentCode = componentCode.replace(`const ${componentName} = (`, advancedCode + `const ${componentName} = (`); } } return { content: [ { type: 'text', text: componentCode } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error generating React component: ${error.message}` } ], isError: true }; } } );
- src/tools/runtime-tools.js:481-488 (schema)Zod schema defining input parameters for the generateReactComponent tool: sceneId (required), componentName, interactivity level, responsive flag, and typescript flag.{ sceneId: z.string().min(1).describe('Scene ID'), componentName: z.string().min(1).default('SplineScene').describe('React component name'), interactivity: z.enum(['none', 'basic', 'advanced']).default('basic') .describe('Level of interactivity'), responsive: z.boolean().default(true).describe('Whether to make the component responsive'), typescript: z.boolean().default(false).describe('Whether to generate TypeScript code'), },