mirror of
https://github.com/soconnor0919/hristudio.git
synced 2026-05-08 13:58:55 -04:00
Compare commits
83 Commits
nao_ros2
...
14182bf078
| Author | SHA1 | Date | |
|---|---|---|---|
| 14182bf078 | |||
| 943c7bd963 | |||
| 6b54724171 | |||
| 86c1f35537 | |||
| 5b5490cb90 | |||
| 6b98cad53e | |||
| 3e2aa894a0 | |||
| 27f633fb4b | |||
| 6243b62d3b | |||
| f16dd4aa22 | |||
| 7483e4a72b | |||
| 426b5e761b | |||
| cf21a27995 | |||
| 74b5507769 | |||
| 5c67fc106e | |||
| 4b04f2c415 | |||
| c959e61f95 | |||
| de1b125b13 | |||
| 143cf2ce50 | |||
| 61c7cc1e94 | |||
| 8f330cf5f0 | |||
| 254805008e | |||
| c923c63099 | |||
| c05384d1a0 | |||
| c0e5a4ffb8 | |||
| 51aaaa5208 | |||
| e402c51483 | |||
| 7c360dc860 | |||
| 1c7f0297a6 | |||
| 3959cf23f7 | |||
| 3270e3f8fe | |||
| bfd1924897 | |||
| 0827a791c6 | |||
| ecf0ab9103 | |||
| 49e0df016a | |||
| 8529d0ef89 | |||
| 67ad904f62 | |||
| 519e6a2606 | |||
| b353ef7c9f | |||
| cbd31e9aa4 | |||
| 37feea8df3 | |||
| cf3597881b | |||
| add3380307 | |||
| 79bb298756 | |||
| a5762ec935 | |||
| 20d6d3de1a | |||
| 4bed537943 | |||
| 73f70f6550 | |||
| 3fafd61553 | |||
| 3491bf4463 | |||
| cc58593891 | |||
| bbbe397ba8 | |||
| bbc34921b5 | |||
| 8e647c958e | |||
| 4e86546311 | |||
| e84c794962 | |||
| 70064f487e | |||
| 91d03a789d | |||
| 31d2173703 | |||
| 4a9abf4ff1 | |||
| 487f97c5c2 | |||
| db147f2294 | |||
| a705c720fb | |||
| e460c1b029 | |||
| eb0d86f570 | |||
| e40c37cfd0 | |||
| f8e6fccae3 | |||
| 3f87588fea | |||
| 18e5aab4a5 | |||
| c16d0d2565 | |||
| c37acad3d2 | |||
| 0051946bde | |||
| 61af467cc8 | |||
| 60d4fae72c | |||
| 72971a4b49 | |||
| 568d408587 | |||
| 93de577939 | |||
| 85b951f742 | |||
| a8c868ad3f | |||
| 0f535f6887 | |||
| 388897c70e | |||
| 0ec63b3c97 | |||
| 89c44efcf7 |
+10
-2
@@ -16,11 +16,19 @@
|
||||
AUTH_SECRET=""
|
||||
|
||||
# Drizzle
|
||||
DATABASE_URL="postgresql://postgres:password@localhost:5433/hristudio"
|
||||
DATABASE_URL="postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@localhost:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-hristudio}"
|
||||
|
||||
# PostgreSQL (used by docker-compose)
|
||||
POSTGRES_USER="postgres"
|
||||
POSTGRES_PASSWORD="postgres"
|
||||
POSTGRES_DB="hristudio"
|
||||
POSTGRES_PORT="5432"
|
||||
|
||||
# MinIO/S3 Configuration
|
||||
MINIO_ENDPOINT="http://localhost:9000"
|
||||
MINIO_ENDPOINT="http://localhost:${MINIO_PORT_API:-9000}"
|
||||
MINIO_REGION="us-east-1"
|
||||
MINIO_ACCESS_KEY="minioadmin"
|
||||
MINIO_SECRET_KEY="minioadmin"
|
||||
MINIO_BUCKET_NAME="hristudio-data"
|
||||
MINIO_PORT_API="9000"
|
||||
MINIO_PORT_CONSOLE="9001"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
"extends": [".eslintrc.cjs"],
|
||||
"rules": {
|
||||
extends: [".eslintrc.cjs"],
|
||||
rules: {
|
||||
// Only enable the rule we want to autofix
|
||||
"@typescript-eslint/prefer-nullish-coalescing": "error"
|
||||
}
|
||||
"@typescript-eslint/prefer-nullish-coalescing": "error",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
[submodule "robot-plugins"]
|
||||
path = robot-plugins
|
||||
url = git@github.com:soconnor0919/robot-plugins.git
|
||||
branch = main
|
||||
@@ -19,12 +19,13 @@ HRIStudio addresses critical challenges in HRI research by providing a comprehen
|
||||
- **Hierarchical Structure**: Study → Experiment → Trial → Step → Action
|
||||
- **Visual Experiment Designer**: Drag-and-drop protocol creation with 26+ core blocks
|
||||
- **Plugin System**: Extensible robot platform integration (RESTful, ROS2, custom)
|
||||
- **Consolidated Wizard Interface**: 3-panel design with trial controls, horizontal timeline, and unified robot controls
|
||||
- **Real-time Trial Execution**: Live wizard control with comprehensive data capture
|
||||
- **Role-Based Access**: Administrator, Researcher, Wizard, Observer (4 distinct roles)
|
||||
- **Unified Form Experiences**: 73% code reduction through standardized patterns
|
||||
- **Enterprise DataTables**: Advanced filtering, pagination, export capabilities
|
||||
- **Real-time Trial Execution**: Professional wizard interface with live monitoring
|
||||
- **Mock Robot Integration**: Complete simulation system for development and testing
|
||||
- **Intelligent Control Flow**: Loops with implicit approval, branching logic, parallel execution
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -63,16 +64,15 @@ bun dev
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Framework**: Next.js 15 with App Router and React 19 RC
|
||||
- **Framework**: Next.js 15 (16.x compatible) with App Router and React 19
|
||||
- **Language**: TypeScript (strict mode) - 100% type safety throughout
|
||||
- **Database**: PostgreSQL with Drizzle ORM for type-safe operations
|
||||
- **Authentication**: NextAuth.js v5 with database sessions and JWT
|
||||
- **Authentication**: Better Auth with database sessions
|
||||
- **API**: tRPC for end-to-end type-safe client-server communication
|
||||
- **UI**: Tailwind CSS + shadcn/ui (built on Radix UI primitives)
|
||||
- **Storage**: Cloudflare R2 (S3-compatible) for media files
|
||||
- **Deployment**: Vercel serverless platform with Edge Runtime
|
||||
- **Real-time**: WebSocket with polling fallback for trial execution
|
||||
- **Package Manager**: Bun exclusively
|
||||
- **Real-time**: WebSocket with Edge Runtime compatibility
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -96,6 +96,9 @@ bun dev
|
||||
- Plugin Store with trust levels (Official, Verified, Community)
|
||||
|
||||
#### 3. Adaptive Wizard Interface
|
||||
- **3-Panel Design**: Trial controls (left), horizontal timeline (center), robot control & status (right)
|
||||
- **Horizontal Step Progress**: Non-scrolling step navigation with visual progress indicators
|
||||
- **Consolidated Robot Controls**: Single location for connection, autonomous life, actions, and monitoring
|
||||
- Real-time experiment execution dashboard
|
||||
- Step-by-step guidance for consistent execution
|
||||
- Quick actions for unscripted interventions
|
||||
@@ -199,14 +202,12 @@ src/
|
||||
|
||||
Comprehensive documentation available in the `docs/` folder:
|
||||
|
||||
- **[Quick Reference](docs/quick-reference.md)**: 5-minute setup guide and essential commands
|
||||
- **[Project Overview](docs/project-overview.md)**: Complete feature overview and architecture
|
||||
- **[Implementation Details](docs/implementation-details.md)**: Architecture decisions and patterns
|
||||
- **[Database Schema](docs/database-schema.md)**: Complete PostgreSQL schema documentation
|
||||
- **[API Routes](docs/api-routes.md)**: Comprehensive tRPC API reference
|
||||
- **[Core Blocks System](docs/core-blocks-system.md)**: Repository-based block architecture
|
||||
- **[Plugin System](docs/plugin-system-implementation-guide.md)**: Robot integration guide
|
||||
- **[Project Status](docs/project-status.md)**: Current completion status (98% complete)
|
||||
- **[Tutorials](docs/tutorials/README.md)**: Step-by-step guides for new users
|
||||
- **[Quick Reference](docs/quick-reference.md)**: Essential commands and setup
|
||||
- **[Implementation Guide](docs/implementation-guide.md)**: Technical implementation details
|
||||
- **[Project Status](docs/project-status.md)**: Current development state
|
||||
- **[NAO6 Integration](docs/nao6-quick-reference.md)**: Robot setup and commands
|
||||
- **[Archive](docs/_archive/)**: Historical documentation (outdated)
|
||||
|
||||
## Research Paper
|
||||
|
||||
@@ -230,19 +231,39 @@ Full paper available at: [docs/paper.md](docs/paper.md)
|
||||
- **4 User Roles**: Complete role-based access control
|
||||
- **Plugin System**: Extensible robot integration architecture
|
||||
- **Trial System**: Unified design with real-time execution capabilities
|
||||
- **WebSocket Ready**: Real-time trial updates with polling fallback
|
||||
- **Docker Integration**: NAO6 deployment via docker-compose
|
||||
- **Conditional Branching**: Experiment flow with wizard choices and convergence paths
|
||||
|
||||
## NAO6 Robot Integration
|
||||
|
||||
Complete NAO6 robot integration is available in the separate **[nao6-hristudio-integration](../nao6-hristudio-integration/)** repository.
|
||||
|
||||
### Features
|
||||
- Complete ROS2 driver integration for NAO V6.0
|
||||
- Complete ROS2 Humble driver integration for NAO V6.0
|
||||
- Docker-based deployment with three services: nao_driver, ros_bridge, ros_api
|
||||
- WebSocket communication via rosbridge
|
||||
- 9 robot actions: speech, movement, gestures, sensors, LEDs
|
||||
- 14 robot actions: speech, movement, gestures, sensors, LEDs, animations
|
||||
- Real-time control from wizard interface
|
||||
- Production-ready with NAOqi 2.8.7.4
|
||||
- Production-ready with NAOqi 2.8.7.4, ROS2 Humble
|
||||
|
||||
### Quick Start
|
||||
### Docker Deployment
|
||||
```bash
|
||||
# Start NAO integration
|
||||
cd nao6-hristudio-integration
|
||||
docker compose up -d
|
||||
|
||||
# Start HRIStudio
|
||||
cd ~/Documents/Projects/hristudio
|
||||
bun dev
|
||||
|
||||
# Access
|
||||
# - HRIStudio: http://localhost:3000
|
||||
# - Test page: http://localhost:3000/nao-test
|
||||
# - rosbridge: ws://localhost:9090
|
||||
```
|
||||
|
||||
### Quick Start Commands
|
||||
```bash
|
||||
# Start NAO integration
|
||||
cd ~/naoqi_ros2_ws
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
scripts/seed-dev.ts(762,61): error TS2769: No overload matches this call.
|
||||
Overload 1 of 2, '(value: { experimentId: string | SQL<unknown> | Placeholder<string, any>; duration?: number | SQL<unknown> | Placeholder<string, any> | null | undefined; id?: string | ... 2 more ... | undefined; ... 11 more ...; parameters?: unknown; }): PgInsertBase<...>', gave the following error.
|
||||
Object literal may only specify known properties, and 'currentStepId' does not exist in type '{ experimentId: string | SQL<unknown> | Placeholder<string, any>; duration?: number | SQL<unknown> | Placeholder<string, any> | null | undefined; id?: string | SQL<...> | Placeholder<...> | undefined; ... 11 more ...; parameters?: unknown; }'.
|
||||
Overload 2 of 2, '(values: { experimentId: string | SQL<unknown> | Placeholder<string, any>; duration?: number | SQL<unknown> | Placeholder<string, any> | null | undefined; id?: string | ... 2 more ... | undefined; ... 11 more ...; parameters?: unknown; }[]): PgInsertBase<...>', gave the following error.
|
||||
Object literal may only specify known properties, and 'experimentId' does not exist in type '{ experimentId: string | SQL<unknown> | Placeholder<string, any>; duration?: number | SQL<unknown> | Placeholder<string, any> | null | undefined; id?: string | SQL<...> | Placeholder<...> | undefined; ... 11 more ...; parameters?: unknown; }[]'.
|
||||
src/app/(dashboard)/studies/[id]/trials/[trialId]/analysis/page.tsx(99,13): error TS2322: Type '{ startedAt: Date | null; completedAt: Date | null; eventCount: any; mediaCount: any; media: { url: string; contentType: string; id: string; trialId: string; mediaType: "video" | "audio" | "image" | null; ... 8 more ...; createdAt: Date; }[]; ... 13 more ...; participant: { ...; }; }' is not assignable to type '{ id: string; status: string; startedAt: Date | null; completedAt: Date | null; duration: number | null; experiment: { name: string; studyId: string; }; participant: { participantCode: string; }; eventCount?: number | undefined; mediaCount?: number | undefined; media?: { ...; }[] | undefined; }'.
|
||||
Types of property 'media' are incompatible.
|
||||
Type '{ url: string; contentType: string; id: string; trialId: string; mediaType: "video" | "audio" | "image" | null; storagePath: string; fileSize: number | null; duration: number | null; format: string | null; ... 4 more ...; createdAt: Date; }[]' is not assignable to type '{ url: string; mediaType: string; format?: string | undefined; contentType?: string | undefined; }[]'.
|
||||
Type '{ url: string; contentType: string; id: string; trialId: string; mediaType: "video" | "audio" | "image" | null; storagePath: string; fileSize: number | null; duration: number | null; format: string | null; ... 4 more ...; createdAt: Date; }' is not assignable to type '{ url: string; mediaType: string; format?: string | undefined; contentType?: string | undefined; }'.
|
||||
Types of property 'mediaType' are incompatible.
|
||||
Type 'string | null' is not assignable to type 'string'.
|
||||
Type 'null' is not assignable to type 'string'.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(2,38): error TS2307: Cannot find module 'vitest' or its corresponding type declarations.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(64,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(65,17): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(70,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(71,17): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(72,17): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(100,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(101,17): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(107,17): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/control-flow.test.ts(108,17): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/hashing.test.ts(2,38): error TS2307: Cannot find module 'vitest' or its corresponding type declarations.
|
||||
src/lib/experiment-designer/__tests__/hashing.test.ts(65,19): error TS2741: Property 'category' is missing in type '{ id: string; type: string; name: string; parameters: { message: string; }; source: { kind: "core"; baseActionId: string; }; execution: { transport: "internal"; }; }' but required in type 'ExperimentAction'.
|
||||
src/lib/experiment-designer/__tests__/hashing.test.ts(86,19): error TS2741: Property 'category' is missing in type '{ id: string; type: string; name: string; parameters: { message: string; }; source: { kind: "core"; baseActionId: string; }; execution: { transport: "internal"; }; }' but required in type 'ExperimentAction'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(2,50): error TS2307: Cannot find module 'vitest' or its corresponding type declarations.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(39,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(58,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(103,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(104,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(107,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(108,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(123,15): error TS2741: Property 'category' is missing in type '{ id: string; type: string; name: string; parameters: {}; source: { kind: "core"; baseActionId: string; }; execution: { transport: "internal"; }; }' but required in type 'ExperimentAction'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(135,16): error TS18048: 'storedStep' is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(136,16): error TS18048: 'storedStep' is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/store.test.ts(136,16): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/validators.test.ts(2,38): error TS2307: Cannot find module 'vitest' or its corresponding type declarations.
|
||||
src/lib/experiment-designer/__tests__/validators.test.ts(11,5): error TS2322: Type '"utility"' is not assignable to type 'ActionCategory'.
|
||||
src/lib/experiment-designer/__tests__/validators.test.ts(14,91): error TS2353: Object literal may only specify known properties, and 'default' does not exist in type 'ActionParameter'.
|
||||
src/lib/experiment-designer/__tests__/validators.test.ts(36,20): error TS2532: Object is possibly 'undefined'.
|
||||
src/lib/experiment-designer/__tests__/validators.test.ts(58,17): error TS2353: Object literal may only specify known properties, and 'order' does not exist in type 'ExperimentAction'.
|
||||
src/lib/experiment-designer/__tests__/validators.test.ts(78,17): error TS2353: Object literal may only specify known properties, and 'order' does not exist in type 'ExperimentAction'.
|
||||
src/lib/experiment-designer/__tests__/validators.test.ts(107,17): error TS2353: Object literal may only specify known properties, and 'order' does not exist in type 'ExperimentAction'.
|
||||
src/lib/experiment-designer/__tests__/validators.test.ts(119,20): error TS2532: Object is possibly 'undefined'.
|
||||
src/server/services/__tests__/trial-execution.test.ts(2,56): error TS2307: Cannot find module 'bun:test' or its corresponding type declarations.
|
||||
+20
-8
@@ -2,13 +2,13 @@ services:
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: hristudio
|
||||
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-hristudio}
|
||||
PGSSLMODE: disable
|
||||
command: -c ssl=off
|
||||
ports:
|
||||
- "5140:5432"
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -20,15 +20,27 @@ services:
|
||||
minio:
|
||||
image: minio/minio
|
||||
ports:
|
||||
- "9000:9000" # API
|
||||
- "9001:9001" # Console
|
||||
- "${MINIO_PORT_API:-9000}:9000" # API
|
||||
- "${MINIO_PORT_CONSOLE:-9001}:9001" # Console
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin}
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
command: server --console-address ":9001" /data
|
||||
|
||||
createbuckets:
|
||||
image: minio/mc
|
||||
depends_on:
|
||||
- minio
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
/usr/bin/mc alias set myminio http://minio:9000 ${MINIO_ACCESS_KEY:-minioadmin} ${MINIO_SECRET_KEY:-minioadmin};
|
||||
/usr/bin/mc mb myminio/${MINIO_BUCKET_NAME:-hristudio-data};
|
||||
/usr/bin/mc anonymous set public myminio/${MINIO_BUCKET_NAME:-hristudio-data};
|
||||
exit 0;
|
||||
"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
minio_data:
|
||||
|
||||
+180
-281
@@ -1,305 +1,204 @@
|
||||
# HRIStudio Documentation
|
||||
|
||||
Welcome to the comprehensive documentation for HRIStudio - a web-based platform for standardizing and improving Wizard of Oz (WoZ) studies in Human-Robot Interaction research.
|
||||
HRIStudio is a web-based Wizard-of-Oz platform for Human-Robot Interaction research.
|
||||
|
||||
## 📚 Documentation Overview
|
||||
## Quick Links
|
||||
|
||||
This documentation suite provides everything needed to understand, build, deploy, and maintain HRIStudio. It's designed for AI agents, developers, and technical teams implementing the platform.
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| **[Tutorials](tutorials/README.md)** | Step-by-step guides for using HRIStudio |
|
||||
| **[Quick Reference](quick-reference.md)** | Essential commands, setup, troubleshooting |
|
||||
| **[Project Status](project-status.md)** | Current development state (March 2026) |
|
||||
| **[Implementation Guide](implementation-guide.md)** | Full technical implementation |
|
||||
| **[NAO6 Integration](nao6-quick-reference.md)** | Robot setup and commands |
|
||||
|
||||
### **🚀 Quick Start**
|
||||
## Tutorials
|
||||
|
||||
**New to HRIStudio?** Start here:
|
||||
1. **[Quick Reference](./quick-reference.md)** - 5-minute setup and key concepts
|
||||
2. **[Project Overview](./project-overview.md)** - Complete feature overview and goals
|
||||
3. **[Implementation Guide](./implementation-guide.md)** - Step-by-step technical implementation
|
||||
New to HRIStudio? Start with our comprehensive tutorials:
|
||||
|
||||
### **📋 Core Documentation** (8 Files)
|
||||
| Tutorial | Description | Time |
|
||||
|----------|-------------|------|
|
||||
| [Getting Started](tutorials/01-getting-started.md) | Installation and first login | 10 min |
|
||||
| [Your First Study](tutorials/02-your-first-study.md) | Creating a research study | 15 min |
|
||||
| [Designing Experiments](tutorials/03-designing-experiments.md) | Building experiment protocols | 25 min |
|
||||
| [Running Trials](tutorials/04-running-trials.md) | Executing experiments | 20 min |
|
||||
| [Wizard Interface](tutorials/05-wizard-interface.md) | Real-time trial control | 15 min |
|
||||
| [Robot Integration](tutorials/06-robot-integration.md) | Connecting NAO6 robot | 20 min |
|
||||
| [Forms & Surveys](tutorials/07-forms-and-surveys.md) | Managing consent and data | 15 min |
|
||||
| [Data & Analysis](tutorials/08-data-and-analysis.md) | Collecting and exporting data | 15 min |
|
||||
| [Simulation Mode](tutorials/09-simulation-mode.md) | Testing without a robot | 10 min |
|
||||
|
||||
#### **Project Specifications**
|
||||
1. **[Project Overview](./project-overview.md)**
|
||||
- Executive summary and project goals
|
||||
- Core features and system architecture
|
||||
- User roles and permissions
|
||||
- Technology stack overview
|
||||
- Key concepts and success metrics
|
||||
|
||||
2. **[Feature Requirements](./feature-requirements.md)**
|
||||
- Detailed user stories and acceptance criteria
|
||||
- Functional requirements by module
|
||||
- Non-functional requirements
|
||||
- UI/UX specifications
|
||||
- Integration requirements
|
||||
|
||||
#### **Technical Implementation**
|
||||
3. **[Database Schema](./database-schema.md)**
|
||||
- Complete PostgreSQL schema with Drizzle ORM
|
||||
- Table definitions and relationships
|
||||
- Indexes and performance optimizations
|
||||
- Views and stored procedures
|
||||
- Migration guidelines
|
||||
|
||||
4. **[API Routes](./api-routes.md)**
|
||||
- Comprehensive tRPC route documentation
|
||||
- Request/response schemas
|
||||
- Authentication requirements
|
||||
- WebSocket events
|
||||
- Rate limiting and error handling
|
||||
|
||||
5. **[Core Blocks System](./core-blocks-system.md)**
|
||||
- Repository-based plugin architecture
|
||||
- 26 essential blocks across 4 categories
|
||||
- Event triggers, wizard actions, control flow, observation
|
||||
- Block loading and validation system
|
||||
- Integration with experiment designer
|
||||
|
||||
6. **[Plugin System Implementation](./plugin-system-implementation-guide.md)**
|
||||
- Robot plugin architecture and development
|
||||
- Repository management and trust levels
|
||||
- Plugin installation and configuration
|
||||
- Action definitions and parameter schemas
|
||||
- ROS2 integration patterns
|
||||
|
||||
7. **[Implementation Guide](./implementation-guide.md)**
|
||||
- Step-by-step technical implementation
|
||||
- Code examples and patterns
|
||||
- Frontend and backend architecture
|
||||
- Real-time features implementation
|
||||
- Testing strategies
|
||||
|
||||
8. **[Implementation Details](./implementation-details.md)**
|
||||
- Architecture decisions and rationale
|
||||
- Unified editor experiences (significant code reduction)
|
||||
- DataTable migration achievements
|
||||
- Development database and seed system
|
||||
- Performance optimization strategies
|
||||
|
||||
#### **Operations & Deployment**
|
||||
9. **[Deployment & Operations](./deployment-operations.md)**
|
||||
- Infrastructure requirements
|
||||
- Vercel deployment strategies
|
||||
- Monitoring and observability
|
||||
- Backup and recovery procedures
|
||||
- Security operations
|
||||
|
||||
10. **[ROS2 Integration](./ros2-integration.md)**
|
||||
- rosbridge WebSocket architecture
|
||||
- Client-side ROS connection management
|
||||
- Message type definitions
|
||||
- Robot plugin implementation
|
||||
- Security considerations for robot communication
|
||||
|
||||
### **📊 Project Status**
|
||||
|
||||
11. **[Project Status](./project-status.md)**
|
||||
- Overall completion status (complete)
|
||||
- Implementation progress by feature
|
||||
- Sprint planning and development velocity
|
||||
- Production readiness assessment
|
||||
- Core blocks system completion
|
||||
|
||||
12. **[Quick Reference](./quick-reference.md)**
|
||||
- 5-minute setup guide
|
||||
- Essential commands and patterns
|
||||
- API reference and common workflows
|
||||
- Core blocks system overview
|
||||
- Key concepts and architecture overview
|
||||
|
||||
13. **[Work in Progress](./work_in_progress.md)**
|
||||
- Recent changes and improvements
|
||||
- Core blocks system implementation
|
||||
- Plugin architecture enhancements
|
||||
- Panel-based wizard interface (matching experiment designer)
|
||||
- Technical debt resolution
|
||||
- UI/UX enhancements
|
||||
|
||||
### **🤖 Robot Integration Guides**
|
||||
|
||||
14. **[NAO6 Complete Integration Guide](./nao6-integration-complete-guide.md)** - Comprehensive NAO6 setup, troubleshooting, and production deployment
|
||||
15. **[NAO6 Quick Reference](./nao6-quick-reference.md)** - Essential commands and troubleshooting for NAO6 integration
|
||||
16. **[NAO6 ROS2 Setup](./nao6-ros2-setup.md)** - Basic NAO6 ROS2 driver installation guide
|
||||
|
||||
### **📖 Academic References**
|
||||
|
||||
17. **[Research Paper](./root.tex)** - Academic LaTeX document
|
||||
18. **[Bibliography](./refs.bib)** - Research references
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Documentation Structure Benefits**
|
||||
|
||||
### **Streamlined Organization**
|
||||
- **Consolidated documentation** - Easier navigation and maintenance
|
||||
- **Logical progression** - From overview → implementation → deployment
|
||||
- **Consolidated achievements** - All progress tracking in unified documents
|
||||
- **Clear entry points** - Quick reference for immediate needs
|
||||
|
||||
### **Comprehensive Coverage**
|
||||
- **Complete technical specs** - Database, API, and implementation details
|
||||
- **Step-by-step guidance** - From project setup to production deployment
|
||||
- **Real-world examples** - Code patterns and configuration samples
|
||||
- **Performance insights** - Optimization strategies and benchmark results
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **Getting Started Paths**
|
||||
|
||||
### **For Developers**
|
||||
1. **[Quick Reference](./quick-reference.md)** - Immediate setup and key commands
|
||||
2. **[Implementation Guide](./implementation-guide.md)** - Technical implementation steps
|
||||
3. **[Database Schema](./database-schema.md)** - Data model understanding
|
||||
4. **[API Routes](./api-routes.md)** - Backend integration
|
||||
|
||||
### **For Project Managers**
|
||||
1. **[Project Overview](./project-overview.md)** - Complete feature understanding
|
||||
2. **[Project Status](./project-status.md)** - Current progress and roadmap
|
||||
3. **[Feature Requirements](./feature-requirements.md)** - Detailed specifications
|
||||
4. **[Deployment & Operations](./deployment-operations.md)** - Infrastructure planning
|
||||
|
||||
### **For Researchers**
|
||||
1. **[Project Overview](./project-overview.md)** - Research platform capabilities
|
||||
2. **[Feature Requirements](./feature-requirements.md)** - User workflows and features
|
||||
3. **[NAO6 Quick Reference](./nao6-quick-reference.md)** - Essential NAO6 robot control commands
|
||||
4. **[ROS2 Integration](./ros2-integration.md)** - Robot platform integration
|
||||
5. **[Research Paper](./root.tex)** - Academic context and methodology
|
||||
|
||||
### **For Robot Integration**
|
||||
1. **[NAO6 Complete Integration Guide](./nao6-integration-complete-guide.md)** - Full NAO6 setup and troubleshooting
|
||||
2. **[NAO6 Quick Reference](./nao6-quick-reference.md)** - Essential commands and quick fixes
|
||||
3. **[ROS2 Integration](./ros2-integration.md)** - General robot integration patterns
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ **Prerequisites**
|
||||
|
||||
### **Development Environment**
|
||||
- **[Bun](https://bun.sh)** - Package manager and runtime
|
||||
- **[PostgreSQL](https://postgresql.org)** 15+ - Primary database
|
||||
- **[Docker](https://docker.com)** - Containerized development (optional)
|
||||
|
||||
### **Production Deployment**
|
||||
- **[Vercel](https://vercel.com)** account - Serverless deployment platform
|
||||
- **PostgreSQL** database - Vercel Postgres or external provider
|
||||
- **[Cloudflare R2](https://cloudflare.com/products/r2/)** - S3-compatible storage
|
||||
|
||||
---
|
||||
|
||||
## ⚡ **Quick Setup (5 Minutes)**
|
||||
## Getting Started
|
||||
|
||||
### 1. Clone & Install
|
||||
```bash
|
||||
# Clone and install
|
||||
git clone <repo-url> hristudio
|
||||
git clone https://github.com/soconnor0919/hristudio.git
|
||||
cd hristudio
|
||||
git submodule update --init --recursive
|
||||
bun install
|
||||
|
||||
# Start database
|
||||
bun run docker:up
|
||||
|
||||
# Setup database and seed data
|
||||
bun db:push
|
||||
bun db:seed
|
||||
|
||||
# Start development
|
||||
bun dev
|
||||
```
|
||||
|
||||
**Default Login**: `sean@soconnor.dev` / `password123`
|
||||
### 2. Start Database
|
||||
```bash
|
||||
bun run docker:up
|
||||
bun db:push
|
||||
bun db:seed
|
||||
```
|
||||
|
||||
---
|
||||
### 3. Start Application
|
||||
```bash
|
||||
bun dev
|
||||
# Visit http://localhost:3000
|
||||
# Login: sean@soconnor.dev / password123
|
||||
```
|
||||
|
||||
## 📋 **Key Features Overview**
|
||||
### 4. Start NAO6 Robot (optional)
|
||||
```bash
|
||||
cd ~/Documents/Projects/nao6-hristudio-integration
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Current Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ HRIStudio Platform │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ UI Layer (Next.js + React + shadcn/ui) │
|
||||
│ ├── Experiment Designer (drag-and-drop) │
|
||||
│ ├── Wizard Interface (real-time trial execution) │
|
||||
│ └── Observer/Participant Views │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ Logic Layer (tRPC + Better Auth) │
|
||||
│ ├── 12 tRPC routers (studies, experiments, trials...) │
|
||||
│ ├── Role-based authentication (4 roles) │
|
||||
│ └── WebSocket for real-time updates │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ Data Layer (PostgreSQL + Drizzle ORM) │
|
||||
│ ├── 31 tables with complete relationships │
|
||||
│ ├── Plugin system with identifier-based lookup │
|
||||
│ └── Comprehensive event logging │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ Robot Integration (ROS2 via WebSocket) │
|
||||
│ Docker: nao_driver, ros_bridge, ros_api │
|
||||
│ Plugin identifier: "nao6-ros2" │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### **Research Workflow Support**
|
||||
- **Hierarchical Structure**: Study → Experiment → Trial → Step → Action
|
||||
- **Visual Experiment Designer**: Repository-based plugin architecture with 26 core blocks
|
||||
- **Core Block Categories**: Events, wizard actions, control flow, observation blocks
|
||||
- **Real-time Trial Execution**: Live wizard control with data capture
|
||||
- **Multi-role Collaboration**: Administrator, Researcher, Wizard, Observer
|
||||
- **Comprehensive Data Management**: Synchronized multi-modal capture
|
||||
- **Visual Designer**: 26+ core blocks (events, wizard actions, control flow, observation)
|
||||
- **Conditional Branching**: Wizard choices with convergence paths
|
||||
- **WebSocket Real-time**: Trial updates with auto-reconnect
|
||||
- **Plugin System**: Robot-agnostic via identifier lookup
|
||||
- **Docker NAO6**: Three-service ROS2 integration
|
||||
- **Forms System**: Consent forms, surveys, questionnaires with templates
|
||||
- **Role-based Access**: Owner, Researcher, Wizard, Observer permissions
|
||||
|
||||
### **Technical Excellence**
|
||||
- **Full Type Safety**: End-to-end TypeScript with strict mode
|
||||
- **Production Ready**: Vercel deployment with Edge Runtime
|
||||
- **Performance Optimized**: Database indexes and query optimization
|
||||
- **Security First**: Role-based access control throughout
|
||||
- **Modern Stack**: Next.js 15, tRPC, Drizzle ORM, shadcn/ui
|
||||
- **Consistent Architecture**: Panel-based interfaces across visual programming tools
|
||||
## System Components
|
||||
|
||||
### **Development Experience**
|
||||
- **Unified Components**: Significant reduction in code duplication
|
||||
- **Panel Architecture**: 90% code sharing between experiment designer and wizard interface
|
||||
- **Enterprise DataTables**: Advanced filtering, export, pagination
|
||||
- **Comprehensive Testing**: Realistic seed data with complete scenarios
|
||||
- **Developer Friendly**: Clear patterns and extensive documentation
|
||||
### Backend (src/server/)
|
||||
- `api/routers/` - 13 tRPC routers (studies, experiments, trials, participants, forms, etc.)
|
||||
- `db/schema.ts` - Drizzle schema (33 tables)
|
||||
- `services/trial-execution.ts` - Trial execution engine
|
||||
- `services/websocket-manager.ts` - Real-time connections
|
||||
|
||||
### **Robot Integration**
|
||||
- **NAO6 Full Support**: Complete ROS2 integration with movement, speech, and sensor control
|
||||
- **Real-time Control**: WebSocket-based robot control through web interface
|
||||
- **Safety Features**: Emergency stops, movement limits, and comprehensive monitoring
|
||||
- **Production Ready**: Tested with NAO V6.0 / NAOqi 2.8.7.4 / ROS2 Humble
|
||||
- **Troubleshooting Guides**: Complete documentation for setup and problem resolution
|
||||
### Frontend (src/)
|
||||
- `app/` - Next.js App Router pages
|
||||
- `components/trials/wizard/` - Wizard interface
|
||||
- `components/trials/forms/` - Form builder and viewer
|
||||
- `hooks/useWebSocket.ts` - Real-time trial updates
|
||||
- `lib/ros/wizard-ros-service.ts` - Robot control
|
||||
|
||||
## Plugin Identifier System
|
||||
|
||||
```typescript
|
||||
// Plugins table has:
|
||||
// - identifier: "nao6-ros2" (unique, machine-readable)
|
||||
// - name: "NAO6 Robot (ROS2 Integration)" (display)
|
||||
|
||||
// Lookup order in trial execution:
|
||||
1. Look up by identifier (e.g., "nao6-ros2")
|
||||
2. Fall back to name (e.g., "NAO6 Robot")
|
||||
3. Return null if not found
|
||||
```
|
||||
|
||||
## Branching Flow
|
||||
|
||||
```
|
||||
Step 3 (Comprehension Check)
|
||||
└── wizard_wait_for_response
|
||||
├── "Correct" → nextStepId = step4a.id
|
||||
└── "Incorrect" → nextStepId = step4b.id
|
||||
|
||||
Step 4a/4b (Branch A/B)
|
||||
└── conditions.nextStepId: step5.id → converge
|
||||
|
||||
Step 5 (Story Continues)
|
||||
└── Linear progression to conclusion
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
```bash
|
||||
# Make changes
|
||||
# ...
|
||||
|
||||
# Validate
|
||||
bun typecheck
|
||||
bun lint
|
||||
|
||||
# Push schema (if changed)
|
||||
bun db:push
|
||||
|
||||
# Reseed (if data changed)
|
||||
bun db:seed
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Build errors | `rm -rf .next && bun build` |
|
||||
| DB issues | `bun db:push --force && bun db:seed` |
|
||||
| Type errors | Check `bun typecheck` output |
|
||||
| WebSocket fails | Verify port 3001 available |
|
||||
|
||||
## External Resources
|
||||
|
||||
- [Thesis (honors-thesis)](https://github.com/soconnor0919/honors-thesis)
|
||||
- [NAO6 Integration](https://github.com/soconnor0919/nao6-hristudio-integration)
|
||||
- [Robot Plugins](https://github.com/soconnor0919/robot-plugins)
|
||||
|
||||
## File Index
|
||||
|
||||
### Primary Documentation
|
||||
- `README.md` - Project overview
|
||||
- `docs/README.md` - This file
|
||||
- `docs/quick-reference.md` - Commands & setup
|
||||
- `docs/nao6-quick-reference.md` - NAO6 commands
|
||||
|
||||
### Tutorials
|
||||
- `docs/tutorials/README.md` - Tutorial overview
|
||||
- `docs/tutorials/01-getting-started.md` - Installation & setup
|
||||
- `docs/tutorials/02-your-first-study.md` - Creating studies
|
||||
- `docs/tutorials/03-designing-experiments.md` - Building protocols
|
||||
- `docs/tutorials/04-running-trials.md` - Executing trials
|
||||
- `docs/tutorials/05-wizard-interface.md` - Trial control
|
||||
- `docs/tutorials/06-robot-integration.md` - Robot setup
|
||||
- `docs/tutorials/07-forms-and-surveys.md` - Forms management
|
||||
- `docs/tutorials/08-data-and-analysis.md` - Data collection
|
||||
- `docs/tutorials/09-simulation-mode.md` - Testing without robot
|
||||
|
||||
### Technical Documentation
|
||||
- `docs/implementation-guide.md` - Full technical implementation
|
||||
- `docs/project-status.md` - Development status
|
||||
- `docs/mock-robot-simulation.md` - Robot simulation
|
||||
|
||||
### Archive (Historical)
|
||||
- `docs/_archive/` - Old documentation (outdated but preserved)
|
||||
|
||||
---
|
||||
|
||||
## 🎊 **Project Status: Production Ready**
|
||||
|
||||
**Current Completion**: Complete ✅
|
||||
**Status**: Ready for immediate deployment
|
||||
**Active Work**: Experiment designer enhancement
|
||||
|
||||
### **Completed Achievements**
|
||||
- ✅ **Complete Backend** - Full API coverage with 11 tRPC routers
|
||||
- ✅ **Professional UI** - Unified experiences with shadcn/ui components
|
||||
- ✅ **Type Safety** - Zero TypeScript errors in production code
|
||||
- ✅ **Database Schema** - 31 tables with comprehensive relationships
|
||||
- ✅ **Authentication** - Role-based access control system
|
||||
- ✅ **Visual Designer** - Repository-based plugin architecture
|
||||
- ✅ **Panel-Based Wizard Interface** - Consistent with experiment designer architecture
|
||||
- ✅ **Core Blocks System** - 26 blocks across events, wizard, control, observation
|
||||
- ✅ **Plugin Architecture** - Unified system for core blocks and robot actions
|
||||
- ✅ **Development Environment** - Realistic test data and scenarios
|
||||
- ✅ **NAO6 Robot Integration** - Full ROS2 integration with comprehensive control and monitoring
|
||||
|
||||
---
|
||||
|
||||
## 📞 **Support and Resources**
|
||||
|
||||
### **Documentation Quality**
|
||||
This documentation is comprehensive and self-contained. For implementation:
|
||||
1. **Start with Quick Reference** for immediate setup
|
||||
2. **Follow Implementation Guide** for step-by-step development
|
||||
3. **Reference Technical Specs** for detailed implementation
|
||||
4. **Check Project Status** for current progress and roadmap
|
||||
|
||||
### **Key Integration Points**
|
||||
- **Authentication**: NextAuth.js v5 with database sessions
|
||||
- **File Storage**: Cloudflare R2 with presigned URLs
|
||||
- **Real-time**: WebSocket with Edge Runtime compatibility
|
||||
- **Robot Control**: ROS2 via rosbridge WebSocket protocol
|
||||
- **Caching**: Vercel KV for serverless-compatible caching
|
||||
- **Monitoring**: Vercel Analytics and structured logging
|
||||
|
||||
---
|
||||
|
||||
## 🏆 **Success Criteria**
|
||||
|
||||
The platform is considered production-ready when:
|
||||
- ✅ All features from requirements are implemented
|
||||
- ✅ All API routes are functional and documented
|
||||
- ✅ Database schema matches specification exactly
|
||||
- ✅ Real-time features work reliably
|
||||
- ✅ Security requirements are met
|
||||
- ✅ Performance targets are achieved
|
||||
- ✅ Type safety is complete throughout
|
||||
|
||||
**All success criteria have been met. HRIStudio is ready for production deployment with full NAO6 robot integration support.**
|
||||
|
||||
---
|
||||
|
||||
## 📝 **Documentation Maintenance**
|
||||
|
||||
- **Version**: 2.0.0 (Streamlined)
|
||||
- **Last Updated**: December 2024
|
||||
- **Target Platform**: HRIStudio v1.0
|
||||
- **Structure**: Consolidated for clarity and maintainability
|
||||
|
||||
This documentation represents a complete, streamlined specification for building and deploying HRIStudio. Every technical decision has been carefully considered to create a robust, scalable platform for HRI research.
|
||||
**Last Updated**: March 22, 2026
|
||||
@@ -0,0 +1,159 @@
|
||||
# HRIStudio - March 2026 Development Summary
|
||||
|
||||
## What We Did This Session
|
||||
|
||||
### 1. Docker Integration for NAO6 Robot
|
||||
**Files**: `nao6-hristudio-integration/`
|
||||
|
||||
- Created `Dockerfile` with ROS2 Humble + naoqi packages
|
||||
- Created `docker-compose.yaml` with 3 services: `nao_driver`, `ros_bridge`, `ros_api`
|
||||
- Created `scripts/init_robot.sh` - Bash script to wake up robot via SSH when Docker starts
|
||||
- Fixed autonomous life disable issue (previously used Python `naoqi` package which isn't on PyPI)
|
||||
|
||||
**Key insight**: Robot init via SSH + `qicli` calls instead of Python SDK
|
||||
|
||||
### 2. Plugin System Fixes
|
||||
**Files**: `robot-plugins/plugins/nao6-ros2.json`, `src/lib/ros/wizard-ros-service.ts`
|
||||
|
||||
- **Topic fixes**: Removed `/naoqi_driver/` prefix from topics (driver already provides unprefixed topics)
|
||||
- **say_with_emotion**: Fixed with proper NAOqi markup (`\rspd=120\^start(animations/...)`)
|
||||
- **wave_goodbye**: Added animated speech with waving gesture
|
||||
- **play_animation**: Added for predefined NAO animations
|
||||
- **Sensor topics**: Fixed camera, IMU, bumper, sonar, touch topics (removed prefix)
|
||||
|
||||
### 3. Database Schema - Plugin Identifier
|
||||
**Files**: `src/server/db/schema.ts`, `src/server/services/trial-execution.ts`
|
||||
|
||||
- Added `identifier` column to `plugins` table (unique, machine-readable ID like `nao6-ros2`)
|
||||
- `name` now for display only ("NAO6 Robot (ROS2 Integration)")
|
||||
- Updated trial-execution to look up by `identifier` first, then `name` (backwards compat)
|
||||
- Created migration script: `scripts/migrate-add-identifier.ts`
|
||||
|
||||
### 4. Seed Script Improvements
|
||||
**Files**: `scripts/seed-dev.ts`
|
||||
|
||||
- Fixed to use local plugin file (not remote `repo.hristudio.com`)
|
||||
- Added `identifier` field for all plugins (nao6, hristudio-core, hristudio-woz)
|
||||
- Experiment structure:
|
||||
- Step 1: The Hook
|
||||
- Step 2: The Narrative
|
||||
- Step 3: Comprehension Check (conditional with wizard choices)
|
||||
- Step 4a/4b: Branch A/B (with `nextStepId` conditions to converge)
|
||||
- Step 5: Story Continues (convergence point)
|
||||
- Step 6: Conclusion
|
||||
|
||||
### 5. Robot Action Timing Fix
|
||||
**Files**: `src/lib/ros/wizard-ros-service.ts`
|
||||
|
||||
- Speech actions now estimate duration: `1500ms emotion overhead + word_count * 300ms`
|
||||
- Added `say_with_emotion` and `wave_goodbye` as explicit built-in actions
|
||||
- Fixed 100ms timeout that was completing actions before robot finished
|
||||
|
||||
### 6. Branching Logic Fixes (Critical!)
|
||||
**Files**: `src/components/trials/wizard/`
|
||||
|
||||
**Bug 1**: `onClick={onNextStep}` passed event object instead of calling function
|
||||
- Fixed: `onClick={() => onNextStep()}`
|
||||
|
||||
**Bug 2**: `onCompleted()` called after branch choice incremented action count
|
||||
- Fixed: Removed `onCompleted()` call after branch selection
|
||||
|
||||
**Bug 3**: Branch A/B had no `nextStepId` condition, fell through to linear progression
|
||||
- Fixed: Added `conditions.nextStepId: step5.id` to Branch A and B
|
||||
|
||||
**Bug 4**: Robot actions from previous step executed on new step (branching jumped but actions from prior step still triggered)
|
||||
- Root cause: `completedActionsCount` not being reset properly
|
||||
- Fixed: `handleNextStep()` now resets `completedActionsCount(0)` on explicit jump
|
||||
|
||||
### 7. Auth.js to Better Auth Migration (Attempted, Reverted)
|
||||
**Status**: Incomplete - 41+ type errors remain
|
||||
|
||||
The migration requires significant changes to how `session.user.roles` is accessed since Better Auth doesn't include roles in session by default. Would need to fetch roles from database on each request.
|
||||
|
||||
**Recommendation**: Defer until more development time available.
|
||||
|
||||
---
|
||||
|
||||
## Current Architecture
|
||||
|
||||
### Plugin Identifier System
|
||||
```
|
||||
plugins table:
|
||||
- id: UUID (primary key)
|
||||
- identifier: varchar (unique, e.g. "nao6-ros2")
|
||||
- name: varchar (display, e.g. "NAO6 Robot (ROS2 Integration)")
|
||||
- robotId: UUID (optional FK to robots)
|
||||
- actionDefinitions: JSONB
|
||||
|
||||
actions table:
|
||||
- type: "plugin.action" (e.g., "nao6-ros2.say_with_emotion")
|
||||
- pluginId: varchar (references plugins.identifier)
|
||||
```
|
||||
|
||||
### Branching Flow
|
||||
```
|
||||
Step 3 (Comprehension Check)
|
||||
└── wizard_wait_for_response action
|
||||
├── Click "Correct" → setLastResponse("Correct") → nextStepId=step4a.id
|
||||
└── Click "Incorrect" → setLastResponse("Incorrect") → nextStepId=step4b.id
|
||||
|
||||
Step 4a/4b (Branches)
|
||||
└── conditions.nextStepId: step5.id → jump to Story Continues
|
||||
|
||||
Step 5 (Story Continues)
|
||||
└── Linear progression to Step 6
|
||||
|
||||
Step 6 (Conclusion)
|
||||
└── Trial complete
|
||||
```
|
||||
|
||||
### ROS Topics (NAO6)
|
||||
```
|
||||
/speech - Text-to-speech
|
||||
/cmd_vel - Velocity commands
|
||||
/joint_angles - Joint position commands
|
||||
/camera/front/image_raw
|
||||
/camera/bottom/image_raw
|
||||
/imu/torso
|
||||
/bumper
|
||||
/{hand,head}_touch
|
||||
/sonar/{left,right}
|
||||
/info
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Known Issues / Remaining Work
|
||||
|
||||
1. **Auth.js to Better Auth Migration** - Deferred, requires significant refactoring
|
||||
2. **robots.executeSystemAction** - Procedure not found error (fallback works but should investigate)
|
||||
3. **say_with_emotion via WebSocket** - May need proper plugin config to avoid fallback
|
||||
|
||||
---
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
```bash
|
||||
cd nao6-hristudio-integration
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Robot init runs automatically on startup (via `init_robot.sh`).
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [x] Docker builds and starts
|
||||
- [x] Robot wakes up (autonomous life disabled)
|
||||
- [x] Seed script runs successfully
|
||||
- [x] Trial executes with proper branching
|
||||
- [x] Branch A → Story Continues (not Branch B)
|
||||
- [x] Robot speaks with emotion (say_with_emotion)
|
||||
- [x] Wave gesture works
|
||||
- [ ] Robot movement (walk, turn) tested
|
||||
- [ ] All NAO6 actions verified
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: March 21, 2026*
|
||||
@@ -37,6 +37,13 @@ HRIStudio is a web-based platform designed to standardize and improve the reprod
|
||||
- Context-sensitive help and best practice guidance
|
||||
- Automatic generation of robot-specific action components
|
||||
- Parameter configuration with validation
|
||||
- **System Plugins**:
|
||||
- **Core (`hristudio-core`)**: Control flow (loops, branches) and observation blocks
|
||||
- **Wizard (`hristudio-woz`)**: Wizard interactions (speech, text input)
|
||||
- **External Robot Plugins**:
|
||||
- Located in `robot-plugins/` repository (e.g., `nao6-ros2`)
|
||||
- Loaded dynamically per study
|
||||
- Map abstract actions (Say, Walk) to ROS2 topics
|
||||
- **Core Block Categories**:
|
||||
- Events (4): Trial triggers, speech detection, timers, key presses
|
||||
- Wizard Actions (6): Speech, gestures, object handling, rating, notes
|
||||
Executable
+367
@@ -0,0 +1,367 @@
|
||||
# Wizard Interface Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The HRIStudio wizard interface provides a comprehensive, real-time trial execution environment with a consolidated 3-panel design optimized for efficient experiment control and monitoring.
|
||||
|
||||
## Interface Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Trial Execution Header │
|
||||
│ [Trial Name] - [Participant] - [Status] │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
┌──────────────┬──────────────────────────────────────┬──────────────────────┐
|
||||
│ │ │ │
|
||||
│ Trial │ Execution Timeline │ Robot Control │
|
||||
│ Control │ │ & Status │
|
||||
│ │ │ │
|
||||
│ ┌──────────┐ │ ┌──┬──┬──┬──┬──┐ Step Progress │ 📷 Camera View │
|
||||
│ │ Start │ │ │✓ │✓ │● │ │ │ │ │
|
||||
│ │ Pause │ │ └──┴──┴──┴──┴──┘ │ Connection: ✓ │
|
||||
│ │ Next Step│ │ │ │
|
||||
│ │ Complete │ │ Current Step: "Greeting" │ Autonomous Life: ON │
|
||||
│ │ Abort │ │ ┌────────────────────────────────┐ │ │
|
||||
│ └──────────┘ │ │ Actions: │ │ Robot Actions: │
|
||||
│ │ │ • Say "Hello" [Run] │ │ ┌──────────────────┐ │
|
||||
│ Progress: │ │ • Wave Hand [Run] │ │ │ Quick Commands │ │
|
||||
│ Step 3/5 │ │ • Wait 2s [Run] │ │ └──────────────────┘ │
|
||||
│ │ └────────────────────────────────┘ │ │
|
||||
│ │ │ Movement Controls │
|
||||
│ │ │ Quick Actions │
|
||||
│ │ │ Status Monitoring │
|
||||
└──────────────┴──────────────────────────────────────┴──────────────────────┘
|
||||
```
|
||||
|
||||
## Panel Descriptions
|
||||
|
||||
### Left Panel: Trial Control
|
||||
|
||||
**Purpose**: Manage overall trial flow and progression
|
||||
|
||||
**Features:**
|
||||
- **Start Trial**: Begin experiment execution
|
||||
- **Pause/Resume**: Temporarily halt trial without aborting
|
||||
- **Next Step**: Manually advance to next step (when all actions complete)
|
||||
- **Complete Trial**: Mark trial as successfully completed
|
||||
- **Abort Trial**: Emergency stop with reason logging
|
||||
|
||||
**Progress Indicators:**
|
||||
- Current step number (e.g., "Step 3 of 5")
|
||||
- Overall trial status
|
||||
- Time elapsed
|
||||
|
||||
**Best Practices:**
|
||||
- Use Pause for participant breaks
|
||||
- Use Abort only for unrecoverable issues
|
||||
- Document abort reasons thoroughly
|
||||
|
||||
---
|
||||
|
||||
### Center Panel: Execution Timeline
|
||||
|
||||
**Purpose**: Visualize experiment flow and execute current step actions
|
||||
|
||||
#### Horizontal Step Progress Bar
|
||||
|
||||
**Features:**
|
||||
- **Visual Overview**: See all steps at a glance
|
||||
- **Step States**:
|
||||
- ✓ **Completed** (green checkmark, primary border)
|
||||
- ● **Current** (highlighted, ring effect)
|
||||
- ○ **Upcoming** (muted appearance)
|
||||
- **Click Navigation**: Jump to any step (unless read-only)
|
||||
- **Horizontal Scroll**: For experiments with many steps
|
||||
|
||||
**Step Card Elements:**
|
||||
- Step number or checkmark icon
|
||||
- Truncated step name (hover for full name)
|
||||
- Visual state indicators
|
||||
|
||||
#### Current Step View
|
||||
|
||||
**Features:**
|
||||
- **Step Header**: Name and description
|
||||
- **Action List**: Vertical timeline of actions
|
||||
- **Action States**:
|
||||
- Completed actions (checkmark)
|
||||
- Active action (highlighted, pulsing)
|
||||
- Pending actions (numbered)
|
||||
- **Action Controls**: Run, Skip, Mark Complete buttons
|
||||
- **Progress Tracking**: Auto-scrolls to active action
|
||||
|
||||
**Action Types:**
|
||||
- **Wizard Actions**: Manual tasks for the wizard
|
||||
- **Robot Actions**: Commands sent to the robot
|
||||
- **Control Flow**: Loops, branches, parallel execution
|
||||
- **Observations**: Data collection and recording
|
||||
|
||||
**Best Practices:**
|
||||
- Review step description before starting
|
||||
- Execute actions in order unless branching
|
||||
- Use Skip sparingly and document reasons
|
||||
- Verify robot action completion before proceeding
|
||||
|
||||
---
|
||||
|
||||
### Right Panel: Robot Control & Status
|
||||
|
||||
**Purpose**: Unified location for all robot-related controls and monitoring
|
||||
|
||||
#### Camera View
|
||||
- Live video feed from robot or environment
|
||||
- Multiple camera support (switchable)
|
||||
- Full-screen mode available
|
||||
|
||||
#### Connection Status
|
||||
- **ROS Bridge**: WebSocket connection state
|
||||
- **Robot Status**: Online/offline indicator
|
||||
- **Reconnect**: Manual reconnection button
|
||||
- **Auto-reconnect**: Automatic retry on disconnect
|
||||
|
||||
#### Autonomous Life Toggle
|
||||
- **Purpose**: Enable/disable robot's autonomous behaviors
|
||||
- **States**:
|
||||
- ON: Robot exhibits idle animations, breathing, awareness
|
||||
- OFF: Robot remains still, fully manual control
|
||||
- **Best Practice**: Turn OFF during precise interactions
|
||||
|
||||
#### Robot Actions Panel
|
||||
- **Quick Commands**: Pre-configured robot actions
|
||||
- **Parameter Controls**: Adjust action parameters
|
||||
- **Execution Status**: Real-time feedback
|
||||
- **Action History**: Recent commands log
|
||||
|
||||
#### Movement Controls
|
||||
- **Directional Pad**: Manual robot navigation
|
||||
- **Speed Control**: Adjust movement speed
|
||||
- **Safety Limits**: Collision detection and boundaries
|
||||
- **Emergency Stop**: Immediate halt
|
||||
|
||||
#### Quick Actions
|
||||
- **Text-to-Speech**: Send custom speech commands
|
||||
- **Preset Gestures**: Common robot gestures
|
||||
- **LED Control**: Change robot LED colors
|
||||
- **Posture Control**: Sit, stand, crouch commands
|
||||
|
||||
#### Status Monitoring
|
||||
- **Battery Level**: Remaining charge percentage
|
||||
- **Joint Status**: Motor temperatures and positions
|
||||
- **Sensor Data**: Ultrasonic, tactile, IMU readings
|
||||
- **Warnings**: Overheating, low battery, errors
|
||||
|
||||
**Best Practices:**
|
||||
- Monitor battery level throughout trial
|
||||
- Check connection status before robot actions
|
||||
- Use Emergency Stop for safety concerns
|
||||
- Document any robot malfunctions
|
||||
|
||||
---
|
||||
|
||||
## Workflow Guide
|
||||
|
||||
### Pre-Trial Setup
|
||||
|
||||
1. **Verify Robot Connection**
|
||||
- Check ROS Bridge status (green indicator)
|
||||
- Test robot responsiveness with quick action
|
||||
- Confirm camera feed is visible
|
||||
|
||||
2. **Review Experiment Protocol**
|
||||
- Scan horizontal step progress bar
|
||||
- Review first step's actions
|
||||
- Prepare any physical materials
|
||||
|
||||
3. **Configure Robot Settings** (Researchers/Admins only)
|
||||
- Click Settings icon in robot panel
|
||||
- Adjust speech, movement, connection parameters
|
||||
- Save configuration for this study
|
||||
|
||||
### During Trial Execution
|
||||
|
||||
1. **Start Trial**
|
||||
- Click "Start" in left panel
|
||||
- First step becomes active
|
||||
- First action highlights in timeline
|
||||
|
||||
2. **Execute Actions**
|
||||
- Follow action sequence in center panel
|
||||
- Use action controls (Run/Skip/Complete)
|
||||
- Monitor robot status in right panel
|
||||
- Document any deviations
|
||||
|
||||
3. **Navigate Steps**
|
||||
- Wait for "Complete Step" button after all actions
|
||||
- Click to advance to next step
|
||||
- Or click step in progress bar to jump
|
||||
|
||||
4. **Handle Issues**
|
||||
- **Participant Question**: Use Pause
|
||||
- **Robot Malfunction**: Check status panel, use Emergency Stop if needed
|
||||
- **Protocol Deviation**: Document in notes, continue or abort as appropriate
|
||||
|
||||
### Post-Trial Completion
|
||||
|
||||
1. **Complete Trial**
|
||||
- Click "Complete Trial" after final step
|
||||
- Confirm completion dialog
|
||||
- Trial marked as completed
|
||||
|
||||
2. **Review Data**
|
||||
- All actions logged with timestamps
|
||||
- Robot commands recorded
|
||||
- Sensor data captured
|
||||
- Video recordings saved
|
||||
|
||||
---
|
||||
|
||||
## Control Flow Features
|
||||
|
||||
### Loops
|
||||
|
||||
**Behavior:**
|
||||
- Loops execute their child actions repeatedly
|
||||
- **Implicit Approval**: Wizard automatically approves each iteration
|
||||
- **Manual Override**: Wizard can skip or abort loop
|
||||
- **Progress Tracking**: Shows current iteration (e.g., "2 of 5")
|
||||
|
||||
**Best Practices:**
|
||||
- Monitor participant engagement during loops
|
||||
- Use abort if participant shows distress
|
||||
- Document any skipped iterations
|
||||
|
||||
### Branches
|
||||
|
||||
**Behavior:**
|
||||
- Conditional execution based on criteria
|
||||
- Wizard selects branch path
|
||||
- Only selected branch actions execute
|
||||
- Other branches are skipped
|
||||
|
||||
**Best Practices:**
|
||||
- Review branch conditions before choosing
|
||||
- Document branch selection rationale
|
||||
- Ensure participant meets branch criteria
|
||||
|
||||
### Parallel Execution
|
||||
|
||||
**Behavior:**
|
||||
- Multiple actions execute simultaneously
|
||||
- All must complete before proceeding
|
||||
- Independent progress tracking
|
||||
|
||||
**Best Practices:**
|
||||
- Monitor all parallel actions
|
||||
- Be prepared for simultaneous robot and wizard tasks
|
||||
- Coordinate timing carefully
|
||||
|
||||
---
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Space` | Start/Pause Trial |
|
||||
| `→` | Next Step |
|
||||
| `Esc` | Abort Trial (with confirmation) |
|
||||
| `R` | Run Current Action |
|
||||
| `S` | Skip Current Action |
|
||||
| `C` | Complete Current Action |
|
||||
| `E` | Emergency Stop Robot |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Robot Not Responding
|
||||
|
||||
1. Check ROS Bridge connection (right panel)
|
||||
2. Click Reconnect button
|
||||
3. Verify robot is powered on
|
||||
4. Check network connectivity
|
||||
5. Restart ROS Bridge if needed
|
||||
|
||||
### Camera Feed Not Showing
|
||||
|
||||
1. Verify camera is enabled in robot settings
|
||||
2. Check camera topic in ROS
|
||||
3. Refresh browser page
|
||||
4. Check camera hardware connection
|
||||
|
||||
### Actions Not Progressing
|
||||
|
||||
1. Verify action has completed
|
||||
2. Check for error messages
|
||||
3. Manually mark complete if stuck
|
||||
4. Document issue in trial notes
|
||||
|
||||
### Timeline Not Updating
|
||||
|
||||
1. Refresh browser page
|
||||
2. Check WebSocket connection
|
||||
3. Verify trial status is "in_progress"
|
||||
4. Contact administrator if persists
|
||||
|
||||
---
|
||||
|
||||
## Role-Specific Features
|
||||
|
||||
### Wizards
|
||||
- Full trial execution control
|
||||
- Action execution and skipping
|
||||
- Robot control (if permitted)
|
||||
- Real-time decision making
|
||||
|
||||
### Researchers
|
||||
- All wizard features
|
||||
- Robot settings configuration
|
||||
- Trial monitoring and oversight
|
||||
- Protocol deviation approval
|
||||
|
||||
### Observers
|
||||
- **Read-only access**
|
||||
- View trial progress
|
||||
- Monitor robot status
|
||||
- Add annotations (no control)
|
||||
|
||||
### Administrators
|
||||
- All features enabled
|
||||
- System configuration
|
||||
- Plugin management
|
||||
- Emergency overrides
|
||||
|
||||
---
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
✅ **Before Trial**
|
||||
- Verify all connections
|
||||
- Test robot responsiveness
|
||||
- Review protocol thoroughly
|
||||
|
||||
✅ **During Trial**
|
||||
- Follow action sequence
|
||||
- Monitor robot status continuously
|
||||
- Document deviations immediately
|
||||
- Use Pause for breaks, not Abort
|
||||
|
||||
✅ **After Trial**
|
||||
- Complete trial properly
|
||||
- Review captured data
|
||||
- Document any issues
|
||||
- Debrief with participant
|
||||
|
||||
❌ **Avoid**
|
||||
- Skipping actions without documentation
|
||||
- Ignoring robot warnings
|
||||
- Aborting trials unnecessarily
|
||||
- Deviating from protocol without approval
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **[Quick Reference](./quick-reference.md)** - Essential commands and shortcuts
|
||||
- **[Implementation Details](./implementation-details.md)** - Technical architecture
|
||||
- **[NAO6 Quick Reference](./nao6-quick-reference.md)** - Robot-specific commands
|
||||
- **[Troubleshooting Guide](./nao6-integration-complete-guide.md)** - Detailed problem resolution
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This guide provides step-by-step technical instructions for implementing HRIStudio using the T3 stack with Next.js, tRPC, Drizzle ORM, NextAuth.js v5, and supporting infrastructure.
|
||||
This guide provides step-by-step technical instructions for implementing HRIStudio using the T3 stack with Next.js, tRPC, Drizzle ORM, Better Auth, and supporting infrastructure.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -25,7 +25,14 @@ This guide provides step-by-step technical instructions for implementing HRIStud
|
||||
### 1. Initialize Project
|
||||
|
||||
```bash
|
||||
# Create new Next.js project with T3 stack
|
||||
# Clone repository (includes robot-plugins as submodule)
|
||||
git clone https://github.com/soconnor0919/hristudio.git
|
||||
cd hristudio
|
||||
|
||||
# Initialize submodules
|
||||
git submodule update --init --recursive
|
||||
|
||||
# Or create from scratch with T3 stack:
|
||||
bunx create-t3-app@latest hristudio \
|
||||
--nextjs \
|
||||
--tailwind \
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# HRIStudio Mock Robot Simulation
|
||||
|
||||
This directory contains a mock robot server for simulating NAO6 robot connections without a physical robot.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Option 1: Standalone Mock Server (Recommended for testing)
|
||||
|
||||
```bash
|
||||
cd scripts/mock-robot
|
||||
bun install
|
||||
bun dev
|
||||
```
|
||||
|
||||
This starts the mock robot WebSocket server on `ws://localhost:9090`.
|
||||
|
||||
### Option 2: Docker Compose Mock Mode
|
||||
|
||||
```bash
|
||||
cd nao6-hristudio-integration
|
||||
docker compose -f docker-compose.yml -f docker-compose.mock.yml --profile mock up -d
|
||||
```
|
||||
|
||||
### Option 3: Client-Side Simulation (No server needed)
|
||||
|
||||
Enable simulation mode in the wizard interface:
|
||||
- Set `NEXT_PUBLIC_SIMULATION_MODE=true` in your `.env` file
|
||||
- Or use the simulation toggle in the UI
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ HRIStudio Platform │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Wizard Interface (Browser) │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ wizard-ros-service.ts │ │
|
||||
│ │ ├── simulationMode: true → Simulates locally │ │
|
||||
│ │ └── simulationMode: false → Connects to server │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────┴────────────┐ │
|
||||
│ │ │ │
|
||||
│ Real Mode Simulation Mode │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ Mock Robot │ │ Local JS │ │
|
||||
│ │ WebSocket │ │ Simulation │ │
|
||||
│ │ Server │ │ (No server) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Mock Robot Server Protocol
|
||||
|
||||
The mock server implements the rosbridge WebSocket protocol:
|
||||
|
||||
### Supported Operations
|
||||
|
||||
| Operation | Description |
|
||||
|-----------|-------------|
|
||||
| `subscribe` | Subscribe to a topic |
|
||||
| `unsubscribe` | Unsubscribe from a topic |
|
||||
| `publish` | Publish a message to a topic |
|
||||
| `call_service` | Call a ROS service |
|
||||
| `advertise` | Advertise a topic |
|
||||
| `unadvertise` | Stop advertising a topic |
|
||||
|
||||
### Simulated Topics
|
||||
|
||||
| Topic | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `/joint_states` | `sensor_msgs/JointState` | Joint positions (26 joints) |
|
||||
| `/naoqi_driver/battery` | `naoqi_bridge_msgs/Battery` | Battery status (85%) |
|
||||
| `/bumper` | `naoqi_bridge_msgs/Bumper` | Bumper contact sensors |
|
||||
| `/hand_touch` | `naoqi_bridge_msgs/HandTouch` | Hand touch sensors |
|
||||
| `/head_touch` | `naoqi_bridge_msgs/HeadTouch` | Head touch sensors |
|
||||
| `/sonar/left` | `sensor_msgs/Range` | Left sonar distance |
|
||||
| `/sonar/right` | `sensor_msgs/Range` | Right sonar distance |
|
||||
|
||||
### Simulated Services
|
||||
|
||||
| Service | Response |
|
||||
|---------|----------|
|
||||
| `/naoqi_driver/get_robot_info` | `{ robotName: "MOCK-NAO6", robotVersion: "6.0" }` |
|
||||
| `/naoqi_driver/get_joint_names` | List of 26 joint names |
|
||||
| `/naoqi_driver/get_position` | Current position `{ x, y, theta }` |
|
||||
| `/naoqi_driver/is_waking_up` | `{ is_waking_up: false }` |
|
||||
|
||||
### Supported Actions
|
||||
|
||||
| Action | Parameters | Description |
|
||||
|--------|------------|-------------|
|
||||
| `say_text` | `text` | Speak text |
|
||||
| `walk_forward` | `speed` | Walk forward |
|
||||
| `walk_backward` | `speed` | Walk backward |
|
||||
| `turn_left` | `speed` | Turn left |
|
||||
| `turn_right` | `speed` | Turn right |
|
||||
| `stop` | - | Stop all movement |
|
||||
| `move_head` | `yaw`, `pitch`, `speed` | Move head |
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# Mock Robot Server (scripts/mock-robot)
|
||||
MOCK_ROBOT_PORT=9090 # WebSocket port
|
||||
MOCK_PUBLISH_INTERVAL=100 # Sensor update interval (ms)
|
||||
|
||||
# HRIStudio Client
|
||||
NEXT_PUBLIC_SIMULATION_MODE=true # Enable client-side simulation
|
||||
NEXT_PUBLIC_ROS_BRIDGE_URL=ws://localhost:9090
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### 1. Start Mock Server
|
||||
```bash
|
||||
cd scripts/mock-robot
|
||||
bun dev
|
||||
```
|
||||
|
||||
### 2. Start HRIStudio
|
||||
```bash
|
||||
cd hristudio
|
||||
bun dev
|
||||
```
|
||||
|
||||
### 3. Test Connection
|
||||
Visit `http://localhost:3000/nao-test` and click "Connect". You should see:
|
||||
- Connection status: `connected`
|
||||
- Battery: ~85%
|
||||
- Joint states updating
|
||||
- Log messages showing subscriptions
|
||||
|
||||
### 4. Test Actions
|
||||
Use the wizard interface to test:
|
||||
- Speech actions
|
||||
- Movement actions
|
||||
- Head control
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Connection timeout" error
|
||||
- Ensure mock server is running: `curl ws://localhost:9090`
|
||||
- Check port is correct (default 9090)
|
||||
|
||||
### "Not connected to ROS bridge" error
|
||||
- Enable simulation mode: `NEXT_PUBLIC_SIMULATION_MODE=true`
|
||||
- Or connect to mock server first
|
||||
|
||||
### Actions not executing
|
||||
- Check connection status in wizard interface
|
||||
- Enable simulation mode if using client-side simulation
|
||||
|
||||
## Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `scripts/mock-robot/src/server.ts` | TypeScript mock server |
|
||||
| `scripts/mock-robot/server.js` | JavaScript mock server (for Docker) |
|
||||
| `src/lib/ros/wizard-ros-service.ts` | Client with simulation mode |
|
||||
| `src/hooks/useWizardRos.ts` | React hook with simulation support |
|
||||
| `docker-compose.mock.yml` | Docker mock service |
|
||||
| `robot-plugins/plugins/nao6-mock.json` | Mock NAO6 plugin |
|
||||
|
||||
## Development
|
||||
|
||||
### Adding New Simulated Actions
|
||||
|
||||
1. Edit `scripts/mock-robot/src/server.ts`
|
||||
2. Add handler in `handlePublish()` or `handleServiceCall()`
|
||||
3. Update `nao6-mock.json` plugin with new action definition
|
||||
|
||||
### Adding New Simulated Sensors
|
||||
|
||||
1. Edit `scripts/mock-robot/src/server.ts`
|
||||
2. Add topic publishing in `publishRobotState()`
|
||||
3. Update subscriber topics in `WizardRosService.subscribeToRobotTopics()`
|
||||
+131
-110
@@ -2,88 +2,112 @@
|
||||
|
||||
Essential commands for using NAO6 robots with HRIStudio.
|
||||
|
||||
## Quick Start
|
||||
## Quick Start (Docker)
|
||||
|
||||
### 1. Start NAO Integration
|
||||
### 1. Start Docker Integration
|
||||
```bash
|
||||
cd ~/naoqi_ros2_ws
|
||||
source install/setup.bash
|
||||
ros2 launch nao_launch nao6_hristudio.launch.py nao_ip:=nao.local password:=robolab
|
||||
cd ~/Documents/Projects/nao6-hristudio-integration
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 2. Wake Robot
|
||||
Press chest button for 3 seconds, or use:
|
||||
```bash
|
||||
# Via SSH (institution-specific password)
|
||||
ssh nao@nao.local
|
||||
# Then run wake-up command (see integration repo docs)
|
||||
```
|
||||
The robot will automatically wake up and autonomous life will be disabled on startup.
|
||||
|
||||
### 3. Start HRIStudio
|
||||
### 2. Start HRIStudio
|
||||
```bash
|
||||
cd ~/Documents/Projects/hristudio
|
||||
bun dev
|
||||
```
|
||||
|
||||
### 4. Test Connection
|
||||
- Open: `http://localhost:3000/nao-test`
|
||||
- Click "Connect"
|
||||
- Test robot commands
|
||||
### 3. Verify Connection
|
||||
- Open: `http://localhost:3000`
|
||||
- Navigate to trial wizard
|
||||
- WebSocket should connect automatically
|
||||
|
||||
## Essential Commands
|
||||
## Docker Services
|
||||
|
||||
### Test Connectivity
|
||||
```bash
|
||||
ping nao.local # Test network
|
||||
ros2 topic list | grep naoqi # Check ROS topics
|
||||
```
|
||||
| Service | Port | Description |
|
||||
|---------|------|-------------|
|
||||
| nao_driver | - | NAOqi driver + robot init |
|
||||
| ros_bridge | 9090 | WebSocket bridge |
|
||||
| ros_api | - | ROS API services |
|
||||
|
||||
### Manual Control
|
||||
```bash
|
||||
# Speech
|
||||
ros2 topic pub --once /speech std_msgs/String "data: 'Hello world'"
|
||||
|
||||
# Movement (robot must be awake!)
|
||||
ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist '{linear: {x: 0.1}}'
|
||||
|
||||
# Stop
|
||||
ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist '{linear: {x: 0.0}}'
|
||||
```
|
||||
|
||||
### Monitor Status
|
||||
```bash
|
||||
ros2 topic echo /naoqi_driver/battery # Battery level
|
||||
ros2 topic echo /naoqi_driver/joint_states # Joint positions
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Robot not moving:** Press chest button for 3 seconds to wake up
|
||||
|
||||
**WebSocket fails:** Check rosbridge is running on port 9090
|
||||
```bash
|
||||
ss -an | grep 9090
|
||||
```
|
||||
|
||||
**Connection lost:** Restart rosbridge
|
||||
```bash
|
||||
pkill -f rosbridge
|
||||
ros2 run rosbridge_server rosbridge_websocket
|
||||
```
|
||||
**Auto-initialization**: On Docker startup, `init_robot.sh` runs automatically via SSH to:
|
||||
- Wake up the robot (`ALMotion.wakeUp`)
|
||||
- Disable autonomous life (`ALAutonomousLife.setState disabled`)
|
||||
- Ensure robot is ready for commands
|
||||
|
||||
## ROS Topics
|
||||
|
||||
**Commands (Input):**
|
||||
- `/speech` - Text-to-speech
|
||||
- `/cmd_vel` - Movement
|
||||
- `/joint_angles` - Joint control
|
||||
**Commands (Publish to these):**
|
||||
```
|
||||
/speech - Text-to-speech
|
||||
/cmd_vel - Velocity commands (movement)
|
||||
/joint_angles - Joint position commands
|
||||
```
|
||||
|
||||
**Sensors (Output):**
|
||||
- `/naoqi_driver/joint_states` - Joint data
|
||||
- `/naoqi_driver/battery` - Battery level
|
||||
- `/naoqi_driver/bumper` - Foot sensors
|
||||
- `/naoqi_driver/sonar/*` - Distance sensors
|
||||
- `/naoqi_driver/camera/*` - Camera feeds
|
||||
**Sensors (Subscribe to these):**
|
||||
```
|
||||
/camera/front/image_raw - Front camera
|
||||
/camera/bottom/image_raw - Bottom camera
|
||||
/joint_states - Joint positions
|
||||
/imu/torso - IMU data
|
||||
/bumper - Foot bumpers
|
||||
/{hand,head}_touch - Touch sensors
|
||||
/sonar/{left,right} - Ultrasonic sensors
|
||||
/info - Robot info
|
||||
```
|
||||
|
||||
## Robot Actions (HRIStudio)
|
||||
|
||||
When actions are triggered via the wizard interface, they publish to these topics:
|
||||
|
||||
| Action | Topic | Message Type |
|
||||
|--------|-------|--------------|
|
||||
| say | `/speech` | `std_msgs/String` |
|
||||
| say_with_emotion | `/speech` | `std_msgs/String` (with NAOqi markup) |
|
||||
| wave_goodbye | `/speech` | `std_msgs/String` + gesture |
|
||||
| walk | `/cmd_vel` | `geometry_msgs/Twist` |
|
||||
| turn | `/cmd_vel` | `geometry_msgs/Twist` |
|
||||
| move_to_posture | `/service/robot_pose` | `naoqi_bridge_msgs/SetRobotPose` |
|
||||
| play_animation | `/animation` | `std_msgs/String` |
|
||||
| set_eye_leds | `/leds/eyes` | `std_msgs/ColorRGBA` |
|
||||
|
||||
## Manual Control
|
||||
|
||||
### Test Connectivity
|
||||
```bash
|
||||
# Network
|
||||
ping 10.0.0.42
|
||||
|
||||
# ROS topics (inside Docker)
|
||||
docker exec -it nao6-hristudio-integration-nao_driver-1 ros2 topic list
|
||||
```
|
||||
|
||||
### Direct Commands (inside Docker)
|
||||
```bash
|
||||
# Speech
|
||||
docker exec -it nao6-hristudio-integration-nao_driver-1 \
|
||||
ros2 topic pub --once /speech std_msgs/String "{data: 'Hello'}"
|
||||
|
||||
# Movement (robot must be awake!)
|
||||
docker exec -it nao6-hristudio-integration-nao_driver-1 \
|
||||
ros2 topic pub --once /cmd_vel geometry_msgs/Twist "{linear: {x: 0.1, y: 0.0, z: 0.0}}"
|
||||
```
|
||||
|
||||
### Robot Control via SSH
|
||||
```bash
|
||||
# SSH to robot
|
||||
sshpass -p "nao" ssh nao@10.0.0.42
|
||||
|
||||
# Wake up
|
||||
qicli call ALMotion.wakeUp
|
||||
|
||||
# Disable autonomous life
|
||||
qicli call ALAutonomousLife.setState disabled
|
||||
|
||||
# Go to stand
|
||||
qicli call ALRobotPosture.goToPosture Stand 0.5
|
||||
```
|
||||
|
||||
## WebSocket
|
||||
|
||||
@@ -99,79 +123,76 @@ ros2 run rosbridge_server rosbridge_websocket
|
||||
}
|
||||
```
|
||||
|
||||
## More Information
|
||||
## Troubleshooting
|
||||
|
||||
See **[nao6-hristudio-integration](../../nao6-hristudio-integration/)** repository for:
|
||||
- Complete installation guide
|
||||
- Detailed usage instructions
|
||||
- Full troubleshooting guide
|
||||
- Plugin definitions
|
||||
- Launch file configurations
|
||||
**Robot not moving:**
|
||||
- Check robot is awake: `qicli call ALMotion.isWakeUp` → returns `true`
|
||||
- If not: `qicli call ALMotion.wakeUp`
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Make Robot Speak
|
||||
**WebSocket fails:**
|
||||
```bash
|
||||
ros2 topic pub --once /speech std_msgs/String "data: 'Welcome to the experiment'"
|
||||
# Check rosbridge is running
|
||||
docker compose ps
|
||||
|
||||
# View logs
|
||||
docker compose logs ros_bridge
|
||||
```
|
||||
|
||||
### Walk Forward 3 Steps
|
||||
**Connection issues:**
|
||||
```bash
|
||||
ros2 topic pub --times 3 /cmd_vel geometry_msgs/msg/Twist '{linear: {x: 0.1, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}'
|
||||
# Restart Docker
|
||||
docker compose down && docker compose up -d
|
||||
|
||||
# Check robot IP in .env
|
||||
cat nao6-hristudio-integration/.env
|
||||
```
|
||||
|
||||
### Turn Head Left
|
||||
```bash
|
||||
ros2 topic pub --once /joint_angles naoqi_bridge_msgs/msg/JointAnglesWithSpeed '{joint_names: ["HeadYaw"], joint_angles: [0.8], speed: 0.2}'
|
||||
```
|
||||
## Environment Variables
|
||||
|
||||
### Emergency Stop
|
||||
```bash
|
||||
ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist '{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}'
|
||||
Create `nao6-hristudio-integration/.env`:
|
||||
```
|
||||
NAO_IP=10.0.0.42
|
||||
NAO_USERNAME=nao
|
||||
NAO_PASSWORD=nao
|
||||
BRIDGE_PORT=9090
|
||||
```
|
||||
|
||||
## 🚨 Safety Notes
|
||||
|
||||
- **Always wake up robot before movement commands**
|
||||
- **Keep emergency stop accessible**
|
||||
- **Always verify robot is awake before movement commands**
|
||||
- **Keep emergency stop accessible** (`qicli call ALMotion.rest()`)
|
||||
- **Start with small movements (0.05 m/s)**
|
||||
- **Monitor battery level during experiments**
|
||||
- **Monitor battery level**
|
||||
- **Ensure clear space around robot**
|
||||
|
||||
## 📝 Credentials
|
||||
## Credentials
|
||||
|
||||
**Default NAO Login:**
|
||||
**NAO Robot:**
|
||||
- IP: `10.0.0.42` (configurable)
|
||||
- Username: `nao`
|
||||
- Password: `robolab` (institution-specific)
|
||||
- Password: `nao`
|
||||
|
||||
**HRIStudio Login:**
|
||||
**HRIStudio:**
|
||||
- Email: `sean@soconnor.dev`
|
||||
- Password: `password123`
|
||||
|
||||
## 🔄 Complete Restart Procedure
|
||||
## Complete Restart
|
||||
|
||||
```bash
|
||||
# 1. Kill all processes
|
||||
sudo fuser -k 9090/tcp
|
||||
pkill -f "rosbridge\|naoqi\|ros2"
|
||||
# 1. Restart Docker integration
|
||||
cd ~/Documents/Projects/nao6-hristudio-integration
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
|
||||
# 2. Restart database
|
||||
sudo docker compose down && sudo docker compose up -d
|
||||
# 2. Verify robot is awake (check logs)
|
||||
docker compose logs nao_driver | grep -i "wake\|autonomous"
|
||||
|
||||
# 3. Start ROS integration
|
||||
cd ~/naoqi_ros2_ws && source install/setup.bash
|
||||
ros2 launch install/nao_launch/share/nao_launch/launch/nao6_hristudio.launch.py nao_ip:=nao.local password:=robolab
|
||||
|
||||
# 4. Wake up robot (in another terminal)
|
||||
sshpass -p "robolab" ssh nao@nao.local "python2 -c \"import sys; sys.path.append('/opt/aldebaran/lib/python2.7/site-packages'); import naoqi; naoqi.ALProxy('ALMotion', '127.0.0.1', 9559).wakeUp()\""
|
||||
|
||||
# 5. Start HRIStudio (in another terminal)
|
||||
cd /home/robolab/Documents/Projects/hristudio && bun dev
|
||||
# 3. Start HRIStudio
|
||||
cd ~/Documents/Projects/hristudio
|
||||
bun dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**📖 For detailed setup instructions, see:** [NAO6 Complete Integration Guide](./nao6-integration-complete-guide.md)
|
||||
|
||||
**✅ Integration Status:** Production Ready
|
||||
**🤖 Tested With:** NAO V6.0 / NAOqi 2.8.7.4 / ROS2 Humble
|
||||
**🤖 Tested With:** NAO V6 / ROS2 Humble / Docker
|
||||
|
||||
+135
-348
@@ -1,402 +1,189 @@
|
||||
# HRIStudio Project Status
|
||||
|
||||
## 🎯 **Current Status: Production Ready**
|
||||
## Current Status: Active Development
|
||||
|
||||
**Project Version**: 1.0.0
|
||||
**Last Updated**: December 2024
|
||||
**Overall Completion**: Complete ✅
|
||||
**Status**: Ready for Production Deployment
|
||||
|
||||
### **🎉 Recent Major Achievement: Wizard Interface Multi-View Implementation Complete**
|
||||
Successfully implemented role-based trial execution interface with Wizard, Observer, and Participant views. Fixed layout issues and eliminated route duplication for clean, production-ready trial execution system.
|
||||
**Last Updated**: March 2026
|
||||
**Overall Completion**: 98%
|
||||
**Status**: Thesis research phase
|
||||
|
||||
---
|
||||
|
||||
## 📊 **Executive Summary**
|
||||
## Executive Summary
|
||||
|
||||
HRIStudio has successfully completed all major development milestones and achieved production readiness. The platform provides a comprehensive, type-safe, and user-friendly environment for conducting Wizard of Oz studies in Human-Robot Interaction research.
|
||||
HRIStudio is a complete platform for Wizard-of-Oz HRI research. Key milestones achieved:
|
||||
|
||||
### **Key Achievements**
|
||||
- ✅ **Complete Backend Infrastructure** - Full API with 12 tRPC routers
|
||||
- ✅ **Complete Frontend Implementation** - Professional UI with unified experiences
|
||||
- ✅ **Full Type Safety** - Zero TypeScript errors in production code
|
||||
- ✅ **Complete Authentication** - Role-based access control system
|
||||
- ✅ **Visual Experiment Designer** - Repository-based plugin architecture
|
||||
- ✅ **Core Blocks System** - 26 blocks across 4 categories (events, wizard, control, observation)
|
||||
- ✅ **Production Database** - 31 tables with comprehensive relationships
|
||||
- ✅ **Development Environment** - Realistic seed data and testing scenarios
|
||||
- ✅ **Trial System Overhaul** - Unified EntityView patterns with real-time execution
|
||||
- ✅ **WebSocket Integration** - Real-time updates with polling fallback
|
||||
- ✅ **Route Consolidation** - Study-scoped architecture with eliminated duplicate components
|
||||
- ✅ **Multi-View Trial Interface** - Role-based Wizard, Observer, and Participant views for thesis research
|
||||
- ✅ **Dashboard Resolution** - Fixed routing issues and implemented proper layout structure
|
||||
### Recent Updates (March 2026)
|
||||
- ✅ WebSocket real-time trial updates implemented
|
||||
- ✅ Better Auth migration complete (replaced NextAuth.js)
|
||||
- ✅ Docker integration for NAO6 (3 services: nao_driver, ros_bridge, ros_api)
|
||||
- ✅ Conditional branching with wizard choices and convergence
|
||||
- ✅ 14 NAO6 robot actions (speech, movement, gestures, sensors, LEDs, animations)
|
||||
- ✅ Plugin identifier system for clean plugin lookup
|
||||
- ✅ Seed script with branching experiment structure
|
||||
|
||||
### Key Achievements
|
||||
- ✅ Complete backend with 12 tRPC routers
|
||||
- ✅ Professional UI with unified experiences
|
||||
- ✅ Full TypeScript coverage (strict mode)
|
||||
- ✅ Role-based access control (4 roles)
|
||||
- ✅ 31 database tables with relationships
|
||||
- ✅ Experiment designer with 26+ core blocks
|
||||
- ✅ Real-time trial execution wizard interface
|
||||
- ✅ NAO6 robot integration via ROS2 Humble
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ **Implementation Status by Feature**
|
||||
## Architecture
|
||||
|
||||
### **Core Infrastructure** ✅ **Complete**
|
||||
### Three-Layer Architecture
|
||||
|
||||
#### **Plugin Architecture** ✅ **Complete**
|
||||
- **Core Blocks System**: Repository-based architecture with 26 essential blocks
|
||||
- **Robot Plugin Integration**: Unified plugin loading for robot actions
|
||||
- **Repository Management**: Admin tools for plugin repositories and trust levels
|
||||
- **Plugin Store**: Study-scoped plugin installation and configuration
|
||||
- **Block Categories**: Events, wizard actions, control flow, observation blocks
|
||||
- **Type Safety**: Full TypeScript support for all plugin definitions
|
||||
- **Documentation**: Complete guides for core blocks and robot plugins
|
||||
|
||||
|
||||
**Database Schema**
|
||||
- ✅ 31 tables covering all research workflows
|
||||
- ✅ Complete relationships with foreign keys and indexes
|
||||
- ✅ Audit logging and soft deletes implemented
|
||||
- ✅ Performance optimizations with strategic indexing
|
||||
- ✅ JSONB support for flexible metadata storage
|
||||
|
||||
**API Infrastructure**
|
||||
- ✅ 12 tRPC routers providing comprehensive functionality
|
||||
- ✅ Type-safe with Zod validation throughout
|
||||
- ✅ Role-based authorization on all endpoints
|
||||
- ✅ Comprehensive error handling and validation
|
||||
- ✅ Optimistic updates and real-time subscriptions ready
|
||||
|
||||
**Authentication & Authorization**
|
||||
- ✅ NextAuth.js v5 with database sessions
|
||||
- ✅ 4 system roles: Administrator, Researcher, Wizard, Observer
|
||||
- ✅ Role-based middleware protecting all routes
|
||||
- ✅ User profile management with password changes
|
||||
- ✅ Admin dashboard for user and role management
|
||||
|
||||
### **User Interface** ✅ **Complete**
|
||||
|
||||
**Core UI Framework**
|
||||
- ✅ shadcn/ui integration with custom theme
|
||||
- ✅ Responsive design across all screen sizes
|
||||
- ✅ Accessibility compliance (WCAG 2.1 AA)
|
||||
- ✅ Loading states and comprehensive error boundaries
|
||||
- ✅ Form validation with react-hook-form + Zod
|
||||
|
||||
**Major Interface Components**
|
||||
- ✅ Dashboard with role-based navigation
|
||||
- ✅ Authentication pages (signin/signup/profile)
|
||||
- ✅ Study management with team collaboration
|
||||
- ✅ Visual experiment designer with drag-and-drop
|
||||
- ✅ Participant management and consent tracking
|
||||
- ✅ Trial execution and monitoring interfaces
|
||||
- ✅ Data tables with advanced filtering and export
|
||||
|
||||
### **Key Feature Implementations** ✅ **Complete**
|
||||
|
||||
**Visual Experiment Designer**
|
||||
- ✅ Professional drag-and-drop interface
|
||||
- ✅ 4 step types: Wizard Action, Robot Action, Parallel Steps, Conditional Branch
|
||||
- ✅ Real-time saving with conflict resolution
|
||||
- ✅ Parameter configuration framework
|
||||
- ✅ Professional UI with loading states and error handling
|
||||
|
||||
**Unified Editor Experiences**
|
||||
- ✅ Significant reduction in form-related code duplication
|
||||
- ✅ Consistent EntityForm component across all entities
|
||||
- ✅ Standardized validation and error handling
|
||||
- ✅ Context-aware creation for nested workflows
|
||||
- ✅ Progressive workflow guidance with next steps
|
||||
|
||||
**DataTable System**
|
||||
- ✅ Unified DataTable component with enterprise features
|
||||
- ✅ Server-side filtering, sorting, and pagination
|
||||
- ✅ Column visibility controls and export functionality
|
||||
- ✅ Responsive design with proper overflow handling
|
||||
- ✅ Consistent experience across all entity lists
|
||||
|
||||
**Robot Integration Framework**
|
||||
- ✅ Plugin system for extensible robot support
|
||||
- ✅ RESTful API and ROS2 integration via WebSocket
|
||||
- ✅ Type-safe action definitions and parameter schemas
|
||||
- ✅ Connection testing and health monitoring
|
||||
|
||||
---
|
||||
|
||||
## 🎊 **Major Development Achievements**
|
||||
|
||||
### **Code Quality Excellence**
|
||||
- **Type Safety**: Complete TypeScript coverage with strict mode
|
||||
- **Code Reduction**: Significant decrease in form-related duplication
|
||||
- **Performance**: Optimized database queries and client bundles
|
||||
- **Security**: Comprehensive role-based access control
|
||||
- **Testing**: Unit, integration, and E2E testing frameworks ready
|
||||
|
||||
### **User Experience Innovation**
|
||||
- **Consistent Interface**: Unified patterns across all features
|
||||
- **Professional Design**: Enterprise-grade UI components
|
||||
- **Accessibility**: WCAG 2.1 AA compliance throughout
|
||||
- **Responsive**: Mobile-friendly across all screen sizes
|
||||
- **Intuitive Workflows**: Clear progression from study to trial execution
|
||||
|
||||
### **Development Infrastructure**
|
||||
- **Comprehensive Seed Data**: 3 studies, 8 participants, 5 experiments, 7 trials
|
||||
- **Realistic Test Scenarios**: Elementary education, elderly care, navigation trust
|
||||
- **Development Database**: Instant setup with `bun db:seed`
|
||||
- **Documentation**: Complete technical and user documentation
|
||||
|
||||
---
|
||||
|
||||
## ✅ **Trial System Overhaul - COMPLETE**
|
||||
|
||||
### **Visual Design Standardization**
|
||||
- **EntityView Integration**: All trial pages now use unified EntityView patterns
|
||||
- **Consistent Headers**: Standard EntityViewHeader with icons, status badges, and actions
|
||||
- **Sidebar Layout**: Professional EntityViewSidebar with organized information panels
|
||||
- **Breadcrumb Integration**: Proper navigation context throughout trial workflow
|
||||
|
||||
### **Wizard Interface Redesign**
|
||||
- **Panel-Based Architecture**: Adopted PanelsContainer system from experiment designer
|
||||
- **Three-Panel Layout**: Left (controls), Center (execution), Right (monitoring)
|
||||
- **Breadcrumb Navigation**: Proper navigation hierarchy matching platform standards
|
||||
- **Component Reuse**: 90% code sharing with experiment designer patterns
|
||||
- **Real-time Status**: Clean connection indicators without UI flashing
|
||||
- **Resizable Panels**: Drag-to-resize functionality with overflow containment
|
||||
|
||||
### **Component Unification**
|
||||
- **ActionControls**: Updated to match unified component interface patterns
|
||||
- **ParticipantInfo**: Streamlined for sidebar display with essential information
|
||||
- **EventsLogSidebar**: New component for real-time event monitoring
|
||||
- **RobotStatus**: Integrated mock robot simulation for development testing
|
||||
|
||||
### **Technical Improvements**
|
||||
- **WebSocket Stability**: Enhanced connection handling with polling fallback
|
||||
- **Error Management**: Improved development mode error handling without UI flashing
|
||||
- **Type Safety**: Complete TypeScript compatibility across all trial components
|
||||
- **State Management**: Simplified trial state updates and real-time synchronization
|
||||
|
||||
### **Production Capabilities**
|
||||
- **Mock Robot Integration**: Complete simulation for development and testing
|
||||
- **Real-time Execution**: WebSocket-based live updates with automatic fallback
|
||||
- **Data Capture**: Comprehensive event logging and trial progression tracking
|
||||
- **Role-based Access**: Proper wizard, researcher, and observer role enforcement
|
||||
|
||||
---
|
||||
|
||||
## ✅ **Experiment Designer Redesign - COMPLETE**
|
||||
|
||||
### **Development Status**
|
||||
**Priority**: High
|
||||
**Target**: Enhanced visual programming capabilities
|
||||
**Status**: ✅ Complete
|
||||
|
||||
**Completed Enhancements**:
|
||||
- ✅ Enhanced visual programming interface with modern iconography
|
||||
- ✅ Advanced step configuration with parameter editing
|
||||
- ✅ Real-time validation with comprehensive error detection
|
||||
- ✅ Deterministic hashing for reproducibility
|
||||
- ✅ Plugin drift detection and signature tracking
|
||||
- ✅ Modern drag-and-drop interface with @dnd-kit
|
||||
- ✅ Type-safe state management with Zustand
|
||||
- ✅ Export/import functionality with integrity verification
|
||||
|
||||
### **Technical Implementation**
|
||||
```typescript
|
||||
// Completed step configuration interface
|
||||
interface StepConfiguration {
|
||||
type: 'wizard_action' | 'robot_action' | 'parallel' | 'conditional' | 'timer' | 'loop';
|
||||
parameters: StepParameters;
|
||||
validation: ValidationRules;
|
||||
dependencies: StepDependency[];
|
||||
}
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ User Interface Layer │
|
||||
│ ├── Experiment Designer (visual programming) │
|
||||
│ ├── Wizard Interface (trial execution) │
|
||||
│ ├── Observer View (live monitoring) │
|
||||
│ └── Participant View (thesis study) │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ Data Management Layer │
|
||||
│ ├── PostgreSQL + Drizzle ORM │
|
||||
│ ├── tRPC API (12 routers) │
|
||||
│ └── Better Auth (role-based auth) │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ Robot Integration Layer │
|
||||
│ ├── Plugin system (robot-agnostic) │
|
||||
│ ├── ROS2 via rosbridge WebSocket │
|
||||
│ └── Docker deployment (nao_driver, ros_bridge) │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### **Key Fixes Applied**
|
||||
- ✅ **Step Addition Bug**: Fixed JSX structure and type import issues
|
||||
- ✅ **TypeScript Compilation**: All type errors resolved
|
||||
- ✅ **Drag and Drop**: Fully functional with DndContext properly configured
|
||||
- ✅ **State Management**: Zustand store working correctly with all actions
|
||||
- ✅ **UI Layout**: Three-panel layout with Action Library, Step Flow, and Properties
|
||||
### Plugin Identifier System
|
||||
|
||||
```
|
||||
plugins table:
|
||||
- id: UUID (primary key)
|
||||
- identifier: varchar (unique, e.g. "nao6-ros2")
|
||||
- name: varchar (display, e.g. "NAO6 Robot (ROS2)")
|
||||
- robotId: UUID (optional FK to robots)
|
||||
- actionDefinitions: JSONB
|
||||
|
||||
actions table:
|
||||
- type: "plugin.action" (e.g., "nao6-ros2.say_with_emotion")
|
||||
- pluginId: varchar (references plugins.identifier)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 **Sprint Planning & Progress**
|
||||
## Branching Flow
|
||||
|
||||
### **Current Sprint (February 2025)**
|
||||
**Theme**: Production Deployment Preparation
|
||||
Experiment steps support conditional branching with wizard choices:
|
||||
|
||||
**Goals**:
|
||||
1. ✅ Complete experiment designer redesign
|
||||
2. ✅ Fix step addition functionality
|
||||
3. ✅ Resolve TypeScript compilation issues
|
||||
4. ⏳ Final code quality improvements
|
||||
```
|
||||
Step 3 (Comprehension Check)
|
||||
└── wizard_wait_for_response
|
||||
├── Click "Correct" → setLastResponse("Correct") → nextStepId=step4a
|
||||
└── Click "Incorrect" → setLastResponse("Incorrect") → nextStepId=step4b
|
||||
|
||||
**Sprint Metrics**:
|
||||
- **Story Points**: 34 total
|
||||
- **Completed**: 30 points
|
||||
- **In Progress**: 4 points
|
||||
- **Planned**: 0 points
|
||||
Step 4a/4b (Branches)
|
||||
└── conditions.nextStepId: step5.id → convergence point
|
||||
|
||||
### **Development Velocity**
|
||||
- **Sprint 1**: 28 story points completed
|
||||
- **Sprint 2**: 32 story points completed
|
||||
- **Sprint 3**: 34 story points completed
|
||||
- **Sprint 4**: 30 story points completed (current)
|
||||
- **Average**: 31.0 story points per sprint
|
||||
|
||||
### **Quality Metrics**
|
||||
- **Critical Bugs**: Zero (all step addition issues resolved)
|
||||
- **Code Coverage**: High coverage maintained across all components
|
||||
- **Build Time**: Consistently under 3 minutes
|
||||
- **TypeScript Errors**: Zero in production code
|
||||
- **Designer Functionality**: 100% operational
|
||||
Step 5 (Story Continues)
|
||||
└── Linear progression to Step 6
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Success Criteria Validation**
|
||||
## NAO6 Robot Actions (14 total)
|
||||
|
||||
### **Technical Requirements** ✅ **Met**
|
||||
- ✅ End-to-end type safety throughout platform
|
||||
- ✅ Role-based access control with 4 distinct roles
|
||||
- ✅ Comprehensive API covering all research workflows
|
||||
- ✅ Visual experiment designer with drag-and-drop interface
|
||||
- ✅ Real-time trial execution framework ready
|
||||
- ✅ Scalable architecture built for research teams
|
||||
|
||||
### **User Experience Goals** ✅ **Met**
|
||||
- ✅ Intuitive interface following modern design principles
|
||||
- ✅ Consistent experience across all features
|
||||
- ✅ Responsive design working on all devices
|
||||
- ✅ Accessibility compliance for inclusive research
|
||||
- ✅ Professional appearance suitable for academic use
|
||||
|
||||
### **Research Workflow Support** ✅ **Met**
|
||||
- ✅ Hierarchical study structure (Study → Experiment → Trial → Step → Action)
|
||||
- ✅ Multi-role collaboration with proper permissions
|
||||
- ✅ Comprehensive data capture for all trial activities
|
||||
- ✅ Flexible robot integration supporting multiple platforms
|
||||
- ✅ Data analysis and export capabilities
|
||||
| Category | Actions |
|
||||
|----------|---------|
|
||||
| Speech | say, say_with_emotion, wave_goodbye |
|
||||
| Movement | walk, turn, move_to_posture |
|
||||
| Gestures | play_animation, gesture |
|
||||
| Sensors | get_sensors, bumper_state, touch_state |
|
||||
| LEDs | set_eye_leds, set_breathing_lights |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **Production Readiness**
|
||||
## Tech Stack
|
||||
|
||||
### **Deployment Checklist** ✅ **Complete**
|
||||
- ✅ Environment variables configured for Vercel
|
||||
- ✅ Database migrations ready for production
|
||||
- ✅ Security headers and CSRF protection configured
|
||||
- ✅ Error tracking and performance monitoring setup
|
||||
- ✅ Build process optimized for Edge Runtime
|
||||
- ✅ Static assets and CDN configuration ready
|
||||
|
||||
### **Performance Validation** ✅ **Passed**
|
||||
- ✅ Page load time < 2 seconds (Currently optimal)
|
||||
- ✅ API response time < 200ms (Currently optimal)
|
||||
- ✅ Database query time < 50ms (Currently optimal)
|
||||
- ✅ Build completes in < 3 minutes (Currently optimal)
|
||||
- ✅ Zero TypeScript compilation errors
|
||||
- ✅ All ESLint rules passing
|
||||
|
||||
### **Security Validation** ✅ **Verified**
|
||||
- ✅ Role-based access control at all levels
|
||||
- ✅ Input validation and sanitization comprehensive
|
||||
- ✅ SQL injection protection via Drizzle ORM
|
||||
- ✅ XSS prevention with proper content handling
|
||||
- ✅ Secure session management with NextAuth.js
|
||||
- ✅ Audit logging for all sensitive operations
|
||||
| Component | Technology | Version |
|
||||
|-----------|------------|---------|
|
||||
| Framework | Next.js | 15-16.x |
|
||||
| Language | TypeScript | 5.x (strict) |
|
||||
| Database | PostgreSQL | 14+ |
|
||||
| ORM | Drizzle | latest |
|
||||
| Auth | NextAuth.js | v5 |
|
||||
| API | tRPC | latest |
|
||||
| UI | Tailwind + shadcn/ui | latest |
|
||||
| Real-time | WebSocket | with polling fallback |
|
||||
| Robot | ROS2 Humble | via rosbridge |
|
||||
| Package Manager | Bun | latest |
|
||||
|
||||
---
|
||||
|
||||
## 📈 **Platform Capabilities**
|
||||
## Development Status
|
||||
|
||||
### **Research Workflow Support**
|
||||
- **Study Management**: Complete lifecycle from creation to analysis
|
||||
- **Team Collaboration**: Multi-user support with role-based permissions
|
||||
- **Experiment Design**: Visual programming interface for protocol creation
|
||||
- **Trial Execution**: Panel-based wizard interface matching experiment designer architecture
|
||||
- **Real-time Updates**: WebSocket integration with intelligent polling fallback
|
||||
- **Data Capture**: Synchronized multi-modal data streams with comprehensive event logging
|
||||
- **Robot Integration**: Plugin-based support for multiple platforms
|
||||
### Completed Features
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Database Schema | ✅ | 31 tables |
|
||||
| Authentication | ✅ | 4 roles |
|
||||
| Experiment Designer | ✅ | 26+ blocks |
|
||||
| Wizard Interface | ✅ | 3-panel design |
|
||||
| Real-time Updates | ✅ | WebSocket |
|
||||
| Plugin System | ✅ | Robot-agnostic |
|
||||
| NAO6 Integration | ✅ | Docker deployment |
|
||||
| Conditional Branching | ✅ | Wizard choices |
|
||||
| Mock Robot | ✅ | Development mode |
|
||||
|
||||
### **Technical Capabilities**
|
||||
- **Scalability**: Architecture supporting large research institutions
|
||||
- **Performance**: Optimized for concurrent multi-user environments
|
||||
- **Security**: Research-grade data protection and access control
|
||||
- **Flexibility**: Customizable workflows for diverse methodologies
|
||||
- **Integration**: Robot platform agnostic with plugin architecture
|
||||
- **Compliance**: Research ethics and data protection compliance
|
||||
### Known Issues
|
||||
| Issue | Status | Notes |
|
||||
|-------|--------|-------|
|
||||
| robots.executeSystemAction | Known error | Fallback works |
|
||||
|
||||
---
|
||||
|
||||
## 🔮 **Roadmap & Future Work**
|
||||
## SSH Deployment Commands
|
||||
|
||||
### **Immediate Priorities** (Next 30 days)
|
||||
- **Wizard Interface Development** - Complete rebuild of trial execution interface
|
||||
- **Robot Control Implementation** - NAO6 integration with WebSocket communication
|
||||
- **Trial Execution Engine** - Step-by-step protocol execution with real-time data capture
|
||||
- **User Experience Testing** - Validate study-scoped workflows with target users
|
||||
```bash
|
||||
# Local development
|
||||
bun dev
|
||||
|
||||
### **Short-term Goals** (Next 60 days)
|
||||
- **IRB Application Preparation** - Complete documentation and study protocols
|
||||
- **Reference Experiment Implementation** - Well-documented HRI experiment for comparison study
|
||||
- **Training Materials Development** - Comprehensive materials for both HRIStudio and Choregraphe
|
||||
- **Platform Validation** - Extensive testing and reliability verification
|
||||
# Database
|
||||
bun db:push # Push schema changes
|
||||
bun db:seed # Seed with test data
|
||||
bun run docker:up # Start PostgreSQL
|
||||
|
||||
### **Long-term Vision** (Next 90+ days)
|
||||
- **User Study Execution** - Comparative study with 10-12 non-engineering participants
|
||||
- **Thesis Research Completion** - Data analysis and academic paper preparation
|
||||
- **Platform Refinement** - Post-study improvements based on real user feedback
|
||||
- **Community Release** - Open source release for broader HRI research community
|
||||
# Quality
|
||||
bun typecheck # TypeScript validation
|
||||
bun lint # ESLint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎊 **Project Success Declaration**
|
||||
## Thesis Timeline
|
||||
|
||||
**HRIStudio is officially ready for production deployment.**
|
||||
Current phase: **March 2026** - Implementation complete, preparing user study
|
||||
|
||||
### **Completion Summary**
|
||||
The platform successfully provides researchers with a comprehensive, professional, and scientifically rigorous environment for conducting Wizard of Oz studies in Human-Robot Interaction research. All major development goals have been achieved, including the complete modernization of the experiment designer with advanced visual programming capabilities and the successful consolidation of routes into a logical study-scoped architecture. Quality standards have been exceeded, and the system is prepared for thesis research and eventual community use.
|
||||
|
||||
### **Key Success Metrics**
|
||||
- **Development Velocity**: Consistently meeting sprint goals with 30+ story points
|
||||
- **Code Quality**: Zero production TypeScript errors, fully functional designer
|
||||
- **Architecture Quality**: Clean study-scoped hierarchy with eliminated code duplication
|
||||
- **User Experience**: Intuitive navigation flow from studies to entity management
|
||||
- **Route Health**: All routes functional with proper error handling and helpful redirects
|
||||
- **User Experience**: Professional, accessible, consistent interface with modern UX
|
||||
- **Performance**: All benchmarks exceeded, sub-100ms hash computation
|
||||
- **Security**: Comprehensive protection and compliance
|
||||
- **Documentation**: Complete technical and user guides
|
||||
- **Designer Functionality**: 100% operational with step addition working perfectly
|
||||
|
||||
### **Ready For**
|
||||
- ✅ Immediate Vercel deployment
|
||||
- ✅ Research team onboarding
|
||||
- ✅ Academic pilot studies
|
||||
- ✅ Full production research use
|
||||
- ✅ Institutional deployment
|
||||
|
||||
**The development team has successfully delivered a world-class platform that will advance Human-Robot Interaction research by providing standardized, reproducible, and efficient tools for conducting high-quality scientific studies.**
|
||||
| Phase | Status | Date |
|
||||
|-------|--------|------|
|
||||
| Proposal | ✅ | Sept 2025 |
|
||||
| IRB Application | ✅ | Dec 2025 |
|
||||
| Implementation | ✅ | Feb 2026 |
|
||||
| User Study | 🔄 In Progress | Mar-Apr 2026 |
|
||||
| Defense | Scheduled | April 2026 |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 **Development Notes**
|
||||
## Next Steps
|
||||
|
||||
### **Technical Debt Status**
|
||||
- **High Priority**: None identified
|
||||
- **Medium Priority**: Minor database query optimizations possible
|
||||
- **Low Priority**: Some older components could benefit from modern React patterns
|
||||
|
||||
### **Development Restrictions**
|
||||
Following Vercel Edge Runtime compatibility:
|
||||
- ❌ No development servers during implementation sessions
|
||||
- ❌ No Drizzle Studio during development work
|
||||
- ✅ Use `bun db:push` for schema changes
|
||||
- ✅ Use `bun typecheck` for validation
|
||||
- ✅ Use `bun build` for production testing
|
||||
|
||||
### **Quality Gates**
|
||||
- ✅ All TypeScript compilation errors resolved
|
||||
- ✅ All ESLint rules passing with autofix enabled
|
||||
- ✅ All Prettier formatting applied consistently
|
||||
- ✅ No security vulnerabilities detected
|
||||
- ✅ Performance benchmarks met
|
||||
- ✅ Accessibility standards validated
|
||||
1. Complete user study (10-12 participants)
|
||||
2. Data analysis and thesis writing
|
||||
3. Final defense April 2026
|
||||
4. Open source release
|
||||
|
||||
---
|
||||
|
||||
*This document consolidates all project status, progress tracking, and achievement documentation. It serves as the single source of truth for HRIStudio's development state and production readiness.*
|
||||
*Last Updated: March 22, 2026*
|
||||
+7
-5
@@ -107,7 +107,7 @@ This work addresses a significant bottleneck in HRI research. By creating HRIStu
|
||||
\hline
|
||||
September & Finalize and submit this proposal (Due: Sept. 20).
|
||||
|
||||
Submit IRB application for the user study. \\
|
||||
Submit IRB application for the study. \\
|
||||
\hline
|
||||
Oct -- Nov & Complete final implementation of core HRIStudio features.
|
||||
|
||||
@@ -119,13 +119,15 @@ Begin recruiting participants. \\
|
||||
\hline
|
||||
\multicolumn{2}{|l|}{\textbf{Spring 2026: Execution, Analysis, and Writing}} \\
|
||||
\hline
|
||||
Jan -- Feb & Upon receiving IRB approval, conduct all user study sessions. \\
|
||||
Jan -- Feb & Run pilot tests with platform.
|
||||
|
||||
Refine based on testing feedback. \\
|
||||
\hline
|
||||
March & Analyze all data from the user study.
|
||||
March & Execute user study sessions (10-12 participants).
|
||||
|
||||
Draft Results and Discussion sections.
|
||||
Analyze data from the user study.
|
||||
|
||||
Submit ``Intent to Defend'' form (Due: March 1). \\
|
||||
Draft Results and Discussion sections. \\
|
||||
\hline
|
||||
April & Submit completed thesis draft to the defense committee (Due: April 1).
|
||||
|
||||
|
||||
+108
-508
@@ -1,566 +1,166 @@
|
||||
# HRIStudio Quick Reference Guide
|
||||
|
||||
## 🚀 **Getting Started (5 Minutes)**
|
||||
## Quick Setup
|
||||
|
||||
### Prerequisites
|
||||
- [Bun](https://bun.sh) (package manager)
|
||||
- [PostgreSQL](https://postgresql.org) 14+
|
||||
- [Docker](https://docker.com) (optional)
|
||||
|
||||
### Quick Setup
|
||||
```bash
|
||||
# Clone and install
|
||||
git clone <repo-url> hristudio
|
||||
# Clone with submodules
|
||||
git clone https://github.com/soconnor0919/hristudio.git
|
||||
cd hristudio
|
||||
git submodule update --init --recursive
|
||||
|
||||
# Install and setup
|
||||
bun install
|
||||
|
||||
# Start database
|
||||
bun run docker:up
|
||||
|
||||
# Setup database
|
||||
bun db:push
|
||||
bun db:seed
|
||||
|
||||
# Single command now syncs all repositories:
|
||||
# - Core blocks from localhost:3000/hristudio-core
|
||||
# - Robot plugins from https://repo.hristudio.com
|
||||
|
||||
# Start development
|
||||
# Start
|
||||
bun dev
|
||||
```
|
||||
|
||||
### Default Login
|
||||
- **Admin**: `sean@soconnor.dev` / `password123`
|
||||
- **Researcher**: `alice.rodriguez@university.edu` / `password123`
|
||||
- **Wizard**: `emily.watson@lab.edu` / `password123`
|
||||
**Login**: `sean@soconnor.dev` / `password123`
|
||||
|
||||
---
|
||||
|
||||
## 📁 **Project Structure**
|
||||
## Key Concepts
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ # Next.js App Router pages
|
||||
│ ├── (auth)/ # Authentication pages
|
||||
│ ├── (dashboard)/ # Main application
|
||||
│ └── api/ # API routes
|
||||
├── components/ # UI components
|
||||
│ ├── ui/ # shadcn/ui components
|
||||
│ ├── experiments/ # Feature components
|
||||
│ ├── studies/
|
||||
│ ├── participants/
|
||||
│ └── trials/
|
||||
├── server/ # Backend code
|
||||
│ ├── api/routers/ # tRPC routers
|
||||
│ ├── auth/ # NextAuth config
|
||||
│ └── db/ # Database schema
|
||||
└── lib/ # Utilities
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Key Concepts**
|
||||
|
||||
### Hierarchical Structure
|
||||
### Hierarchy
|
||||
```
|
||||
Study → Experiment → Trial → Step → Action
|
||||
```
|
||||
|
||||
### User Roles
|
||||
- **Administrator**: Full system access
|
||||
- **Researcher**: Create studies, design experiments
|
||||
- **Wizard**: Execute trials, control robots
|
||||
- **Observer**: Read-only access
|
||||
### User Roles (Study-level)
|
||||
- **Owner**: Full study control, manage members
|
||||
- **Researcher**: Design experiments, manage participants
|
||||
- **Wizard**: Execute trials, control robot during sessions
|
||||
- **Observer**: Read-only access to study data
|
||||
|
||||
### Core Workflows
|
||||
1. **Study Creation** → Team setup → Participant recruitment
|
||||
2. **Experiment Design** → Visual designer → Protocol validation
|
||||
3. **Trial Execution** → Wizard interface → Data capture
|
||||
4. **Data Analysis** → Export → Insights
|
||||
### Plugin Identifier System
|
||||
- `identifier`: Machine-readable key (e.g., `nao6-ros2`)
|
||||
- `name`: Display name (e.g., `NAO6 Robot (ROS2 Integration)`)
|
||||
- Lookup order: identifier → name → fallback
|
||||
|
||||
---
|
||||
|
||||
## 🛠 **Development Commands**
|
||||
## Development Commands
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `bun dev` | Start development server |
|
||||
| `bun build` | Build for production |
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `bun dev` | Start dev server |
|
||||
| `bun build` | Production build |
|
||||
| `bun typecheck` | TypeScript validation |
|
||||
| `bun lint` | Code quality checks |
|
||||
| `bun db:push` | Push schema changes |
|
||||
| `bun db:seed` | Seed data & sync repositories |
|
||||
| `bun db:studio` | Open database GUI |
|
||||
| `bun db:seed` | Seed data + sync plugins + forms |
|
||||
| `bun run docker:up` | Start PostgreSQL + MinIO |
|
||||
|
||||
## Forms System
|
||||
|
||||
### Form Types
|
||||
- **Consent**: Legal/IRB consent documents with signature fields
|
||||
- **Survey**: Multi-question questionnaires (ratings, multiple choice)
|
||||
- **Questionnaire**: Custom data collection forms
|
||||
|
||||
### Templates (seeded by default)
|
||||
- Informed Consent - Standard consent template
|
||||
- Post-Session Survey - Participant feedback form
|
||||
- Demographics - Basic demographic collection
|
||||
|
||||
### Routes
|
||||
- `/studies/[id]/forms` - List forms
|
||||
- `/studies/[id]/forms/new` - Create form (from template or scratch)
|
||||
- `/studies/[id]/forms/[formId]` - View/edit form, preview, responses
|
||||
|
||||
---
|
||||
|
||||
## 🌐 **API Reference**
|
||||
## NAO6 Robot Docker
|
||||
|
||||
### Base URL
|
||||
```
|
||||
http://localhost:3000/api/trpc/
|
||||
```bash
|
||||
cd ~/Documents/Projects/nao6-hristudio-integration
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Key Routers
|
||||
- **`auth`**: Login, logout, registration
|
||||
- **`studies`**: CRUD operations, team management
|
||||
- **`experiments`**: Design, configuration, validation
|
||||
- **`participants`**: Registration, consent, demographics
|
||||
- **`trials`**: Execution, monitoring, data capture, real-time control
|
||||
- **`robots`**: Integration, communication, actions, plugins
|
||||
- **`dashboard`**: Overview stats, recent activity, study progress
|
||||
- **`admin`**: Repository management, system settings
|
||||
**Services**: nao_driver, ros_bridge (:9090), ros_api
|
||||
|
||||
### Example Usage
|
||||
```typescript
|
||||
// Get user's studies
|
||||
const studies = api.studies.getUserStudies.useQuery();
|
||||
**Topics**:
|
||||
- `/speech` - TTS
|
||||
- `/cmd_vel` - Movement
|
||||
- `/leds/eyes` - LEDs
|
||||
|
||||
// Create new experiment
|
||||
const createExperiment = api.experiments.create.useMutation();
|
||||
---
|
||||
|
||||
## Architecture Layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ UI: Design / Execute / Playback │
|
||||
├─────────────────────────────────────┤
|
||||
│ Server: tRPC, Auth, Trial Logic │
|
||||
├─────────────────────────────────────┤
|
||||
│ Data: PostgreSQL, File Storage │
|
||||
│ Robot: ROS2 via WebSocket │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ **Database Quick Reference**
|
||||
## WebSocket Architecture
|
||||
|
||||
- **Trial Updates**: `ws://localhost:3001/api/websocket`
|
||||
- **ROS Bridge**: `ws://localhost:9090` (rosbridge)
|
||||
- **Real-time**: Auto-reconnect with exponential backoff
|
||||
- **Message Types**: trial_event, trial_status, connection_established
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Core Tables
|
||||
```sql
|
||||
users -- Authentication & profiles
|
||||
studies -- Research projects
|
||||
experiments -- Protocol templates
|
||||
participants -- Study participants
|
||||
trials -- Experiment instances
|
||||
steps -- Experiment phases
|
||||
trial_events -- Execution logs
|
||||
robots -- Available platforms
|
||||
```
|
||||
- `users` - Authentication
|
||||
- `studies` - Research projects
|
||||
- `experiments` - Protocol templates
|
||||
- `trials` - Execution instances
|
||||
- `steps` - Experiment phases
|
||||
- `actions` - Atomic tasks
|
||||
- `plugins` - Robot integrations (identifier column)
|
||||
- `trial_events` - Execution logs
|
||||
|
||||
---
|
||||
|
||||
## Route Structure
|
||||
|
||||
### Key Relationships
|
||||
```
|
||||
studies → experiments → trials
|
||||
studies → participants
|
||||
trials → trial_events
|
||||
experiments → steps
|
||||
/dashboard - Global overview
|
||||
/studies - Study list
|
||||
/studies/[id] - Study details
|
||||
/studies/[id]/experiments
|
||||
/studies/[id]/trials
|
||||
/studies/[id]/participants
|
||||
/trials/[id]/wizard - Trial execution
|
||||
/experiments/[id]/designer - Visual editor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 **UI Components**
|
||||
## Troubleshooting
|
||||
|
||||
**Build errors**: `rm -rf .next && bun build`
|
||||
|
||||
**Database reset**: `bun db:push --force && bun db:seed`
|
||||
|
||||
**Check types**: `bun typecheck`
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Trial System Quick Reference**
|
||||
## Plugin System
|
||||
|
||||
### Trial Workflow
|
||||
```
|
||||
1. Create Study → 2. Design Experiment → 3. Add Participants → 4. Schedule Trial → 5. Execute with Wizard Interface → 6. Analyze Results
|
||||
```
|
||||
|
||||
### Key Trial Pages
|
||||
- **`/studies/[id]/trials`**: List trials for specific study
|
||||
- **`/trials/[id]`**: Individual trial details and management
|
||||
- **`/trials/[id]/wizard`**: Panel-based real-time execution interface
|
||||
- **`/trials/[id]/analysis`**: Post-trial data analysis
|
||||
|
||||
### Trial Status Flow
|
||||
```
|
||||
scheduled → in_progress → completed
|
||||
↘ aborted
|
||||
↘ failed
|
||||
```
|
||||
|
||||
### Wizard Interface Architecture (Panel-Based)
|
||||
The wizard interface uses the same proven panel system as the experiment designer:
|
||||
|
||||
#### **Layout Components**
|
||||
- **PageHeader**: Consistent navigation with breadcrumbs
|
||||
- **PanelsContainer**: Three-panel resizable layout
|
||||
- **Proper Navigation**: Dashboard → Studies → [Study] → Trials → [Trial] → Wizard Control
|
||||
|
||||
#### **Panel Organization**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ PageHeader: Wizard Control │
|
||||
├──────────┬─────────────────────────┬────────────────────┤
|
||||
│ Left │ Center │ Right │
|
||||
│ Panel │ Panel │ Panel │
|
||||
│ │ │ │
|
||||
│ Trial │ Current Step │ Robot Status │
|
||||
│ Controls │ & Wizard Actions │ Participant Info │
|
||||
│ Step │ │ Live Events │
|
||||
│ List │ │ Connection Status │
|
||||
└──────────┴─────────────────────────┴────────────────────┘
|
||||
```
|
||||
|
||||
#### **Panel Features**
|
||||
- **Left Panel**: Trial controls, status, step navigation
|
||||
- **Center Panel**: Main execution area with current step and wizard actions
|
||||
- **Right Panel**: Real-time monitoring and context information
|
||||
- **Resizable**: Drag separators to adjust panel sizes
|
||||
- **Overflow Contained**: No page-level scrolling, internal panel scrolling
|
||||
|
||||
### Technical Features
|
||||
- **Real-time Control**: Step-by-step protocol execution
|
||||
- **WebSocket Integration**: Live updates with polling fallback
|
||||
- **Component Reuse**: 90% code sharing with experiment designer
|
||||
- **Type Safety**: Complete TypeScript compatibility
|
||||
- **Mock Robot System**: TurtleBot3 simulation ready for development
|
||||
|
||||
---
|
||||
|
||||
### Layout Components
|
||||
```typescript
|
||||
// Page wrapper with navigation
|
||||
<PageLayout title="Studies" description="Manage research studies">
|
||||
<StudiesTable />
|
||||
</PageLayout>
|
||||
// Loading a plugin by identifier
|
||||
const plugin = await trialExecution.loadPlugin("nao6-ros2");
|
||||
|
||||
// Entity forms (unified pattern)
|
||||
<EntityForm
|
||||
mode="create"
|
||||
entityName="Study"
|
||||
form={form}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
|
||||
// Data tables (consistent across entities)
|
||||
<DataTable
|
||||
columns={studiesColumns}
|
||||
data={studies}
|
||||
searchKey="name"
|
||||
/>
|
||||
```
|
||||
|
||||
### Form Patterns
|
||||
```typescript
|
||||
// Standard form setup
|
||||
const form = useForm<StudyFormData>({
|
||||
resolver: zodResolver(studySchema),
|
||||
defaultValues: { /* ... */ }
|
||||
});
|
||||
|
||||
// Unified submission
|
||||
const onSubmit = async (data: StudyFormData) => {
|
||||
await createStudy.mutateAsync(data);
|
||||
router.push(`/studies/${result.id}`);
|
||||
};
|
||||
// Action execution
|
||||
await robot.execute("nao6-ros2.say_with_emotion", { text: "Hello" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Route Structure**
|
||||
|
||||
### Study-Scoped Architecture
|
||||
All study-dependent functionality flows through studies for complete organizational consistency:
|
||||
|
||||
```
|
||||
Platform Routes (Global):
|
||||
/dashboard # Global overview with study filtering
|
||||
/studies # Study management hub
|
||||
/profile # User account management
|
||||
/admin # System administration
|
||||
|
||||
Study-Scoped Routes (All Study-Dependent):
|
||||
/studies/[id] # Study details and overview
|
||||
/studies/[id]/participants # Study participants
|
||||
/studies/[id]/trials # Study trials
|
||||
/studies/[id]/experiments # Study experiment protocols
|
||||
/studies/[id]/plugins # Study robot plugins
|
||||
/studies/[id]/analytics # Study analytics
|
||||
|
||||
Individual Entity Routes (Cross-Study):
|
||||
/trials/[id] # Individual trial details
|
||||
/trials/[id]/wizard # Trial execution interface (TO BE BUILT)
|
||||
/experiments/[id] # Individual experiment details
|
||||
/experiments/[id]/designer # Visual experiment designer
|
||||
|
||||
Helpful Redirects (User Guidance):
|
||||
/participants # → Study selection guidance
|
||||
/trials # → Study selection guidance
|
||||
/experiments # → Study selection guidance
|
||||
/plugins # → Study selection guidance
|
||||
/analytics # → Study selection guidance
|
||||
```
|
||||
|
||||
### Architecture Benefits
|
||||
- **Complete Consistency**: All study-dependent functionality properly scoped
|
||||
- **Clear Mental Model**: Platform-level vs study-level separation
|
||||
- **No Duplication**: Single source of truth for each functionality
|
||||
- **User-Friendly**: Helpful guidance for moved functionality
|
||||
|
||||
## 🔐 **Authentication**
|
||||
|
||||
### Protecting Routes
|
||||
```typescript
|
||||
// Middleware protection
|
||||
export default withAuth(
|
||||
function middleware(request) {
|
||||
// Route logic
|
||||
},
|
||||
{
|
||||
callbacks: {
|
||||
authorized: ({ token }) => !!token,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Component protection
|
||||
const { data: session, status } = useSession();
|
||||
if (status === "loading") return <Loading />;
|
||||
if (!session) return <SignIn />;
|
||||
```
|
||||
|
||||
### Role Checking
|
||||
```typescript
|
||||
// Server-side
|
||||
ctx.session.user.role === "administrator"
|
||||
|
||||
// Client-side
|
||||
import { useSession } from "next-auth/react";
|
||||
const hasRole = (role: string) => session?.user.role === role;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤖 **Robot Integration**
|
||||
|
||||
### Core Block System
|
||||
```typescript
|
||||
// Core blocks loaded from local repository during development
|
||||
// Repository sync: localhost:3000/hristudio-core → database
|
||||
|
||||
// Block categories (27 total blocks in 4 groups):
|
||||
// - Events (4): when_trial_starts, when_participant_speaks, etc.
|
||||
// - Wizard Actions (6): wizard_say, wizard_gesture, etc.
|
||||
// - Control Flow (8): wait, repeat, if_condition, etc.
|
||||
// - Observation (9): observe_behavior, record_audio, etc.
|
||||
```
|
||||
|
||||
### Plugin Repository System
|
||||
```typescript
|
||||
// Repository sync (admin only)
|
||||
await api.admin.repositories.sync.mutate({ id: repoId });
|
||||
|
||||
// Plugin installation
|
||||
await api.robots.plugins.install.mutate({
|
||||
studyId: 'study-id',
|
||||
pluginId: 'plugin-id'
|
||||
});
|
||||
|
||||
// Get study plugins
|
||||
const plugins = api.robots.plugins.getStudyPlugins.useQuery({
|
||||
studyId: selectedStudyId
|
||||
});
|
||||
```
|
||||
|
||||
### Plugin Structure
|
||||
```typescript
|
||||
interface Plugin {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
trustLevel: 'official' | 'verified' | 'community';
|
||||
actionDefinitions: RobotAction[];
|
||||
metadata: {
|
||||
platform: string;
|
||||
category: string;
|
||||
repositoryId: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Repository Integration
|
||||
- **Robot Plugins**: `https://repo.hristudio.com` (live)
|
||||
- **Core Blocks**: `localhost:3000/hristudio-core` (development)
|
||||
- **Auto-sync**: Integrated into `bun db:seed` command
|
||||
- **Plugin Store**: Browse → Install → Use in experiments
|
||||
|
||||
---
|
||||
|
||||
## 📊 **Common Patterns**
|
||||
|
||||
### Error Handling
|
||||
```typescript
|
||||
try {
|
||||
await mutation.mutateAsync(data);
|
||||
toast.success("Success!");
|
||||
router.push("/success-page");
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
toast.error("Failed to save");
|
||||
}
|
||||
```
|
||||
|
||||
### Loading States
|
||||
```typescript
|
||||
const { data, isLoading, error } = api.studies.getAll.useQuery();
|
||||
|
||||
if (isLoading) return <Skeleton />;
|
||||
if (error) return <ErrorMessage error={error} />;
|
||||
return <DataTable data={data} />;
|
||||
```
|
||||
|
||||
### Form Validation
|
||||
```typescript
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, "Name required"),
|
||||
description: z.string().min(10, "Description too short"),
|
||||
duration: z.number().min(5, "Minimum 5 minutes")
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **Deployment**
|
||||
|
||||
### Vercel Deployment
|
||||
```bash
|
||||
# Install Vercel CLI
|
||||
bun add -g vercel
|
||||
|
||||
# Deploy
|
||||
vercel --prod
|
||||
|
||||
# Environment variables
|
||||
vercel env add DATABASE_URL
|
||||
vercel env add NEXTAUTH_SECRET
|
||||
vercel env add CLOUDFLARE_R2_*
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
```bash
|
||||
# Required
|
||||
DATABASE_URL=postgresql://...
|
||||
NEXTAUTH_URL=https://your-domain.com
|
||||
NEXTAUTH_SECRET=your-secret
|
||||
|
||||
# Storage
|
||||
CLOUDFLARE_R2_ACCOUNT_ID=...
|
||||
CLOUDFLARE_R2_ACCESS_KEY_ID=...
|
||||
CLOUDFLARE_R2_SECRET_ACCESS_KEY=...
|
||||
CLOUDFLARE_R2_BUCKET_NAME=hristudio-files
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Experiment Designer — Quick Tips
|
||||
|
||||
- Panels layout
|
||||
- Uses Tailwind-first grid via `PanelsContainer` with fraction-based columns (no hardcoded px).
|
||||
- Left/Center/Right panels are minmax(0, …) columns to prevent horizontal overflow.
|
||||
- Status bar lives inside the bordered container; no gap below the panels.
|
||||
|
||||
- Resizing (no persistence)
|
||||
- Drag separators between Left↔Center and Center↔Right to resize panels.
|
||||
- Fractions are clamped (min/max) to keep panels usable and avoid page overflow.
|
||||
- Keyboard on handles: Arrow keys to resize; Shift+Arrow for larger steps.
|
||||
|
||||
- Overflow rules (no page-level X scroll)
|
||||
- Root containers: `overflow-hidden`, `min-h-0`.
|
||||
- Each panel wrapper: `min-w-0 overflow-hidden`.
|
||||
- Each panel content: `overflow-y-auto overflow-x-hidden` (scroll inside the panel).
|
||||
- If X scroll appears, clamp the offending child (truncate, `break-words`, `overflow-x-hidden`).
|
||||
|
||||
- Action Library scroll
|
||||
- Search/categories header and footer are fixed; the list uses internal scroll (`ScrollArea` with `flex-1`).
|
||||
- Long lists never scroll the page — only the panel.
|
||||
|
||||
- Inspector tabs (shadcn/ui)
|
||||
- Single Tabs root controls both header and content.
|
||||
- TabsList uses simple grid or inline-flex; triggers are plain `TabsTrigger`.
|
||||
- Active state is styled globally (via `globals.css`) using Radix `data-state="active"`.
|
||||
|
||||
## 🔧 **Troubleshooting**
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Build Errors**
|
||||
```bash
|
||||
# Clear cache and rebuild
|
||||
rm -rf .next
|
||||
bun run build
|
||||
```
|
||||
|
||||
**Database Issues**
|
||||
```bash
|
||||
# Reset database
|
||||
bun db:push --force
|
||||
bun db:seed
|
||||
```
|
||||
|
||||
**TypeScript Errors**
|
||||
```bash
|
||||
# Check types
|
||||
bun typecheck
|
||||
|
||||
# Common fixes
|
||||
# - Check imports
|
||||
# - Verify API return types
|
||||
# - Update schema types
|
||||
```
|
||||
|
||||
### Performance Tips
|
||||
- Use React Server Components where possible
|
||||
- Implement proper pagination for large datasets
|
||||
- Add database indexes for frequently queried fields
|
||||
- Use optimistic updates for better UX
|
||||
|
||||
---
|
||||
|
||||
## 📚 **Further Reading**
|
||||
|
||||
### Documentation Files
|
||||
- **[Project Overview](./project-overview.md)**: Complete feature overview
|
||||
- **[Implementation Details](./implementation-details.md)**: Architecture decisions and patterns
|
||||
- **[Database Schema](./database-schema.md)**: Complete database documentation
|
||||
- **[API Routes](./api-routes.md)**: Comprehensive API reference
|
||||
- **[Core Blocks System](./core-blocks-system.md)**: Repository-based block architecture
|
||||
- **[Plugin System Guide](./plugin-system-implementation-guide.md)**: Robot integration guide
|
||||
- **[Project Status](./project-status.md)**: Current development status
|
||||
- **[Work in Progress](./work_in_progress.md)**: Recent changes and active development
|
||||
|
||||
### External Resources
|
||||
- [Next.js Documentation](https://nextjs.org/docs)
|
||||
- [tRPC Documentation](https://trpc.io/docs)
|
||||
- [Drizzle ORM Guide](https://orm.drizzle.team/docs)
|
||||
- [shadcn/ui Components](https://ui.shadcn.com)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Quick Tips**
|
||||
### Quick Tips
|
||||
|
||||
### Development Workflow
|
||||
1. Always run `bun typecheck` before commits
|
||||
2. Use the unified `EntityForm` for all CRUD operations
|
||||
3. Follow the established component patterns
|
||||
4. Add proper error boundaries for new features
|
||||
5. Test with multiple user roles
|
||||
6. Use single `bun db:seed` for complete setup
|
||||
|
||||
### Code Standards
|
||||
- Use TypeScript strict mode
|
||||
- Prefer Server Components over Client Components
|
||||
- Implement proper error handling
|
||||
- Add loading states for all async operations
|
||||
- Use Zod for input validation
|
||||
|
||||
### Best Practices
|
||||
- Keep components focused and composable
|
||||
- Use the established file naming conventions
|
||||
- Implement proper RBAC for new features
|
||||
- Add comprehensive logging for debugging
|
||||
- Follow accessibility guidelines (WCAG 2.1 AA)
|
||||
- Use repository-based plugins instead of hardcoded robot actions
|
||||
- Test plugin installation/uninstallation in different studies
|
||||
|
||||
### Route Architecture
|
||||
- **Study-Scoped**: All entity management flows through studies
|
||||
- **Individual Entities**: Trial/experiment details maintain separate routes
|
||||
- **Helpful Redirects**: Old routes guide users to new locations
|
||||
- **Consistent Navigation**: Breadcrumbs reflect the study → entity hierarchy
|
||||
|
||||
---
|
||||
|
||||
*This quick reference covers the most commonly needed information for HRIStudio development. For detailed implementation guidance, refer to the comprehensive documentation files.*
|
||||
Last updated: March 2026
|
||||
@@ -0,0 +1,151 @@
|
||||
# Tutorial 1: Getting Started
|
||||
|
||||
Learn how to set up HRIStudio and log in for the first time.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Install HRIStudio dependencies
|
||||
- Start the development environment
|
||||
- Log in and explore the interface
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Bun](https://bun.sh) installed
|
||||
- [Docker](https://docker.com) installed
|
||||
- [Git](https://git-scm.com) installed
|
||||
|
||||
## Step 1: Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/soconnor0919/hristudio.git
|
||||
cd hristudio
|
||||
```
|
||||
|
||||
## Step 2: Install Dependencies
|
||||
|
||||
HRIStudio uses Bun as its package manager:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
## Step 3: Start the Database
|
||||
|
||||
HRIStudio requires PostgreSQL. The easiest way is using Docker:
|
||||
|
||||
```bash
|
||||
# Start PostgreSQL and MinIO (for file storage)
|
||||
bun run docker:up
|
||||
|
||||
# Push database schema
|
||||
bun db:push
|
||||
|
||||
# Seed with sample data
|
||||
bun db:seed
|
||||
```
|
||||
|
||||
This creates the database schema and populates it with:
|
||||
- 4 default user accounts
|
||||
- Sample study and experiments
|
||||
- Test participants and trials
|
||||
|
||||
## Step 4: Start the Development Server
|
||||
|
||||
```bash
|
||||
bun dev
|
||||
```
|
||||
|
||||
The application will be available at `http://localhost:3000`.
|
||||
|
||||
## Step 5: Log In
|
||||
|
||||
Use one of the default accounts:
|
||||
|
||||
| Role | Email | Password |
|
||||
|------|-------|----------|
|
||||
| Administrator | `sean@soconnor.dev` | `password123` |
|
||||
| Researcher | `felipe.perrone@bucknell.edu` | `password123` |
|
||||
| Wizard | `emily.watson@lab.edu` | `password123` |
|
||||
| Observer | `maria.santos@tech.edu` | `password123` |
|
||||
|
||||
## Exploring the Interface
|
||||
|
||||
After logging in, you'll see the main dashboard:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ HRIStudio [User] [Settings] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ Studies │ │ Trials │ │Plugins │ │ Admin │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ Recent Activity │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ • Study: Comparative WoZ Study - Ready │ │
|
||||
│ │ • Trial: P101 - Completed (5 min ago) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
- **Studies** - View and manage your research studies
|
||||
- **Trials** - Monitor and manage experiment trials
|
||||
- **Plugins** - Manage robot integrations
|
||||
- **Admin** - System administration (admins only)
|
||||
|
||||
## Using Simulation Mode
|
||||
|
||||
If you don't have a physical robot, enable simulation mode:
|
||||
|
||||
1. Create or edit `hristudio/.env.local`
|
||||
2. Add: `NEXT_PUBLIC_SIMULATION_MODE=true`
|
||||
3. Restart the dev server
|
||||
|
||||
Simulation mode allows you to test experiments without connecting to a real robot.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Check if Docker is running
|
||||
docker ps
|
||||
|
||||
# Restart the database
|
||||
bun run docker:down
|
||||
bun run docker:up
|
||||
bun db:push
|
||||
```
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
If port 3000 is in use:
|
||||
|
||||
```bash
|
||||
# Use a different port
|
||||
PORT=3001 bun dev
|
||||
```
|
||||
|
||||
### Seed Script Fails
|
||||
|
||||
```bash
|
||||
# Reset the database
|
||||
bun run docker:down -v
|
||||
bun run docker:up
|
||||
bun db:push
|
||||
bun db:seed
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you're set up:
|
||||
|
||||
1. **[Your First Study](02-your-first-study.md)** - Create a research study
|
||||
2. **[Designing Experiments](03-designing-experiments.md)** - Build your first protocol
|
||||
3. **[Simulation Mode](09-simulation-mode.md)** - Test without a robot
|
||||
|
||||
---
|
||||
|
||||
**Previous**: [Tutorials Overview](../tutorials/README.md) | **Next**: [Your First Study](02-your-first-study.md)
|
||||
@@ -0,0 +1,222 @@
|
||||
# Tutorial 2: Your First Study
|
||||
|
||||
Learn how to create a research study and configure team access.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Create a new research study
|
||||
- Configure study settings (IRB, institution)
|
||||
- Add team members with appropriate roles
|
||||
|
||||
## What is a Study?
|
||||
|
||||
In HRIStudio, a **Study** is the top-level container for your research:
|
||||
|
||||
```
|
||||
Study
|
||||
├── Experiments (multiple protocols)
|
||||
├── Participants (study participants)
|
||||
├── Team Members (collaborators)
|
||||
├── Forms & Surveys (consent, questionnaires)
|
||||
└── Trials (individual experiment runs)
|
||||
```
|
||||
|
||||
## Step 1: Create a New Study
|
||||
|
||||
1. Log in as **Researcher** or **Administrator**
|
||||
2. Click **Studies** in the sidebar
|
||||
3. Click **Create Study**
|
||||
|
||||
### Study Settings
|
||||
|
||||
| Field | Description | Required |
|
||||
|-------|-------------|----------|
|
||||
| Name | Study title | Yes |
|
||||
| Description | Brief overview of research goals | Yes |
|
||||
| Institution | University or organization | No |
|
||||
| IRB Protocol | Protocol number (e.g., 2024-HRI-001) | No |
|
||||
| Status | Draft, Active, Completed, Archived | Yes |
|
||||
|
||||
### Example: Creating "Robot Trust Study"
|
||||
|
||||
```
|
||||
Name: Robot Trust Study
|
||||
Description: Investigating how robot appearance affects human trust in collaborative tasks.
|
||||
Institution: Bucknell University
|
||||
IRB Protocol: 2024-HRI-TRUST
|
||||
Status: Draft
|
||||
```
|
||||
|
||||
## Step 2: Add Team Members
|
||||
|
||||
Studies can have multiple collaborators with different roles:
|
||||
|
||||
| Role | Permissions |
|
||||
|------|-------------|
|
||||
| Owner | Full access, can delete study |
|
||||
| Researcher | Create/edit experiments, manage participants |
|
||||
| Wizard | Execute trials, control robot |
|
||||
| Observer | View-only access, add annotations |
|
||||
|
||||
### Adding a Wizard
|
||||
|
||||
1. Open your study
|
||||
2. Go to **Team** tab
|
||||
3. Click **Add Member**
|
||||
4. Enter the wizard's email
|
||||
5. Select **Wizard** role
|
||||
6. Click **Invite**
|
||||
|
||||
The wizard will receive access to:
|
||||
- View the study and experiments
|
||||
- Execute trials
|
||||
- Control the robot during trials
|
||||
- Add notes to trials
|
||||
|
||||
## Step 3: Install Robot Plugins
|
||||
|
||||
For studies involving robots, you need to install the appropriate plugin:
|
||||
|
||||
1. Go to **Plugins** in the sidebar
|
||||
2. Select your study from the dropdown
|
||||
3. Click **Browse Plugins**
|
||||
4. Find your robot (e.g., "NAO6 Robot (ROS2 Integration)")
|
||||
5. Click **Install**
|
||||
6. Configure robot settings (IP address, etc.)
|
||||
|
||||
### Plugin Configuration
|
||||
|
||||
For NAO6 robots:
|
||||
|
||||
```
|
||||
Robot IP: 192.168.1.100
|
||||
Connection Type: ROS2 Bridge
|
||||
WebSocket URL: ws://localhost:9090
|
||||
```
|
||||
|
||||
## Step 4: Create Forms
|
||||
|
||||
Before running trials, you need consent forms:
|
||||
|
||||
1. Go to **Forms** tab in your study
|
||||
2. Click **Create Form**
|
||||
3. Select form type:
|
||||
- **Consent** - Informed consent documents
|
||||
- **Survey** - Post-session questionnaires
|
||||
- **Questionnaire** - Demographic forms
|
||||
|
||||
### Form Templates
|
||||
|
||||
HRIStudio provides templates to get started:
|
||||
|
||||
| Template | Use Case |
|
||||
|----------|----------|
|
||||
| Informed Consent | Required for all participants |
|
||||
| Post-Session Survey | Collect feedback after trials |
|
||||
| Demographics | Collect participant information |
|
||||
|
||||
## Step 5: Add Participants
|
||||
|
||||
1. Go to **Participants** tab
|
||||
2. Click **Add Participant**
|
||||
3. Enter participant code (e.g., "P001")
|
||||
4. Fill in optional details
|
||||
|
||||
### Batch Import
|
||||
|
||||
For large studies, import from CSV:
|
||||
|
||||
```csv
|
||||
participantCode,name,email,notes
|
||||
P001,John Smith,john@email.com,Condition A
|
||||
P002,Jane Doe,jane@email.com,Condition B
|
||||
```
|
||||
|
||||
## Study Workflow
|
||||
|
||||
```
|
||||
Draft → Active → Recruiting → In Progress → Completed
|
||||
│ │ │ │ │
|
||||
│ │ │ │ └── All trials done
|
||||
│ │ │ └── Trials running
|
||||
│ │ └── Recruiting participants
|
||||
│ └── Ready to collect data
|
||||
└── Setting up study
|
||||
```
|
||||
|
||||
## Study Settings Deep Dive
|
||||
|
||||
### IRB Compliance
|
||||
|
||||
Store your IRB information:
|
||||
- Protocol number
|
||||
- Approval date
|
||||
- Expiration date
|
||||
- Consent form versions
|
||||
|
||||
### Data Management
|
||||
|
||||
Configure data retention:
|
||||
- Anonymization settings
|
||||
- Export formats (CSV, JSON)
|
||||
- Backup frequency
|
||||
|
||||
### Notification Settings
|
||||
|
||||
Configure alerts for:
|
||||
- Trial completion
|
||||
- Participant issues
|
||||
- Robot disconnection
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Clone a Study
|
||||
|
||||
Create a copy of an existing study:
|
||||
|
||||
1. Open the study
|
||||
2. Click **Settings** (gear icon)
|
||||
3. Select **Duplicate Study**
|
||||
4. Enter new study name
|
||||
|
||||
### Archive a Study
|
||||
|
||||
When a study is complete:
|
||||
|
||||
1. Go to study settings
|
||||
2. Change status to **Archived**
|
||||
3. Data is preserved but study is read-only
|
||||
|
||||
### Transfer Ownership
|
||||
|
||||
Change the study owner:
|
||||
|
||||
1. Go to **Team** tab
|
||||
2. Find the new owner
|
||||
3. Click **Make Owner**
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Can't Add Team Member
|
||||
|
||||
- Check email is correct
|
||||
- User must have an HRIStudio account
|
||||
- You must be an owner or admin
|
||||
|
||||
### Plugin Installation Failed
|
||||
|
||||
- Check robot is on the network
|
||||
- Verify WebSocket URL is correct
|
||||
- Check Docker services are running
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that your study is set up:
|
||||
|
||||
1. **[Designing Experiments](03-designing-experiments.md)** - Create your first experiment protocol
|
||||
2. **[Forms & Surveys](07-forms-and-surveys.md)** - Customize your consent forms
|
||||
3. **[Running Trials](04-running-trials.md)** - Learn about trial management
|
||||
|
||||
---
|
||||
|
||||
**Previous**: [Getting Started](01-getting-started.md) | **Next**: [Designing Experiments](03-designing-experiments.md)
|
||||
@@ -0,0 +1,341 @@
|
||||
# Tutorial 3: Designing Experiments
|
||||
|
||||
Learn how to create experiment protocols using the visual block designer.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Navigate the experiment designer
|
||||
- Use core blocks (events, wizard actions, control flow)
|
||||
- Build a branching experiment protocol
|
||||
|
||||
## What is an Experiment?
|
||||
|
||||
An **Experiment** defines the protocol for your study:
|
||||
|
||||
```
|
||||
Experiment
|
||||
├── Steps (ordered sequence)
|
||||
│ ├── Actions (robot behaviors)
|
||||
│ ├── Wizard Blocks (human decisions)
|
||||
│ └── Control Flow (loops, branches)
|
||||
├── Robot Actions (from plugins)
|
||||
└── Parameters (configurable values)
|
||||
```
|
||||
|
||||
## Step 1: Create an Experiment
|
||||
|
||||
1. Open your study
|
||||
2. Go to **Experiments** tab
|
||||
3. Click **New Experiment**
|
||||
|
||||
### Experiment Settings
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| Name | Protocol title |
|
||||
| Description | What the experiment measures |
|
||||
| Robot | Which robot to use |
|
||||
| Version | Track protocol versions |
|
||||
|
||||
## Step 2: The Experiment Designer Interface
|
||||
|
||||
The designer has three main areas:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Experiment: Robot Trust Study v1 [Save] │
|
||||
├────────────┬─────────────────────────────────────────────────┤
|
||||
│ │ │
|
||||
│ Blocks │ Canvas │
|
||||
│ Library │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ │
|
||||
│ ┌──────┐ │ │ Step 1 │───▶│ Step 2 │ │
|
||||
│ │Events│ │ │ Hook │ │ Story │ │
|
||||
│ ├──────┤ │ └─────────┘ └────┬────┘ │
|
||||
│ │Wizard│ │ │ │
|
||||
│ ├──────┤ │ ┌────▼────┐ │
|
||||
│ │Control│ │ │ Step 3 │ │
|
||||
│ ├──────┤ │ │ Check │ │
|
||||
│ │Robot │ │ └────┬────┘ │
|
||||
│ └──────┘ │ ┌────┴────┐ │
|
||||
│ │ ┌────┴───┐ ┌───┴────┐ │
|
||||
│ │ │Step 4a │ │Step 4b │ │
|
||||
│ │ │Correct │ │ Wrong │ │
|
||||
│ │ └───┬────┘ └───┬────┘ │
|
||||
│ │ └─────┬─────┘ │
|
||||
│ │ ┌────▼────┐ │
|
||||
│ │ │ Step 5 │ │
|
||||
│ │ │Conclude │ │
|
||||
│ │ └─────────┘ │
|
||||
├────────────┴─────────────────────────────────────────────────┤
|
||||
│ Properties Panel │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Step 1: The Hook │ │
|
||||
│ │ Duration: 25 seconds │ │
|
||||
│ │ Actions: 2 blocks │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Step 3: Understanding Block Categories
|
||||
|
||||
### Events (Triggers)
|
||||
|
||||
Start your experiment with these blocks:
|
||||
|
||||
| Block | Description |
|
||||
|-------|-------------|
|
||||
| **Trial Start** | Triggers when trial begins |
|
||||
| **Wizard Button** | Waits for wizard to press a button |
|
||||
| **Timer** | Waits for a specified duration |
|
||||
| **Participant Response** | Waits for participant input |
|
||||
|
||||
### Wizard Actions
|
||||
|
||||
Blocks the wizard can control:
|
||||
|
||||
| Block | Description |
|
||||
|-------|-------------|
|
||||
| **Say Text** | Robot speaks text |
|
||||
| **Play Animation** | Play a predefined animation |
|
||||
| **Show Image** | Display image on robot screen |
|
||||
| **Move Robot** | Move robot to position |
|
||||
|
||||
### Control Flow
|
||||
|
||||
Control experiment progression:
|
||||
|
||||
| Block | Description |
|
||||
|-------|-------------|
|
||||
| **Branch** | Split into multiple paths |
|
||||
| **Loop** | Repeat a sequence |
|
||||
| **Wait** | Pause for duration |
|
||||
| **Converge** | Merge multiple paths back |
|
||||
|
||||
### Robot Actions
|
||||
|
||||
Actions from your installed robot plugin:
|
||||
|
||||
| Block | Description |
|
||||
|-------|-------------|
|
||||
| **say_text** | Robot speaks |
|
||||
| **walk_forward** | Robot walks forward |
|
||||
| **turn_left** | Robot turns |
|
||||
| **wave** | Robot waves |
|
||||
|
||||
## Step 4: Building "The Interactive Storyteller"
|
||||
|
||||
Let's build a simple storytelling experiment with branching:
|
||||
|
||||
### Step 1: The Hook (Start)
|
||||
|
||||
1. Click **+ Add Step**
|
||||
2. Name it "The Hook"
|
||||
3. Set type to **Robot**
|
||||
4. Drag **Say Text** block:
|
||||
```
|
||||
text: "Hello! I have a story to tell you. Are you ready?"
|
||||
```
|
||||
5. Drag **Move Arm** block:
|
||||
```
|
||||
arm: right
|
||||
gesture: welcome
|
||||
```
|
||||
|
||||
### Step 2: The Narrative
|
||||
|
||||
1. Add new step "The Narrative"
|
||||
2. Connect from Step 1
|
||||
3. Add **Say Text**:
|
||||
```
|
||||
text: "Once upon a time, a traveler flew to Mars..."
|
||||
```
|
||||
4. Add **Turn Head** for gaze behavior:
|
||||
```
|
||||
yaw: 1.5
|
||||
pitch: 0.0
|
||||
```
|
||||
|
||||
### Step 3: Comprehension Check (Branching)
|
||||
|
||||
1. Add new step "Comprehension Check"
|
||||
2. Set type to **Conditional**
|
||||
3. Add **Ask Question**:
|
||||
```
|
||||
question: "What color was the rock?"
|
||||
options:
|
||||
- Correct: "Red"
|
||||
- Incorrect: "Blue"
|
||||
```
|
||||
4. This creates two paths automatically
|
||||
|
||||
### Step 4: Branch Paths
|
||||
|
||||
**Branch A (Correct):**
|
||||
```
|
||||
Say: "Yes! It was a glowing red rock."
|
||||
Emotion: Happy
|
||||
```
|
||||
|
||||
**Branch B (Incorrect):**
|
||||
```
|
||||
Say: "Actually, it was red."
|
||||
Emotion: Sad
|
||||
```
|
||||
|
||||
### Step 5: Converge
|
||||
|
||||
1. Add new step "Story Continues"
|
||||
2. Set type to **Converge**
|
||||
3. Connect both branches to this step
|
||||
4. Add concluding speech
|
||||
|
||||
### Step 6: Conclusion
|
||||
|
||||
1. Add final step "Conclusion"
|
||||
2. Add **Say Text**: "The End. Thank you for listening!"
|
||||
3. Add **Bow** animation
|
||||
|
||||
## Step 5: Block Properties
|
||||
|
||||
Each block has configurable properties:
|
||||
|
||||
### Say Text Block
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "Hello, how are you?",
|
||||
"language": "en-US",
|
||||
"speed": 1.0,
|
||||
"emotion": "neutral"
|
||||
}
|
||||
```
|
||||
|
||||
### Branch Block
|
||||
|
||||
```json
|
||||
{
|
||||
"variable": "last_response",
|
||||
"options": [
|
||||
{ "label": "Yes", "value": "yes", "nextStepId": "step_abc" },
|
||||
{ "label": "No", "value": "no", "nextStepId": "step_xyz" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Loop Block
|
||||
|
||||
```json
|
||||
{
|
||||
"iterations": 3,
|
||||
"maxDuration": 60,
|
||||
"children": [...]
|
||||
}
|
||||
```
|
||||
|
||||
## Step 6: Testing Your Experiment
|
||||
|
||||
### Preview Mode
|
||||
|
||||
Test your experiment without running a real trial:
|
||||
|
||||
1. Click **Preview** button
|
||||
2. Step through each block
|
||||
3. See timing and flow
|
||||
4. Test branching decisions
|
||||
|
||||
### Simulation Mode
|
||||
|
||||
Run with a simulated robot:
|
||||
|
||||
1. Enable `NEXT_PUBLIC_SIMULATION_MODE=true`
|
||||
2. Start a trial
|
||||
3. Robot actions are logged but not executed
|
||||
4. Great for protocol testing
|
||||
|
||||
## Advanced: Parallel Execution
|
||||
|
||||
Run multiple actions simultaneously:
|
||||
|
||||
```
|
||||
Step: Greeting
|
||||
├── Parallel Block
|
||||
│ ├── Say: "Hello!"
|
||||
│ ├── Move Arm: Wave
|
||||
│ └── Move Head: Look at participant
|
||||
```
|
||||
|
||||
## Experiment Versioning
|
||||
|
||||
Track protocol changes:
|
||||
|
||||
1. **Draft** - Experiment being designed
|
||||
2. **Testing** - Being tested with participants
|
||||
3. **Ready** - Approved for data collection
|
||||
4. **Deprecated** - Superseded by newer version
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Linear Protocol
|
||||
|
||||
```
|
||||
Start → Step 1 → Step 2 → Step 3 → End
|
||||
```
|
||||
|
||||
### Branching Protocol
|
||||
|
||||
```
|
||||
Start → Step 1
|
||||
├── Condition A → Step 2a
|
||||
└── Condition B → Step 2b
|
||||
```
|
||||
|
||||
### Loop Protocol
|
||||
|
||||
```
|
||||
Start → Step 1 → Loop (3x) → Step 2 → End
|
||||
↑
|
||||
└── (back to Step 1)
|
||||
```
|
||||
|
||||
### Parallel Protocol
|
||||
|
||||
```
|
||||
Start → Parallel
|
||||
├── Action A
|
||||
├── Action B
|
||||
└── Action C
|
||||
→ Continue
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Block Not Connecting
|
||||
|
||||
- Check step types are compatible
|
||||
- Ensure no circular dependencies
|
||||
- Verify conditions are complete
|
||||
|
||||
### Robot Action Not Available
|
||||
|
||||
- Install the robot plugin
|
||||
- Check plugin is enabled for study
|
||||
- Verify robot is connected
|
||||
|
||||
### Timing Issues
|
||||
|
||||
- Adjust duration estimates
|
||||
- Use explicit wait blocks
|
||||
- Test with real timing
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you've designed your experiment:
|
||||
|
||||
1. **[Running Trials](04-running-trials.md)** - Execute your experiment
|
||||
2. **[Wizard Interface](05-wizard-interface.md)** - Learn real-time control
|
||||
3. **[Robot Integration](06-robot-integration.md)** - Connect your robot
|
||||
|
||||
---
|
||||
|
||||
**Previous**: [Your First Study](02-your-first-study.md) | **Next**: [Running Trials](04-running-trials.md)
|
||||
@@ -0,0 +1,366 @@
|
||||
# Tutorial 4: Running Trials
|
||||
|
||||
Learn how to execute experiments and manage participant trials.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Schedule and start trials
|
||||
- Monitor trial progress
|
||||
- Handle trial interruptions
|
||||
- Collect trial data
|
||||
|
||||
## What is a Trial?
|
||||
|
||||
A **Trial** is a single execution of an experiment with one participant:
|
||||
|
||||
```
|
||||
Trial
|
||||
├── Participant (who took part)
|
||||
├── Experiment (which protocol)
|
||||
├── Status (scheduled, in_progress, completed)
|
||||
├── Events (timestamped actions)
|
||||
└── Data (collected responses)
|
||||
```
|
||||
|
||||
## Trial Lifecycle
|
||||
|
||||
```
|
||||
Scheduled → In Progress → Completed
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ Aborted ◄────────┤
|
||||
│ │ │
|
||||
└────────► Failed ◄───────┘
|
||||
```
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| Scheduled | Trial is planned but not started |
|
||||
| In Progress | Trial is currently running |
|
||||
| Completed | Trial finished successfully |
|
||||
| Aborted | Trial stopped early by wizard |
|
||||
| Failed | Trial failed due to error |
|
||||
|
||||
## Step 1: Schedule a Trial
|
||||
|
||||
### Create Trial for Participant
|
||||
|
||||
1. Go to your **Study**
|
||||
2. Open **Trials** tab
|
||||
3. Click **Schedule Trial**
|
||||
4. Select:
|
||||
- **Participant**: P001
|
||||
- **Experiment**: The Interactive Storyteller
|
||||
- **Scheduled Time**: Today, 2:00 PM
|
||||
|
||||
### Batch Scheduling
|
||||
|
||||
For multiple participants:
|
||||
|
||||
1. Click **Batch Schedule**
|
||||
2. Select participants (P001-P020)
|
||||
3. Select experiment
|
||||
4. Set time slots
|
||||
|
||||
```
|
||||
| Time | Participant |
|
||||
|------------|-------------|
|
||||
| 2:00 PM | P001 |
|
||||
| 2:15 PM | P002 |
|
||||
| 2:30 PM | P003 |
|
||||
| ... | ... |
|
||||
```
|
||||
|
||||
## Step 2: Prepare for Trial
|
||||
|
||||
Before starting:
|
||||
|
||||
1. **Verify Robot Connection**
|
||||
- Check robot is powered on
|
||||
- Verify network connection
|
||||
- Test WebSocket connection
|
||||
|
||||
2. **Review Experiment**
|
||||
- Ensure experiment is "Ready" status
|
||||
- Check step count and timing
|
||||
- Verify all actions are configured
|
||||
|
||||
3. **Prepare Environment**
|
||||
- Ensure participant consent is obtained
|
||||
- Set up recording equipment (if needed)
|
||||
- Remove distractions
|
||||
|
||||
## Step 3: Start a Trial
|
||||
|
||||
### From Trials List
|
||||
|
||||
1. Find the scheduled trial
|
||||
2. Click **Start Trial**
|
||||
3. Confirm participant is ready
|
||||
4. Click **Begin**
|
||||
|
||||
### From Wizard Interface
|
||||
|
||||
1. Open **Wizard Interface**
|
||||
2. Select trial from queue
|
||||
3. Click **Start**
|
||||
|
||||
## Step 4: During the Trial
|
||||
|
||||
### Wizard Interface Overview
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Trial: P001 - Interactive Storyteller [00:05:23]│
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ ┌──────────────┐ ┌────────────────────┐ ┌─────────────────┐ │
|
||||
│ │ Trial │ │ Timeline │ │ Robot Control │ │
|
||||
│ │ Controls │ │ │ │ │ │
|
||||
│ │ │ │ ●───●───○───○ │ │ ┌─────────────┐ │ │
|
||||
│ │ [▶ Play] │ │ Step 1 2 3 4 │ │ │ Connected ✓ │ │ │
|
||||
│ │ [⏸ Pause] │ │ ↑ │ │ │ Battery: 85%│ │ │
|
||||
│ │ [⏹ Stop] │ │ Current: Step 2 │ │ └─────────────┘ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ [📝 Notes] │ │ Progress: 40% │ │ [Say Text] │ │
|
||||
│ │ [⚠ Alert] │ │ │ │ [Move Robot] │ │
|
||||
│ └──────────────┘ └────────────────────┘ │ [Custom Action]│ │
|
||||
│ └─────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Trial Controls
|
||||
|
||||
| Button | Action | Keyboard |
|
||||
|--------|--------|----------|
|
||||
| Play | Resume trial | Space |
|
||||
| Pause | Pause trial | Space |
|
||||
| Stop | End trial early | Escape |
|
||||
| Notes | Add timestamped note | N |
|
||||
| Alert | Send alert notification | A |
|
||||
|
||||
### Monitoring Progress
|
||||
|
||||
**Timeline View:**
|
||||
- Visual step progression
|
||||
- Current step highlighted
|
||||
- Completed steps checked
|
||||
- Estimated time remaining
|
||||
|
||||
**Event Log:**
|
||||
- Timestamped events
|
||||
- Action executions
|
||||
- Wizard interventions
|
||||
- Robot responses
|
||||
|
||||
## Step 5: Wizard Interventions
|
||||
|
||||
During Wizard-of-Oz studies, wizards can intervene:
|
||||
|
||||
### Add Intervention
|
||||
|
||||
1. Click **+ Intervention**
|
||||
2. Select type:
|
||||
- **Pause**: Temporarily stop trial
|
||||
- **Resume**: Continue after pause
|
||||
- **Note**: Add observation
|
||||
- **Alert**: Notify researcher
|
||||
|
||||
### Branch Selection
|
||||
|
||||
When reaching a conditional step:
|
||||
|
||||
1. Observe participant response
|
||||
2. Select appropriate branch:
|
||||
- **Correct**: Proceed to positive path
|
||||
- **Incorrect**: Proceed to correction path
|
||||
3. Select is logged for analysis
|
||||
|
||||
### Manual Actions
|
||||
|
||||
Execute unplanned actions:
|
||||
|
||||
1. Click **+ Action**
|
||||
2. Select from robot actions
|
||||
3. Configure parameters
|
||||
4. Execute immediately
|
||||
|
||||
## Step 6: Trial Completion
|
||||
|
||||
### Automatic Completion
|
||||
|
||||
When all steps complete:
|
||||
1. Final step executes
|
||||
2. Trial status → "Completed"
|
||||
3. Data is saved automatically
|
||||
4. Summary shown
|
||||
|
||||
### Manual Completion
|
||||
|
||||
To end early:
|
||||
|
||||
1. Click **Stop Trial**
|
||||
2. Confirm completion
|
||||
3. Select reason:
|
||||
- Participant fatigue
|
||||
- Technical issue
|
||||
- Protocol complete
|
||||
4. Save partial data
|
||||
|
||||
## Step 7: Post-Trial
|
||||
|
||||
### Automatic Prompts
|
||||
|
||||
After trial completion:
|
||||
|
||||
1. **Participant Debrief**
|
||||
- Thank participant
|
||||
- Answer questions
|
||||
- Collect final feedback
|
||||
|
||||
2. **Survey Distribution**
|
||||
- Send post-session survey
|
||||
- Collect responses
|
||||
|
||||
3. **Data Export**
|
||||
- Download trial data
|
||||
- Export event log
|
||||
|
||||
### Trial Summary
|
||||
|
||||
View trial summary:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Trial Summary - P001 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Duration: 5:23 │
|
||||
│ Steps Completed: 6/6 (100%) │
|
||||
│ Interventions: 2 │
|
||||
│ │
|
||||
│ Actions: │
|
||||
│ ✓ Say Text: "Hello..." (2.3s) │
|
||||
│ ✓ Turn Head: yaw=1.5 (1.1s) │
|
||||
│ ✓ Say Text: "What color..." (3.2s) │
|
||||
│ ⚠ Intervention: Pause (10s) │
|
||||
│ ✓ Branch: Correct selected │
|
||||
│ ✓ Say Text: "Yes! It was red" (2.8s) │
|
||||
│ │
|
||||
│ Events: 18 logged │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Managing Multiple Trials
|
||||
|
||||
### Trial Queue
|
||||
|
||||
View upcoming trials:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Trial Queue [Refresh] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 2:00 PM │ P001 │ Interactive Storyteller │ Scheduled │
|
||||
│ 2:20 PM │ P002 │ Interactive Storyteller │ Scheduled │
|
||||
│ 2:40 PM │ P003 │ Interactive Storyteller │ Scheduled │
|
||||
│ 3:00 PM │ P004 │ Interactive Storyteller │ Scheduled │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Trial History
|
||||
|
||||
View past trials:
|
||||
|
||||
| Participant | Started | Duration | Status | Interventions |
|
||||
|-------------|---------|----------|--------|---------------|
|
||||
| P001 | Today 2:00 PM | 5:23 | Completed | 2 |
|
||||
| P002 | Today 2:20 PM | 4:58 | Completed | 1 |
|
||||
| P003 | Today 2:45 PM | - | In Progress | 0 |
|
||||
|
||||
## Data Collection
|
||||
|
||||
### Automatic Data Capture
|
||||
|
||||
HRIStudio automatically logs:
|
||||
|
||||
- Timestamps for all events
|
||||
- Action executions
|
||||
- Robot responses
|
||||
- Wizard interventions
|
||||
- Participant responses
|
||||
- Timing data
|
||||
|
||||
### Manual Data
|
||||
|
||||
Wizards can add:
|
||||
|
||||
- Timestamped notes
|
||||
- Observation categories
|
||||
- Participant behavior codes
|
||||
- Custom annotations
|
||||
|
||||
### Export Formats
|
||||
|
||||
Download trial data:
|
||||
|
||||
| Format | Contents |
|
||||
|--------|----------|
|
||||
| CSV | Tabular data for spreadsheets |
|
||||
| JSON | Full event log with metadata |
|
||||
| Video | Screen recording (if enabled) |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Trial Won't Start
|
||||
|
||||
1. Check robot connection
|
||||
2. Verify experiment is "Ready"
|
||||
3. Check participant consent
|
||||
4. Review error logs
|
||||
|
||||
### Trial Paused Unexpectedly
|
||||
|
||||
- Robot may have disconnected
|
||||
- Check network connection
|
||||
- Resume when connection restored
|
||||
|
||||
### Data Not Saved
|
||||
|
||||
- Ensure database connection
|
||||
- Check disk space
|
||||
- Export data manually
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Before Trials
|
||||
|
||||
- [ ] Robot connected and tested
|
||||
- [ ] Experiment verified
|
||||
- [ ] Participant consent obtained
|
||||
- [ ] Recording equipment ready
|
||||
- [ ] Wizard briefed on protocol
|
||||
|
||||
### During Trials
|
||||
|
||||
- [ ] Monitor timeline progress
|
||||
- [ ] Take timestamped notes
|
||||
- [ ] Document interventions
|
||||
- [ ] Watch for issues
|
||||
|
||||
### After Trials
|
||||
|
||||
- [ ] Review trial summary
|
||||
- [ ] Export data promptly
|
||||
- [ ] Send follow-up surveys
|
||||
- [ ] Update participant status
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you can run trials:
|
||||
|
||||
1. **[Wizard Interface](05-wizard-interface.md)** - Master real-time control
|
||||
2. **[Data & Analysis](08-data-and-analysis.md)** - Analyze your results
|
||||
3. **[Forms & Surveys](07-forms-and-surveys.md)** - Collect post-trial data
|
||||
|
||||
---
|
||||
|
||||
**Previous**: [Designing Experiments](03-designing-experiments.md) | **Next**: [Wizard Interface](05-wizard-interface.md)
|
||||
@@ -0,0 +1,393 @@
|
||||
# Tutorial 5: Wizard Interface
|
||||
|
||||
Learn how to use the real-time wizard control interface for Wizard-of-Oz studies.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Navigate the wizard interface
|
||||
- Control robot actions in real-time
|
||||
- Make branching decisions
|
||||
- Handle trial interruptions
|
||||
|
||||
## What is the Wizard Interface?
|
||||
|
||||
The **Wizard Interface** is your control center during trials. It provides:
|
||||
|
||||
- Real-time trial monitoring
|
||||
- Robot action controls
|
||||
- Decision-making tools
|
||||
- Intervention capabilities
|
||||
- Event logging
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ WIZARD INTERFACE │
|
||||
├────────────────┬─────────────────────┬──────────────────────┤
|
||||
│ │ │ │
|
||||
│ Trial │ Timeline │ Robot │
|
||||
│ Controls │ Progress │ Status │
|
||||
│ │ │ │
|
||||
│ ┌──────────┐ │ ┌───────────────┐ │ ┌────────────────┐ │
|
||||
│ │ ▶ Play │ │ │ 1 → 2 → 3 → │ │ │ ● Connected │ │
|
||||
│ │ ⏸ Pause │ │ │ ↑ │ │ │ Battery: 85% │ │
|
||||
│ │ ⏹ Stop │ │ │ Step 2 │ │ │ Position: (0,0)│ │
|
||||
│ └──────────┘ │ └───────────────┘ │ └────────────────┘ │
|
||||
│ │ │ │
|
||||
│ ┌──────────┐ │ Progress: 40% │ ┌────────────────┐ │
|
||||
│ │ 📝 Notes │ │ Time: 00:05:23 │ │ Action Panel │ │
|
||||
│ │ ⚠ Alert │ │ │ │ │ │
|
||||
│ └──────────┘ │ │ │ [Say Text] │ │
|
||||
│ │ │ │ [Move Robot] │ │
|
||||
│ │ │ │ [Wave] │ │
|
||||
│ │ │ │ [Custom...] │ │
|
||||
│ │ │ └────────────────┘ │
|
||||
└────────────────┴─────────────────────┴──────────────────────┘
|
||||
```
|
||||
|
||||
## Step 1: Accessing the Wizard Interface
|
||||
|
||||
### Method 1: From Trials List
|
||||
|
||||
1. Go to **Trials** in sidebar
|
||||
2. Find your scheduled trial
|
||||
3. Click **Open Wizard**
|
||||
|
||||
### Method 2: Direct URL
|
||||
|
||||
```
|
||||
/trials/{trialId}/wizard
|
||||
```
|
||||
|
||||
### Method 3: Trial Queue
|
||||
|
||||
1. Go to **Wizard Queue**
|
||||
2. See all pending trials
|
||||
3. Click **Start** on any trial
|
||||
|
||||
## Step 2: Understanding the Layout
|
||||
|
||||
### Left Panel: Trial Controls
|
||||
|
||||
| Control | Function |
|
||||
|---------|----------|
|
||||
| Play/Pause | Start or pause trial |
|
||||
| Stop | End trial early |
|
||||
| Notes | Add timestamped observations |
|
||||
| Alert | Send alert to researchers |
|
||||
|
||||
### Center Panel: Timeline
|
||||
|
||||
- **Visual Progress**: See step progression
|
||||
- **Current Position**: Highlighted current step
|
||||
- **Navigation**: Click to jump to step (if allowed)
|
||||
- **Time Display**: Elapsed and estimated remaining
|
||||
|
||||
### Right Panel: Robot Control
|
||||
|
||||
**Status Section:**
|
||||
- Connection indicator
|
||||
- Battery level
|
||||
- Position tracking
|
||||
- Sensor readings
|
||||
|
||||
**Action Section:**
|
||||
- Quick action buttons
|
||||
- Custom action builder
|
||||
- Action history
|
||||
|
||||
## Step 3: Controlling the Robot
|
||||
|
||||
### Quick Actions
|
||||
|
||||
Pre-configured robot actions:
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| Say Text | Make robot speak |
|
||||
| Wave | Wave gesture |
|
||||
| Look at Me | Turn head toward participant |
|
||||
| Look Away | Turn head elsewhere |
|
||||
| Nod | Confirmation nod |
|
||||
| Shake Head | Negation shake |
|
||||
|
||||
### Custom Say Text
|
||||
|
||||
1. Click **Say Text**
|
||||
2. Enter text in popup:
|
||||
```
|
||||
"Hello! Nice to meet you."
|
||||
```
|
||||
3. Select options:
|
||||
- Speed: Normal / Slow / Fast
|
||||
- Emotion: Neutral / Happy / Excited
|
||||
4. Click **Execute**
|
||||
5. Robot speaks the text
|
||||
|
||||
### Move Robot
|
||||
|
||||
1. Click **Move Robot**
|
||||
2. Select movement type:
|
||||
- Walk Forward/Back
|
||||
- Turn Left/Right
|
||||
- Move Head
|
||||
- Move Arm
|
||||
3. Set parameters
|
||||
4. Execute
|
||||
|
||||
### Custom Actions
|
||||
|
||||
For advanced control:
|
||||
|
||||
1. Click **Custom...**
|
||||
2. Select action from plugin
|
||||
3. Configure parameters
|
||||
4. Execute
|
||||
|
||||
## Step 4: Making Decisions
|
||||
|
||||
When the experiment reaches a branching point:
|
||||
|
||||
### Decision Popup
|
||||
|
||||
A popup appears with options:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Branch Decision Required │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Step: Comprehension Check │
|
||||
│ Question: "What color was the rock?" │
|
||||
│ │
|
||||
│ Participant's response: They said "blue" (incorrect) │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ ○ Correct Response (Red) │ │
|
||||
│ │ → Robot celebrates │ │
|
||||
│ │ │ │
|
||||
│ │ ● Incorrect Response (Other) │ │
|
||||
│ │ → Robot gently corrects │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Cancel] [Confirm Selection] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Decision Guidelines
|
||||
|
||||
1. **Observe** participant's actual response
|
||||
2. **Consider** protocol criteria
|
||||
3. **Select** appropriate branch
|
||||
4. **Confirm** selection
|
||||
|
||||
### After Selection
|
||||
|
||||
- Decision is logged with timestamp
|
||||
- Trial continues on selected path
|
||||
- Both participant and robot continue
|
||||
|
||||
## Step 5: Handling Interruptions
|
||||
|
||||
### Pause Trial
|
||||
|
||||
When you need to pause:
|
||||
|
||||
1. Click **Pause** button
|
||||
2. Optionally add reason:
|
||||
- Participant needs break
|
||||
- Technical issue
|
||||
- External interruption
|
||||
3. Trial pauses, robot holds position
|
||||
|
||||
### Resume Trial
|
||||
|
||||
1. Click **Play** button
|
||||
2. Trial resumes from pause point
|
||||
3. Pause duration is logged
|
||||
|
||||
### Stop Trial
|
||||
|
||||
For early termination:
|
||||
|
||||
1. Click **Stop** button
|
||||
2. Select reason:
|
||||
- Participant fatigue
|
||||
- Technical failure
|
||||
- Protocol deviation
|
||||
- Participant withdrawal
|
||||
3. Confirm stop
|
||||
4. Partial data is saved
|
||||
|
||||
### Add Notes
|
||||
|
||||
Record observations:
|
||||
|
||||
1. Click **Notes** button
|
||||
2. Enter observation:
|
||||
```
|
||||
Participant laughed at the robot's gesture.
|
||||
```
|
||||
3. Note is timestamped automatically
|
||||
4. Notes appear in event log
|
||||
|
||||
### Send Alert
|
||||
|
||||
Notify researchers:
|
||||
|
||||
1. Click **Alert** button
|
||||
2. Select alert type:
|
||||
- Technical issue
|
||||
- Safety concern
|
||||
- Protocol question
|
||||
- Other
|
||||
3. Add description
|
||||
4. Send alert
|
||||
|
||||
## Step 6: Monitoring Robot Status
|
||||
|
||||
### Connection Status
|
||||
|
||||
| Status | Icon | Meaning |
|
||||
|--------|------|---------|
|
||||
| Connected | ● Green | Robot responding |
|
||||
| Connecting | ● Yellow | Attempting connection |
|
||||
| Disconnected | ● Red | No robot connection |
|
||||
| Error | ⚠ Orange | Connection error |
|
||||
|
||||
### Battery Monitor
|
||||
|
||||
View battery level:
|
||||
- Green: > 50%
|
||||
- Yellow: 20-50%
|
||||
- Red: < 20%
|
||||
|
||||
### Sensor Display
|
||||
|
||||
Real-time sensor readings:
|
||||
- Joint positions
|
||||
- Touch sensors
|
||||
- Sonar distances
|
||||
- Camera feed (if available)
|
||||
|
||||
### Action Queue
|
||||
|
||||
See pending/executing actions:
|
||||
```
|
||||
Executing: Say Text "Hello!"
|
||||
Pending: Move Head (queued)
|
||||
```
|
||||
|
||||
## Step 7: Keyboard Shortcuts
|
||||
|
||||
Speed up your workflow:
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Space | Play/Pause toggle |
|
||||
| Escape | Stop trial |
|
||||
| N | Add note |
|
||||
| A | Send alert |
|
||||
| 1-9 | Execute quick action |
|
||||
| ← → | Navigate timeline |
|
||||
| ↑ ↓ | Select branch option |
|
||||
|
||||
## Step 8: Event Logging
|
||||
|
||||
All actions are logged automatically:
|
||||
|
||||
```
|
||||
[14:32:05] Trial started
|
||||
[14:32:07] Step 1: The Hook
|
||||
[14:32:08] Action: Say Text "Hello!"
|
||||
[14:32:11] Action: Move Arm Wave
|
||||
[14:32:15] Step 2: The Narrative
|
||||
[14:32:16] Action: Say Text "Once upon a time..."
|
||||
[14:33:05] Step 3: Comprehension Check
|
||||
[14:33:06] Action: Say Text "What color was the rock?"
|
||||
[14:33:28] Wizard Note: "Participant said blue"
|
||||
[14:33:30] Branch: Incorrect selected
|
||||
[14:33:31] Step 4b: Correction
|
||||
[14:33:32] Action: Say Text "Actually, it was red."
|
||||
[14:34:05] Trial completed
|
||||
```
|
||||
|
||||
## Trial Modes
|
||||
|
||||
### Observer Mode
|
||||
|
||||
For observers (read-only):
|
||||
- View trial progress
|
||||
- See robot status
|
||||
- Cannot execute actions
|
||||
- Can add notes
|
||||
|
||||
### Active Wizard Mode
|
||||
|
||||
Full control:
|
||||
- Execute actions
|
||||
- Make decisions
|
||||
- Pause/resume
|
||||
- Add notes/alerts
|
||||
|
||||
### Training Mode
|
||||
|
||||
Practice without real data:
|
||||
- Simulated robot
|
||||
- No data saved
|
||||
- Safe to experiment
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Before Trial
|
||||
|
||||
- [ ] Review experiment protocol
|
||||
- [ ] Test robot connection
|
||||
- [ ] Familiarize with action panel
|
||||
- [ ] Know decision criteria
|
||||
|
||||
### During Trial
|
||||
|
||||
- [ ] Stay focused on participant
|
||||
- [ ] Make decisions based on observation
|
||||
- [ ] Document notable events
|
||||
- [ ] Keep action log clean
|
||||
|
||||
### After Trial
|
||||
|
||||
- [ ] Review event log
|
||||
- [ ] Add final notes
|
||||
- [ ] Confirm data saved
|
||||
- [ ] Prepare for next trial
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Robot Not Responding
|
||||
|
||||
1. Check connection indicator
|
||||
2. Verify network
|
||||
3. Check robot power
|
||||
4. Restart connection
|
||||
|
||||
### Actions Not Executing
|
||||
|
||||
1. Check action queue
|
||||
2. Verify parameters
|
||||
3. Check robot state (not in rest mode)
|
||||
|
||||
### Decision Popup Not Appearing
|
||||
|
||||
1. Check if step has branches
|
||||
2. Verify step type is "conditional"
|
||||
3. Contact researcher
|
||||
|
||||
## Next Steps
|
||||
|
||||
Mastered the wizard interface?
|
||||
|
||||
1. **[Robot Integration](06-robot-integration.md)** - Deep dive into robot control
|
||||
2. **[Data & Analysis](08-data-and-analysis.md)** - Review trial data
|
||||
3. **[Simulation Mode](09-simulation-mode.md)** - Practice without a robot
|
||||
|
||||
---
|
||||
|
||||
**Previous**: [Running Trials](04-running-trials.md) | **Next**: [Robot Integration](06-robot-integration.md)
|
||||
@@ -0,0 +1,386 @@
|
||||
# Tutorial 6: Robot Integration
|
||||
|
||||
Learn how to connect and configure robots for your HRI studies.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Connect NAO6 robot to HRIStudio
|
||||
- Configure robot plugins
|
||||
- Test robot connection
|
||||
- Troubleshoot common issues
|
||||
|
||||
## Supported Robots
|
||||
|
||||
HRIStudio supports multiple robot platforms:
|
||||
|
||||
| Robot | Protocol | Actions |
|
||||
|-------|----------|---------|
|
||||
| **NAO6** | ROS2 | Speech, movement, gestures, sensors |
|
||||
| **TurtleBot3** | ROS2 | Navigation, sensors |
|
||||
| **Mock Robot** | WebSocket | All actions (simulation) |
|
||||
|
||||
## Understanding the Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ HRIStudio Platform │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────────────┐ │
|
||||
│ │ Wizard │◄────────────►│ Robot Communication │ │
|
||||
│ │ Interface │ WebSocket │ Service │ │
|
||||
│ └──────────────┘ └──────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ │ ROS Bridge │
|
||||
│ ┌─────▼─────┐ │
|
||||
│ │ rosbridge │ │
|
||||
│ │ :9090 │ │
|
||||
│ └─────┬─────┘ │
|
||||
└────────────────────────────────────────────┼─────────────────┘
|
||||
│
|
||||
┌─────────────────┼─────────────────┐
|
||||
│ │ │
|
||||
┌─────▼─────┐ ┌─────▼─────┐ │
|
||||
│ NAO │ │ NAO │ │
|
||||
│ Driver │ │ Robot │ │
|
||||
│ (ROS2) │◄───►│ (naoqi) │ │
|
||||
└───────────┘ └───────────┘ │
|
||||
Network Robot │
|
||||
```
|
||||
|
||||
## Step 1: Set Up NAO6 Robot
|
||||
|
||||
### Network Configuration
|
||||
|
||||
1. Connect NAO6 to your network:
|
||||
```
|
||||
# On the robot, say "Connect to Wi-Fi"
|
||||
# Or use the Choregraphe interface
|
||||
```
|
||||
|
||||
2. Note the robot's IP address:
|
||||
```
|
||||
# On the robot, say "What is my IP address?"
|
||||
# Or check robot's network settings
|
||||
```
|
||||
|
||||
3. Verify network access:
|
||||
```bash
|
||||
ping nao.local
|
||||
# Or ping the IP directly:
|
||||
ping 192.168.1.100
|
||||
```
|
||||
|
||||
### Robot Credentials
|
||||
|
||||
Default credentials:
|
||||
```
|
||||
Username: nao
|
||||
Password: robolab
|
||||
```
|
||||
|
||||
### Wake Up Robot
|
||||
|
||||
Before connecting, wake up the robot:
|
||||
|
||||
```bash
|
||||
ssh nao@192.168.1.100
|
||||
# Enter password when prompted
|
||||
|
||||
# Wake up the robot
|
||||
python -c "from naoqi import ALProxy; proxy = ALProxy('ALMotion', '192.168.1.100', 9559); proxy.wakeUp()"
|
||||
```
|
||||
|
||||
## Step 2: Start Docker Services
|
||||
|
||||
### Using Docker Compose
|
||||
|
||||
```bash
|
||||
cd ~/nao6-hristudio-integration
|
||||
|
||||
# Set robot IP
|
||||
export NAO_IP=192.168.1.100
|
||||
|
||||
# Start services
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Services Overview
|
||||
|
||||
| Service | Port | Purpose |
|
||||
|---------|------|---------|
|
||||
| `nao_driver` | - | ROS2 driver for NAO |
|
||||
| `ros_bridge` | 9090 | WebSocket bridge |
|
||||
| `ros_api` | - | Topic introspection |
|
||||
|
||||
### Verify Services
|
||||
|
||||
```bash
|
||||
# Check running containers
|
||||
docker ps
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
|
||||
# Test WebSocket connection
|
||||
ws://localhost:9090
|
||||
```
|
||||
|
||||
## Step 3: Configure HRIStudio
|
||||
|
||||
### Install Robot Plugin
|
||||
|
||||
1. Go to **Plugins** in sidebar
|
||||
2. Select your study
|
||||
3. Click **Browse Plugins**
|
||||
4. Find **NAO6 Robot (ROS2 Integration)**
|
||||
5. Click **Install**
|
||||
|
||||
### Configure Plugin
|
||||
|
||||
Set robot connection:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ NAO6 Robot Configuration │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Robot Name: NAO6-Lab │
|
||||
│ Robot IP: 192.168.1.100 │
|
||||
│ WebSocket URL: ws://localhost:9090 │
|
||||
│ │
|
||||
│ Advanced Settings: │
|
||||
│ □ Use Simulation Mode │
|
||||
│ Connection Timeout: 30 seconds │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create `hristudio/.env.local`:
|
||||
|
||||
```bash
|
||||
# Robot connection
|
||||
NAO_ROBOT_IP=192.168.1.100
|
||||
NAO_PASSWORD=robolab
|
||||
NAO_USERNAME=nao
|
||||
|
||||
# WebSocket bridge
|
||||
NEXT_PUBLIC_ROS_BRIDGE_URL=ws://localhost:9090
|
||||
```
|
||||
|
||||
## Step 4: Test Connection
|
||||
|
||||
### Using the NAO Test Page
|
||||
|
||||
1. Navigate to: `http://localhost:3000/nao-test`
|
||||
2. Click **Connect**
|
||||
3. Verify connection status
|
||||
|
||||
### Connection Status Indicators
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| **Connected** | Robot responding normally |
|
||||
| **Connecting** | Attempting connection |
|
||||
| **Error** | Connection failed |
|
||||
| **Timeout** | Robot not responding |
|
||||
|
||||
### Test Actions
|
||||
|
||||
Test basic robot actions:
|
||||
|
||||
| Action | Expected Behavior |
|
||||
|--------|-------------------|
|
||||
| Say Text | Robot speaks |
|
||||
| Wave | Robot waves arm |
|
||||
| Walk Forward | Robot walks |
|
||||
| Turn Left | Robot turns |
|
||||
|
||||
## Step 5: Robot Actions Reference
|
||||
|
||||
### Speech Actions
|
||||
|
||||
| Action | Parameters | Description |
|
||||
|--------|------------|-------------|
|
||||
| `say_text` | `text` | Speak text |
|
||||
| `say_with_emotion` | `text`, `emotion` | Emotional speech |
|
||||
| `set_volume` | `level` | Set speech volume |
|
||||
| `set_language` | `language` | Set speech language |
|
||||
|
||||
### Movement Actions
|
||||
|
||||
| Action | Parameters | Description |
|
||||
|--------|------------|-------------|
|
||||
| `walk_forward` | `speed`, `duration` | Walk forward |
|
||||
| `walk_backward` | `speed` | Walk backward |
|
||||
| `turn_left` | `speed` | Turn left |
|
||||
| `turn_right` | `speed` | Turn right |
|
||||
| `stop` | - | Stop all movement |
|
||||
|
||||
### Head Actions
|
||||
|
||||
| Action | Parameters | Description |
|
||||
|--------|------------|-------------|
|
||||
| `move_head` | `yaw`, `pitch`, `speed` | Move head to position |
|
||||
| `turn_head` | `yaw`, `pitch` | Turn head (relative) |
|
||||
|
||||
### Arm Actions
|
||||
|
||||
| Action | Parameters | Description |
|
||||
|--------|------------|-------------|
|
||||
| `move_arm` | `arm`, joint angles | Move arm to position |
|
||||
| `wave` | `arm` | Wave gesture |
|
||||
|
||||
### Autonomous Life
|
||||
|
||||
| Action | Parameters | Description |
|
||||
|--------|------------|-------------|
|
||||
| `wake_up` | - | Wake robot from rest |
|
||||
| `rest` | - | Put robot to rest |
|
||||
| `set_autonomous_life` | `enabled` | Toggle autonomous behavior |
|
||||
|
||||
## Step 6: Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Robot Not Found
|
||||
|
||||
```
|
||||
Error: Cannot connect to robot at 192.168.1.100
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. Verify IP address: `ping 192.168.1.100`
|
||||
2. Check robot is powered on
|
||||
3. Verify network connectivity
|
||||
4. Try `nao.local` hostname
|
||||
|
||||
#### WebSocket Connection Failed
|
||||
|
||||
```
|
||||
Error: WebSocket connection to ws://localhost:9090 failed
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. Check Docker is running
|
||||
2. Verify ros_bridge container: `docker ps`
|
||||
3. Check port 9090 is not blocked
|
||||
4. Restart services: `docker compose restart`
|
||||
|
||||
#### Robot Not Responding
|
||||
|
||||
Robot connected but actions don't execute.
|
||||
|
||||
**Solutions:**
|
||||
1. Wake up robot: `ssh nao@IP python -c "from naoqi import ALProxy; p=ALProxy('ALMotion','IP',9559);p.wakeUp()"`
|
||||
2. Check robot is not in rest mode
|
||||
3. Verify no blocking software on robot
|
||||
|
||||
#### Action Timeout
|
||||
|
||||
```
|
||||
Error: Action timed out after 30 seconds
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. Robot may be busy with previous action
|
||||
2. Check network latency
|
||||
3. Increase timeout in settings
|
||||
|
||||
### Diagnostic Commands
|
||||
|
||||
```bash
|
||||
# Check Docker containers
|
||||
docker ps
|
||||
|
||||
# View all logs
|
||||
docker compose logs
|
||||
|
||||
# View specific service
|
||||
docker compose logs ros_bridge
|
||||
|
||||
# Restart services
|
||||
docker compose restart
|
||||
|
||||
# Stop and start fresh
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Network Troubleshooting
|
||||
|
||||
```bash
|
||||
# Check robot IP
|
||||
ssh nao@IP "ifconfig"
|
||||
|
||||
# Test from robot
|
||||
ssh nao@IP "curl localhost:9090"
|
||||
|
||||
# Check firewall
|
||||
sudo iptables -L
|
||||
```
|
||||
|
||||
## Step 7: Robot Maintenance
|
||||
|
||||
### Battery Management
|
||||
|
||||
- Check battery before each session
|
||||
- Aim for >50% battery
|
||||
- Charge during breaks
|
||||
- Replace battery if <20% capacity
|
||||
|
||||
### Calibration
|
||||
|
||||
Periodically calibrate:
|
||||
- Joint positions
|
||||
- Camera alignment
|
||||
- Touch sensors
|
||||
- Sound localization
|
||||
|
||||
### Software Updates
|
||||
|
||||
Keep robot software updated:
|
||||
- NAOqi version
|
||||
- ROS2 packages
|
||||
- HRIStudio plugin
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Network Security
|
||||
|
||||
- Use encrypted network (WPA2/WPA3)
|
||||
- Firewall robot from internet
|
||||
- Use strong passwords
|
||||
|
||||
### SSH Access
|
||||
|
||||
- Change default passwords
|
||||
- Use SSH keys when possible
|
||||
- Limit SSH access
|
||||
|
||||
### Data Security
|
||||
|
||||
- Robot camera data may be sensitive
|
||||
- Store data securely
|
||||
- Follow IRB guidelines
|
||||
|
||||
## Simulation Mode
|
||||
|
||||
For testing without a robot:
|
||||
|
||||
1. Enable simulation mode in settings
|
||||
2. Or set `NEXT_PUBLIC_SIMULATION_MODE=true`
|
||||
3. All actions are simulated locally
|
||||
|
||||
See [Simulation Mode Tutorial](09-simulation-mode.md) for details.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that your robot is connected:
|
||||
|
||||
1. **[Running Trials](04-running-trials.md)** - Execute trials with robot
|
||||
2. **[Wizard Interface](05-wizard-interface.md)** - Control the robot
|
||||
3. **[Data & Analysis](08-data-and-analysis.md)** - Collect interaction data
|
||||
|
||||
---
|
||||
|
||||
**Previous**: [Wizard Interface](05-wizard-interface.md) | **Next**: [Forms & Surveys](07-forms-and-surveys.md)
|
||||
@@ -0,0 +1,505 @@
|
||||
# Tutorial 7: Forms & Surveys
|
||||
|
||||
Learn how to create and manage consent forms, surveys, and questionnaires.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Create consent forms for IRB compliance
|
||||
- Build post-session surveys
|
||||
- Collect participant responses
|
||||
- Manage form templates
|
||||
|
||||
## Form Types
|
||||
|
||||
HRIStudio supports three form types:
|
||||
|
||||
| Type | Purpose | When |
|
||||
|------|---------|------|
|
||||
| **Consent** | Informed consent for participation | Before trial |
|
||||
| **Survey** | Collect feedback and observations | After trial |
|
||||
| **Questionnaire** | Demographic data collection | Any time |
|
||||
|
||||
## Step 1: Access Forms
|
||||
|
||||
1. Go to your **Study**
|
||||
2. Click **Forms** tab
|
||||
3. View existing forms and templates
|
||||
|
||||
### Form List View
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Forms [+ Create] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Name Type Responses Status │
|
||||
│ ─────────────────────────────────────────────────────────── │
|
||||
│ Informed Consent Consent 12/20 Active │
|
||||
│ Post-Session Survey Survey 8/20 Active │
|
||||
│ Demographics Questionnaire 15/20 Active │
|
||||
│ Template: Standard Consent - Template │
|
||||
│ Template: Feedback Survey - Template │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Step 2: Create a Form
|
||||
|
||||
### Using a Template
|
||||
|
||||
1. Click **Create Form**
|
||||
2. Select **Use Template**
|
||||
3. Choose template:
|
||||
- Informed Consent
|
||||
- Post-Session Survey
|
||||
- Demographics
|
||||
4. Customize as needed
|
||||
|
||||
### From Scratch
|
||||
|
||||
1. Click **Create Form**
|
||||
2. Select **Blank Form**
|
||||
3. Choose form type
|
||||
4. Build fields manually
|
||||
|
||||
## Step 3: Form Builder
|
||||
|
||||
The form builder lets you create custom fields:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Form Builder: Post-Session Survey │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Form Settings │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Title: Post-Session Survey │ │
|
||||
│ │ Type: Survey │ │
|
||||
│ │ Active: ☑ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Fields │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 1. [Rating] How engaging was the robot? [✕] │ │
|
||||
│ │ 2. [Text] What did you enjoy most? [✕] │ │
|
||||
│ │ 3. [Multiple Choice] Robot personality? [✕] │ │
|
||||
│ │ │ │
|
||||
│ │ [+ Add Field] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Preview │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ How engaging was the robot? │ │
|
||||
│ │ ○ 1 ○ 2 ○ 3 ○ 4 ○ 5 │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Cancel] [Save] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Step 4: Field Types
|
||||
|
||||
### Text Field
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Field Type: Text │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Label: Participant Age │
|
||||
│ Required: ☑ │
|
||||
│ Placeholder: e.g., 25 │
|
||||
│ │
|
||||
│ Preview: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Participant Age * │ │
|
||||
│ │ [e.g., 25 ] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Rating Scale
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Field Type: Rating │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Label: How engaging was the robot? │
|
||||
│ Required: ☑ │
|
||||
│ Scale: 1 to [5] │
|
||||
│ Low Label: Not at all engaging │
|
||||
│ High Label: Very engaging │
|
||||
│ │
|
||||
│ Preview: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ How engaging was the robot? * │ │
|
||||
│ │ │ │
|
||||
│ │ 1 2 3 4 5 │ │
|
||||
│ │ ○ ○ ○ ○ ○ │ │
|
||||
│ │ Not at all Very engaging │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Multiple Choice
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Field Type: Multiple Choice │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Label: Did the robot respond appropriately? │
|
||||
│ Required: ☑ │
|
||||
│ Options: │
|
||||
│ 1. Yes, always │
|
||||
│ 2. Yes, most of the time │
|
||||
│ 3. Sometimes │
|
||||
│ 4. Rarely │
|
||||
│ 5. No │
|
||||
│ │
|
||||
│ Preview: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Did the robot respond appropriately? * │ │
|
||||
│ │ │ │
|
||||
│ │ ○ Yes, always │ │
|
||||
│ │ ○ Yes, most of the time │ │
|
||||
│ │ ○ Sometimes │ │
|
||||
│ │ ○ Rarely │ │
|
||||
│ │ ○ No │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Yes/No
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Field Type: Yes/No │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Label: Would you interact with this robot again? │
|
||||
│ Required: ☐ │
|
||||
│ │
|
||||
│ Preview: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Would you interact with this robot again? │ │
|
||||
│ │ │ │
|
||||
│ │ ○ Yes ○ No │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Text Area
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Field Type: Text Area │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Label: What did you enjoy most about the interaction? │
|
||||
│ Required: ☐ │
|
||||
│ Rows: [4] │
|
||||
│ │
|
||||
│ Preview: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ What did you enjoy most about the interaction? │ │
|
||||
│ │ │ │
|
||||
│ │ [ ] │ │
|
||||
│ │ [ ] │ │
|
||||
│ │ [ ] │ │
|
||||
│ │ [ ] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Date
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Field Type: Date │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Label: Session Date │
|
||||
│ Required: ☑ │
|
||||
│ │
|
||||
│ Preview: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Session Date * │ │
|
||||
│ │ [📅 Select date ] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Signature
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Field Type: Signature │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Label: Participant Signature │
|
||||
│ Required: ☑ │
|
||||
│ │
|
||||
│ Preview: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Participant Signature * │ │
|
||||
│ │ │ │
|
||||
│ │ ┌───────────────────────────────────────────────┐ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ [Sign here] │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ └───────────────────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Step 5: Consent Forms
|
||||
|
||||
### Required Elements
|
||||
|
||||
For IRB compliance, consent forms must include:
|
||||
|
||||
- [ ] Study title and purpose
|
||||
- [ ] Principal investigator
|
||||
- [ ] Procedures description
|
||||
- [ ] Risks and benefits
|
||||
- [ ] Confidentiality statement
|
||||
- [ ] Voluntary participation note
|
||||
- [ ] Signature and date fields
|
||||
|
||||
### Consent Form Template
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Informed Consent",
|
||||
"type": "consent",
|
||||
"fields": [
|
||||
{ "type": "text", "label": "Study Title", "required": true },
|
||||
{ "type": "text", "label": "Principal Investigator", "required": true },
|
||||
{ "type": "textarea", "label": "Purpose of the Study", "required": true },
|
||||
{ "type": "textarea", "label": "Procedures", "required": true },
|
||||
{ "type": "textarea", "label": "Risks and Benefits", "required": true },
|
||||
{ "type": "textarea", "label": "Confidentiality", "required": true },
|
||||
{ "type": "yes_no", "label": "I consent to participate", "required": true },
|
||||
{ "type": "signature", "label": "Participant Signature", "required": true },
|
||||
{ "type": "date", "label": "Date", "required": true }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Step 6: Surveys
|
||||
|
||||
### Post-Session Survey Example
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Post-Session Questionnaire",
|
||||
"type": "survey",
|
||||
"fields": [
|
||||
{
|
||||
"type": "rating",
|
||||
"label": "How engaging was the robot?",
|
||||
"settings": { "scale": 5 }
|
||||
},
|
||||
{
|
||||
"type": "rating",
|
||||
"label": "How natural did the interaction feel?",
|
||||
"settings": { "scale": 5 }
|
||||
},
|
||||
{
|
||||
"type": "multiple_choice",
|
||||
"label": "Did the robot respond appropriately?",
|
||||
"options": ["Always", "Usually", "Sometimes", "Rarely", "Never"]
|
||||
},
|
||||
{
|
||||
"type": "textarea",
|
||||
"label": "What did you like most?"
|
||||
},
|
||||
{
|
||||
"type": "textarea",
|
||||
"label": "What could be improved?"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Questionnaire Example (Demographics)
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Demographics",
|
||||
"type": "questionnaire",
|
||||
"fields": [
|
||||
{ "type": "text", "label": "Age" },
|
||||
{
|
||||
"type": "multiple_choice",
|
||||
"label": "Gender",
|
||||
"options": ["Male", "Female", "Non-binary", "Prefer not to say"]
|
||||
},
|
||||
{
|
||||
"type": "multiple_choice",
|
||||
"label": "Experience with robots",
|
||||
"options": ["None", "A little", "Moderate", "Extensive"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Step 7: Form Versions
|
||||
|
||||
Forms support versioning for IRB compliance:
|
||||
|
||||
1. Create new version when modifying:
|
||||
- Question text changes
|
||||
- New fields added
|
||||
- Required fields changed
|
||||
|
||||
2. Version history:
|
||||
```
|
||||
Version 1 (Current) - Active
|
||||
Version 2 - Draft
|
||||
Version 3 - Archived
|
||||
```
|
||||
|
||||
3. Track changes:
|
||||
- Version number
|
||||
- Change date
|
||||
- Change description
|
||||
|
||||
## Step 8: Distributing Forms
|
||||
|
||||
### Automatic Distribution
|
||||
|
||||
Configure automatic form sending:
|
||||
|
||||
1. Open form settings
|
||||
2. Enable **Auto-distribute**
|
||||
3. Set trigger:
|
||||
- Before trial (consent)
|
||||
- After trial (survey)
|
||||
4. Select participants
|
||||
|
||||
### Manual Distribution
|
||||
|
||||
Send forms manually:
|
||||
|
||||
1. Open form
|
||||
2. Click **Distribute**
|
||||
3. Select participants
|
||||
4. Choose delivery method
|
||||
|
||||
### Participant Link
|
||||
|
||||
Generate shareable link:
|
||||
|
||||
```
|
||||
https://hristudio.example.com/forms/{formId}?participant={participantCode}
|
||||
```
|
||||
|
||||
## Step 9: Collecting Responses
|
||||
|
||||
### View Responses
|
||||
|
||||
1. Open form
|
||||
2. Click **Responses** tab
|
||||
3. View individual submissions
|
||||
|
||||
### Response Dashboard
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Form Responses: Post-Session Survey │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Total Responses: 15/20 (75%) │
|
||||
│ │
|
||||
│ Question: How engaging was the robot? │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 5 ████████████████████████████████████ 8 responses │ │
|
||||
│ │ 4 ██████████████████ 5 responses │ │
|
||||
│ │ 3 ████████ 2 responses │ │
|
||||
│ │ 2 ████ 1 response │ │
|
||||
│ │ 1 ████ 1 response │ │
|
||||
│ │ │ │
|
||||
│ │ Average: 4.2 / 5.0 │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Export Responses
|
||||
|
||||
Download collected data:
|
||||
|
||||
| Format | Contents |
|
||||
|--------|----------|
|
||||
| CSV | Tabular data |
|
||||
| JSON | Full response objects |
|
||||
| PDF | Printed consent forms |
|
||||
|
||||
## Step 10: Form Templates
|
||||
|
||||
### Creating Templates
|
||||
|
||||
1. Create form with desired fields
|
||||
2. Click **Save as Template**
|
||||
3. Enter template name
|
||||
4. Template is available for reuse
|
||||
|
||||
### Template Library
|
||||
|
||||
| Template | Use Case |
|
||||
|----------|----------|
|
||||
| Standard Consent | Generic research consent |
|
||||
| Child Consent | Studies with minors |
|
||||
| Extended Consent | Complex procedures |
|
||||
| Feedback Survey | Post-session feedback |
|
||||
| NASA-TLX | Workload assessment |
|
||||
| SUS | System usability |
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Consent Forms
|
||||
|
||||
- [ ] Review with IRB before use
|
||||
- [ ] Keep language simple
|
||||
- [ ] Include all required elements
|
||||
- [ ] Version control for changes
|
||||
- [ ] Store signed forms securely
|
||||
|
||||
### Surveys
|
||||
|
||||
- [ ] Keep questions concise
|
||||
- [ ] Use appropriate scales
|
||||
- [ ] Test with pilot participants
|
||||
- [ ] Randomize order when appropriate
|
||||
- [ ] Include open-ended questions
|
||||
|
||||
### Data Management
|
||||
|
||||
- [ ] Export data regularly
|
||||
- [ ] Backup responses
|
||||
- [ ] Anonymize data for analysis
|
||||
- [ ] Follow data retention policy
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Form Not Loading
|
||||
|
||||
- Check form is active
|
||||
- Verify participant access
|
||||
- Check network connection
|
||||
|
||||
### Response Not Saving
|
||||
|
||||
- Check required fields
|
||||
- Verify session active
|
||||
- Try again or refresh
|
||||
|
||||
### Participant Can't Access
|
||||
|
||||
- Verify participant code valid
|
||||
- Check form is distributed
|
||||
- Confirm study is active
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you've created your forms:
|
||||
|
||||
1. **[Running Trials](04-running-trials.md)** - Connect forms to trials
|
||||
2. **[Data & Analysis](08-data-and-analysis.md)** - Analyze collected data
|
||||
3. **[Your First Study](02-your-first-study.md)** - Set up your study
|
||||
|
||||
---
|
||||
|
||||
**Previous**: [Robot Integration](06-robot-integration.md) | **Next**: [Data & Analysis](08-data-and-analysis.md)
|
||||
@@ -0,0 +1,505 @@
|
||||
# Tutorial 8: Data & Analysis
|
||||
|
||||
Learn how to collect, export, and analyze trial data from HRIStudio.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Understand data collection in HRIStudio
|
||||
- Export trial data in various formats
|
||||
- Analyze event logs
|
||||
- Generate reports
|
||||
|
||||
## Data Collection Overview
|
||||
|
||||
HRIStudio automatically captures comprehensive data during trials:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Data Collection │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Trial Metadata │
|
||||
│ ├── Start/End times │
|
||||
│ ├── Duration │
|
||||
│ ├── Participant info │
|
||||
│ └── Experiment version │
|
||||
│ │
|
||||
│ Event Log (Timestamped) │
|
||||
│ ├── Step changes │
|
||||
│ ├── Action executions │
|
||||
│ ├── Robot responses │
|
||||
│ └── Wizard interventions │
|
||||
│ │
|
||||
│ Form Responses │
|
||||
│ ├── Consent forms │
|
||||
│ ├── Surveys │
|
||||
│ └── Questionnaires │
|
||||
│ │
|
||||
│ Sensor Data │
|
||||
│ ├── Joint positions │
|
||||
│ ├── Touch events │
|
||||
│ └── Audio/video (if enabled) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Step 1: Accessing Trial Data
|
||||
|
||||
### From Trial List
|
||||
|
||||
1. Go to **Trials** tab
|
||||
2. Find completed trial
|
||||
3. Click **View Details**
|
||||
|
||||
### From Study Dashboard
|
||||
|
||||
1. Open your study
|
||||
2. Go to **Data** tab
|
||||
3. Select trial or view aggregate
|
||||
|
||||
## Step 2: Trial Event Log
|
||||
|
||||
Each trial generates a complete event log:
|
||||
|
||||
```json
|
||||
{
|
||||
"trialId": "trial_abc123",
|
||||
"participantCode": "P001",
|
||||
"experimentName": "Interactive Storyteller",
|
||||
"startedAt": "2024-03-15T14:00:00Z",
|
||||
"completedAt": "2024-03-15T14:05:23Z",
|
||||
"duration": 323,
|
||||
"status": "completed",
|
||||
"events": [
|
||||
{
|
||||
"timestamp": "2024-03-15T14:00:00.123Z",
|
||||
"type": "trial_started",
|
||||
"stepId": null,
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2024-03-15T14:00:02.456Z",
|
||||
"type": "step_changed",
|
||||
"stepId": "step_1",
|
||||
"stepName": "The Hook",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2024-03-15T14:00:03.789Z",
|
||||
"type": "action_executed",
|
||||
"actionName": "Say Text",
|
||||
"parameters": { "text": "Hello!" },
|
||||
"duration": 2300,
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"timestamp": "2024-03-15T14:00:08.012Z",
|
||||
"type": "action_executed",
|
||||
"actionName": "Wave",
|
||||
"duration": 1500,
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"timestamp": "2024-03-15T14:02:30.123Z",
|
||||
"type": "intervention",
|
||||
"interventionType": "note",
|
||||
"data": { "note": "Participant laughed" }
|
||||
},
|
||||
{
|
||||
"timestamp": "2024-03-15T14:03:00.456Z",
|
||||
"type": "wizard_response",
|
||||
"variable": "last_response",
|
||||
"selectedValue": "correct",
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2024-03-15T14:05:23.789Z",
|
||||
"type": "trial_completed",
|
||||
"data": { "stepsCompleted": 6 }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Event Types
|
||||
|
||||
| Event Type | Description | Data Captured |
|
||||
|------------|-------------|---------------|
|
||||
| `trial_started` | Trial began | Timestamp |
|
||||
| `step_changed` | New step began | Step ID, name |
|
||||
| `action_executed` | Robot action | Action details, duration |
|
||||
| `action_completed` | Action finished | Duration, result |
|
||||
| `action_failed` | Action failed | Error details |
|
||||
| `wizard_response` | Wizard decision | Selected option |
|
||||
| `intervention` | Wizard intervention | Type, note |
|
||||
| `trial_paused` | Trial paused | Reason |
|
||||
| `trial_resumed` | Trial resumed | Pause duration |
|
||||
| `trial_completed` | Trial finished | Summary |
|
||||
|
||||
## Step 3: Exporting Data
|
||||
|
||||
### Export Single Trial
|
||||
|
||||
1. Open trial details
|
||||
2. Click **Export**
|
||||
3. Select format
|
||||
|
||||
### Export Study Data
|
||||
|
||||
1. Open study
|
||||
2. Go to **Data** tab
|
||||
3. Click **Export All**
|
||||
4. Select options:
|
||||
- Date range
|
||||
- Trial status
|
||||
- Include forms
|
||||
|
||||
### Export Formats
|
||||
|
||||
#### CSV Format
|
||||
|
||||
```csv
|
||||
trial_id,participant,experiment,started_at,duration,status,steps_completed
|
||||
trial_abc,P001,Interactive Storyteller,2024-03-15T14:00:00Z,323,completed,6
|
||||
trial_def,P002,Interactive Storyteller,2024-03-15T14:20:00Z,298,completed,6
|
||||
trial_ghi,P003,Interactive Storyteller,2024-03-15T14:40:00Z,0,failed,1
|
||||
```
|
||||
|
||||
#### JSON Format
|
||||
|
||||
```json
|
||||
{
|
||||
"exportDate": "2024-03-15T15:00:00Z",
|
||||
"studyName": "Robot Trust Study",
|
||||
"trials": [...],
|
||||
"forms": [...],
|
||||
"metadata": {
|
||||
"totalTrials": 20,
|
||||
"completedTrials": 18,
|
||||
"averageDuration": 312
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Event Log CSV
|
||||
|
||||
```csv
|
||||
timestamp,event_type,step_name,action_name,parameters,duration,status
|
||||
2024-03-15T14:00:00.123Z,trial_started,,,,,
|
||||
2024-03-15T14:00:02.456Z,step_changed,The Hook,,,,
|
||||
2024-03-15T14:00:03.789Z,action_executed,The Hook,Say Text,"{""text"":""Hello!""}",2300,completed
|
||||
2024-03-15T14:00:08.012Z,action_executed,The Hook,Wave,,1500,completed
|
||||
2024-03-15T14:02:30.123Z,intervention,The Narrative,Note,"{""note"":""Participant laughed""}",,,
|
||||
2024-03-15T14:03:00.456Z,wizard_response,Comprehension Check,Correct,,,,
|
||||
2024-03-15T14:05:23.789Z,trial_completed,,,,323,
|
||||
```
|
||||
|
||||
## Step 4: Data Dashboard
|
||||
|
||||
### Study Dashboard
|
||||
|
||||
View aggregate statistics:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Study Dashboard: Robot Trust Study │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Overview │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ 20 │ │ 18 │ │ 5m12s │ │ 2 │ │
|
||||
│ │ Trials │ │ Complete│ │ Avg Time│ │ Failed │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ Completion Rate │
|
||||
│ ████████████████████████████████████░░░░ 90% │
|
||||
│ │
|
||||
│ Timeline │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ P001 ████████████████████████████████ 5:23 │ │
|
||||
│ │ P002 ██████████████████████████████ 5:02 │ │
|
||||
│ │ P003 ██████████████████████████ 4:45 │ │
|
||||
│ │ ... │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Metrics
|
||||
|
||||
| Metric | Description |
|
||||
|--------|-------------|
|
||||
| Total Trials | Number of scheduled trials |
|
||||
| Completed | Successfully completed trials |
|
||||
| Average Duration | Mean trial time |
|
||||
| Completion Rate | % of trials completed |
|
||||
| Failed | Trials that failed |
|
||||
| Average Steps | Mean steps per trial |
|
||||
|
||||
## Step 5: Analyzing Event Data
|
||||
|
||||
### Timing Analysis
|
||||
|
||||
Calculate action durations:
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
with open('trial_events.json') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Calculate action durations
|
||||
for event in data['events']:
|
||||
if event['type'] == 'action_executed':
|
||||
duration = event.get('duration', 0)
|
||||
print(f"{event['actionName']}: {duration/1000:.1f}s")
|
||||
```
|
||||
|
||||
### Intervention Analysis
|
||||
|
||||
Track wizard interventions:
|
||||
|
||||
```python
|
||||
# Count interventions by type
|
||||
interventions = [
|
||||
e for e in data['events']
|
||||
if e['type'] == 'intervention'
|
||||
]
|
||||
|
||||
by_type = {}
|
||||
for i in interventions:
|
||||
itype = i['data'].get('type', 'unknown')
|
||||
by_type[itype] = by_type.get(itype, 0) + 1
|
||||
|
||||
print(by_type)
|
||||
# {'note': 15, 'pause': 3, 'alert': 1}
|
||||
```
|
||||
|
||||
### Branch Selection Analysis
|
||||
|
||||
Analyze wizard decisions:
|
||||
|
||||
```python
|
||||
# Get wizard responses
|
||||
responses = [
|
||||
e for e in data['events']
|
||||
if e['type'] == 'wizard_response'
|
||||
]
|
||||
|
||||
# Count by value
|
||||
by_value = {}
|
||||
for r in responses:
|
||||
value = r.get('selectedValue', 'unknown')
|
||||
by_value[value] = by_value.get(value, 0) + 1
|
||||
|
||||
print(by_value)
|
||||
# {'correct': 12, 'incorrect': 6}
|
||||
```
|
||||
|
||||
## Step 6: Form Data Analysis
|
||||
|
||||
### Response Aggregation
|
||||
|
||||
Aggregate survey responses:
|
||||
|
||||
```python
|
||||
# Calculate average rating
|
||||
ratings = [
|
||||
r['responses']['engagement_rating']
|
||||
for r in form_responses
|
||||
]
|
||||
|
||||
avg_rating = sum(ratings) / len(ratings)
|
||||
print(f"Average engagement: {avg_rating:.2f}/5")
|
||||
```
|
||||
|
||||
### Cross-Tabulation
|
||||
|
||||
Compare responses across conditions:
|
||||
|
||||
```
|
||||
| Condition A | Condition B | Total
|
||||
--------------------|------------|-------------|-------
|
||||
Robot engaged | 4.2 | 4.5 | 4.35
|
||||
Natural interaction | 3.8 | 4.1 | 3.95
|
||||
Would use again | 78% | 85% | 81%
|
||||
```
|
||||
|
||||
## Step 7: Data Visualization
|
||||
|
||||
### Trial Timeline
|
||||
|
||||
Visualize trial progression:
|
||||
|
||||
```
|
||||
P001: ████████████████░░░░░░░░░░░░░░░░░ 5:23
|
||||
P002: ███████████████░░░░░░░░░░░░░░░░░░ 4:58
|
||||
P003: ██████████████████████████████░░░░ 6:02
|
||||
P004: ████████████████░░░░░░░░░░░░░░░░░░ 5:15
|
||||
```
|
||||
|
||||
### Action Distribution
|
||||
|
||||
```
|
||||
Action Frequency
|
||||
────────────────
|
||||
Say Text ████████████████████ 45
|
||||
Wave ████████████ 25
|
||||
Turn Head ████████████ 25
|
||||
Move Arm ████ 5
|
||||
```
|
||||
|
||||
### Branch Outcomes
|
||||
|
||||
```
|
||||
Branch Selection
|
||||
────────────────
|
||||
Correct Response (A): ██████████████████████████ 67%
|
||||
Incorrect Response (B): █████████████ 33%
|
||||
```
|
||||
|
||||
## Step 8: Generating Reports
|
||||
|
||||
### Trial Summary Report
|
||||
|
||||
Generate PDF summary:
|
||||
|
||||
```
|
||||
═══════════════════════════════════════════════════════════
|
||||
TRIAL SUMMARY REPORT
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Study: Robot Trust Study
|
||||
Participant: P001
|
||||
Date: March 15, 2024
|
||||
Experiment: Interactive Storyteller v1
|
||||
|
||||
EXECUTIVE SUMMARY
|
||||
───────────────────────────────────────────────────────────
|
||||
Duration: 5 minutes 23 seconds
|
||||
Status: Completed successfully
|
||||
Steps Completed: 6/6
|
||||
Interventions: 2
|
||||
|
||||
TIMELINE
|
||||
───────────────────────────────────────────────────────────
|
||||
14:00:00 Trial started
|
||||
14:00:02 Step 1: The Hook
|
||||
14:00:08 Step 2: The Narrative
|
||||
14:02:30 Wizard note: "Participant engaged"
|
||||
14:03:00 Step 3: Comprehension Check
|
||||
14:03:28 Branch selected: Correct
|
||||
14:03:30 Step 4a: Correct Response
|
||||
14:05:23 Trial completed
|
||||
|
||||
METRICS
|
||||
───────────────────────────────────────────────────────────
|
||||
Actions Executed: 12
|
||||
Action Success Rate: 100%
|
||||
Average Action Duration: 2.1s
|
||||
Wizard Intervention Rate: 0.37/min
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
### Study Report
|
||||
|
||||
Aggregate across participants:
|
||||
|
||||
```
|
||||
═══════════════════════════════════════════════════════════
|
||||
STUDY REPORT
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Study: Robot Trust Study
|
||||
Date Range: March 1-15, 2024
|
||||
Participants: 20
|
||||
|
||||
PARTICIPATION
|
||||
───────────────────────────────────────────────────────────
|
||||
Enrolled: 20
|
||||
Completed: 18 (90%)
|
||||
Withdrew: 1 (5%)
|
||||
Failed: 1 (5%)
|
||||
|
||||
TIMING
|
||||
───────────────────────────────────────────────────────────
|
||||
Mean Duration: 5m 12s ± 28s
|
||||
Min Duration: 4m 45s
|
||||
Max Duration: 6m 02s
|
||||
|
||||
INTERVENTIONS
|
||||
───────────────────────────────────────────────────────────
|
||||
Total Interventions: 34
|
||||
Notes: 25 (73%)
|
||||
Pauses: 7 (21%)
|
||||
Alerts: 2 (6%)
|
||||
|
||||
BRANCH SELECTION
|
||||
───────────────────────────────────────────────────────────
|
||||
Branch A (Correct): 12 (67%)
|
||||
Branch B (Incorrect): 6 (33%)
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
## Step 9: Data Privacy
|
||||
|
||||
### Anonymization
|
||||
|
||||
Remove identifying information:
|
||||
|
||||
```python
|
||||
# Replace participant codes with anonymous IDs
|
||||
participant_map = {
|
||||
'P001': 'S001',
|
||||
'P002': 'S002',
|
||||
'P003': 'S003',
|
||||
}
|
||||
```
|
||||
|
||||
### Export Settings
|
||||
|
||||
Configure export options:
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| Include participant codes | Keep or anonymize |
|
||||
| Include timestamps | Full or relative |
|
||||
| Include notes | Include/exclude |
|
||||
| Include form responses | Include/exclude |
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Data Collection
|
||||
|
||||
- [ ] Enable all event logging
|
||||
- [ ] Configure sensor data capture
|
||||
- [ ] Set up automatic backups
|
||||
- [ ] Test data export before study
|
||||
|
||||
### Data Storage
|
||||
|
||||
- [ ] Export regularly (daily/weekly)
|
||||
- [ ] Store in secure location
|
||||
- [ ] Follow IRB data retention
|
||||
- [ ] Backup critical data
|
||||
|
||||
### Data Analysis
|
||||
|
||||
- [ ] Document analysis methods
|
||||
- [ ] Track protocol versions
|
||||
- [ ] Note data quality issues
|
||||
- [ ] Share data dictionary
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you understand data collection:
|
||||
|
||||
1. **[Your First Study](02-your-first-study.md)** - Apply data practices
|
||||
2. **[Simulation Mode](09-simulation-mode.md)** - Test data collection
|
||||
3. **[Running Trials](04-running-trials.md)** - Practice with data capture
|
||||
|
||||
---
|
||||
|
||||
**Previous**: [Forms & Surveys](07-forms-and-surveys.md) | **Next**: [Simulation Mode](09-simulation-mode.md)
|
||||
@@ -0,0 +1,389 @@
|
||||
# Tutorial 9: Simulation Mode
|
||||
|
||||
Learn how to test HRIStudio experiments without a physical robot.
|
||||
|
||||
## Objectives
|
||||
|
||||
- Enable simulation mode
|
||||
- Use the mock robot server
|
||||
- Test experiments end-to-end
|
||||
- Practice trial execution
|
||||
|
||||
## Why Simulation Mode?
|
||||
|
||||
Simulation mode allows you to:
|
||||
|
||||
- **Test protocols** without a robot
|
||||
- **Train wizards** before live sessions
|
||||
- **Debug experiments** in development
|
||||
- **Run pilots** without robot access
|
||||
- **Develop** on any computer
|
||||
|
||||
## Understanding Simulation Options
|
||||
|
||||
HRIStudio offers two simulation approaches:
|
||||
|
||||
| Approach | Pros | Cons |
|
||||
|----------|------|------|
|
||||
| **Client-side** | No server needed, instant | Limited robot simulation |
|
||||
| **Mock Server** | Full rosbridge protocol | Requires running server |
|
||||
|
||||
### Client-Side Simulation
|
||||
|
||||
Simulates robot locally in the browser:
|
||||
- No network required
|
||||
- Instant startup
|
||||
- Basic action timing
|
||||
- Fake sensor data
|
||||
|
||||
### Mock Server
|
||||
|
||||
Full WebSocket server simulating rosbridge:
|
||||
- Complete protocol support
|
||||
- Realistic timing
|
||||
- Sensor data simulation
|
||||
- Better for integration testing
|
||||
|
||||
## Step 1: Enable Client-Side Simulation
|
||||
|
||||
### Quick Start
|
||||
|
||||
1. Create or edit `hristudio/.env.local`
|
||||
2. Add:
|
||||
```bash
|
||||
NEXT_PUBLIC_SIMULATION_MODE=true
|
||||
```
|
||||
3. Restart the dev server:
|
||||
```bash
|
||||
bun dev
|
||||
```
|
||||
|
||||
### Verify Enabled
|
||||
|
||||
Look for the simulation indicator in the UI:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Wizard Interface [🔵 SIMULATION MODE] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
```
|
||||
|
||||
### Features Available
|
||||
|
||||
In simulation mode:
|
||||
|
||||
- ✅ All robot actions execute (simulated timing)
|
||||
- ✅ Speech actions show estimated duration
|
||||
- ✅ Movement actions track position
|
||||
- ✅ Sensor data is simulated
|
||||
- ✅ Trial execution works normally
|
||||
- ❌ Real robot not controlled
|
||||
- ❌ Physical interactions not possible
|
||||
|
||||
## Step 2: Start Mock Robot Server
|
||||
|
||||
For more complete testing, use the mock server:
|
||||
|
||||
### Option 1: Standalone Server
|
||||
|
||||
```bash
|
||||
cd hristudio/scripts/mock-robot
|
||||
bun install
|
||||
bun dev
|
||||
```
|
||||
|
||||
Server starts on `ws://localhost:9090`
|
||||
|
||||
### Option 2: Docker
|
||||
|
||||
```bash
|
||||
cd nao6-hristudio-integration
|
||||
docker compose -f docker-compose.yml -f docker-compose.mock.yml --profile mock up -d
|
||||
```
|
||||
|
||||
### Verify Server Running
|
||||
|
||||
```bash
|
||||
# Check container
|
||||
docker ps
|
||||
|
||||
# Should show:
|
||||
# CONTAINER ID IMAGE STATUS
|
||||
# abc123def456 hristudio-mock-robot Up 2 minutes
|
||||
```
|
||||
|
||||
## Step 3: Connect to Mock Server
|
||||
|
||||
1. Go to the **NAO Test Page**: `/nao-test`
|
||||
2. Ensure `NEXT_PUBLIC_SIMULATION_MODE` is NOT set (or set to false)
|
||||
3. Click **Connect**
|
||||
4. You should see:
|
||||
```
|
||||
Connected to rosbridge
|
||||
Subscribed to: /joint_states, /bumper, /sonar/left, ...
|
||||
```
|
||||
|
||||
## Step 4: Test Robot Actions
|
||||
|
||||
### From NAO Test Page
|
||||
|
||||
1. **Speech Test**
|
||||
- Enter text: "Hello, this is a test"
|
||||
- Click **Say**
|
||||
- See simulated speech duration
|
||||
|
||||
2. **Movement Test**
|
||||
- Set walk speed: 0.1 m/s
|
||||
- Click **Walk Forward**
|
||||
- Watch position update
|
||||
|
||||
3. **Head Control**
|
||||
- Set yaw: 1.0, pitch: 0.0
|
||||
- Click **Move Head**
|
||||
- See joint angles update
|
||||
|
||||
### From Wizard Interface
|
||||
|
||||
1. Start a trial
|
||||
2. Execute actions as normal
|
||||
3. Actions are sent to mock server
|
||||
4. Mock server responds with simulated data
|
||||
|
||||
## Step 5: Simulated Actions Reference
|
||||
|
||||
### Speech Actions
|
||||
|
||||
| Action | Simulation Behavior |
|
||||
|--------|---------------------|
|
||||
| `say_text` | Duration = 1.5s + 300ms × word_count |
|
||||
| `say_with_emotion` | Duration = 1.5s + 300ms × word_count + emotion_overhead |
|
||||
| `wave_goodbye` | Duration = 3.0s |
|
||||
|
||||
### Movement Actions
|
||||
|
||||
| Action | Simulation Behavior |
|
||||
|--------|---------------------|
|
||||
| `walk_forward` | Position updates over 500ms |
|
||||
| `walk_backward` | Position updates over 500ms |
|
||||
| `turn_left` | Angle decreases over 500ms |
|
||||
| `turn_right` | Angle increases over 500ms |
|
||||
| `stop` | Velocity set to 0 |
|
||||
|
||||
### Sensor Simulation
|
||||
|
||||
| Sensor | Simulated Value |
|
||||
|--------|-----------------|
|
||||
| Battery | 85% ± 2% variation |
|
||||
| Joint States | Random positions ±0.1 rad |
|
||||
| Bumper | False (no contact) |
|
||||
| Sonar | 0.5-1.0m (random) |
|
||||
| Touch | False (no touch) |
|
||||
|
||||
## Step 6: Testing Experiment Protocols
|
||||
|
||||
### Full Protocol Test
|
||||
|
||||
1. Enable simulation mode
|
||||
2. Create or open experiment
|
||||
3. Schedule trial
|
||||
4. Start trial in wizard interface
|
||||
5. Execute through all steps
|
||||
6. Verify timing and flow
|
||||
|
||||
### Test Checklist
|
||||
|
||||
- [ ] All steps execute in order
|
||||
- [ ] Branching decisions work
|
||||
- [ ] Timing estimates are accurate
|
||||
- [ ] Event log captures everything
|
||||
- [ ] No errors or warnings
|
||||
- [ ] Trial completes successfully
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable verbose logging:
|
||||
|
||||
```bash
|
||||
# In browser console, run:
|
||||
localStorage.setItem('debug', 'true')
|
||||
|
||||
# Refresh page
|
||||
# Now see detailed action logs in console
|
||||
```
|
||||
|
||||
## Step 7: Training Wizards
|
||||
|
||||
Simulation mode is perfect for training:
|
||||
|
||||
### Training Scenario 1: Basic Operation
|
||||
|
||||
1. Enable simulation mode
|
||||
2. Load simple experiment
|
||||
3. Practice:
|
||||
- Starting/pausing trials
|
||||
- Executing quick actions
|
||||
- Adding notes
|
||||
|
||||
### Training Scenario 2: Decision Making
|
||||
|
||||
1. Load branching experiment
|
||||
2. Practice:
|
||||
- Observing participant cues
|
||||
- Selecting appropriate branches
|
||||
- Documenting decisions
|
||||
|
||||
### Training Scenario 3: Handling Issues
|
||||
|
||||
1. Practice:
|
||||
- Pausing for breaks
|
||||
- Responding to alerts
|
||||
- Stopping trials early
|
||||
|
||||
## Step 8: Development Workflow
|
||||
|
||||
### TDD with Simulation
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Development Cycle │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 1. Design experiment in UI │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ 2. Enable simulation mode │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ 3. Run test trial │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ 4. Review event log │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ 5. Fix issues found │
|
||||
│ │ │
|
||||
│ └────────────┐ │
|
||||
│ │ │
|
||||
│ └ (repeat) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Testing Checklist
|
||||
|
||||
Before running real trials:
|
||||
|
||||
- [ ] Experiment works in simulation
|
||||
- [ ] All actions execute correctly
|
||||
- [ ] Timing is acceptable
|
||||
- [ ] Branching works as expected
|
||||
- [ ] Wizard notes function properly
|
||||
- [ ] Data exports correctly
|
||||
|
||||
## Step 9: Transitioning to Real Robot
|
||||
|
||||
When ready to test with real robot:
|
||||
|
||||
### Step 1: Disable Simulation
|
||||
|
||||
Remove or set to false:
|
||||
```bash
|
||||
NEXT_PUBLIC_SIMULATION_MODE=false
|
||||
```
|
||||
|
||||
### Step 2: Connect Robot
|
||||
|
||||
1. Start Docker services
|
||||
2. Verify robot connection
|
||||
3. Test with NAO Test Page
|
||||
|
||||
### Step 3: Run Comparison Trial
|
||||
|
||||
1. Run same experiment on real robot
|
||||
2. Compare timing and behavior
|
||||
3. Adjust parameters as needed
|
||||
|
||||
### Step 4: Document Differences
|
||||
|
||||
Note any protocol adjustments needed:
|
||||
- Timing differences
|
||||
- Action parameter changes
|
||||
- Branch criteria updates
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Simulation Actions Not Working
|
||||
|
||||
1. Check `NEXT_PUBLIC_SIMULATION_MODE=true` is set
|
||||
2. Verify no errors in browser console
|
||||
3. Try refreshing the page
|
||||
|
||||
### Mock Server Connection Failed
|
||||
|
||||
```bash
|
||||
# Check if server is running
|
||||
docker ps | grep mock
|
||||
|
||||
# Check server logs
|
||||
docker compose logs mock_robot
|
||||
|
||||
# Restart if needed
|
||||
docker compose restart mock_robot
|
||||
```
|
||||
|
||||
### Actions Execute But Nothing Happens
|
||||
|
||||
1. Check WebSocket URL is correct
|
||||
2. Verify port 9090 is not blocked
|
||||
3. Try client-side simulation instead
|
||||
|
||||
## Comparison: Simulation vs Real
|
||||
|
||||
| Aspect | Simulation | Real Robot |
|
||||
|--------|------------|------------|
|
||||
| Setup time | 1 min | 30+ min |
|
||||
| Availability | Always | Requires robot |
|
||||
| Cost | Free | Robot access needed |
|
||||
| Timing accuracy | Estimated | Actual |
|
||||
| Physical interaction | ✗ | ✓ |
|
||||
| Sensor accuracy | Fake | Real |
|
||||
| Network dependent | No | Yes |
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When to Use Simulation
|
||||
|
||||
- During experiment design
|
||||
- While robot unavailable
|
||||
- For wizard training
|
||||
- For debugging protocols
|
||||
- For quick iteration
|
||||
|
||||
### When to Use Real Robot
|
||||
|
||||
- Final protocol validation
|
||||
- Timing accuracy critical
|
||||
- Physical interaction matters
|
||||
- Sensor data needed
|
||||
- Pre-study pilot
|
||||
|
||||
### Transition Checklist
|
||||
|
||||
Before real trials:
|
||||
- [ ] Protocol tested in simulation
|
||||
- [ ] Timing verified
|
||||
- [ ] Actions calibrated
|
||||
- [ ] Wizard team trained
|
||||
- [ ] Backup plan ready
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you've mastered simulation:
|
||||
|
||||
1. **[Robot Integration](06-robot-integration.md)** - Connect real robot
|
||||
2. **[Running Trials](04-running-trials.md)** - Execute live trials
|
||||
3. **[Your First Study](02-your-first-study.md)** - Run complete study
|
||||
|
||||
---
|
||||
|
||||
**Previous**: [Data & Analysis](08-data-and-analysis.md) | **Back**: [Tutorials Overview](../tutorials/README.md)
|
||||
@@ -0,0 +1,71 @@
|
||||
# HRIStudio Tutorials
|
||||
|
||||
Welcome to the HRIStudio tutorials! These guides will help you get up and running with the platform for your HRI research.
|
||||
|
||||
## Tutorial Overview
|
||||
|
||||
| Tutorial | Description | Time |
|
||||
|----------|-------------|------|
|
||||
| **[Getting Started](tutorials/01-getting-started.md)** | Installation, setup, and first login | 10 min |
|
||||
| **[Your First Study](tutorials/02-your-first-study.md)** | Creating a study and adding team members | 15 min |
|
||||
| **[Designing Experiments](tutorials/03-designing-experiments.md)** | Building experiment protocols with blocks | 25 min |
|
||||
| **[Running Trials](tutorials/04-running-trials.md)** | Executing trials and managing participants | 20 min |
|
||||
| **[Wizard Interface](tutorials/05-wizard-interface.md)** | Real-time trial control and monitoring | 15 min |
|
||||
| **[Robot Integration](tutorials/06-robot-integration.md)** | Connecting NAO6 and other robots | 20 min |
|
||||
| **[Forms & Surveys](tutorials/07-forms-and-surveys.md)** | Creating consent forms and questionnaires | 15 min |
|
||||
| **[Data & Analysis](tutorials/08-data-and-analysis.md)** | Collecting and exporting trial data | 15 min |
|
||||
| **[Simulation Mode](tutorials/09-simulation-mode.md)** | Testing without a physical robot | 10 min |
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
### For Researchers
|
||||
1. [Getting Started](tutorials/01-getting-started.md) - Set up your environment
|
||||
2. [Your First Study](tutorials/02-your-first-study.md) - Create your study
|
||||
3. [Designing Experiments](tutorials/03-designing-experiments.md) - Build your protocol
|
||||
4. [Running Trials](tutorials/04-running-trials.md) - Execute your study
|
||||
5. [Data & Analysis](tutorials/08-data-and-analysis.md) - Analyze results
|
||||
|
||||
### For Wizards
|
||||
1. [Getting Started](tutorials/01-getting-started.md) - Basic setup
|
||||
2. [Wizard Interface](tutorials/05-wizard-interface.md) - Control trials
|
||||
3. [Robot Integration](tutorials/06-robot-integration.md) - Connect to robot
|
||||
|
||||
### For Administrators
|
||||
1. [Getting Started](tutorials/01-getting-started.md) - Full setup
|
||||
2. [Robot Integration](tutorials/06-robot-integration.md) - Configure robots
|
||||
3. [Forms & Surveys](tutorials/07-forms-and-surveys.md) - Manage templates
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Basic HRI Experiment
|
||||
```
|
||||
Create Study → Design Experiment → Add Participants → Run Trials → Collect Data
|
||||
```
|
||||
|
||||
### Wizard-of-Oz Study
|
||||
```
|
||||
Create Study → Design Experiment with Wizard Blocks → Configure Robot →
|
||||
Add Wizards → Run Trials with Live Control → Collect Data
|
||||
```
|
||||
|
||||
### Pilot Testing
|
||||
```
|
||||
Create Study → Design Experiment → Enable Simulation Mode → Run Test Trials →
|
||||
Refine Protocol → Connect Real Robot → Run Study
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **For local development**: Bun, Docker, PostgreSQL
|
||||
- **For robot studies**: NAO6 robot or compatible robot
|
||||
- **For cloud deployment**: Vercel, Cloudflare R2, PostgreSQL database
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Check the [Quick Reference](../quick-reference.md) for common commands
|
||||
- Review the [Implementation Guide](../implementation-guide.md) for technical details
|
||||
- Visit the [NAO6 Integration](../nao6-quick-reference.md) for robot-specific help
|
||||
|
||||
---
|
||||
|
||||
**Next**: [Getting Started](tutorials/01-getting-started.md)
|
||||
@@ -1,279 +0,0 @@
|
||||
# Wizard Interface Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The Wizard Interface is a real-time control panel for conducting Human-Robot Interaction (HRI) trials. It provides wizards with comprehensive tools to execute experiment protocols, monitor participant interactions, and control robot behaviors in real-time.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Real-time Trial Execution**: Live step-by-step protocol execution with WebSocket connectivity
|
||||
- **Robot Status Monitoring**: Battery levels, connection status, sensor readings, and position tracking
|
||||
- **Participant Information**: Demographics, consent status, and session details
|
||||
- **Live Event Logging**: Real-time capture of all trial events and wizard interventions
|
||||
- **Action Controls**: Quick access to common wizard actions and robot commands
|
||||
|
||||
## WebSocket System
|
||||
|
||||
### Connection Setup
|
||||
|
||||
The wizard interface automatically connects to a WebSocket server for real-time communication:
|
||||
|
||||
```typescript
|
||||
// WebSocket URL format
|
||||
wss://your-domain.com/api/websocket?trialId={TRIAL_ID}&token={AUTH_TOKEN}
|
||||
```
|
||||
|
||||
### Message Types
|
||||
|
||||
#### Incoming Messages (from server):
|
||||
- `connection_established` - Connection acknowledgment
|
||||
- `trial_status` - Current trial state and step information
|
||||
- `trial_action_executed` - Confirmation of action execution
|
||||
- `step_changed` - Step transition notifications
|
||||
- `intervention_logged` - Wizard intervention confirmations
|
||||
|
||||
#### Outgoing Messages (to server):
|
||||
- `heartbeat` - Keep connection alive
|
||||
- `trial_action` - Execute trial actions (start, complete, abort)
|
||||
- `wizard_intervention` - Log wizard interventions
|
||||
- `step_transition` - Advance to next step
|
||||
|
||||
### Example Usage
|
||||
|
||||
```typescript
|
||||
// Start a trial
|
||||
webSocket.sendMessage({
|
||||
type: "trial_action",
|
||||
data: {
|
||||
actionType: "start_trial",
|
||||
step_index: 0,
|
||||
data: { notes: "Trial started by wizard" }
|
||||
}
|
||||
});
|
||||
|
||||
// Log wizard intervention
|
||||
webSocket.sendMessage({
|
||||
type: "wizard_intervention",
|
||||
data: {
|
||||
action_type: "manual_correction",
|
||||
step_index: currentStepIndex,
|
||||
action_data: { message: "Clarified instruction" }
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Trial Execution Workflow
|
||||
|
||||
### 1. Pre-Trial Setup
|
||||
- Verify participant consent and demographics
|
||||
- Check robot connection and status
|
||||
- Review experiment protocol steps
|
||||
- Confirm WebSocket connectivity
|
||||
|
||||
### 2. Starting a Trial
|
||||
1. Click "Start Trial" button
|
||||
2. System automatically:
|
||||
- Updates trial status to "in_progress"
|
||||
- Records start timestamp
|
||||
- Loads first protocol step
|
||||
- Broadcasts status to all connected clients
|
||||
|
||||
### 3. Step-by-Step Execution
|
||||
- **Current Step Display**: Shows active step details and actions
|
||||
- **Execute Step**: Trigger step-specific actions (robot commands, wizard prompts)
|
||||
- **Next Step**: Advance to subsequent protocol step
|
||||
- **Quick Actions**: Access common wizard interventions
|
||||
|
||||
### 4. Real-time Monitoring
|
||||
- **Robot Status**: Live updates on battery, signal, position, sensors
|
||||
- **Event Log**: Chronological list of all trial events
|
||||
- **Progress Tracking**: Visual progress bar and step completion status
|
||||
|
||||
### 5. Trial Completion
|
||||
- Click "Complete" for successful trials
|
||||
- Click "Abort" for early termination
|
||||
- System records end timestamp and final status
|
||||
- Automatic redirect to analysis page
|
||||
|
||||
## Experiment Data Integration
|
||||
|
||||
### Loading Real Experiment Steps
|
||||
|
||||
The wizard interface automatically loads experiment steps from the database:
|
||||
|
||||
```typescript
|
||||
// Steps are fetched from the experiments API
|
||||
const { data: experimentSteps } = api.experiments.getSteps.useQuery({
|
||||
experimentId: trial.experimentId
|
||||
});
|
||||
```
|
||||
|
||||
### Step Types and Actions
|
||||
|
||||
Supported step types from the experiment designer:
|
||||
- **Wizard Steps**: Manual wizard actions and prompts
|
||||
- **Robot Steps**: Automated robot behaviors and movements
|
||||
- **Parallel Steps**: Concurrent actions executed simultaneously
|
||||
- **Conditional Steps**: Branching logic based on participant responses
|
||||
|
||||
## Seed Data and Testing
|
||||
|
||||
### Available Test Data
|
||||
|
||||
The development database includes realistic test scenarios:
|
||||
|
||||
```bash
|
||||
# Seed the database with test data
|
||||
bun db:seed
|
||||
|
||||
# Default login credentials
|
||||
Email: sean@soconnor.dev
|
||||
Password: password123
|
||||
```
|
||||
|
||||
### Test Experiments
|
||||
|
||||
1. **"Basic Interaction Protocol 1"** (Study: Real-time HRI Coordination)
|
||||
- 3 steps: Introduction, Wait for Response, Robot Feedback
|
||||
- Includes wizard actions and NAO robot integration
|
||||
- Estimated duration: 25 minutes
|
||||
|
||||
2. **"Dialogue Timing Pilot"** (Study: Wizard-of-Oz Dialogue Study)
|
||||
- Multi-step protocol with parallel and conditional actions
|
||||
- Timer-based transitions and conditional follow-ups
|
||||
- Estimated duration: 35 minutes
|
||||
|
||||
### Test Participants
|
||||
|
||||
Pre-loaded participants with complete demographics:
|
||||
- Various age groups (18-65)
|
||||
- Different educational backgrounds
|
||||
- Robot experience levels
|
||||
- Consent already verified
|
||||
|
||||
## Robot Integration
|
||||
|
||||
### Supported Robots
|
||||
|
||||
- **TurtleBot3 Burger**: Navigation and sensing capabilities
|
||||
- **NAO Humanoid Robot**: Speech, gestures, and animations
|
||||
- **Plugin System**: Extensible support for additional platforms
|
||||
|
||||
### Robot Actions
|
||||
|
||||
Common robot actions available during trials:
|
||||
- **Speech**: Text-to-speech with configurable speed/volume
|
||||
- **Movement**: Navigation commands and position control
|
||||
- **Gestures**: Pre-defined animation sequences
|
||||
- **LED Control**: Visual feedback through color changes
|
||||
- **Sensor Readings**: Real-time environmental data
|
||||
|
||||
## Error Handling and Troubleshooting
|
||||
|
||||
### WebSocket Connection Issues
|
||||
|
||||
- **Connection Failed**: Check network connectivity and server status
|
||||
- **Frequent Disconnections**: Verify firewall settings and WebSocket support
|
||||
- **Authentication Errors**: Ensure valid session and proper token generation
|
||||
|
||||
### Trial Execution Problems
|
||||
|
||||
- **Steps Not Loading**: Verify experiment has published steps in database
|
||||
- **Robot Commands Failing**: Check robot connection and plugin configuration
|
||||
- **Progress Not Updating**: Confirm WebSocket messages are being sent/received
|
||||
|
||||
### Recovery Procedures
|
||||
|
||||
1. **Connection Loss**: Interface automatically attempts reconnection with exponential backoff
|
||||
2. **Trial State Mismatch**: Use "Refresh" button to sync with server state
|
||||
3. **Robot Disconnect**: Monitor robot status panel for connection recovery
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Wizard Guidelines
|
||||
|
||||
1. **Pre-Trial Preparation**
|
||||
- Review complete experiment protocol
|
||||
- Test robot functionality before participant arrival
|
||||
- Verify audio/video recording systems
|
||||
|
||||
2. **During Trial Execution**
|
||||
- Follow protocol steps in sequence
|
||||
- Use intervention logging for any deviations
|
||||
- Monitor participant comfort and engagement
|
||||
- Watch robot status for any issues
|
||||
|
||||
3. **Post-Trial Procedures**
|
||||
- Complete trial properly (don't just abort)
|
||||
- Add summary notes about participant behavior
|
||||
- Review event log for any anomalies
|
||||
|
||||
### Technical Considerations
|
||||
|
||||
- **Browser Compatibility**: Use modern browsers with WebSocket support
|
||||
- **Network Requirements**: Stable internet connection for real-time features
|
||||
- **Performance**: Close unnecessary browser tabs during trials
|
||||
- **Backup Plans**: Have manual procedures ready if technology fails
|
||||
|
||||
## Development and Customization
|
||||
|
||||
### Adding Custom Actions
|
||||
|
||||
```typescript
|
||||
// Register new wizard action
|
||||
const handleCustomAction = async (actionData: Record<string, unknown>) => {
|
||||
await logEventMutation.mutateAsync({
|
||||
trialId: trial.id,
|
||||
type: "wizard_action",
|
||||
data: {
|
||||
action_type: "custom_intervention",
|
||||
...actionData
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### Extending Robot Support
|
||||
|
||||
1. Create new robot plugin following plugin system guidelines
|
||||
2. Define action schemas in plugin configuration
|
||||
3. Implement communication protocol (REST/ROS2/WebSocket)
|
||||
4. Test integration with wizard interface
|
||||
|
||||
### Custom Step Types
|
||||
|
||||
To add new step types:
|
||||
1. Update database schema (`stepTypeEnum`)
|
||||
2. Add type mapping in `WizardInterface.tsx`
|
||||
3. Create step-specific UI components
|
||||
4. Update execution engine logic
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Authentication**: All WebSocket connections require valid session tokens
|
||||
- **Authorization**: Role-based access control for trial operations
|
||||
- **Data Protection**: All trial data encrypted in transit and at rest
|
||||
- **Session Management**: Automatic cleanup of expired connections
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
- **Connection Pooling**: Efficient WebSocket connection management
|
||||
- **Event Batching**: Group related events to reduce message overhead
|
||||
- **Selective Updates**: Only broadcast relevant changes to connected clients
|
||||
- **Caching**: Local state management for responsive UI updates
|
||||
|
||||
---
|
||||
|
||||
## Quick Start Checklist
|
||||
|
||||
- [ ] Database seeded with test data (`bun db:seed`)
|
||||
- [ ] Development server running (`bun dev`)
|
||||
- [ ] Logged in as administrator (sean@soconnor.dev)
|
||||
- [ ] Navigate to Trials section
|
||||
- [ ] Select a trial and click "Wizard Control"
|
||||
- [ ] Verify WebSocket connection (green "Real-time" badge)
|
||||
- [ ] Start trial and execute steps
|
||||
- [ ] Monitor robot status and event log
|
||||
- [ ] Complete trial and review analysis page
|
||||
|
||||
For additional support, refer to the complete HRIStudio documentation in the `docs/` folder.
|
||||
@@ -9,4 +9,5 @@ export default {
|
||||
url: env.DATABASE_URL,
|
||||
},
|
||||
tablesFilter: ["hs_*"],
|
||||
out: "./migrations",
|
||||
} satisfies Config;
|
||||
|
||||
+9
-37
@@ -1,55 +1,27 @@
|
||||
import type { Session } from "next-auth";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "./src/server/auth";
|
||||
|
||||
export default auth((req: NextRequest & { auth: Session | null }) => {
|
||||
const { nextUrl } = req;
|
||||
const isLoggedIn = !!req.auth;
|
||||
export default async function middleware(request: NextRequest) {
|
||||
const { nextUrl } = request;
|
||||
|
||||
// Define route patterns
|
||||
const isApiAuthRoute = nextUrl.pathname.startsWith("/api/auth");
|
||||
const isPublicRoute = ["/", "/auth/signin", "/auth/signup"].includes(
|
||||
nextUrl.pathname,
|
||||
);
|
||||
// Skip session checks for now to debug the auth issue
|
||||
const isApiRoute = nextUrl.pathname.startsWith("/api");
|
||||
const isAuthRoute = nextUrl.pathname.startsWith("/auth");
|
||||
|
||||
// Allow API auth routes to pass through
|
||||
if (isApiAuthRoute) {
|
||||
if (isApiRoute) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// If user is on auth pages and already logged in, redirect to dashboard
|
||||
if (isAuthRoute && isLoggedIn) {
|
||||
return NextResponse.redirect(new URL("/", nextUrl));
|
||||
}
|
||||
|
||||
// If user is not logged in and trying to access protected routes
|
||||
if (!isLoggedIn && !isPublicRoute && !isAuthRoute) {
|
||||
let callbackUrl = nextUrl.pathname;
|
||||
if (nextUrl.search) {
|
||||
callbackUrl += nextUrl.search;
|
||||
}
|
||||
|
||||
const encodedCallbackUrl = encodeURIComponent(callbackUrl);
|
||||
return NextResponse.redirect(
|
||||
new URL(`/auth/signin?callbackUrl=${encodedCallbackUrl}`, nextUrl),
|
||||
);
|
||||
// Allow auth routes through for now
|
||||
if (isAuthRoute) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
});
|
||||
}
|
||||
|
||||
// Configure which routes the middleware should run on
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
* - public files (images, etc.)
|
||||
*/
|
||||
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -0,0 +1,605 @@
|
||||
CREATE TYPE "public"."block_category" AS ENUM('wizard', 'robot', 'control', 'sensor', 'logic', 'event');--> statement-breakpoint
|
||||
CREATE TYPE "public"."block_shape" AS ENUM('action', 'control', 'value', 'boolean', 'hat', 'cap');--> statement-breakpoint
|
||||
CREATE TYPE "public"."communication_protocol" AS ENUM('rest', 'ros2', 'custom');--> statement-breakpoint
|
||||
CREATE TYPE "public"."experiment_status" AS ENUM('draft', 'testing', 'ready', 'deprecated');--> statement-breakpoint
|
||||
CREATE TYPE "public"."export_status" AS ENUM('pending', 'processing', 'completed', 'failed');--> statement-breakpoint
|
||||
CREATE TYPE "public"."form_field_type" AS ENUM('text', 'textarea', 'multiple_choice', 'checkbox', 'rating', 'yes_no', 'date', 'signature');--> statement-breakpoint
|
||||
CREATE TYPE "public"."form_response_status" AS ENUM('pending', 'completed', 'rejected');--> statement-breakpoint
|
||||
CREATE TYPE "public"."form_type" AS ENUM('consent', 'survey', 'questionnaire');--> statement-breakpoint
|
||||
CREATE TYPE "public"."media_type" AS ENUM('video', 'audio', 'image');--> statement-breakpoint
|
||||
CREATE TYPE "public"."plugin_status" AS ENUM('active', 'deprecated', 'disabled');--> statement-breakpoint
|
||||
CREATE TYPE "public"."step_type" AS ENUM('wizard', 'robot', 'parallel', 'conditional');--> statement-breakpoint
|
||||
CREATE TYPE "public"."study_member_role" AS ENUM('owner', 'researcher', 'wizard', 'observer');--> statement-breakpoint
|
||||
CREATE TYPE "public"."study_status" AS ENUM('draft', 'active', 'completed', 'archived');--> statement-breakpoint
|
||||
CREATE TYPE "public"."system_role" AS ENUM('administrator', 'researcher', 'wizard', 'observer');--> statement-breakpoint
|
||||
CREATE TYPE "public"."trial_status" AS ENUM('scheduled', 'in_progress', 'completed', 'aborted', 'failed');--> statement-breakpoint
|
||||
CREATE TYPE "public"."trust_level" AS ENUM('official', 'verified', 'community');--> statement-breakpoint
|
||||
CREATE TABLE "hs_account" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"provider_id" varchar(255) NOT NULL,
|
||||
"account_id" varchar(255) NOT NULL,
|
||||
"refresh_token" text,
|
||||
"access_token" text,
|
||||
"expires_at" timestamp with time zone,
|
||||
"scope" varchar(255),
|
||||
"password" text,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_account_provider_id_account_id_unique" UNIQUE("provider_id","account_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_action" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"step_id" uuid NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"type" varchar(100) NOT NULL,
|
||||
"order_index" integer NOT NULL,
|
||||
"parameters" jsonb DEFAULT '{}'::jsonb,
|
||||
"validation_schema" jsonb,
|
||||
"timeout" integer,
|
||||
"retry_count" integer DEFAULT 0 NOT NULL,
|
||||
"source_kind" varchar(20),
|
||||
"plugin_id" varchar(255),
|
||||
"plugin_version" varchar(50),
|
||||
"robot_id" varchar(255),
|
||||
"base_action_id" varchar(255),
|
||||
"category" varchar(50),
|
||||
"transport" varchar(20),
|
||||
"ros2_config" jsonb,
|
||||
"rest_config" jsonb,
|
||||
"retryable" boolean,
|
||||
"parameter_schema_raw" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_action_step_id_order_index_unique" UNIQUE("step_id","order_index")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_activity_log" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"study_id" uuid,
|
||||
"user_id" text,
|
||||
"action" varchar(100) NOT NULL,
|
||||
"resource_type" varchar(50),
|
||||
"resource_id" uuid,
|
||||
"description" text,
|
||||
"ip_address" "inet",
|
||||
"user_agent" text,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_annotation" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"trial_id" uuid NOT NULL,
|
||||
"annotator_id" text NOT NULL,
|
||||
"timestamp_start" timestamp with time zone NOT NULL,
|
||||
"timestamp_end" timestamp with time zone,
|
||||
"category" varchar(100),
|
||||
"label" varchar(100),
|
||||
"description" text,
|
||||
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_attachment" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"resource_type" varchar(50) NOT NULL,
|
||||
"resource_id" uuid NOT NULL,
|
||||
"file_name" varchar(255) NOT NULL,
|
||||
"file_size" bigint NOT NULL,
|
||||
"file_path" text NOT NULL,
|
||||
"content_type" varchar(100),
|
||||
"description" text,
|
||||
"uploaded_by" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_audit_log" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text,
|
||||
"action" varchar(100) NOT NULL,
|
||||
"resource_type" varchar(50),
|
||||
"resource_id" uuid,
|
||||
"changes" jsonb DEFAULT '{}'::jsonb,
|
||||
"ip_address" "inet",
|
||||
"user_agent" text,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_block_registry" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"block_type" varchar(100) NOT NULL,
|
||||
"plugin_id" uuid,
|
||||
"shape" "block_shape" NOT NULL,
|
||||
"category" "block_category" NOT NULL,
|
||||
"display_name" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"icon" varchar(100),
|
||||
"color" varchar(50),
|
||||
"config" jsonb NOT NULL,
|
||||
"parameter_schema" jsonb NOT NULL,
|
||||
"execution_handler" varchar(100),
|
||||
"timeout" integer,
|
||||
"retry_policy" jsonb,
|
||||
"requires_connection" boolean DEFAULT false,
|
||||
"preview_mode" boolean DEFAULT true,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_block_registry_block_type_plugin_id_unique" UNIQUE("block_type","plugin_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_comment" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"parent_id" uuid,
|
||||
"resource_type" varchar(50) NOT NULL,
|
||||
"resource_id" uuid NOT NULL,
|
||||
"author_id" text NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"metadata" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_consent_form" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"study_id" uuid NOT NULL,
|
||||
"version" integer DEFAULT 1 NOT NULL,
|
||||
"title" varchar(255) NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"active" boolean DEFAULT true NOT NULL,
|
||||
"created_by" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"storage_path" text,
|
||||
CONSTRAINT "hs_consent_form_study_id_version_unique" UNIQUE("study_id","version")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_experiment" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"study_id" uuid NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"version" integer DEFAULT 1 NOT NULL,
|
||||
"robot_id" uuid,
|
||||
"status" "experiment_status" DEFAULT 'draft' NOT NULL,
|
||||
"estimated_duration" integer,
|
||||
"created_by" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb,
|
||||
"visual_design" jsonb,
|
||||
"execution_graph" jsonb,
|
||||
"plugin_dependencies" text[],
|
||||
"integrity_hash" varchar(128),
|
||||
"deleted_at" timestamp with time zone,
|
||||
CONSTRAINT "hs_experiment_study_id_name_version_unique" UNIQUE("study_id","name","version")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_export_job" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"study_id" uuid NOT NULL,
|
||||
"requested_by" text NOT NULL,
|
||||
"export_type" varchar(50) NOT NULL,
|
||||
"format" varchar(20) NOT NULL,
|
||||
"filters" jsonb DEFAULT '{}'::jsonb,
|
||||
"status" "export_status" DEFAULT 'pending' NOT NULL,
|
||||
"storage_path" text,
|
||||
"expires_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"completed_at" timestamp with time zone,
|
||||
"error_message" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_form_response" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"form_id" uuid NOT NULL,
|
||||
"participant_id" uuid NOT NULL,
|
||||
"responses" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"status" "form_response_status" DEFAULT 'pending',
|
||||
"signature_data" text,
|
||||
"signed_at" timestamp with time zone,
|
||||
"ip_address" "inet",
|
||||
"submitted_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_form_response_form_id_participant_id_unique" UNIQUE("form_id","participant_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_form" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"study_id" uuid NOT NULL,
|
||||
"type" "form_type" NOT NULL,
|
||||
"title" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"version" integer DEFAULT 1 NOT NULL,
|
||||
"active" boolean DEFAULT true NOT NULL,
|
||||
"is_template" boolean DEFAULT false NOT NULL,
|
||||
"template_name" varchar(100),
|
||||
"fields" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
"settings" jsonb DEFAULT '{}'::jsonb,
|
||||
"created_by" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_form_study_id_version_unique" UNIQUE("study_id","version")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_media_capture" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"trial_id" uuid NOT NULL,
|
||||
"media_type" "media_type",
|
||||
"storage_path" text NOT NULL,
|
||||
"file_size" bigint,
|
||||
"duration" integer,
|
||||
"format" varchar(20),
|
||||
"resolution" varchar(20),
|
||||
"start_timestamp" timestamp with time zone,
|
||||
"end_timestamp" timestamp with time zone,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_participant_consent" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"participant_id" uuid NOT NULL,
|
||||
"consent_form_id" uuid NOT NULL,
|
||||
"signed_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"signature_data" text,
|
||||
"ip_address" "inet",
|
||||
"storage_path" text,
|
||||
CONSTRAINT "hs_participant_consent_participant_id_consent_form_id_unique" UNIQUE("participant_id","consent_form_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_participant_document" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"participant_id" uuid NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"type" varchar(100),
|
||||
"storage_path" text NOT NULL,
|
||||
"file_size" integer,
|
||||
"uploaded_by" text,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_participant" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"study_id" uuid NOT NULL,
|
||||
"participant_code" varchar(50) NOT NULL,
|
||||
"email" varchar(255),
|
||||
"name" varchar(255),
|
||||
"demographics" jsonb DEFAULT '{}'::jsonb,
|
||||
"consent_given" boolean DEFAULT false NOT NULL,
|
||||
"consent_date" timestamp with time zone,
|
||||
"notes" text,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_participant_study_id_participant_code_unique" UNIQUE("study_id","participant_code")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_permission" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(100) NOT NULL,
|
||||
"description" text,
|
||||
"resource" varchar(50) NOT NULL,
|
||||
"action" varchar(50) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_permission_name_unique" UNIQUE("name")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_plugin_repository" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"url" text NOT NULL,
|
||||
"description" text,
|
||||
"trust_level" "trust_level" DEFAULT 'community' NOT NULL,
|
||||
"is_enabled" boolean DEFAULT true NOT NULL,
|
||||
"is_official" boolean DEFAULT false NOT NULL,
|
||||
"last_sync_at" timestamp with time zone,
|
||||
"sync_status" varchar(50) DEFAULT 'pending',
|
||||
"sync_error" text,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"created_by" text NOT NULL,
|
||||
CONSTRAINT "hs_plugin_repository_url_unique" UNIQUE("url")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_plugin" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"robot_id" uuid,
|
||||
"identifier" varchar(100) NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"version" varchar(50) NOT NULL,
|
||||
"description" text,
|
||||
"author" varchar(255),
|
||||
"repository_url" text,
|
||||
"trust_level" "trust_level",
|
||||
"status" "plugin_status" DEFAULT 'active' NOT NULL,
|
||||
"configuration_schema" jsonb,
|
||||
"action_definitions" jsonb DEFAULT '[]'::jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb,
|
||||
CONSTRAINT "hs_plugin_identifier_unique" UNIQUE("identifier"),
|
||||
CONSTRAINT "hs_plugin_name_version_unique" UNIQUE("name","version")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_robot_plugin" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"version" varchar(50) NOT NULL,
|
||||
"manufacturer" varchar(255),
|
||||
"description" text,
|
||||
"robot_id" uuid,
|
||||
"communication_protocol" "communication_protocol",
|
||||
"status" "plugin_status" DEFAULT 'active' NOT NULL,
|
||||
"config_schema" jsonb,
|
||||
"capabilities" jsonb DEFAULT '[]'::jsonb,
|
||||
"trust_level" "trust_level" DEFAULT 'community' NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_robot" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"manufacturer" varchar(255),
|
||||
"model" varchar(255),
|
||||
"description" text,
|
||||
"capabilities" jsonb DEFAULT '[]'::jsonb,
|
||||
"communication_protocol" "communication_protocol",
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_role_permission" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"role" "system_role" NOT NULL,
|
||||
"permission_id" uuid NOT NULL,
|
||||
CONSTRAINT "hs_role_permission_role_permission_id_unique" UNIQUE("role","permission_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_sensor_data" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"trial_id" uuid NOT NULL,
|
||||
"sensor_type" varchar(50) NOT NULL,
|
||||
"timestamp" timestamp with time zone NOT NULL,
|
||||
"data" jsonb NOT NULL,
|
||||
"robot_state" jsonb DEFAULT '{}'::jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_session" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"token" varchar(255) NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"ip_address" text,
|
||||
"user_agent" text,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_session_token_unique" UNIQUE("token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_shared_resource" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"study_id" uuid NOT NULL,
|
||||
"resource_type" varchar(50) NOT NULL,
|
||||
"resource_id" uuid NOT NULL,
|
||||
"shared_by" text NOT NULL,
|
||||
"share_token" varchar(255),
|
||||
"permissions" jsonb DEFAULT '["read"]'::jsonb,
|
||||
"expires_at" timestamp with time zone,
|
||||
"access_count" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_shared_resource_share_token_unique" UNIQUE("share_token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_step" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"experiment_id" uuid NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"type" "step_type" NOT NULL,
|
||||
"order_index" integer NOT NULL,
|
||||
"duration_estimate" integer,
|
||||
"required" boolean DEFAULT true NOT NULL,
|
||||
"conditions" jsonb DEFAULT '{}'::jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_step_experiment_id_order_index_unique" UNIQUE("experiment_id","order_index")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_study" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" text,
|
||||
"institution" varchar(255),
|
||||
"irb_protocol" varchar(100),
|
||||
"status" "study_status" DEFAULT 'draft' NOT NULL,
|
||||
"created_by" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb,
|
||||
"settings" jsonb DEFAULT '{}'::jsonb,
|
||||
"deleted_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_study_member" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"study_id" uuid NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"role" "study_member_role" NOT NULL,
|
||||
"permissions" jsonb DEFAULT '[]'::jsonb,
|
||||
"joined_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"invited_by" text,
|
||||
CONSTRAINT "hs_study_member_study_id_user_id_unique" UNIQUE("study_id","user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_study_plugin" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"study_id" uuid NOT NULL,
|
||||
"plugin_id" uuid NOT NULL,
|
||||
"configuration" jsonb DEFAULT '{}'::jsonb,
|
||||
"installed_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"installed_by" text NOT NULL,
|
||||
CONSTRAINT "hs_study_plugin_study_id_plugin_id_unique" UNIQUE("study_id","plugin_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_system_setting" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"key" varchar(100) NOT NULL,
|
||||
"value" jsonb NOT NULL,
|
||||
"description" text,
|
||||
"updated_by" text,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_system_setting_key_unique" UNIQUE("key")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_trial_event" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"trial_id" uuid NOT NULL,
|
||||
"event_type" varchar(50) NOT NULL,
|
||||
"action_id" uuid,
|
||||
"timestamp" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"data" jsonb DEFAULT '{}'::jsonb,
|
||||
"created_by" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_trial" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"experiment_id" uuid NOT NULL,
|
||||
"participant_id" uuid,
|
||||
"wizard_id" text,
|
||||
"session_number" integer DEFAULT 1 NOT NULL,
|
||||
"status" "trial_status" DEFAULT 'scheduled' NOT NULL,
|
||||
"scheduled_at" timestamp with time zone,
|
||||
"started_at" timestamp with time zone,
|
||||
"completed_at" timestamp with time zone,
|
||||
"duration" integer,
|
||||
"notes" text,
|
||||
"parameters" jsonb DEFAULT '{}'::jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_user_system_role" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"role" "system_role" NOT NULL,
|
||||
"granted_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"granted_by" text,
|
||||
CONSTRAINT "hs_user_system_role_user_id_role_unique" UNIQUE("user_id","role")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_user" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255),
|
||||
"email" varchar(255) NOT NULL,
|
||||
"email_verified" boolean DEFAULT false NOT NULL,
|
||||
"image" text,
|
||||
"password" varchar(255),
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"deleted_at" timestamp with time zone,
|
||||
CONSTRAINT "hs_user_email_unique" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_verification_token" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"identifier" varchar(255) NOT NULL,
|
||||
"value" varchar(255) NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_verification_token_value_unique" UNIQUE("value")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_wizard_intervention" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"trial_id" uuid NOT NULL,
|
||||
"wizard_id" text NOT NULL,
|
||||
"intervention_type" varchar(100) NOT NULL,
|
||||
"description" text,
|
||||
"timestamp" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
"parameters" jsonb DEFAULT '{}'::jsonb,
|
||||
"reason" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "hs_ws_connection" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"trial_id" uuid NOT NULL,
|
||||
"client_id" text NOT NULL,
|
||||
"user_id" text,
|
||||
"connected_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT "hs_ws_connection_client_id_unique" UNIQUE("client_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "hs_account" ADD CONSTRAINT "hs_account_user_id_hs_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."hs_user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_action" ADD CONSTRAINT "hs_action_step_id_hs_step_id_fk" FOREIGN KEY ("step_id") REFERENCES "public"."hs_step"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_activity_log" ADD CONSTRAINT "hs_activity_log_study_id_hs_study_id_fk" FOREIGN KEY ("study_id") REFERENCES "public"."hs_study"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_activity_log" ADD CONSTRAINT "hs_activity_log_user_id_hs_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_annotation" ADD CONSTRAINT "hs_annotation_trial_id_hs_trial_id_fk" FOREIGN KEY ("trial_id") REFERENCES "public"."hs_trial"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_annotation" ADD CONSTRAINT "hs_annotation_annotator_id_hs_user_id_fk" FOREIGN KEY ("annotator_id") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_attachment" ADD CONSTRAINT "hs_attachment_uploaded_by_hs_user_id_fk" FOREIGN KEY ("uploaded_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_audit_log" ADD CONSTRAINT "hs_audit_log_user_id_hs_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_block_registry" ADD CONSTRAINT "hs_block_registry_plugin_id_hs_robot_plugin_id_fk" FOREIGN KEY ("plugin_id") REFERENCES "public"."hs_robot_plugin"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_comment" ADD CONSTRAINT "hs_comment_author_id_hs_user_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_consent_form" ADD CONSTRAINT "hs_consent_form_study_id_hs_study_id_fk" FOREIGN KEY ("study_id") REFERENCES "public"."hs_study"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_consent_form" ADD CONSTRAINT "hs_consent_form_created_by_hs_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_experiment" ADD CONSTRAINT "hs_experiment_study_id_hs_study_id_fk" FOREIGN KEY ("study_id") REFERENCES "public"."hs_study"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_experiment" ADD CONSTRAINT "hs_experiment_robot_id_hs_robot_id_fk" FOREIGN KEY ("robot_id") REFERENCES "public"."hs_robot"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_experiment" ADD CONSTRAINT "hs_experiment_created_by_hs_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_export_job" ADD CONSTRAINT "hs_export_job_study_id_hs_study_id_fk" FOREIGN KEY ("study_id") REFERENCES "public"."hs_study"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_export_job" ADD CONSTRAINT "hs_export_job_requested_by_hs_user_id_fk" FOREIGN KEY ("requested_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_form_response" ADD CONSTRAINT "hs_form_response_form_id_hs_form_id_fk" FOREIGN KEY ("form_id") REFERENCES "public"."hs_form"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_form_response" ADD CONSTRAINT "hs_form_response_participant_id_hs_participant_id_fk" FOREIGN KEY ("participant_id") REFERENCES "public"."hs_participant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_form" ADD CONSTRAINT "hs_form_study_id_hs_study_id_fk" FOREIGN KEY ("study_id") REFERENCES "public"."hs_study"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_form" ADD CONSTRAINT "hs_form_created_by_hs_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_media_capture" ADD CONSTRAINT "hs_media_capture_trial_id_hs_trial_id_fk" FOREIGN KEY ("trial_id") REFERENCES "public"."hs_trial"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_participant_consent" ADD CONSTRAINT "hs_participant_consent_participant_id_hs_participant_id_fk" FOREIGN KEY ("participant_id") REFERENCES "public"."hs_participant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_participant_consent" ADD CONSTRAINT "hs_participant_consent_consent_form_id_hs_consent_form_id_fk" FOREIGN KEY ("consent_form_id") REFERENCES "public"."hs_consent_form"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_participant_document" ADD CONSTRAINT "hs_participant_document_participant_id_hs_participant_id_fk" FOREIGN KEY ("participant_id") REFERENCES "public"."hs_participant"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_participant_document" ADD CONSTRAINT "hs_participant_document_uploaded_by_hs_user_id_fk" FOREIGN KEY ("uploaded_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_participant" ADD CONSTRAINT "hs_participant_study_id_hs_study_id_fk" FOREIGN KEY ("study_id") REFERENCES "public"."hs_study"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_plugin_repository" ADD CONSTRAINT "hs_plugin_repository_created_by_hs_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_plugin" ADD CONSTRAINT "hs_plugin_robot_id_hs_robot_id_fk" FOREIGN KEY ("robot_id") REFERENCES "public"."hs_robot"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_robot_plugin" ADD CONSTRAINT "hs_robot_plugin_robot_id_hs_robot_id_fk" FOREIGN KEY ("robot_id") REFERENCES "public"."hs_robot"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_role_permission" ADD CONSTRAINT "hs_role_permission_permission_id_hs_permission_id_fk" FOREIGN KEY ("permission_id") REFERENCES "public"."hs_permission"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_sensor_data" ADD CONSTRAINT "hs_sensor_data_trial_id_hs_trial_id_fk" FOREIGN KEY ("trial_id") REFERENCES "public"."hs_trial"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_session" ADD CONSTRAINT "hs_session_user_id_hs_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."hs_user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_shared_resource" ADD CONSTRAINT "hs_shared_resource_study_id_hs_study_id_fk" FOREIGN KEY ("study_id") REFERENCES "public"."hs_study"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_shared_resource" ADD CONSTRAINT "hs_shared_resource_shared_by_hs_user_id_fk" FOREIGN KEY ("shared_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_step" ADD CONSTRAINT "hs_step_experiment_id_hs_experiment_id_fk" FOREIGN KEY ("experiment_id") REFERENCES "public"."hs_experiment"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_study" ADD CONSTRAINT "hs_study_created_by_hs_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_study_member" ADD CONSTRAINT "hs_study_member_study_id_hs_study_id_fk" FOREIGN KEY ("study_id") REFERENCES "public"."hs_study"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_study_member" ADD CONSTRAINT "hs_study_member_user_id_hs_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."hs_user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_study_member" ADD CONSTRAINT "hs_study_member_invited_by_hs_user_id_fk" FOREIGN KEY ("invited_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_study_plugin" ADD CONSTRAINT "hs_study_plugin_study_id_hs_study_id_fk" FOREIGN KEY ("study_id") REFERENCES "public"."hs_study"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_study_plugin" ADD CONSTRAINT "hs_study_plugin_plugin_id_hs_plugin_id_fk" FOREIGN KEY ("plugin_id") REFERENCES "public"."hs_plugin"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_study_plugin" ADD CONSTRAINT "hs_study_plugin_installed_by_hs_user_id_fk" FOREIGN KEY ("installed_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_system_setting" ADD CONSTRAINT "hs_system_setting_updated_by_hs_user_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_trial_event" ADD CONSTRAINT "hs_trial_event_trial_id_hs_trial_id_fk" FOREIGN KEY ("trial_id") REFERENCES "public"."hs_trial"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_trial_event" ADD CONSTRAINT "hs_trial_event_action_id_hs_action_id_fk" FOREIGN KEY ("action_id") REFERENCES "public"."hs_action"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_trial_event" ADD CONSTRAINT "hs_trial_event_created_by_hs_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_trial" ADD CONSTRAINT "hs_trial_experiment_id_hs_experiment_id_fk" FOREIGN KEY ("experiment_id") REFERENCES "public"."hs_experiment"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_trial" ADD CONSTRAINT "hs_trial_participant_id_hs_participant_id_fk" FOREIGN KEY ("participant_id") REFERENCES "public"."hs_participant"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_trial" ADD CONSTRAINT "hs_trial_wizard_id_hs_user_id_fk" FOREIGN KEY ("wizard_id") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_user_system_role" ADD CONSTRAINT "hs_user_system_role_user_id_hs_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."hs_user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_user_system_role" ADD CONSTRAINT "hs_user_system_role_granted_by_hs_user_id_fk" FOREIGN KEY ("granted_by") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_wizard_intervention" ADD CONSTRAINT "hs_wizard_intervention_trial_id_hs_trial_id_fk" FOREIGN KEY ("trial_id") REFERENCES "public"."hs_trial"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_wizard_intervention" ADD CONSTRAINT "hs_wizard_intervention_wizard_id_hs_user_id_fk" FOREIGN KEY ("wizard_id") REFERENCES "public"."hs_user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "hs_ws_connection" ADD CONSTRAINT "hs_ws_connection_trial_id_hs_trial_id_fk" FOREIGN KEY ("trial_id") REFERENCES "public"."hs_trial"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "account_user_id_idx" ON "hs_account" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "activity_logs_study_created_idx" ON "hs_activity_log" USING btree ("study_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX "audit_logs_created_idx" ON "hs_audit_log" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "block_registry_category_idx" ON "hs_block_registry" USING btree ("category");--> statement-breakpoint
|
||||
CREATE INDEX "experiment_visual_design_idx" ON "hs_experiment" USING gin ("visual_design");--> statement-breakpoint
|
||||
CREATE INDEX "participant_document_participant_idx" ON "hs_participant_document" USING btree ("participant_id");--> statement-breakpoint
|
||||
CREATE INDEX "sensor_data_trial_timestamp_idx" ON "hs_sensor_data" USING btree ("trial_id","timestamp");--> statement-breakpoint
|
||||
CREATE INDEX "session_user_id_idx" ON "hs_session" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "trial_events_trial_timestamp_idx" ON "hs_trial_event" USING btree ("trial_id","timestamp");--> statement-breakpoint
|
||||
CREATE INDEX "verification_token_identifier_idx" ON "hs_verification_token" USING btree ("identifier");--> statement-breakpoint
|
||||
CREATE INDEX "verification_token_value_idx" ON "hs_verification_token" USING btree ("value");
|
||||
@@ -0,0 +1,34 @@
|
||||
-- Migration 0001: Minimal Seed Data
|
||||
-- HRIStudio - Only essential data needed for auth
|
||||
|
||||
-- ======================
|
||||
-- USERS & AUTH
|
||||
-- ======================
|
||||
|
||||
-- Users (using valid UUID v4 format)
|
||||
INSERT INTO "hs_user" ("id", "name", "email", "email_verified", "image", "created_at", "updated_at")
|
||||
VALUES
|
||||
('11111111-1111-4111-8111-111111111111', 'Sean O''Connor', 'sean@soconnor.dev', true, 'https://www.gravatar.com/avatar/4b20f4a15f9a0e0f5e5e5a0f5e5e5a0f?d=identicon', NOW(), NOW()),
|
||||
('22222222-2222-4222-8222-222222222222', 'Dr. Felipe Perrone', 'felipe.perrone@bucknell.edu', true, 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felipe', NOW(), NOW())
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Accounts
|
||||
INSERT INTO "hs_account" ("id", "user_id", "provider_id", "account_id", "password", "created_at", "updated_at")
|
||||
VALUES
|
||||
('aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa', '11111111-1111-4111-8111-111111111111', 'credential', '11111111-1111-4111-8111-111111111111', '$2b$12$50kgpkp.qZrZXCWjHuVSHOZBjAQUrX50VdtWc6WBj27HnzUYFwwPm', NOW(), NOW()),
|
||||
('bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbbbb', '22222222-2222-4222-8222-222222222222', 'credential', '22222222-2222-4222-8222-222222222222', '$2b$12$50kgpkp.qZrZXCWjHuVSHOZBjAQUrX50VdtWc6WBj27HnzUYFwwPm', NOW(), NOW())
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- System Roles
|
||||
INSERT INTO "hs_user_system_role" ("id", "user_id", "role", "granted_at", "granted_by")
|
||||
VALUES
|
||||
(gen_random_uuid(), '11111111-1111-4111-8111-111111111111', 'administrator', NOW(), '11111111-1111-4111-8111-111111111111'),
|
||||
(gen_random_uuid(), '22222222-2222-4222-8222-222222222222', 'researcher', NOW(), '11111111-1111-4111-8111-111111111111')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Minimal seed migration complete';
|
||||
RAISE NOTICE 'Admin: sean@soconnor.dev / password123';
|
||||
RAISE NOTICE 'Use bun db:seed for full demo data';
|
||||
END $$;
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 0,
|
||||
"tag": "0000_init_schema",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1,
|
||||
"tag": "0001_seed_data",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
+5
-2
@@ -5,6 +5,9 @@
|
||||
import "./src/env.js";
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const config = {};
|
||||
const nextConfig = {
|
||||
// Mark server-only packages as external to prevent bundling in client
|
||||
serverExternalPackages: ["postgres", "minio", "child_process"],
|
||||
};
|
||||
|
||||
export default config;
|
||||
export default nextConfig;
|
||||
|
||||
+88
-59
@@ -5,105 +5,130 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"check": "next lint && tsc --noEmit",
|
||||
"check": "eslint . && tsc --noEmit",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:seed": "bun db:push && bun scripts/seed-dev.ts",
|
||||
"dev": "next dev --turbo",
|
||||
"db:reset": "docker compose rm -s -f -v db && docker compose up -d db && sleep 2 && bun db:seed",
|
||||
"db:restart": "docker compose restart db",
|
||||
"dev": "bun run dev:ws & next dev",
|
||||
"dev:ws": "bun run ws-server.ts",
|
||||
"docker:up": "if [ \"$(uname)\" = \"Darwin\" ]; then colima start; fi && docker compose up -d",
|
||||
"docker:down": "docker compose down && if [ \"$(uname)\" = \"Darwin\" ]; then colima stop; fi",
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||
"lint": "next lint",
|
||||
"lint:fix": "next lint --fix",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"preview": "next build && next start",
|
||||
"start": "next start",
|
||||
"start": "bun run start:ws & next start",
|
||||
"start:ws": "bun run ws-server.ts",
|
||||
"start:web": "next start",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/drizzle-adapter": "^1.10.0",
|
||||
"@aws-sdk/client-s3": "^3.859.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.859.0",
|
||||
"@auth/drizzle-adapter": "^1.11.1",
|
||||
"@aws-sdk/client-s3": "^3.989.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.989.0",
|
||||
"@better-auth/drizzle-adapter": "^1.5.5",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@hookform/resolvers": "^5.1.1",
|
||||
"@radix-ui/react-accordion": "^1.2.11",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.14",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.8",
|
||||
"@radix-ui/react-avatar": "^1.1.10",
|
||||
"@radix-ui/react-checkbox": "^1.3.2",
|
||||
"@radix-ui/react-collapsible": "^1.1.11",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-scroll-area": "^1.2.9",
|
||||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-slider": "^1.3.5",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-switch": "^1.2.5",
|
||||
"@radix-ui/react-avatar": "^1.1.11",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-progress": "^1.1.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slider": "^1.3.6",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@shadcn/ui": "^0.0.4",
|
||||
"@t3-oss/env-nextjs": "^0.13.8",
|
||||
"@tanstack/react-query": "^5.69.0",
|
||||
"@t3-oss/env-nextjs": "^0.13.10",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tanstack/react-query": "^5.90.21",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@trpc/client": "^11.0.0",
|
||||
"@trpc/react-query": "^11.0.0",
|
||||
"@trpc/server": "^11.0.0",
|
||||
"@tiptap/extension-table": "^3.20.0",
|
||||
"@tiptap/extension-table-cell": "^3.20.0",
|
||||
"@tiptap/extension-table-header": "^3.20.0",
|
||||
"@tiptap/extension-table-row": "^3.20.0",
|
||||
"@tiptap/pm": "^3.20.0",
|
||||
"@tiptap/react": "^3.20.0",
|
||||
"@tiptap/starter-kit": "^3.20.0",
|
||||
"@trpc/client": "^11.10.0",
|
||||
"@trpc/react-query": "^11.10.0",
|
||||
"@trpc/server": "^11.10.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/ws": "^8.18.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"better-auth": "^1.5.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"driver.js": "^1.4.0",
|
||||
"drizzle-orm": "^0.41.0",
|
||||
"html2pdf.js": "^0.14.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.536.0",
|
||||
"minio": "^8.0.6",
|
||||
"next": "^16.1.6",
|
||||
"next-auth": "^5.0.0-beta.29",
|
||||
"next": "16.2.1",
|
||||
"next-auth": "^5.0.0-beta.30",
|
||||
"next-themes": "^0.4.6",
|
||||
"postgres": "^3.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.60.0",
|
||||
"react-resizable-panels": "^3.0.4",
|
||||
"postgres": "^3.4.8",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "19.2.4",
|
||||
"react-day-picker": "^9.13.2",
|
||||
"react-dom": "19.2.4",
|
||||
"react-hook-form": "^7.71.1",
|
||||
"react-resizable-panels": "^3.0.6",
|
||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||
"react-webcam": "^7.2.0",
|
||||
"server-only": "^0.0.1",
|
||||
"sonner": "^2.0.7",
|
||||
"superjson": "^2.2.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"ws": "^8.18.3",
|
||||
"zod": "^4.0.5",
|
||||
"zustand": "^4.5.5"
|
||||
"superjson": "^2.2.6",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tiptap-markdown": "^0.9.0",
|
||||
"uuid": "^13.0.0",
|
||||
"ws": "^8.19.0",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^4.5.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@tailwindcss/postcss": "^4.0.15",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@types/bcryptjs": "^3.0.0",
|
||||
"@types/bun": "^1.3.9",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-config-next": "^15.2.3",
|
||||
"@types/node": "^20.19.33",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/uuid": "^11.0.0",
|
||||
"drizzle-kit": "^0.30.6",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "16.2.1",
|
||||
"eslint-plugin-drizzle": "^0.2.3",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^4.0.15",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"ts-unused-exports": "^11.0.1",
|
||||
"tw-animate-css": "^1.3.5",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.27.0"
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.55.0",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.39.3"
|
||||
@@ -113,5 +138,9 @@
|
||||
"esbuild",
|
||||
"sharp",
|
||||
"unrs-resolver"
|
||||
]
|
||||
],
|
||||
"overrides": {
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# Homepage Screenshots
|
||||
|
||||
Add your app screenshots here. The homepage will display them automatically.
|
||||
|
||||
## Required Screenshots
|
||||
|
||||
1. **experiment-designer.png** - Visual experiment designer showing block-based workflow
|
||||
2. **wizard-interface.png** - Wizard execution interface with trial controls
|
||||
3. **dashboard.png** - Study dashboard showing experiments and trials
|
||||
|
||||
## Recommended Size
|
||||
|
||||
- Width: 1200px
|
||||
- Format: PNG or WebP
|
||||
- Quality: High (screenshot at 2x for retina displays)
|
||||
|
||||
## Preview in Browser
|
||||
|
||||
After adding screenshots, uncomment the `<Image>` component in `src/app/page.tsx`:
|
||||
|
||||
```tsx
|
||||
<Image
|
||||
src={screenshot.src}
|
||||
alt={screenshot.alt}
|
||||
fill
|
||||
className="object-cover transition-transform group-hover:scale-105"
|
||||
/>
|
||||
```
|
||||
+1
-1
Submodule robot-plugins updated: c6310d3144...8334b809f2
@@ -0,0 +1,46 @@
|
||||
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 seeded actions...");
|
||||
|
||||
const actions = await db.query.actions.findMany({
|
||||
where: (actions, { or, eq, like }) =>
|
||||
or(
|
||||
eq(actions.type, "sequence"),
|
||||
eq(actions.type, "parallel"),
|
||||
eq(actions.type, "loop"),
|
||||
eq(actions.type, "branch"),
|
||||
like(actions.type, "hristudio-core%"),
|
||||
),
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
console.log(`Found ${actions.length} control actions.`);
|
||||
|
||||
for (const action of actions) {
|
||||
console.log(`\nAction: ${action.name} (${action.type})`);
|
||||
console.log(`ID: ${action.id}`);
|
||||
// Explicitly log parameters to check structure
|
||||
console.log("Parameters:", JSON.stringify(action.parameters, null, 2));
|
||||
|
||||
const params = action.parameters as any;
|
||||
if (params.children) {
|
||||
console.log(`✅ Has ${params.children.length} children in parameters.`);
|
||||
} else if (params.trueBranch || params.falseBranch) {
|
||||
console.log(`✅ Has branches in parameters.`);
|
||||
} else {
|
||||
console.log(`❌ No children/branches found in parameters.`);
|
||||
}
|
||||
}
|
||||
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,66 @@
|
||||
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);
|
||||
@@ -0,0 +1,60 @@
|
||||
import { db } from "~/server/db";
|
||||
import { steps, experiments, actions } from "~/server/db/schema";
|
||||
import { eq, asc } from "drizzle-orm";
|
||||
|
||||
async function debugExperimentStructure() {
|
||||
console.log("Debugging Experiment Structure for Interactive Storyteller...");
|
||||
|
||||
// Find the experiment
|
||||
const experiment = await db.query.experiments.findFirst({
|
||||
where: eq(experiments.name, "The Interactive Storyteller"),
|
||||
with: {
|
||||
steps: {
|
||||
orderBy: [asc(steps.orderIndex)],
|
||||
with: {
|
||||
actions: {
|
||||
orderBy: [asc(actions.orderIndex)],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!experiment) {
|
||||
console.error("Experiment not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Experiment: ${experiment.name} (${experiment.id})`);
|
||||
console.log(`Plugin Dependencies:`, experiment.pluginDependencies);
|
||||
console.log("---------------------------------------------------");
|
||||
|
||||
experiment.steps.forEach((step, index) => {
|
||||
console.log(`Step ${index + 1}: ${step.name}`);
|
||||
console.log(` ID: ${step.id}`);
|
||||
console.log(` Type: ${step.type}`);
|
||||
console.log(` Order: ${step.orderIndex}`);
|
||||
console.log(` Conditions:`, JSON.stringify(step.conditions, null, 2));
|
||||
|
||||
if (step.actions && step.actions.length > 0) {
|
||||
console.log(` Actions (${step.actions.length}):`);
|
||||
step.actions.forEach((action, actionIndex) => {
|
||||
console.log(` ${actionIndex + 1}. [${action.type}] ${action.name}`);
|
||||
if (action.type === "wizard_wait_for_response") {
|
||||
console.log(
|
||||
` Options:`,
|
||||
JSON.stringify((action.parameters as any)?.options, null, 2),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log("---------------------------------------------------");
|
||||
});
|
||||
}
|
||||
|
||||
debugExperimentStructure()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { db } from "../../src/server/db";
|
||||
import { experiments, steps } from "../../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
async function inspectAllSteps() {
|
||||
const result = await db.query.experiments.findMany({
|
||||
with: {
|
||||
steps: {
|
||||
orderBy: (steps, { asc }) => [asc(steps.orderIndex)],
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
type: true,
|
||||
orderIndex: true,
|
||||
conditions: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Found ${result.length} experiments.`);
|
||||
|
||||
for (const exp of result) {
|
||||
console.log(`Experiment: ${exp.name} (${exp.id})`);
|
||||
for (const step of exp.steps) {
|
||||
// Only print conditional steps or the first step
|
||||
if (step.type === "conditional" || step.orderIndex === 0) {
|
||||
console.log(` [${step.orderIndex}] ${step.name} (${step.type})`);
|
||||
console.log(` Conditions: ${JSON.stringify(step.conditions)}`);
|
||||
}
|
||||
}
|
||||
console.log("---");
|
||||
}
|
||||
}
|
||||
|
||||
inspectAllSteps()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { db } from "~/server/db";
|
||||
import { actions, steps } from "~/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
async function inspectAction() {
|
||||
console.log("Inspecting Action 10851aef-e720-45fc-ba5e-05e1e3425dab...");
|
||||
|
||||
const actionId = "10851aef-e720-45fc-ba5e-05e1e3425dab";
|
||||
|
||||
const action = await db.query.actions.findFirst({
|
||||
where: eq(actions.id, actionId),
|
||||
with: {
|
||||
step: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
type: true,
|
||||
conditions: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!action) {
|
||||
console.error("Action not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Action Found:");
|
||||
console.log(" Name:", action.name);
|
||||
console.log(" Type:", action.type);
|
||||
console.log(" Parameters:", JSON.stringify(action.parameters, null, 2));
|
||||
|
||||
console.log("Parent Step:");
|
||||
console.log(" ID:", action.step.id);
|
||||
console.log(" Name:", action.step.name);
|
||||
console.log(" Type:", action.step.type);
|
||||
console.log(" Conditions:", JSON.stringify(action.step.conditions, null, 2));
|
||||
}
|
||||
|
||||
inspectAction()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { db } from "~/server/db";
|
||||
import { steps } from "~/server/db/schema";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
|
||||
async function inspectBranchSteps() {
|
||||
console.log("Inspecting Steps 4 (Branch A) and 5 (Branch B)...");
|
||||
|
||||
const step4Id = "3a2dc0b7-a43e-4236-9b9e-f957abafc1e5";
|
||||
const step5Id = "3ae2fe8a-fc5d-4a04-baa5-699a21f19e30";
|
||||
|
||||
const branchSteps = await db.query.steps.findMany({
|
||||
where: inArray(steps.id, [step4Id, step5Id]),
|
||||
});
|
||||
|
||||
branchSteps.forEach((step) => {
|
||||
console.log(`Step: ${step.name} (${step.id})`);
|
||||
console.log(` Type: ${step.type}`);
|
||||
console.log(` Order: ${step.orderIndex}`);
|
||||
console.log(` Conditions:`, JSON.stringify(step.conditions, null, 2));
|
||||
console.log("---------------------------------------------------");
|
||||
});
|
||||
}
|
||||
|
||||
inspectBranchSteps()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { db } from "../../src/server/db";
|
||||
import { steps } from "../../src/server/db/schema";
|
||||
import { eq, like } from "drizzle-orm";
|
||||
|
||||
async function checkSteps() {
|
||||
const allSteps = await db
|
||||
.select()
|
||||
.from(steps)
|
||||
.where(like(steps.name, "%Comprehension Check%"));
|
||||
|
||||
console.log("Found steps:", allSteps.length);
|
||||
|
||||
for (const step of allSteps) {
|
||||
console.log("Step Name:", step.name);
|
||||
console.log("Type:", step.type);
|
||||
console.log("Conditions (typeof):", typeof step.conditions);
|
||||
console.log(
|
||||
"Conditions (value):",
|
||||
JSON.stringify(step.conditions, null, 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
checkSteps()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { db } from "~/server/db";
|
||||
import { steps, experiments } from "~/server/db/schema";
|
||||
import { eq, asc } from "drizzle-orm";
|
||||
|
||||
async function inspectExperimentSteps() {
|
||||
// Find experiment by ID
|
||||
const experiment = await db.query.experiments.findFirst({
|
||||
where: eq(experiments.id, "961d0cb1-256d-4951-8387-6d855a0ae603"),
|
||||
});
|
||||
|
||||
if (!experiment) {
|
||||
console.log("Experiment not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Inspecting Experiment: ${experiment.name} (${experiment.id})`);
|
||||
|
||||
const experimentSteps = await db.query.steps.findMany({
|
||||
where: eq(steps.experimentId, experiment.id),
|
||||
orderBy: [asc(steps.orderIndex)],
|
||||
with: {
|
||||
actions: {
|
||||
orderBy: (actions, { asc }) => [asc(actions.orderIndex)],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Found ${experimentSteps.length} steps.`);
|
||||
|
||||
for (const step of experimentSteps) {
|
||||
console.log("--------------------------------------------------");
|
||||
console.log(`Step [${step.orderIndex}] ID: ${step.id}`);
|
||||
console.log(`Name: ${step.name}`);
|
||||
console.log(`Type: ${step.type}`);
|
||||
|
||||
if (step.type === "conditional") {
|
||||
console.log("Conditions:", JSON.stringify(step.conditions, null, 2));
|
||||
}
|
||||
|
||||
if (step.actions.length > 0) {
|
||||
console.log("Actions:");
|
||||
for (const action of step.actions) {
|
||||
console.log(
|
||||
` - [${action.orderIndex}] ${action.name} (${action.type})`,
|
||||
);
|
||||
if (action.type === "wizard_wait_for_response") {
|
||||
console.log(
|
||||
" Parameters:",
|
||||
JSON.stringify(action.parameters, null, 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inspectExperimentSteps()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { db } from "../../src/server/db";
|
||||
import { experiments } from "../../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
async function inspectVisualDesign() {
|
||||
const exps = await db.select().from(experiments);
|
||||
|
||||
for (const exp of exps) {
|
||||
console.log(`Experiment: ${exp.name}`);
|
||||
if (exp.visualDesign) {
|
||||
const vd = exp.visualDesign as any;
|
||||
console.log("Visual Design Steps:");
|
||||
if (vd.steps && Array.isArray(vd.steps)) {
|
||||
vd.steps.forEach((s: any, i: number) => {
|
||||
console.log(` [${i}] ${s.name} (${s.type})`);
|
||||
console.log(` Trigger: ${JSON.stringify(s.trigger)}`);
|
||||
});
|
||||
} else {
|
||||
console.log(" No steps in visualDesign or invalid format.");
|
||||
}
|
||||
} else {
|
||||
console.log(" No visualDesign blob.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inspectVisualDesign()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { db } from "~/server/db";
|
||||
import { actions, steps } from "~/server/db/schema";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
|
||||
async function patchActionParams() {
|
||||
console.log("Patching Action Parameters for Interactive Storyteller...");
|
||||
|
||||
// Target Step IDs
|
||||
const step3CondId = "b9d43f8c-c40c-4f1c-9fdc-9076338d3c85"; // Step 3: Comprehension Check
|
||||
const actionId = "10851aef-e720-45fc-ba5e-05e1e3425dab"; // Action: Wait for Choice
|
||||
|
||||
// 1. Get the authoritative conditions from the Step
|
||||
const step = await db.query.steps.findFirst({
|
||||
where: eq(steps.id, step3CondId),
|
||||
});
|
||||
|
||||
if (!step) {
|
||||
console.error("Step 3 not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
const conditions = step.conditions as any;
|
||||
const richOptions = conditions?.options;
|
||||
|
||||
if (!richOptions || !Array.isArray(richOptions)) {
|
||||
console.error("Step 3 conditions are missing valid options!");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Found rich options in Step:",
|
||||
JSON.stringify(richOptions, null, 2),
|
||||
);
|
||||
|
||||
// 2. Get the Action
|
||||
const action = await db.query.actions.findFirst({
|
||||
where: eq(actions.id, actionId),
|
||||
});
|
||||
|
||||
if (!action) {
|
||||
console.error("Action not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Current Action Parameters:",
|
||||
JSON.stringify(action.parameters, null, 2),
|
||||
);
|
||||
|
||||
// 3. Patch the Action Parameters
|
||||
// We replace the simple string options with the rich object options
|
||||
const currentParams = action.parameters as any;
|
||||
const newParams = {
|
||||
...currentParams,
|
||||
options: richOptions, // Overwrite with rich options from step
|
||||
};
|
||||
|
||||
console.log("New Action Parameters:", JSON.stringify(newParams, null, 2));
|
||||
|
||||
await db.execute(sql`
|
||||
UPDATE hs_action
|
||||
SET parameters = ${JSON.stringify(newParams)}::jsonb
|
||||
WHERE id = ${actionId}
|
||||
`);
|
||||
|
||||
console.log("Action parameters successfully patched.");
|
||||
}
|
||||
|
||||
patchActionParams()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { db } from "~/server/db";
|
||||
import { steps } from "~/server/db/schema";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
|
||||
async function patchBranchSteps() {
|
||||
console.log("Patching branch steps for Interactive Storyteller...");
|
||||
|
||||
// Target Step IDs (From debug output)
|
||||
const step3CondId = "b9d43f8c-c40c-4f1c-9fdc-9076338d3c85"; // Step 3: Comprehension Check
|
||||
const stepBranchAId = "3a2dc0b7-a43e-4236-9b9e-f957abafc1e5"; // Step 4: Branch A (Correct)
|
||||
const stepBranchBId = "3ae2fe8a-fc5d-4a04-baa5-699a21f19e30"; // Step 5: Branch B (Incorrect)
|
||||
const stepConclusionId = "cc3fbc7f-29e5-45e0-8d46-e80813c54292"; // Step 6: Conclusion
|
||||
|
||||
// Update Step 3 (The Conditional Step)
|
||||
console.log("Updating Step 3 (Conditional Step)...");
|
||||
const step3Conditional = await db.query.steps.findFirst({
|
||||
where: eq(steps.id, step3CondId),
|
||||
});
|
||||
|
||||
if (step3Conditional) {
|
||||
const currentConditions = (step3Conditional.conditions as any) || {};
|
||||
const options = currentConditions.options || [];
|
||||
|
||||
// Patch options to point to real step IDs
|
||||
const newOptions = options.map((opt: any) => {
|
||||
if (opt.value === "Correct") return { ...opt, nextStepId: stepBranchAId };
|
||||
if (opt.value === "Incorrect")
|
||||
return { ...opt, nextStepId: stepBranchBId };
|
||||
return opt;
|
||||
});
|
||||
|
||||
const newConditions = { ...currentConditions, options: newOptions };
|
||||
|
||||
await db.execute(sql`
|
||||
UPDATE hs_step
|
||||
SET conditions = ${JSON.stringify(newConditions)}::jsonb
|
||||
WHERE id = ${step3CondId}
|
||||
`);
|
||||
console.log("Step 3 (Conditional) updated links.");
|
||||
} else {
|
||||
console.log("Step 3 (Conditional) not found.");
|
||||
}
|
||||
|
||||
// Update Step 4 (Branch A)
|
||||
console.log("Updating Step 4 (Branch A)...");
|
||||
/*
|
||||
Note: We already patched Step 4 in previous run but under wrong assumption?
|
||||
Let's re-patch to be safe.
|
||||
Debug output showed ID: 3a2dc0b7-a43e-4236-9b9e-f957abafc1e5
|
||||
It should jump to Conclusion (cc3fbc7f...)
|
||||
*/
|
||||
const stepBranchA = await db.query.steps.findFirst({
|
||||
where: eq(steps.id, stepBranchAId),
|
||||
});
|
||||
|
||||
if (stepBranchA) {
|
||||
const currentConditions =
|
||||
(stepBranchA.conditions as Record<string, unknown>) || {};
|
||||
const newConditions = {
|
||||
...currentConditions,
|
||||
nextStepId: stepConclusionId,
|
||||
};
|
||||
|
||||
await db.execute(sql`
|
||||
UPDATE hs_step
|
||||
SET conditions = ${JSON.stringify(newConditions)}::jsonb
|
||||
WHERE id = ${stepBranchAId}
|
||||
`);
|
||||
console.log("Step 4 (Branch A) updated jump target.");
|
||||
}
|
||||
|
||||
// Update Step 5 (Branch B)
|
||||
console.log("Updating Step 5 (Branch B)...");
|
||||
const stepBranchB = await db.query.steps.findFirst({
|
||||
where: eq(steps.id, stepBranchBId),
|
||||
});
|
||||
|
||||
if (stepBranchB) {
|
||||
const currentConditions =
|
||||
(stepBranchB.conditions as Record<string, unknown>) || {};
|
||||
const newConditions = {
|
||||
...currentConditions,
|
||||
nextStepId: stepConclusionId,
|
||||
};
|
||||
|
||||
await db.execute(sql`
|
||||
UPDATE hs_step
|
||||
SET conditions = ${JSON.stringify(newConditions)}::jsonb
|
||||
WHERE id = ${stepBranchBId}
|
||||
`);
|
||||
console.log("Step 5 (Branch B) updated jump target.");
|
||||
}
|
||||
}
|
||||
|
||||
patchBranchSteps()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { convertDatabaseToSteps } from "../../src/lib/experiment-designer/block-converter";
|
||||
import { type ExperimentStep } from "../../src/lib/experiment-designer/types";
|
||||
|
||||
// Mock DB Steps (simulating what experimentsRouter returns before conversion)
|
||||
const mockDbSteps = [
|
||||
{
|
||||
id: "step-1",
|
||||
name: "Step 1",
|
||||
type: "wizard",
|
||||
orderIndex: 0,
|
||||
actions: [
|
||||
{
|
||||
id: "seq-1",
|
||||
name: "Test Sequence",
|
||||
type: "sequence",
|
||||
parameters: {
|
||||
children: [
|
||||
{
|
||||
id: "child-1",
|
||||
name: "Child 1",
|
||||
type: "wait",
|
||||
parameters: { duration: 1 },
|
||||
},
|
||||
{
|
||||
id: "child-2",
|
||||
name: "Child 2",
|
||||
type: "wait",
|
||||
parameters: { duration: 2 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Mock Store Logic (simulating store.ts)
|
||||
function cloneActions(actions: any[]): any[] {
|
||||
return actions.map((a) => ({
|
||||
...a,
|
||||
children: a.children ? cloneActions(a.children) : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
function cloneSteps(steps: any[]): any[] {
|
||||
return steps.map((s) => ({
|
||||
...s,
|
||||
actions: cloneActions(s.actions),
|
||||
}));
|
||||
}
|
||||
|
||||
console.log("🔹 Testing Hydration & Cloning...");
|
||||
|
||||
// 1. Convert DB -> Runtime
|
||||
const runtimeSteps = convertDatabaseToSteps(mockDbSteps);
|
||||
const seq = runtimeSteps[0]?.actions[0];
|
||||
|
||||
if (!seq) {
|
||||
console.error("❌ Conversion Failed: Sequence action not found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Runtime Children Count: ${seq.children?.length ?? "undefined"}`);
|
||||
|
||||
if (!seq.children || seq.children.length === 0) {
|
||||
console.error("❌ Conversion Failed: Children not hydrated from parameters.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 2. Store Cloning
|
||||
const clonedSteps = cloneSteps(runtimeSteps);
|
||||
const clonedSeq = clonedSteps[0]?.actions[0];
|
||||
|
||||
if (!clonedSeq) {
|
||||
console.error("❌ Cloning Failed: Sequence action lost.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Cloned Children Count: ${clonedSeq.children?.length ?? "undefined"}`,
|
||||
);
|
||||
|
||||
if (clonedSeq.children?.length === 2) {
|
||||
console.log("✅ SUCCESS: Data hydrated and cloned correctly.");
|
||||
} else {
|
||||
console.error("❌ CLONING FAILED: Children lost during clone.");
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
// Database connection
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
async function main() {
|
||||
console.log("🌱 Seeding 'Control Flow Demo' experiment...");
|
||||
|
||||
try {
|
||||
// 1. Find Admin User & Study
|
||||
const user = await db.query.users.findFirst({
|
||||
where: (users, { eq }) => eq(users.email, "sean@soconnor.dev"),
|
||||
});
|
||||
if (!user)
|
||||
throw new Error(
|
||||
"Admin user 'sean@soconnor.dev' not found. Run seed-dev.ts first.",
|
||||
);
|
||||
|
||||
const study = await db.query.studies.findFirst({
|
||||
where: (studies, { eq }) => eq(studies.name, "Comparative WoZ Study"),
|
||||
});
|
||||
if (!study)
|
||||
throw new Error(
|
||||
"Study 'Comparative WoZ Study' not found. Run seed-dev.ts first.",
|
||||
);
|
||||
|
||||
// Find Robot
|
||||
const robot = await db.query.robots.findFirst({
|
||||
where: (robots, { eq }) => eq(robots.name, "NAO6"),
|
||||
});
|
||||
if (!robot)
|
||||
throw new Error("Robot 'NAO6' not found. Run seed-dev.ts first.");
|
||||
|
||||
// 2. Create Experiment
|
||||
const [experiment] = await db
|
||||
.insert(schema.experiments)
|
||||
.values({
|
||||
studyId: study.id,
|
||||
name: "Control Flow Demo",
|
||||
description:
|
||||
"Demonstration of enhanced control flow actions: Sequence, Parallel, Wait, Loop, Branch.",
|
||||
version: 1,
|
||||
status: "draft",
|
||||
robotId: robot.id,
|
||||
createdBy: user.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (!experiment) throw new Error("Failed to create experiment");
|
||||
console.log(`✅ Created Experiment: ${experiment.id}`);
|
||||
|
||||
// 3. Create Steps
|
||||
|
||||
// Step 1: Sequence & Parallel
|
||||
const [step1] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment.id,
|
||||
name: "Complex Action Structures",
|
||||
description: "Demonstrating Sequence and Parallel groups",
|
||||
type: "robot",
|
||||
orderIndex: 0,
|
||||
required: true,
|
||||
durationEstimate: 30,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Step 2: Loops & Waits
|
||||
const [step2] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment.id,
|
||||
name: "Repetition & Delays",
|
||||
description: "Demonstrating Loop and Wait actions",
|
||||
type: "robot",
|
||||
orderIndex: 1,
|
||||
required: true,
|
||||
durationEstimate: 45,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// 4. Create Actions
|
||||
|
||||
// --- Step 1 Actions ---
|
||||
|
||||
// Top-level Sequence
|
||||
const seqId = `seq-${Date.now()}`;
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step1!.id,
|
||||
name: "Introduction Sequence",
|
||||
type: "sequence", // New type
|
||||
orderIndex: 0,
|
||||
parameters: {},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
// No explicit children column in schema?
|
||||
// Wait, schema.actions has "children" as jsonb or it's a recursive relationship?
|
||||
// Let's check schema/types.
|
||||
// Looking at ActionChip, it expects `action.children`.
|
||||
// In DB, it's likely stored in `children` jsonb column if it exists, OR we need to perform recursive inserts if schema supports parentId.
|
||||
// Checking `types.ts` or schema...
|
||||
// Assuming flat list references for now or JSONB.
|
||||
// Wait, `ExperimentAction` in types has `children?: ExperimentAction[]`.
|
||||
// If the DB schema `actions` table handles nesting via `parameters` or specific column, I need to know.
|
||||
// Defaulting to "children" property in JSON parameter if DB doesn't have parentId.
|
||||
// Checking `schema.ts`: "children" is likely NOT a column if I haven't seen it in seed-dev.
|
||||
// However, `ActionChip` uses `action.children`. Steps map to `actions`.
|
||||
// If `actions` table has `parentId` or `children` JSONB.
|
||||
// I will assume `children` is part of the `parameters` or a simplified representation for now,
|
||||
// BUT `FlowWorkspace` treats `action.children` as real actions.
|
||||
// Let's check `schema.ts` quickly.
|
||||
});
|
||||
|
||||
// I need to check schema.actions definition effectively.
|
||||
// For this pass, I will insert them as flat actions since I can't confirm nesting storage without checking schema.
|
||||
// But the user WANTS to see the nesting (Sequence, Parallel).
|
||||
// The `SortableActionChip` renders `action.children`.
|
||||
// The `TrialExecutionEngine` executes `action.children`.
|
||||
// So the data MUST include children.
|
||||
// Most likely `actions` table has a `children` JSONB column.
|
||||
|
||||
// I will insert a Parallel action with embedded children in the `children` column (if it exists) or `parameters`.
|
||||
// Re-reading `scripts/seed-dev.ts`: It doesn't show any nested actions.
|
||||
// I will read `src/server/db/schema.ts` to be sure.
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// I'll write the file AFTER checking schema to ensure I structure the nested actions correctly.
|
||||
@@ -0,0 +1,254 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
// Database connection
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
async function main() {
|
||||
console.log("🌱 Seeding 'Control Flow Demo' experiment...");
|
||||
|
||||
try {
|
||||
// 1. Find Admin User & Study
|
||||
const user = await db.query.users.findFirst({
|
||||
where: (users, { eq }) => eq(users.email, "sean@soconnor.dev"),
|
||||
});
|
||||
if (!user)
|
||||
throw new Error(
|
||||
"Admin user 'sean@soconnor.dev' not found. Run seed-dev.ts first.",
|
||||
);
|
||||
|
||||
const study = await db.query.studies.findFirst({
|
||||
where: (studies, { eq }) => eq(studies.name, "Comparative WoZ Study"),
|
||||
});
|
||||
if (!study)
|
||||
throw new Error(
|
||||
"Study 'Comparative WoZ Study' not found. Run seed-dev.ts first.",
|
||||
);
|
||||
|
||||
// Find Robot
|
||||
const robot = await db.query.robots.findFirst({
|
||||
where: (robots, { eq }) => eq(robots.name, "NAO6"),
|
||||
});
|
||||
if (!robot)
|
||||
throw new Error("Robot 'NAO6' not found. Run seed-dev.ts first.");
|
||||
|
||||
// 2. Create Experiment
|
||||
const [experiment] = await db
|
||||
.insert(schema.experiments)
|
||||
.values({
|
||||
studyId: study.id,
|
||||
name: "Control Flow Demo",
|
||||
description:
|
||||
"Demonstration of enhanced control flow actions: Sequence, Parallel, Wait, Loop, Branch.",
|
||||
version: 1,
|
||||
status: "draft",
|
||||
robotId: robot.id,
|
||||
createdBy: user.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (!experiment) throw new Error("Failed to create experiment");
|
||||
console.log(`✅ Created Experiment: ${experiment.id}`);
|
||||
|
||||
// 3. Create Steps
|
||||
|
||||
// Step 1: Sequence & Parallel
|
||||
const [step1] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment.id,
|
||||
name: "Complex Action Structures",
|
||||
description: "Demonstrating Sequence and Parallel groups",
|
||||
type: "robot",
|
||||
orderIndex: 0,
|
||||
required: true,
|
||||
durationEstimate: 30,
|
||||
})
|
||||
.returning();
|
||||
if (!step1) throw new Error("Failed to create step1");
|
||||
|
||||
// Step 2: Loops & Waits
|
||||
const [step2] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment.id,
|
||||
name: "Repetition & Delays",
|
||||
description: "Demonstrating Loop and Wait actions",
|
||||
type: "robot",
|
||||
orderIndex: 1,
|
||||
required: true,
|
||||
durationEstimate: 45,
|
||||
})
|
||||
.returning();
|
||||
if (!step2) throw new Error("Failed to create step2");
|
||||
|
||||
// 4. Create Actions
|
||||
|
||||
// --- Step 1 Actions ---
|
||||
|
||||
// Action 1: Sequence
|
||||
// Note: Nested children are stored in 'children' property of the action object in frontend,
|
||||
// but in DB 'parameters' is the JSONB field.
|
||||
// However, looking at ActionChip, it expects `action.children`.
|
||||
// The `ExperimentAction` type usually has `children` at top level.
|
||||
// If the DB doesn't have it, the API must be hydrating it.
|
||||
// BUT, for the purpose of this seed which writes to DB directly, I will put it in `parameters.children`
|
||||
// and assume the frontend/API handles it or I'm missing a column.
|
||||
// Actually, looking at schema again, `actions` table DOES NOT have children.
|
||||
// So it MUST be in `parameters` or it's not persisted in this table structure yet (which would be a bug, but I'm seeding what exists).
|
||||
// Wait, if I put it in parameters, does the UI read it?
|
||||
// `ActionChip` reads `action.children`.
|
||||
// I will try to put it in `parameters` and distinct `children` property in the JSON passed to `parameters`?
|
||||
// No, `parameters` is jsonb.
|
||||
// I will assume for now that the system expects it in parameters if it's not a column, OR it's not fully supported in DB yet.
|
||||
// I will stick to what the UI likely consumes. `parameters: { children: [...] }`
|
||||
|
||||
// Sequence
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step1.id,
|
||||
name: "Introduction Sequence",
|
||||
type: "sequence",
|
||||
orderIndex: 0,
|
||||
// Embedding children here to demonstrate.
|
||||
// Real implementation might vary if keys are strictly checked.
|
||||
parameters: {
|
||||
children: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Say Hello",
|
||||
type: "nao6-ros2.say_text",
|
||||
parameters: { text: "Hello there!" },
|
||||
category: "interaction",
|
||||
},
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Wave Hand",
|
||||
type: "nao6-ros2.move_arm",
|
||||
parameters: { arm: "right", action: "wave" },
|
||||
category: "movement",
|
||||
},
|
||||
],
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core",
|
||||
});
|
||||
|
||||
// Parallel
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step1.id,
|
||||
name: "Parallel Actions",
|
||||
type: "parallel",
|
||||
orderIndex: 1,
|
||||
parameters: {
|
||||
children: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Say 'Moving'",
|
||||
type: "nao6-ros2.say_text",
|
||||
parameters: { text: "I am moving and talking." },
|
||||
category: "interaction",
|
||||
},
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Walk Forward",
|
||||
type: "nao6-ros2.move_to",
|
||||
parameters: { x: 0.5, y: 0 },
|
||||
category: "movement",
|
||||
},
|
||||
],
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core",
|
||||
});
|
||||
|
||||
// --- Step 2 Actions ---
|
||||
|
||||
// Loop
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step2.id,
|
||||
name: "Repeat Message",
|
||||
type: "loop",
|
||||
orderIndex: 0,
|
||||
parameters: {
|
||||
iterations: 3,
|
||||
children: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Say 'Echo'",
|
||||
type: "nao6-ros2.say_text",
|
||||
parameters: { text: "Echo" },
|
||||
category: "interaction",
|
||||
},
|
||||
],
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core",
|
||||
});
|
||||
|
||||
// Wait
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step2.id,
|
||||
name: "Wait 5 Seconds",
|
||||
type: "wait",
|
||||
orderIndex: 1,
|
||||
parameters: { duration: 5 },
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core",
|
||||
});
|
||||
|
||||
// Branch (Controls step routing, not nested actions)
|
||||
// Note: Branch configuration is stored in step.trigger.conditions, not action.parameters
|
||||
// The branch action itself is just a marker that this step has conditional routing
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step2.id,
|
||||
name: "Conditional Routing",
|
||||
type: "branch",
|
||||
orderIndex: 2,
|
||||
parameters: {
|
||||
// Branch actions don't have nested children
|
||||
// Routing is configured at the step level via trigger.conditions
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core",
|
||||
});
|
||||
|
||||
// Update step2 to have conditional routing
|
||||
await db
|
||||
.update(schema.steps)
|
||||
.set({
|
||||
type: "conditional",
|
||||
conditions: {
|
||||
options: [
|
||||
{
|
||||
label: "High Score Path",
|
||||
nextStepIndex: 2, // Would go to a hypothetical step 3
|
||||
variant: "default",
|
||||
},
|
||||
{
|
||||
label: "Low Score Path",
|
||||
nextStepIndex: 0, // Loop back to step 1
|
||||
variant: "outline",
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.where(sql`id = ${step2.id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -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:
|
||||
@@ -0,0 +1,274 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
// Database connection
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
async function main() {
|
||||
console.log("🌱 Seeding 'Story: Red Rock' experiment...");
|
||||
|
||||
try {
|
||||
// 1. Find Admin User & Study
|
||||
const user = await db.query.users.findFirst({
|
||||
where: (users, { eq }) => eq(users.email, "sean@soconnor.dev"),
|
||||
});
|
||||
if (!user) throw new Error("Admin user 'sean@soconnor.dev' not found.");
|
||||
|
||||
const study = await db.query.studies.findFirst({
|
||||
where: (studies, { eq }) => eq(studies.name, "Comparative WoZ Study"),
|
||||
});
|
||||
if (!study) throw new Error("Study 'Comparative WoZ Study' not found.");
|
||||
|
||||
const robot = await db.query.robots.findFirst({
|
||||
where: (robots, { eq }) => eq(robots.name, "NAO6"),
|
||||
});
|
||||
if (!robot) throw new Error("Robot 'NAO6' not found.");
|
||||
|
||||
// 2. Create Experiment
|
||||
const [experiment] = await db
|
||||
.insert(schema.experiments)
|
||||
.values({
|
||||
studyId: study.id,
|
||||
name: "Story: Red Rock",
|
||||
description:
|
||||
"A story about a red rock on Mars with comprehension check and branching.",
|
||||
version: 1,
|
||||
status: "draft",
|
||||
robotId: robot.id,
|
||||
createdBy: user.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
if (!experiment) throw new Error("Failed to create experiment");
|
||||
console.log(`✅ Created Experiment: ${experiment.id}`);
|
||||
|
||||
// 3. Create Steps (in reverse for ID references if needed, but we'll use uuid placeholders)
|
||||
const conclusionId = uuidv4();
|
||||
const branchAId = uuidv4();
|
||||
const branchBId = uuidv4();
|
||||
const checkId = uuidv4();
|
||||
|
||||
// Step 1: The Hook
|
||||
const [step1] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment.id,
|
||||
name: "The Hook",
|
||||
type: "wizard",
|
||||
orderIndex: 0,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Step 2: The Narrative
|
||||
const [step2] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
experimentId: experiment.id,
|
||||
name: "The Narrative",
|
||||
type: "wizard",
|
||||
orderIndex: 1,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Step 3: Comprehension Check (Conditional)
|
||||
const [step3] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
id: checkId,
|
||||
experimentId: experiment.id,
|
||||
name: "Comprehension Check",
|
||||
type: "conditional",
|
||||
orderIndex: 2,
|
||||
conditions: {
|
||||
variable: "last_wizard_response",
|
||||
options: [
|
||||
{
|
||||
label: "Answer: Red (Correct)",
|
||||
value: "Red",
|
||||
variant: "default",
|
||||
nextStepId: branchAId,
|
||||
},
|
||||
{
|
||||
label: "Answer: Other (Incorrect)",
|
||||
value: "Incorrect",
|
||||
variant: "destructive",
|
||||
nextStepId: branchBId,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Step 4: Branch A (Correct)
|
||||
const [step4] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
id: branchAId,
|
||||
experimentId: experiment.id,
|
||||
name: "Branch A: Correct Response",
|
||||
type: "wizard",
|
||||
orderIndex: 3,
|
||||
conditions: { nextStepId: conclusionId }, // SKIP BRANCH B
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Step 5: Branch B (Incorrect)
|
||||
const [step5] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
id: branchBId,
|
||||
experimentId: experiment.id,
|
||||
name: "Branch B: Incorrect Response",
|
||||
type: "wizard",
|
||||
orderIndex: 4,
|
||||
conditions: { nextStepId: conclusionId },
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Step 6: Conclusion
|
||||
const [step6] = await db
|
||||
.insert(schema.steps)
|
||||
.values({
|
||||
id: conclusionId,
|
||||
experimentId: experiment.id,
|
||||
name: "Conclusion",
|
||||
type: "wizard",
|
||||
orderIndex: 5,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// 4. Create Actions
|
||||
|
||||
// The Hook
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step1!.id,
|
||||
name: "Say Hello",
|
||||
type: "nao6-ros2.say_text",
|
||||
orderIndex: 0,
|
||||
parameters: { text: "Hello! Are you ready for a story?" },
|
||||
},
|
||||
{
|
||||
stepId: step1!.id,
|
||||
name: "Wave",
|
||||
type: "nao6-ros2.move_arm",
|
||||
orderIndex: 1,
|
||||
parameters: { arm: "right", shoulder_pitch: 0.5 },
|
||||
},
|
||||
]);
|
||||
|
||||
// The Narrative
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step2!.id,
|
||||
name: "The Story",
|
||||
type: "nao6-ros2.say_text",
|
||||
orderIndex: 0,
|
||||
parameters: {
|
||||
text: "Once, a traveler went to Mars. He found a bright red rock that glowed.",
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: step2!.id,
|
||||
name: "Look Left",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 1,
|
||||
parameters: { yaw: 0.5, speed: 0.3 },
|
||||
},
|
||||
{
|
||||
stepId: step2!.id,
|
||||
name: "Look Right",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 2,
|
||||
parameters: { yaw: -0.5, speed: 0.3 },
|
||||
},
|
||||
]);
|
||||
|
||||
// Comprehension Check
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step3!.id,
|
||||
name: "Ask Color",
|
||||
type: "nao6-ros2.say_text",
|
||||
orderIndex: 0,
|
||||
parameters: { text: "What color was the rock I found on Mars?" },
|
||||
},
|
||||
{
|
||||
stepId: step3!.id,
|
||||
name: "Wait for Color",
|
||||
type: "wizard_wait_for_response",
|
||||
orderIndex: 1,
|
||||
parameters: {
|
||||
options: ["Red", "Blue", "Green", "Incorrect"],
|
||||
prompt_text: "What color did the participant say?",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Branch A (Using say_with_emotion)
|
||||
await db
|
||||
.insert(schema.actions)
|
||||
.values([
|
||||
{
|
||||
stepId: step4!.id,
|
||||
name: "Happy Response",
|
||||
type: "nao6-ros2.say_with_emotion",
|
||||
orderIndex: 0,
|
||||
parameters: {
|
||||
text: "Exacty! It was a glowing red rock.",
|
||||
emotion: "happy",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Branch B
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step5!.id,
|
||||
name: "Correct them",
|
||||
type: "nao6-ros2.say_text",
|
||||
orderIndex: 0,
|
||||
parameters: { text: "Actually, it was red." },
|
||||
},
|
||||
{
|
||||
stepId: step5!.id,
|
||||
name: "Shake Head",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 1,
|
||||
parameters: { yaw: 0.3, speed: 0.5 },
|
||||
},
|
||||
]);
|
||||
|
||||
// Conclusion
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step6!.id,
|
||||
name: "Final Goodbye",
|
||||
type: "nao6-ros2.say_text",
|
||||
orderIndex: 0,
|
||||
parameters: { text: "That is all for today. Goodbye!" },
|
||||
},
|
||||
{
|
||||
stepId: step6!.id,
|
||||
name: "Rest",
|
||||
type: "nao6-ros2.move_arm",
|
||||
orderIndex: 1,
|
||||
parameters: { shoulder_pitch: 1.5 },
|
||||
},
|
||||
]);
|
||||
|
||||
console.log("✅ Seed completed successfully!");
|
||||
} catch (err) {
|
||||
console.error("❌ Seed failed:", err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,92 @@
|
||||
// Mock of the logic in WizardInterface.tsx handleNextStep
|
||||
const steps = [
|
||||
{
|
||||
id: "b9d43f8c-c40c-4f1c-9fdc-9076338d3c85",
|
||||
name: "Step 3 (Conditional)",
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
id: "3a2dc0b7-a43e-4236-9b9e-f957abafc1e5",
|
||||
name: "Step 4 (Branch A)",
|
||||
order: 3,
|
||||
conditions: {
|
||||
nextStepId: "cc3fbc7f-29e5-45e0-8d46-e80813c54292",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "3ae2fe8a-fc5d-4a04-baa5-699a21f19e30",
|
||||
name: "Step 5 (Branch B)",
|
||||
order: 4,
|
||||
conditions: {
|
||||
nextStepId: "cc3fbc7f-29e5-45e0-8d46-e80813c54292",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "cc3fbc7f-29e5-45e0-8d46-e80813c54292",
|
||||
name: "Step 6 (Conclusion)",
|
||||
order: 5,
|
||||
},
|
||||
];
|
||||
|
||||
function simulateNextStep(currentStepIndex: number) {
|
||||
const currentStep = steps[currentStepIndex];
|
||||
|
||||
if (!currentStep) {
|
||||
console.log("No step found at index:", currentStepIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\n--- Simulating Next Step from: ${currentStep.name} ---`);
|
||||
console.log("Current Step Data:", JSON.stringify(currentStep, null, 2));
|
||||
|
||||
// Logic from WizardInterface.tsx
|
||||
console.log(
|
||||
"[WizardInterface] Checking for nextStepId condition:",
|
||||
currentStep?.conditions,
|
||||
);
|
||||
|
||||
if (currentStep?.conditions?.nextStepId) {
|
||||
const nextId = String(currentStep.conditions.nextStepId);
|
||||
const targetIndex = steps.findIndex((s) => s.id === nextId);
|
||||
|
||||
console.log(`Target ID: ${nextId}`);
|
||||
console.log(`Target Index Found: ${targetIndex}`);
|
||||
|
||||
if (targetIndex !== -1) {
|
||||
console.log(
|
||||
`[WizardInterface] Condition-based jump to step ${targetIndex} (${nextId})`,
|
||||
);
|
||||
return targetIndex;
|
||||
} else {
|
||||
console.warn(
|
||||
`[WizardInterface] Targeted nextStepId ${nextId} not found in steps list.`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
"[WizardInterface] No nextStepId found in conditions, proceeding linearly.",
|
||||
);
|
||||
}
|
||||
|
||||
// Default: Linear progression
|
||||
const nextIndex = currentStepIndex + 1;
|
||||
console.log(`Proceeding linearly to index ${nextIndex}`);
|
||||
return nextIndex;
|
||||
}
|
||||
|
||||
// Simulate Branch A (Index 1 in this array, but 3 in real experiment?)
|
||||
// In real exp, Step 3 is index 2. Step 4 (Branch A) is index 3.
|
||||
console.log("Real experiment indices:");
|
||||
// 0: Hook, 1: Narrative, 2: Conditional, 3: Branch A, 4: Branch B, 5: Conclusion
|
||||
const indexStep4 = 1; // logical index in my mock array
|
||||
const indexStep5 = 2; // logical index
|
||||
|
||||
console.log("Testing Branch A Logic:");
|
||||
const resultA = simulateNextStep(indexStep4);
|
||||
if (resultA === 3) console.log("SUCCESS: Branch A jumped to Conclusion");
|
||||
else console.log("FAILURE: Branch A fell through");
|
||||
|
||||
console.log("\nTesting Branch B Logic:");
|
||||
const resultB = simulateNextStep(indexStep5);
|
||||
if (resultB === 3) console.log("SUCCESS: Branch B jumped to Conclusion");
|
||||
else console.log("FAILURE: Branch B fell through");
|
||||
@@ -0,0 +1,59 @@
|
||||
import { convertDatabaseToAction } from "../../src/lib/experiment-designer/block-converter";
|
||||
|
||||
const mockDbAction = {
|
||||
id: "eaf8f85b-75cf-4973-b436-092516b4e0e4",
|
||||
name: "Introduction Sequence",
|
||||
description: null,
|
||||
type: "sequence",
|
||||
orderIndex: 0,
|
||||
parameters: {
|
||||
children: [
|
||||
{
|
||||
id: "75018b01-a964-41fb-8612-940a29020d4a",
|
||||
name: "Say Hello",
|
||||
type: "nao6-ros2.say_text",
|
||||
category: "interaction",
|
||||
parameters: {
|
||||
text: "Hello there!",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "d7020530-6477-41f3-84a4-5141778c93da",
|
||||
name: "Wave Hand",
|
||||
type: "nao6-ros2.move_arm",
|
||||
category: "movement",
|
||||
parameters: {
|
||||
arm: "right",
|
||||
action: "wave",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
timeout: null,
|
||||
retryCount: 0,
|
||||
sourceKind: "core",
|
||||
pluginId: "hristudio-core",
|
||||
pluginVersion: null,
|
||||
robotId: null,
|
||||
baseActionId: null,
|
||||
category: "control",
|
||||
transport: null,
|
||||
ros2: null,
|
||||
rest: null,
|
||||
retryable: null,
|
||||
parameterSchemaRaw: null,
|
||||
};
|
||||
|
||||
console.log("Testing convertDatabaseToAction...");
|
||||
try {
|
||||
const result = convertDatabaseToAction(mockDbAction);
|
||||
console.log("Result:", JSON.stringify(result, null, 2));
|
||||
|
||||
if (result.children && result.children.length > 0) {
|
||||
console.log("✅ Children hydrated successfully.");
|
||||
} else {
|
||||
console.error("❌ Children NOT hydrated.");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("❌ Error during conversion:", e);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { appRouter } from "../../src/server/api/root";
|
||||
import { createCallerFactory } from "../../src/server/api/trpc";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
// 1. Setup DB Context
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
// 2. Mock Session
|
||||
const mockSession = {
|
||||
user: {
|
||||
id: "0e830889-ab46-4b48-a8ba-1d4bd3e665ed", // Admin user ID from seed
|
||||
name: "Sean O'Connor",
|
||||
email: "sean@soconnor.dev",
|
||||
},
|
||||
expires: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// 3. Create Caller
|
||||
const createCaller = createCallerFactory(appRouter);
|
||||
const caller = createCaller({
|
||||
db,
|
||||
session: mockSession as any,
|
||||
headers: new Headers(),
|
||||
});
|
||||
|
||||
async function main() {
|
||||
console.log("🔍 Fetching experiment via TRPC caller...");
|
||||
|
||||
// Get ID first
|
||||
const exp = await db.query.experiments.findFirst({
|
||||
where: eq(schema.experiments.name, "Control Flow Demo"),
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
if (!exp) {
|
||||
console.error("❌ Experiment not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await caller.experiments!.get({ id: exp.id });
|
||||
|
||||
console.log(`✅ Fetched experiment: ${result.name} (${result.id})`);
|
||||
|
||||
if (result.steps && result.steps.length > 0) {
|
||||
console.log(`Checking ${result.steps.length} steps...`);
|
||||
const actions = result.steps[0]!.actions; // Step 1 actions
|
||||
console.log(`Step 1 has ${actions.length} actions.`);
|
||||
|
||||
actions.forEach((a) => {
|
||||
if (["sequence", "parallel", "loop", "branch"].includes(a.type)) {
|
||||
console.log(`\nAction: ${a.name} (${a.type})`);
|
||||
console.log(
|
||||
`Children Count: ${a.children ? a.children.length : "UNDEFINED"}`,
|
||||
);
|
||||
if (a.children && a.children.length > 0) {
|
||||
console.log(
|
||||
`First Child: ${a.children[0]!.name} (${a.children[0]!.type})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error("❌ No steps found in result.");
|
||||
}
|
||||
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,46 @@
|
||||
import { db } from "../../src/server/db";
|
||||
import { experiments } from "../../src/server/db/schema";
|
||||
import { eq, asc } from "drizzle-orm";
|
||||
import { convertDatabaseToSteps } from "../../src/lib/experiment-designer/block-converter";
|
||||
|
||||
async function verifyConversion() {
|
||||
const experiment = await db.query.experiments.findFirst({
|
||||
with: {
|
||||
steps: {
|
||||
orderBy: (steps, { asc }) => [asc(steps.orderIndex)],
|
||||
with: {
|
||||
actions: {
|
||||
orderBy: (actions, { asc }) => [asc(actions.orderIndex)],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!experiment) {
|
||||
console.log("No experiment found");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Raw DB Steps Count:", experiment.steps.length);
|
||||
const converted = convertDatabaseToSteps(experiment.steps);
|
||||
|
||||
console.log("Converted Steps:");
|
||||
converted.forEach((s, idx) => {
|
||||
console.log(`[${idx}] ${s.name} (${s.type})`);
|
||||
console.log(` Trigger:`, JSON.stringify(s.trigger));
|
||||
if (s.type === "conditional") {
|
||||
console.log(
|
||||
` Conditions populated?`,
|
||||
Object.keys(s.trigger.conditions).length > 0,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
verifyConversion()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const client = postgres(connectionString);
|
||||
const db = drizzle(client, { schema });
|
||||
|
||||
async function verify() {
|
||||
console.log("🔍 Verifying Study Readiness...");
|
||||
|
||||
// 1. Check Study
|
||||
const study = await db.query.studies.findFirst({
|
||||
where: eq(schema.studies.name, "Comparative WoZ Study"),
|
||||
});
|
||||
|
||||
if (!study) {
|
||||
console.error("❌ Study 'Comparative WoZ Study' not found.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("✅ Study found:", study.name);
|
||||
|
||||
// 2. Check Experiment
|
||||
const experiment = await db.query.experiments.findFirst({
|
||||
where: eq(schema.experiments.name, "The Interactive Storyteller"),
|
||||
});
|
||||
|
||||
if (!experiment) {
|
||||
console.error("❌ Experiment 'The Interactive Storyteller' not found.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("✅ Experiment found:", experiment.name);
|
||||
|
||||
// 3. Check Steps
|
||||
const steps = await db.query.steps.findMany({
|
||||
where: eq(schema.steps.experimentId, experiment.id),
|
||||
orderBy: schema.steps.orderIndex,
|
||||
});
|
||||
|
||||
console.log(`ℹ️ Found ${steps.length} steps.`);
|
||||
if (steps.length < 5) {
|
||||
console.error("❌ Expected at least 5 steps, found " + steps.length);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Verify Step Names
|
||||
const expectedSteps = [
|
||||
"The Hook",
|
||||
"The Narrative - Part 1",
|
||||
"Comprehension Check",
|
||||
"Positive Feedback",
|
||||
"Conclusion",
|
||||
];
|
||||
for (let i = 0; i < expectedSteps.length; i++) {
|
||||
const step = steps[i];
|
||||
if (!step) continue;
|
||||
|
||||
if (step.name !== expectedSteps[i]) {
|
||||
console.error(
|
||||
`❌ Step mismatch at index ${i}. Expected '${expectedSteps[i]}', got '${step.name}'`,
|
||||
);
|
||||
} else {
|
||||
console.log(`✅ Step ${i + 1}: ${step.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check Plugin Actions
|
||||
// Find the NAO6 plugin
|
||||
const plugin = await db.query.plugins.findFirst({
|
||||
where: (plugins, { eq, and }) =>
|
||||
and(
|
||||
eq(plugins.name, "NAO6 Robot (Enhanced ROS2 Integration)"),
|
||||
eq(plugins.status, "active"),
|
||||
),
|
||||
});
|
||||
|
||||
if (!plugin) {
|
||||
console.error("❌ NAO6 Plugin not found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const actions = plugin.actionDefinitions as any[];
|
||||
const requiredActions = [
|
||||
"nao_nod",
|
||||
"nao_shake_head",
|
||||
"nao_bow",
|
||||
"nao_open_hand",
|
||||
];
|
||||
|
||||
for (const actionId of requiredActions) {
|
||||
const found = actions.find((a) => a.id === actionId);
|
||||
if (!found) {
|
||||
console.error(`❌ Plugin missing action: ${actionId}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`✅ Plugin has action: ${actionId}`);
|
||||
}
|
||||
|
||||
console.log("🎉 Verification Complete: Platform is ready for the study!");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
verify().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { db } from "~/server/db";
|
||||
import { experiments, steps, actions } from "~/server/db/schema";
|
||||
import { eq, asc, desc } from "drizzle-orm";
|
||||
import { convertDatabaseToSteps } from "~/lib/experiment-designer/block-converter";
|
||||
|
||||
async function verifyTrpcLogic() {
|
||||
console.log("Verifying TRPC Logic for Interactive Storyteller...");
|
||||
|
||||
// 1. Simulate the DB Query from experiments.ts
|
||||
const experiment = await db.query.experiments.findFirst({
|
||||
where: eq(experiments.name, "The Interactive Storyteller"),
|
||||
with: {
|
||||
study: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
createdBy: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
robot: true,
|
||||
steps: {
|
||||
with: {
|
||||
actions: {
|
||||
orderBy: [asc(actions.orderIndex)],
|
||||
},
|
||||
},
|
||||
orderBy: [asc(steps.orderIndex)],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!experiment) {
|
||||
console.error("Experiment not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Simulate the Transformation
|
||||
console.log("Transforming DB steps to Designer steps...");
|
||||
const transformedSteps = convertDatabaseToSteps(experiment.steps);
|
||||
|
||||
// 3. Inspect Step 4 (Branch A)
|
||||
// Step index 3 (0-based) is Branch A
|
||||
const branchAStep = transformedSteps[3];
|
||||
|
||||
if (branchAStep) {
|
||||
console.log("Step 4 (Branch A):", branchAStep.name);
|
||||
console.log(" Type:", branchAStep.type);
|
||||
console.log(" Trigger:", JSON.stringify(branchAStep.trigger, null, 2));
|
||||
} else {
|
||||
console.error("Step 4 (Branch A) not found in transformed steps!");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check conditions specifically
|
||||
const conditions = branchAStep.trigger?.conditions as any;
|
||||
if (conditions?.nextStepId) {
|
||||
console.log(
|
||||
"SUCCESS: nextStepId found in conditions:",
|
||||
conditions.nextStepId,
|
||||
);
|
||||
} else {
|
||||
console.error("FAILURE: nextStepId MISSING in conditions!");
|
||||
}
|
||||
|
||||
// Inspect Step 5 (Branch B) for completeness
|
||||
const branchBStep = transformedSteps[4];
|
||||
if (branchBStep) {
|
||||
console.log("Step 5 (Branch B):", branchBStep.name);
|
||||
console.log(" Trigger:", JSON.stringify(branchBStep.trigger, null, 2));
|
||||
} else {
|
||||
console.warn("Step 5 (Branch B) not found in transformed steps.");
|
||||
}
|
||||
}
|
||||
|
||||
verifyTrpcLogic()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
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() {
|
||||
const exp = await db.query.experiments.findFirst({
|
||||
where: eq(schema.experiments.name, "Control Flow Demo"),
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
if (exp) {
|
||||
console.log(`Experiment ID: ${exp.id}`);
|
||||
} else {
|
||||
console.error("Experiment not found");
|
||||
}
|
||||
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,25 @@
|
||||
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() {
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(schema.users.email, "sean@soconnor.dev"),
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
if (user) {
|
||||
console.log(`User ID: ${user.id}`);
|
||||
} else {
|
||||
console.error("User not found");
|
||||
}
|
||||
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,37 @@
|
||||
import { db } from "~/server/db";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
async function migrate() {
|
||||
console.log("Adding identifier column to hs_plugin...");
|
||||
|
||||
try {
|
||||
await db.execute(
|
||||
sql`ALTER TABLE hs_plugin ADD COLUMN identifier varchar(100)`,
|
||||
);
|
||||
console.log("✓ Added identifier column");
|
||||
} catch (e: any) {
|
||||
console.log("Column may already exist:", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
await db.execute(
|
||||
sql`UPDATE hs_plugin SET identifier = name WHERE identifier IS NULL`,
|
||||
);
|
||||
console.log("✓ Copied name to identifier");
|
||||
} catch (e: any) {
|
||||
console.log("Error copying:", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
await db.execute(
|
||||
sql`ALTER TABLE hs_plugin ADD CONSTRAINT hs_plugin_identifier_unique UNIQUE (identifier)`,
|
||||
);
|
||||
console.log("✓ Added unique constraint");
|
||||
} catch (e: any) {
|
||||
console.log("Constraint may already exist:", e.message);
|
||||
}
|
||||
|
||||
console.log("Migration complete!");
|
||||
}
|
||||
|
||||
migrate().catch(console.error);
|
||||
@@ -0,0 +1,18 @@
|
||||
# Mock Robot Configuration
|
||||
# Copy this file to .env and adjust as needed
|
||||
|
||||
# Port for mock robot WebSocket server (default: 9090, same as rosbridge)
|
||||
MOCK_ROBOT_PORT=9090
|
||||
|
||||
# How often to publish robot state (ms)
|
||||
MOCK_PUBLISH_INTERVAL=100
|
||||
|
||||
# Robot configuration
|
||||
MOCK_ROBOT_NAME=MOCK-NAO6
|
||||
MOCK_ROBOT_VERSION=6.0
|
||||
MOCK_BATTERY_LEVEL=85
|
||||
|
||||
# Enable simulation features
|
||||
MOCK_ENABLE_SPEECH=true
|
||||
MOCK_ENABLE_MOVEMENT=true
|
||||
MOCK_ENABLE_SENSORS=true
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@hristudio/mock-robot",
|
||||
"version": "1.0.0",
|
||||
"description": "Mock robot server for simulating NAO6 robot connections",
|
||||
"type": "module",
|
||||
"main": "dist/server.js",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/server.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/ws": "^8.5.10",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user