feat: Implement collapsible left and right panels with dynamic column spanning, updated styling, and integrated a bottom status bar in the DesignerRoot.

This commit is contained in:
2026-02-03 13:58:47 -05:00
parent 0ec63b3c97
commit 388897c70e
17 changed files with 1147 additions and 719 deletions

65
scripts/check-db.ts Normal file
View File

@@ -0,0 +1,65 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "../src/server/db/schema";
import { eq } from "drizzle-orm";
const connectionString = process.env.DATABASE_URL!;
const connection = postgres(connectionString);
const db = drizzle(connection, { schema });
async function main() {
console.log("🔍 Checking Database State...");
// 1. Check Plugin
const plugins = await db.query.plugins.findMany();
console.log(`\nFound ${plugins.length} plugins.`);
const expectedKeys = new Set<string>();
for (const p of plugins) {
const meta = p.metadata as any;
const defs = p.actionDefinitions as any[];
console.log(`Plugin [${p.name}] (ID: ${p.id}):`);
console.log(` - Robot ID (Column): ${p.robotId}`);
console.log(` - Metadata.robotId: ${meta?.robotId}`);
console.log(` - Action Definitions: ${defs?.length ?? 0} found.`);
if (defs && meta?.robotId) {
defs.forEach(d => {
const key = `${meta.robotId}.${d.id}`;
expectedKeys.add(key);
// console.log(` -> Registers: ${key}`);
});
}
}
// 2. Check Actions
const actions = await db.query.actions.findMany();
console.log(`\nFound ${actions.length} actions.`);
let errorCount = 0;
for (const a of actions) {
// Only check plugin actions
if (a.sourceKind === 'plugin' || a.type.includes(".")) {
const isRegistered = expectedKeys.has(a.type);
const pluginIdMatch = a.pluginId === 'nao6-ros2';
console.log(`Action [${a.name}] (Type: ${a.type}):`);
console.log(` - PluginId: ${a.pluginId} ${pluginIdMatch ? '✅' : '❌'}`);
console.log(` - In Registry: ${isRegistered ? '✅' : '❌'}`);
if (!isRegistered || !pluginIdMatch) errorCount++;
}
}
if (errorCount > 0) {
console.log(`\n❌ Found ${errorCount} actions with issues.`);
} else {
console.log("\n✅ All plugin actions validated successfully against registry definitions.");
}
await connection.end();
}
main().catch(console.error);

View File

