components/scene.tsx (108 lines of code) (raw):
import Spline from "@splinetool/react-spline";
import React, { useEffect, useState, useRef } from "react";
import type { Application } from "@splinetool/runtime";
import "./scene.css";
import { getComponent } from "@/lib/components-mapping";
interface ToolCall {
name: string;
arguments: any;
}
interface SceneProps {
toolCall: ToolCall;
}
const Scene: React.FC<SceneProps> = ({ toolCall }) => {
const spline = useRef<Application | null>(null);
const [currentCamera, setCurrentCamera] = useState<string>("main");
const [displayComponent, setDisplayComponent] =
useState<React.ReactNode | null>(null);
// Set the Spline scene on load
const onLoad = (splineApp: Application) => {
spline.current = splineApp;
};
// Keydown listener
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
// Reset UI when "d" key is pressed, to clear data without tool calls from the model
if (event.code === "d") {
setDisplayComponent(null);
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, []);
// Tool calls handling
useEffect(() => {
if (!toolCall) return;
// Parse arguments
const { name, arguments: toolArgs } = toolCall;
let args: any = {};
try {
args = JSON.parse(toolArgs);
} catch (error) {
console.error("Failed to parse toolCall arguments:", error);
return;
}
// Trigger animation in spline for a given object
// The mouseDown events need to be set up in the spline scene
function triggerAnimation(objectName: string) {
if (spline.current) {
try {
spline.current.emitEvent("mouseDown", objectName);
} catch (error) {
console.error("Failed to trigger animation:", error);
}
}
}
function displayData() {
try {
const { chart, title, text, data } = JSON.parse(toolCall.arguments);
const component = getComponent({ chart, title, text, data });
setDisplayComponent(component || null);
} catch (error) {
console.error("Failed to parse toolCall arguments:", error);
}
}
// Show moons in sequence
async function showMoons(moons: string[]) {
for (const moon of moons) {
triggerAnimation(moon);
// small delay to make the moons appear sequentially
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
function resetCamera() {
console.log("Resetting camera to initial position");
triggerAnimation("trigger_reset");
if (currentCamera !== "main") {
triggerAnimation("trigger_camera_main");
setCurrentCamera("main");
}
}
// Reset UI before handling a tool call
setDisplayComponent(null);
switch (name) {
case "focus_planet":
triggerAnimation(args.planet);
break;
case "display_data":
displayData();
break;
case "show_moons":
showMoons(args.moons);
break;
case "get_iss_position":
triggerAnimation("ISS");
break;
case "reset_camera":
resetCamera();
break;
case "show_orbit":
triggerAnimation("trigger_camera_high_level");
setCurrentCamera("high_level");
break;
default:
// No matching toolCall
break;
}
}, [toolCall, currentCamera]);
return (
<div className="size-full relative scene-bg">
<Spline
scene="https://prod.spline.design/yH4ADQUBzWTJ2ITk/scene.splinecode"
onLoad={onLoad}
/>
{displayComponent && (
<div className="absolute top-0 text-white right-16 h-full flex items-center justify-center">
{displayComponent}
</div>
)}
</div>
);
};
export default Scene;