diff --git a/scripts/archive/seed-nao6-plugin.ts b/scripts/archive/seed-nao6-plugin.ts index 6a2782c..94687eb 100644 --- a/scripts/archive/seed-nao6-plugin.ts +++ b/scripts/archive/seed-nao6-plugin.ts @@ -564,6 +564,7 @@ async function seedNAO6Plugin() { const pluginData: InsertPlugin = { robotId: robotId, + identifier: "nao6-ros2", name: "NAO6 Robot (Enhanced ROS2 Integration)", version: "2.0.0", description: diff --git a/src/components/trials/wizard/panels/WizardActionItem.tsx b/src/components/trials/wizard/panels/WizardActionItem.tsx index e54c035..8fe9526 100644 --- a/src/components/trials/wizard/panels/WizardActionItem.tsx +++ b/src/components/trials/wizard/panels/WizardActionItem.tsx @@ -536,7 +536,8 @@ export function WizardActionItem({ onClick={(e) => { e.preventDefault(); onExecute(action.id, { value, label, nextStepId }); - onCompleted(); + // Don't call onCompleted() here - the branching logic in handleWizardResponse + // will handle the jump and reset completedActionsCount }} disabled={readOnly || isExecuting} > diff --git a/src/lib/ros/wizard-ros-service.ts b/src/lib/ros/wizard-ros-service.ts index c691457..347d6c0 100644 --- a/src/lib/ros/wizard-ros-service.ts +++ b/src/lib/ros/wizard-ros-service.ts @@ -438,8 +438,25 @@ export class WizardRosService extends EventEmitter { this.publish(config.topic, config.messageType, msg); - // Wait for action completion (simple delay for now) - await new Promise((resolve) => setTimeout(resolve, 100)); + // Wait for action completion based on topic type + if (config.topic === "/speech") { + // Estimate speech duration based on text content + const text = + typeof msg === "object" && msg !== null && "data" in msg + ? String((msg as any).data || "") + : JSON.stringify(msg); + const wordCount = text.split(/\s+/).filter(Boolean).length; + // Emotion markup adds overhead: ~200ms per word base + emotion animation time + const emotionOverhead = 1500; // Animation prep time + const duration = emotionOverhead + Math.max(1000, wordCount * 300); + console.log( + `[WizardROS] Speech action estimated duration: ${duration}ms (${wordCount} words)`, + ); + await new Promise((resolve) => setTimeout(resolve, duration)); + } else { + // Short delay for non-speech actions + await new Promise((resolve) => setTimeout(resolve, 500)); + } } /** @@ -452,13 +469,25 @@ export class WizardRosService extends EventEmitter { ): Promise { switch (actionId) { case "say_text": + case "say_with_emotion": const text = String(parameters.text || "Hello"); this.publish("/speech", "std_msgs/String", { data: text }); - const wordCount = text.split(/\s+/).length; - const duration = Math.max(800, wordCount * 250 + 500); + const wordCount = text.split(/\s+/).filter(Boolean).length; + const emotion = String(parameters.emotion || "neutral"); + const emotionOverhead = 1500; + const duration = emotionOverhead + Math.max(1000, wordCount * 300); + console.log( + `[WizardROS] Speech action (${actionId}) estimated: ${duration}ms`, + ); await new Promise((resolve) => setTimeout(resolve, duration)); break; + case "wave_goodbye": + const waveText = String(parameters.text || "Goodbye"); + this.publish("/speech", "std_msgs/String", { data: waveText }); + await new Promise((resolve) => setTimeout(resolve, 3000)); + break; + case "walk_forward": this.publish("/cmd_vel", "geometry_msgs/Twist", { linear: { x: Number(parameters.speed) || 0.1, y: 0, z: 0 }, diff --git a/src/server/api/routers/admin.ts b/src/server/api/routers/admin.ts index c6f07f7..09f0975 100755 --- a/src/server/api/routers/admin.ts +++ b/src/server/api/routers/admin.ts @@ -927,11 +927,17 @@ export const adminRouter = createTRPCRouter({ if (existingPlugin.length === 0) { // Create new plugin + const pluginName = pluginData.name ?? "unknown"; + const slugIdentifier = pluginName + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-|-$/g, ""); const newPlugin = await db .insert(plugins) .values({ + identifier: slugIdentifier, robotId, - name: pluginData.name ?? "", + name: pluginName, version: pluginData.version ?? "", description: pluginData.description, author: