mirror of
https://github.com/soconnor0919/hristudio.git
synced 2026-03-23 19:27:51 -04:00
Fix say_with_emotion with proper NAOqi markup, add transform functions, update seed script for linear branching
This commit is contained in:
Submodule robot-plugins updated: 9e0921c69c...14137ba631
@@ -262,6 +262,35 @@ async function main() {
|
||||
// 5. Create Steps & Actions (The Interactive Storyteller Protocol)
|
||||
console.log("🎬 Creating experiment steps (Interactive Storyteller)...");
|
||||
|
||||
// Pre-create steps that will be referenced before they're defined
|
||||
// --- Step 5: Story Continues (Convergence point for both branches) ---
|
||||
const [step5] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment!.id,
|
||||
name: "Story Continues",
|
||||
description: "Both branches converge here",
|
||||
type: "robot",
|
||||
orderIndex: 5,
|
||||
required: true,
|
||||
durationEstimate: 15,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// --- Step 6: Conclusion ---
|
||||
const [step6] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment!.id,
|
||||
name: "Conclusion",
|
||||
description: "End the story and thank participant",
|
||||
type: "robot",
|
||||
orderIndex: 6,
|
||||
required: true,
|
||||
durationEstimate: 25,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// --- Step 1: The Hook ---
|
||||
const [step1] = await db
|
||||
.insert(schema.steps)
|
||||
@@ -363,38 +392,6 @@ async function main() {
|
||||
},
|
||||
]);
|
||||
|
||||
// --- Step 3: Comprehension Check (Wizard Decision Point) ---
|
||||
const [step3] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment!.id,
|
||||
name: "Comprehension Check",
|
||||
description:
|
||||
"Ask participant about rock color and wait for wizard input",
|
||||
type: "conditional",
|
||||
orderIndex: 2,
|
||||
required: true,
|
||||
durationEstimate: 30,
|
||||
conditions: {
|
||||
variable: "last_wizard_response",
|
||||
options: [
|
||||
{
|
||||
label: "Correct Response (Red)",
|
||||
value: "Correct",
|
||||
nextStepId: step5!.id,
|
||||
variant: "default",
|
||||
},
|
||||
{
|
||||
label: "Incorrect Response",
|
||||
value: "Incorrect",
|
||||
nextStepId: step5!.id,
|
||||
variant: "destructive",
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.returning();
|
||||
|
||||
// --- Step 4a: Correct Response Branch ---
|
||||
const [step4a] = await db
|
||||
.insert(schema.steps)
|
||||
@@ -423,6 +420,38 @@ async function main() {
|
||||
})
|
||||
.returning();
|
||||
|
||||
// --- Step 3: Comprehension Check (Wizard Decision Point) ---
|
||||
const [step3] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment!.id,
|
||||
name: "Comprehension Check",
|
||||
description:
|
||||
"Ask participant about rock color and wait for wizard input",
|
||||
type: "conditional",
|
||||
orderIndex: 2,
|
||||
required: true,
|
||||
durationEstimate: 30,
|
||||
conditions: {
|
||||
variable: "last_wizard_response",
|
||||
options: [
|
||||
{
|
||||
label: "Correct Response (Red)",
|
||||
value: "Correct",
|
||||
nextStepId: step4a!.id,
|
||||
variant: "default",
|
||||
},
|
||||
{
|
||||
label: "Incorrect Response",
|
||||
value: "Incorrect",
|
||||
nextStepId: step4b!.id,
|
||||
variant: "destructive",
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.returning();
|
||||
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step3!.id,
|
||||
@@ -440,12 +469,11 @@ async function main() {
|
||||
name: "Wait for Choice",
|
||||
type: "wizard_wait_for_response",
|
||||
orderIndex: 1,
|
||||
// Define the options that will be presented to the Wizard
|
||||
parameters: {
|
||||
prompt_text: "Did participant answer 'Red' correctly?",
|
||||
options: [
|
||||
{ label: "Correct", value: "Correct", nextStepId: step5!.id },
|
||||
{ label: "Incorrect", value: "Incorrect", nextStepId: step5!.id },
|
||||
{ label: "Correct", value: "Correct", nextStepId: step4a!.id },
|
||||
{ label: "Incorrect", value: "Incorrect", nextStepId: step4b!.id },
|
||||
],
|
||||
},
|
||||
sourceKind: "core",
|
||||
@@ -551,20 +579,7 @@ async function main() {
|
||||
},
|
||||
]);
|
||||
|
||||
// --- Step 5: Story Continues (Convergence point for both branches) ---
|
||||
const [step5] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment!.id,
|
||||
name: "Story Continues",
|
||||
description: "Both branches converge here",
|
||||
type: "robot",
|
||||
orderIndex: 5,
|
||||
required: true,
|
||||
durationEstimate: 15,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// --- Step 5 actions: Story Continues ---
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step5!.id,
|
||||
@@ -584,37 +599,19 @@ async function main() {
|
||||
{
|
||||
stepId: step5!.id,
|
||||
name: "Wave Goodbye",
|
||||
type: "nao6-ros2.move_arm",
|
||||
type: "nao6-ros2.wave_goodbye",
|
||||
orderIndex: 1,
|
||||
parameters: {
|
||||
arm: "right",
|
||||
shoulder_pitch: 0.5,
|
||||
shoulder_roll: 0.3,
|
||||
elbow_yaw: -0.5,
|
||||
elbow_roll: 0.8,
|
||||
speed: 0.4,
|
||||
text: "See you later!",
|
||||
},
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
category: "interaction",
|
||||
retryable: true,
|
||||
},
|
||||
]);
|
||||
|
||||
// --- Step 6: Conclusion ---
|
||||
const [step6] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment!.id,
|
||||
name: "Conclusion",
|
||||
description: "End the story and thank participant",
|
||||
type: "robot",
|
||||
orderIndex: 6,
|
||||
required: true,
|
||||
durationEstimate: 25,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// --- Step 6 actions: Conclusion ---
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step6!.id,
|
||||
|
||||
@@ -493,21 +493,28 @@ export class WizardRosService extends EventEmitter {
|
||||
|
||||
case "move_head":
|
||||
case "turn_head":
|
||||
this.publish("/joint_angles", "naoqi_bridge_msgs/JointAnglesWithSpeed", {
|
||||
this.publish(
|
||||
"/joint_angles",
|
||||
"naoqi_bridge_msgs/JointAnglesWithSpeed",
|
||||
{
|
||||
joint_names: ["HeadYaw", "HeadPitch"],
|
||||
joint_angles: [
|
||||
Number(parameters.yaw) || 0,
|
||||
Number(parameters.pitch) || 0,
|
||||
],
|
||||
speed: Number(parameters.speed) || 0.3,
|
||||
});
|
||||
},
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
break;
|
||||
|
||||
case "move_arm":
|
||||
const arm = String(parameters.arm || "right");
|
||||
const prefix = arm.toLowerCase() === "left" ? "L" : "R";
|
||||
this.publish("/joint_angles", "naoqi_bridge_msgs/JointAnglesWithSpeed", {
|
||||
this.publish(
|
||||
"/joint_angles",
|
||||
"naoqi_bridge_msgs/JointAnglesWithSpeed",
|
||||
{
|
||||
joint_names: [
|
||||
`${prefix}ShoulderPitch`,
|
||||
`${prefix}ShoulderRoll`,
|
||||
@@ -521,7 +528,8 @@ export class WizardRosService extends EventEmitter {
|
||||
Number(parameters.elbow_roll) || 0,
|
||||
],
|
||||
speed: Number(parameters.speed) || 0.3,
|
||||
});
|
||||
},
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
break;
|
||||
|
||||
@@ -533,7 +541,9 @@ export class WizardRosService extends EventEmitter {
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown action: ${actionId}. Define this action in your robot plugin.`);
|
||||
throw new Error(
|
||||
`Unknown action: ${actionId}. Define this action in your robot plugin.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,12 +751,74 @@ export class WizardRosService extends EventEmitter {
|
||||
speed: Number(parameters.speed) || 0.2,
|
||||
};
|
||||
|
||||
case "transformToEmotionalSpeech":
|
||||
return this.transformToEmotionalSpeech(parameters);
|
||||
|
||||
default:
|
||||
console.warn(`Unknown transform function: ${transformFn}`);
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform parameters for emotional speech
|
||||
* NAOqi markup: \rspd=<speed>\<text>
|
||||
* For animated speech: ^start(animations/Stand/Gestures/...)
|
||||
*/
|
||||
private transformToEmotionalSpeech(parameters: Record<string, unknown>): {
|
||||
data: string;
|
||||
} {
|
||||
const text = String(parameters.text || "Hello");
|
||||
const emotion = String(parameters.emotion || "neutral");
|
||||
const speed = Number(parameters.speed || 1.0);
|
||||
const speedPercent = Math.round(speed * 100);
|
||||
|
||||
let markedText = text;
|
||||
|
||||
switch (emotion) {
|
||||
case "happy":
|
||||
markedText = `\\rspd=120\\^start(animations/Stand/Gestures/Happy_4) ${text}`;
|
||||
break;
|
||||
case "excited":
|
||||
markedText = `\\rspd=140\\^start(animations/Stand/Gestures/Enthusiastic_1) ${text}`;
|
||||
break;
|
||||
case "sad":
|
||||
markedText = `\\rspd=80\\vct=80\\${text}`;
|
||||
break;
|
||||
case "calm":
|
||||
markedText = `\\rspd=90\\${text}`;
|
||||
break;
|
||||
case "neutral":
|
||||
default:
|
||||
markedText = `\\rspd=${speedPercent}\\${text}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return { data: markedText };
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform for wave goodbye - animated speech with waving
|
||||
*/
|
||||
private transformToWaveGoodbye(parameters: Record<string, unknown>): {
|
||||
data: string;
|
||||
} {
|
||||
const text = String(parameters.text || "Goodbye!");
|
||||
const markedText = `\\rspd=110\\^start(animations/Stand/Gestures/Hey_1) ${text} ^start(animations/Stand/Gestures/Hey_1)`;
|
||||
return { data: markedText };
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform for playing animations
|
||||
*/
|
||||
private transformToAnimation(parameters: Record<string, unknown>): {
|
||||
data: string;
|
||||
} {
|
||||
const animation = String(parameters.animation || "Hey_1");
|
||||
const markedText = `^start(animations/Stand/Gestures/${animation})`;
|
||||
return { data: markedText };
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule reconnection attempt
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user