Spaces:
Running
Running
/** | |
* Copyright 2024 Google LLC | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
import { SchemaType } from "@google/generative-ai"; | |
import { useEffect, useRef, memo } from "react"; | |
import { useLiveAPIContext } from "../../contexts/LiveAPIContext"; | |
import type { ToolCall } from "../../multimodal-live-types"; | |
// Add type definitions for window functions | |
declare global { | |
interface Window { | |
initSketch: (container: HTMLElement) => void; | |
updateSketch: (code: string, container: HTMLElement) => boolean; | |
removeSketch: () => void; | |
} | |
} | |
interface SketchArgs { | |
sketch: string; | |
} | |
function P5SketchComponent() { | |
const containerRef = useRef<HTMLDivElement>(null); | |
const { client, setConfig } = useLiveAPIContext(); | |
useEffect(() => { | |
if (containerRef.current) { | |
window.initSketch(containerRef.current); | |
} | |
// Cleanup on unmount | |
return () => { | |
window.removeSketch(); | |
}; | |
}, []); | |
useEffect(() => { | |
setConfig({ | |
model: "models/gemini-2.0-flash-exp", | |
generationConfig: { | |
temperature: 0.1, | |
responseModalities: "audio", | |
speechConfig: { | |
voiceConfig: { | |
prebuiltVoiceConfig: { | |
voiceName: "Puck" | |
} | |
} | |
} | |
}, | |
systemInstruction: { | |
parts: [ | |
{ | |
text: `You are a P5.js creative coding expert that helps users create interactive sketches. | |
When a user requests a sketch: | |
1. Always use the updateSketch function to create or modify sketches | |
2. NEVER output code directly in the response - only use the function | |
3. After the sketch is created, explain what the sketch does and how to interact with it | |
4. If the user's request is unclear, just take your best guess to create a sketch | |
You can create sketches using: | |
- Basic shapes, colors, and animations | |
- Mouse and keyboard interaction (mouseX, mouseY, keyPressed, keyCode, etc.) | |
- Sound effects (p5.sound library) | |
- Sprite-based games (p5.play library) | |
- Full window canvas with automatic resizing | |
Focus on creating visually engaging and interactive experiences. As soon as the user requests a sketch, confirm you heard them, THEN you should create the sketch and then explain what it does and how to interact with it. Be EXTREMELY brief and pithy.` | |
}, | |
], | |
}, | |
tools: [ | |
{ | |
functionDeclarations: [ | |
{ | |
name: "updateSketch", | |
description: "Create or update the P5.js sketch with new code. The sketch will run in a full-window canvas.", | |
parameters: { | |
type: SchemaType.OBJECT, | |
properties: { | |
sketch: { | |
type: SchemaType.STRING, | |
description: "Complete P5.js sketch code including all variable declarations, setup(), draw(), and any additional functions needed. The code should be a complete, self-contained sketch." | |
} | |
}, | |
required: ["sketch"] | |
} | |
} | |
], | |
}, | |
], | |
}); | |
}, [setConfig]); | |
useEffect(() => { | |
const onToolCall = async (toolCall: ToolCall) => { | |
console.log("Received tool call:", toolCall); | |
for (const fc of toolCall.functionCalls) { | |
if (fc.name === "updateSketch" && containerRef.current) { | |
const args = fc.args as SketchArgs; | |
const result = window.updateSketch(args.sketch, containerRef.current); | |
// Send the function response back to Gemini | |
await client.sendToolResponse({ | |
functionResponses: [ | |
{ | |
response: { | |
success: result, | |
message: result ? "Sketch updated successfully" : "Failed to update sketch" | |
}, | |
id: fc.id, | |
}, | |
], | |
}); | |
} else { | |
console.error("Unhandled function call:", fc.name); | |
} | |
} | |
}; | |
client.on("toolcall", onToolCall); | |
return () => { | |
client.off("toolcall", onToolCall); | |
}; | |
}, [client]); | |
return ( | |
<div | |
ref={containerRef} | |
style={{ | |
width: "100%", | |
height: "100%", | |
alignItems: "center", | |
justifyContent: "center", | |
display: "flex", | |
position: "relative", | |
background: "#000" | |
}} | |
/> | |
); | |
} | |
export const P5Sketch = memo(P5SketchComponent); | |