@@ -159,6 +159,7 @@ async function main() {
status: "ready",
robotId: naoRobot!.id,
createdBy: adminUser.id,
// visualDesign will be auto-generated by designer from DB steps
}).returning();
// 5. Create Steps & Actions (The Interactive Storyteller Protocol)
@@ -168,98 +169,116 @@ async function main() {
const [step1] = await db.insert(schema.steps).values({
experimentId: experiment!.id,
name: "The Hook",
description: "Initial greeting and engagement",
description: "Initial greeting and story introduction",
type: "robot",
orderIndex: 0,
required: true,
durationEstimate: 30
durationEstimate: 25
}).returning();
await db.insert(schema.actions).values([
{
stepId: step1!.id,
name: "Greet Participant",
type: "nao6-ros2.say_with_emotion",
name: "Introduce Story",
type: "nao6-ros2.say_text",
orderIndex: 0,
parameters: { text: "Hello there! I have a wonderful story to share with you today.", emotion: "happy", speed: 1.0 },
pluginId: naoPlugin!.id,
parameters: { text: "Hello. I have a story to tell you about a space traveler. Are you ready?" },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "interaction",
retryable: true
},
{
stepId: step1!.id,
name: "Wave Greeting",
name: "Welcome Gesture",
type: "nao6-ros2.move_arm",
orderIndex: 1,
// Raising right arm to wave position
// Open hand/welcome position
parameters: {
arm: "right",
shoulder_pitch: -1.0,
shoulder_roll: -0.3,
elbow_yaw: 1.5,
elbow_roll: 0.5,
speed: 0.5
shoulder_pitch: 1.0,
shoulder_roll: -0.2,
elbow_yaw: 0.5,
elbow_roll: -0.4,
speed: 0.4
},
pluginId: naoPlugin!.id,
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "movement",
retryable: true
}
]);
// --- Step 2: The Narrative (Part 1) ---
// --- Step 2: The Narrative ---
const [step2] = await db.insert(schema.steps).values({
experimentId: experiment!.id,
name: "The Narrative - Part 1",
description: "Robot tells the first part of the story",
name: "The Narrative",
description: "Robot tells the space traveler story with gaze behavior",
type: "robot",
orderIndex: 1,
required: true,
durationEstimate: 60
}).returning();
await db.insert(schema.actions).values([
{
stepId: step2!.id,
name: "Tell Story Part 1",
type: "nao6-ros2.say_text",
orderIndex: 0,
parameters: { text: "Once upon a time, in a land far away, there lived a curious robot named Alpha." },
pluginId: naoPlugin!.id,
category: "interaction"
},
{
stepId: step2!.id,
name: "Look at Audience",
type: "nao6-ros2.move_head",
orderIndex: 1,
parameters: { yaw: 0.0, pitch: -0.2, speed: 0.5 },
pluginId: naoPlugin!.id,
category: "movement"
}
]);
// --- Step 3: Comprehension Check (Wizard Decision) ---
// Note: In a real visual designer, this would be a Branch/Conditional.
// Here we model it as a Wizard Step where they explicitly choose the next robot action.
const [step3] = await db.insert(schema.steps).values({
experimentId: experiment!.id,
name: "Comprehension Check",
description: "Wizard verifies participant understanding",
type: "wizard",
orderIndex: 2,
required: true,
durationEstimate: 45
}).returning();
await db.insert(schema.actions).values([
{
stepId: step2!.id,
name: "Tell Story",
type: "nao6-ros2.say_text",
orderIndex: 0,
parameters: { text: "The traveler flew to Mars. He found a red rock that glowed in the dark. He put it in his pocket." },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "interaction",
retryable: true
},
{
stepId: step2!.id,
name: "Look Away (Thinking)",
type: "nao6-ros2.turn_head",
orderIndex: 1,
parameters: { yaw: 1.5, pitch: 0.0, speed: 0.3 },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "movement",
retryable: true
},
{
stepId: step2!.id,
name: "Look Back at Participant",
type: "nao6-ros2.turn_head",
orderIndex: 2,
parameters: { yaw: 0.0, pitch: -0.1, speed: 0.4 },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "movement",
retryable: true
}
]);
// --- Step 3: Comprehension Check (Wizard Decision Point) ---
// Note: Wizard will choose to proceed to Step 4a (Correct) or 4b (Incorrect)
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: "wizard",
orderIndex: 2,
required: true,
durationEstimate: 30
}).returning();
await db.insert(schema.actions).values([
{
stepId: step3!.id,
name: "Ask Question",
type: "nao6-ros2.say_with_emotion",
type: "nao6-ros2.say_text",
orderIndex: 0,
parameters: { text: "Did you understand the story so far?", emotion: "happy", speed: 1.0 },
pluginId: naoPlugin!.id,
category: "interaction"
parameters: { text: "What color was the rock the traveler found?" },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "interaction",
retryable: true
},
{
stepId: step3!.id,
@@ -267,7 +286,7 @@ async function main() {
type: "wizard_wait_for_response",
orderIndex: 1,
parameters: {
prompt_text: "Did participant answer 'Alpha'?",
prompt_text: "Did participant answer 'Red' correctly?",
response_type: "verbal",
timeout: 60
},
@@ -276,36 +295,108 @@ async function main() {
}
]);
// --- Step 4: Feedback (Positive/Negative branches implied) ---
// For linear seed, we just add the Positive feedback step
const [step4] = await db.insert(schema.steps).values({
// --- Step 4a: Correct Response Branch ---
const [step4a] = await db.insert(schema.steps).values({
experimentId: experiment!.id,
name: "Positive Feedback",
description: "Correct answer response",
name: "Branch A: Correct Response",
description: "Response when participant says 'Red'",
type: "robot",
orderIndex: 3,
required: true,
durationEstimate: 15
required: false,
durationEstimate: 20
}).returning();
await db.insert(schema.actions).values([
{
stepId: step4!.id,
name: "Express Agreement",
stepId: step4a!.id,
name: "Confirm Correct Answer",
type: "nao6-ros2.say_with_emotion",
orderIndex: 0,
parameters: { text: "Yes, exactly!", emotion: "happy", speed: 1.0 },
pluginId: naoPlugin!.id,
category: "interaction"
parameters: { text: "Yes! It was a glowing red rock.", emotion: "happy", speed: 1.0 },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "interaction",
retryable: true
},
{
stepId: step4!.id,
name: "Say Correct",
type: "nao6-ros2.say_text",
stepId: step4a!.id,
name: "Nod Head",
type: "nao6-ros2.turn_head",
orderIndex: 1,
parameters: { text: "That is correct! Well done." },
pluginId: naoPlugin!.id,
category: "interaction"
parameters: { yaw: 0.0, pitch: -0.3, speed: 0.5 },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "movement",
retryable: true
},
{
stepId: step4a!.id,
name: "Return to Neutral",
type: "nao6-ros2.turn_head",
orderIndex: 2,
parameters: { yaw: 0.0, pitch: 0.0, speed: 0.4 },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "movement",
retryable: true
}
]);
// --- Step 4b: Incorrect Response Branch ---
const [step4b] = await db.insert(schema.steps).values({
experimentId: experiment!.id,
name: "Branch B: Incorrect Response",
description: "Response when participant gives wrong answer",
type: "robot",
orderIndex: 4,
required: false,
durationEstimate: 20
}).returning();
await db.insert(schema.actions).values([
{
stepId: step4b!.id,
name: "Correct Participant",
type: "nao6-ros2.say_text",
orderIndex: 0,
parameters: { text: "Actually, it was red." },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "interaction",
retryable: true
},
{
stepId: step4b!.id,
name: "Shake Head (Left)",
type: "nao6-ros2.turn_head",
orderIndex: 1,
parameters: { yaw: -0.5, pitch: 0.0, speed: 0.5 },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "movement",
retryable: true
},
{
stepId: step4b!.id,
name: "Shake Head (Right)",
type: "nao6-ros2.turn_head",
orderIndex: 2,
parameters: { yaw: 0.5, pitch: 0.0, speed: 0.5 },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "movement",
retryable: true
},
{
stepId: step4b!.id,
name: "Return to Center",
type: "nao6-ros2.turn_head",
orderIndex: 3,
parameters: { yaw: 0.0, pitch: 0.0, speed: 0.4 },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "movement",
retryable: true
}
]);
@@ -313,31 +404,42 @@ async function main() {
const [step5] = await db.insert(schema.steps).values({
experimentId: experiment!.id,
name: "Conclusion",
description: "Wrap up the story",
description: "End the story and thank participant",
type: "robot",
orderIndex: 4,
orderIndex: 5,
required: true,
durationEstimate: 30
durationEstimate: 25
}).returning();
await db.insert(schema.actions).values([
{
stepId: step5!.id,
name: "Finish Story",
name: "End Story",
type: "nao6-ros2.say_text",
orderIndex: 0,
parameters: { text: "Alpha explored the world and learned many things. The end." },
pluginId: naoPlugin!.id,
category: "interaction"
parameters: { text: "The End. Thank you for listening." },
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "interaction",
retryable: true
},
{
stepId: step5!.id,
name: "Say Goodbye",
type: "nao6-ros2.say_with_emotion",
name: "Bow Gesture",
type: "nao6-ros2.move_arm",
orderIndex: 1,
parameters: { text: "Goodbye everyone!", emotion: "happy", speed: 1.0 },
pluginId: naoPlugin!.id,
category: "interaction"
parameters: {
arm: "right",
shoulder_pitch: 1.8,
shoulder_roll: 0.1,
elbow_yaw: 0.0,
elbow_roll: -0.3,
speed: 0.3
},
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
pluginVersion: "2.1.0",
category: "movement",
retryable: true
}
]);
@@ -360,7 +462,13 @@ async function main() {
console.log(`Summary:`);
console.log(`- 1 Admin User (sean@soconnor.dev)`);
console.log(`- Study: 'Comparative WoZ Study'`);
console.log(`- Experiment: 'The Interactive Storyteller' (${5} steps created)`);
console.log(`- Experiment: 'The Interactive Storyteller' (6 steps created)`);
console.log(` - Step 1: The Hook (greeting + welcome gesture)`);
console.log(` - Step 2: The Narrative (story + gaze sequence)`);
console.log(` - Step 3: Comprehension Check (question + wizard wait)`);
console.log(` - Step 4a: Branch A - Correct Response (affirmation + nod)`);
console.log(` - Step 4b: Branch B - Incorrect Response (correction + head shake)`);
console.log(` - Step 5: Conclusion (ending + bow)`);
console.log(`- ${insertedParticipants.length} Participants`);
} catch (error) {