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)
|
// 5. Create Steps & Actions (The Interactive Storyteller Protocol)
|
||||||
console.log("🎬 Creating experiment steps (Interactive Storyteller)...");
|
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 ---
|
// --- Step 1: The Hook ---
|
||||||
const [step1] = await db
|
const [step1] = await db
|
||||||
.insert(schema.steps)
|
.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 ---
|
// --- Step 4a: Correct Response Branch ---
|
||||||
const [step4a] = await db
|
const [step4a] = await db
|
||||||
.insert(schema.steps)
|
.insert(schema.steps)
|
||||||
@@ -423,6 +420,38 @@ async function main() {
|
|||||||
})
|
})
|
||||||
.returning();
|
.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([
|
await db.insert(schema.actions).values([
|
||||||
{
|
{
|
||||||
stepId: step3!.id,
|
stepId: step3!.id,
|
||||||
@@ -440,12 +469,11 @@ async function main() {
|
|||||||
name: "Wait for Choice",
|
name: "Wait for Choice",
|
||||||
type: "wizard_wait_for_response",
|
type: "wizard_wait_for_response",
|
||||||
orderIndex: 1,
|
orderIndex: 1,
|
||||||
// Define the options that will be presented to the Wizard
|
|
||||||
parameters: {
|
parameters: {
|
||||||
prompt_text: "Did participant answer 'Red' correctly?",
|
prompt_text: "Did participant answer 'Red' correctly?",
|
||||||
options: [
|
options: [
|
||||||
{ label: "Correct", value: "Correct", nextStepId: step5!.id },
|
{ label: "Correct", value: "Correct", nextStepId: step4a!.id },
|
||||||
{ label: "Incorrect", value: "Incorrect", nextStepId: step5!.id },
|
{ label: "Incorrect", value: "Incorrect", nextStepId: step4b!.id },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
sourceKind: "core",
|
sourceKind: "core",
|
||||||
@@ -551,20 +579,7 @@ async function main() {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// --- Step 5: Story Continues (Convergence point for both branches) ---
|
// --- Step 5 actions: Story Continues ---
|
||||||
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();
|
|
||||||
|
|
||||||
await db.insert(schema.actions).values([
|
await db.insert(schema.actions).values([
|
||||||
{
|
{
|
||||||
stepId: step5!.id,
|
stepId: step5!.id,
|
||||||
@@ -584,37 +599,19 @@ async function main() {
|
|||||||
{
|
{
|
||||||
stepId: step5!.id,
|
stepId: step5!.id,
|
||||||
name: "Wave Goodbye",
|
name: "Wave Goodbye",
|
||||||
type: "nao6-ros2.move_arm",
|
type: "nao6-ros2.wave_goodbye",
|
||||||
orderIndex: 1,
|
orderIndex: 1,
|
||||||
parameters: {
|
parameters: {
|
||||||
arm: "right",
|
text: "See you later!",
|
||||||
shoulder_pitch: 0.5,
|
|
||||||
shoulder_roll: 0.3,
|
|
||||||
elbow_yaw: -0.5,
|
|
||||||
elbow_roll: 0.8,
|
|
||||||
speed: 0.4,
|
|
||||||
},
|
},
|
||||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||||
pluginVersion: "2.2.0",
|
pluginVersion: "2.2.0",
|
||||||
category: "movement",
|
category: "interaction",
|
||||||
retryable: true,
|
retryable: true,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// --- Step 6: Conclusion ---
|
// --- Step 6 actions: 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();
|
|
||||||
|
|
||||||
await db.insert(schema.actions).values([
|
await db.insert(schema.actions).values([
|
||||||
{
|
{
|
||||||
stepId: step6!.id,
|
stepId: step6!.id,
|
||||||
@@ -894,7 +891,7 @@ async function main() {
|
|||||||
// 7. Pre-create a pending trial for immediate testing
|
// 7. Pre-create a pending trial for immediate testing
|
||||||
console.log("🧪 Creating a pre-seeded pending trial for testing...");
|
console.log("🧪 Creating a pre-seeded pending trial for testing...");
|
||||||
const p001 = insertedParticipants.find((p) => p.participantCode === "P101");
|
const p001 = insertedParticipants.find((p) => p.participantCode === "P101");
|
||||||
|
|
||||||
const [pendingTrial] = await db
|
const [pendingTrial] = await db
|
||||||
.insert(schema.trials)
|
.insert(schema.trials)
|
||||||
.values({
|
.values({
|
||||||
@@ -904,7 +901,7 @@ async function main() {
|
|||||||
scheduledAt: new Date(),
|
scheduledAt: new Date(),
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
console.log(` Created pending trial: ${pendingTrial?.id}`);
|
console.log(` Created pending trial: ${pendingTrial?.id}`);
|
||||||
|
|
||||||
console.log("\n✅ Database seeded successfully!");
|
console.log("\n✅ Database seeded successfully!");
|
||||||
|
|||||||
@@ -493,35 +493,43 @@ export class WizardRosService extends EventEmitter {
|
|||||||
|
|
||||||
case "move_head":
|
case "move_head":
|
||||||
case "turn_head":
|
case "turn_head":
|
||||||
this.publish("/joint_angles", "naoqi_bridge_msgs/JointAnglesWithSpeed", {
|
this.publish(
|
||||||
joint_names: ["HeadYaw", "HeadPitch"],
|
"/joint_angles",
|
||||||
joint_angles: [
|
"naoqi_bridge_msgs/JointAnglesWithSpeed",
|
||||||
Number(parameters.yaw) || 0,
|
{
|
||||||
Number(parameters.pitch) || 0,
|
joint_names: ["HeadYaw", "HeadPitch"],
|
||||||
],
|
joint_angles: [
|
||||||
speed: Number(parameters.speed) || 0.3,
|
Number(parameters.yaw) || 0,
|
||||||
});
|
Number(parameters.pitch) || 0,
|
||||||
|
],
|
||||||
|
speed: Number(parameters.speed) || 0.3,
|
||||||
|
},
|
||||||
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "move_arm":
|
case "move_arm":
|
||||||
const arm = String(parameters.arm || "right");
|
const arm = String(parameters.arm || "right");
|
||||||
const prefix = arm.toLowerCase() === "left" ? "L" : "R";
|
const prefix = arm.toLowerCase() === "left" ? "L" : "R";
|
||||||
this.publish("/joint_angles", "naoqi_bridge_msgs/JointAnglesWithSpeed", {
|
this.publish(
|
||||||
joint_names: [
|
"/joint_angles",
|
||||||
`${prefix}ShoulderPitch`,
|
"naoqi_bridge_msgs/JointAnglesWithSpeed",
|
||||||
`${prefix}ShoulderRoll`,
|
{
|
||||||
`${prefix}ElbowYaw`,
|
joint_names: [
|
||||||
`${prefix}ElbowRoll`,
|
`${prefix}ShoulderPitch`,
|
||||||
],
|
`${prefix}ShoulderRoll`,
|
||||||
joint_angles: [
|
`${prefix}ElbowYaw`,
|
||||||
Number(parameters.shoulder_pitch) || 0,
|
`${prefix}ElbowRoll`,
|
||||||
Number(parameters.shoulder_roll) || 0,
|
],
|
||||||
Number(parameters.elbow_yaw) || 0,
|
joint_angles: [
|
||||||
Number(parameters.elbow_roll) || 0,
|
Number(parameters.shoulder_pitch) || 0,
|
||||||
],
|
Number(parameters.shoulder_roll) || 0,
|
||||||
speed: Number(parameters.speed) || 0.3,
|
Number(parameters.elbow_yaw) || 0,
|
||||||
});
|
Number(parameters.elbow_roll) || 0,
|
||||||
|
],
|
||||||
|
speed: Number(parameters.speed) || 0.3,
|
||||||
|
},
|
||||||
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -533,7 +541,9 @@ export class WizardRosService extends EventEmitter {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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,
|
speed: Number(parameters.speed) || 0.2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case "transformToEmotionalSpeech":
|
||||||
|
return this.transformToEmotionalSpeech(parameters);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn(`Unknown transform function: ${transformFn}`);
|
console.warn(`Unknown transform function: ${transformFn}`);
|
||||||
return parameters;
|
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
|
* Schedule reconnection attempt
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user