83 Commits

Author SHA1 Message Date
soconnor 14182bf078 fix: resolve all three functional issues in trial execution
- WebSocket broadcasts: Next.js tRPC router now routes broadcasts via
  POST /internal/broadcast on the Bun ws-server process, which holds
  the actual client connections. Broadcasts were previously silently
  dropped due to the split singleton across processes.

- ws-server stubs: request_trial_status and request_trial_events now
  use the real async DB methods instead of the stub getTrialStatusSync/
  getTrialEventsSync that always returned null/[].

- Duplicate branch case: removed the unreachable second case "branch"
  block in executeAction switch; server-side branching is a pass-through
  since routing is client-orchestrated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:24:32 -04:00
soconnor 943c7bd963 fix(wizard): skip unchosen branch steps during linear progression
When the wizard makes a branch choice, mark all other branch targets as
skipped. Linear progression now advances past skipped steps, so path
1→2→4 no longer executes step 3 when branch A was chosen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:15:58 -04:00
soconnor 6b54724171 feat(wizard): enhance branching logic and add next step selection in PropertiesPanel 2026-04-08 12:08:33 -04:00
soconnor 86c1f35537 fix: SSH actions in experiment runner, branch ID serialization, and branch UI
- robot-communication.ts: add sshCommand to payloadMapping type
- trial-execution.ts: fix executeRobotActionWithComm to use ros2 key as
  implementation fallback and skip ROS connection for SSH actions
- route.ts: move studyId membership check inside initialize/executeSystemAction
  cases so executeSSH works without studyId; fix command param location
- experiments.ts: build tempId→dbUUID map on step insert and replace branch
  nextStepId references after all steps are saved
- WizardInterface.tsx: stop filtering branch actions from step action list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 12:31:44 -04:00
soconnor 5b5490cb90 fix(wizard-ros): use executeSSH for animations to bypass studyId requirement 2026-04-01 19:48:40 -04:00
soconnor 6b98cad53e fix(api): add missing posture actions (stand, stand_init, sit, crouch) 2026-04-01 19:46:31 -04:00
soconnor 3e2aa894a0 fix(trial-execution): handle SSH actions without requiring ROS connection
- Add startTime parameter to executeRobotActionInternal for proper duration tracking
- SSH actions (animations, posture commands) now work without ROS bridge connection
- Refactor executeAction to handle SSH and ROS paths separately
2026-04-01 19:44:08 -04:00
soconnor 27f633fb4b feat(nao6): add SSH-based posture actions (wake_up, rest, stand, sit, crouch)
- Update plugin with sshCommand payloadMapping type
- Add server-side SSH command execution in robot-communication.ts
- Add client-side SSH command execution in wizard-ros-service.ts
- Update API route to handle executeSSH action
2026-04-01 19:37:28 -04:00
soconnor 6243b62d3b Fix robot action ID namespacing for animation detection 2026-04-01 19:34:27 -04:00
soconnor f16dd4aa22 fix: handle namespaced action IDs in animation execution 2026-04-01 19:30:51 -04:00
soconnor 7483e4a72b fix: remove double-escaped NAOqi markup from speech transforms 2026-04-01 19:30:21 -04:00
soconnor 426b5e761b fix: allow timeoutMs=0 for wait blocks 2026-04-01 19:28:09 -04:00
soconnor cf21a27995 fix(ui): add number input for sliders, use textarea for text inputs
- Allow typing numbers directly in slider inputs
- Use textarea for text parameters like say_text
2026-04-01 19:20:42 -04:00
soconnor 74b5507769 fix: add transformToEmotionSpeech alias 2026-04-01 19:16:36 -04:00
soconnor 5c67fc106e chore: update robot-plugins submodule 2026-04-01 19:11:30 -04:00
soconnor 4b04f2c415 fix(nao6): route /animation via SSH, clean up working animations
- Fix executeWithConfig to route play_animation actions through SSH
- Remove broken animations: friendly, think, show_sole
- Keep working: bow, hey, show_floor, enthusiastic, yes, no, idontknow
2026-04-01 19:06:21 -04:00
soconnor c959e61f95 fix(wizard): use API route for animations instead of ROS topic
- Add executeAnimationSSH that calls /api/robots/command
- Remove ROS topic publishing for animations
- Fix play_animation_show_sole -> play_animation_friendly
2026-04-01 18:58:26 -04:00
soconnor de1b125b13 feat(nao6): add SSH-based animation execution for NAO6 robot
- Add play_animation actions to robots/command API using qicli SSH
- Add SSH-based animation execution to robot-communication service
- Animations: bow, hey, show_floor, show_sole, enthusiastic, think, yes, no, idontknow

This bypasses ROS2 cross-container issues by using direct SSH connection.
2026-04-01 18:51:40 -04:00
soconnor 143cf2ce50 chore: update robot-plugins with animation actions 2026-04-01 18:35:50 -04:00
soconnor 61c7cc1e94 feat(ros): add animation topic handler for play_animation actions 2026-04-01 18:35:45 -04:00
soconnor 8f330cf5f0 feat(ros): add animation handler and fix gesture action pipeline
- Add AnimationMovement interface: { joint_names, joint_angles, speed?,
  delay_after? } for describing individual frames in a joint animation
- Add executeAnimationSequence() public method: steps through frames,
  publishing each to /joint_angles with configurable per-frame delays
- Add executeSimulationAnimationSequence() for mock/sim mode
- Fix subscribeToRobotTopics: advertise /joint_angles as
  naoqi_bridge_msgs/msg/JointAnglesWithSpeed — without this rosbridge
  silently dropped every gesture publish (root cause of broken gestures)
- Fix executeBuiltinAction: correct ROS2 message type for /joint_angles
  (was naoqi_bridge_msgs/JointAnglesWithSpeed, ROS1 format)
- Add service payloadMapping type to executeWithConfig: routes actions
  to callService() instead of publish() — used by wake_up/rest
- Add wake_up/rest fallback service call chains in executeBuiltinAction
- Route gesture_sequence payloads through executeAnimationSequence
  instead of the old inline loop (which used a broken delay formula)
- Improve sim mode to handle gesture_sequence configs with realistic timing
- Update robot-plugins submodule pointer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 18:25:32 -04:00
soconnor 254805008e feat: add gesture sequence support in wizard-ros-service
- Add transformToGestureSequence for multi-movement gestures
- Update executeWithConfig to handle gesture sequences
- Add sequence execution with delays between movements
- Fix experiment description to be optional
2026-04-01 17:09:33 -04:00
soconnor c923c63099 chore: update robot-plugins submodule 2026-04-01 17:06:07 -04:00
soconnor c05384d1a0 feat: Add Test Action button, fix ros2 config copying, fix transform functions
- Add Test Action button in experiment designer properties panel
- Fix DesignerRoot to copy full ros2 config when adding actions
- Add transformToWaveGoodbye and transformToAnimation cases
- Fix escape sequences for NAOqi markup
- Update TrialForm with FormSection, sidebar, and visible validation
- Add db:reset and db:restart scripts
- Update docker-compose with configurable PostgreSQL and MinIO vars
2026-04-01 17:00:03 -04:00
soconnor c0e5a4ffb8 chore: update robot-plugins with beckon joint angle fix 2026-04-01 16:33:27 -04:00
soconnor 51aaaa5208 chore: update robot-plugins with valid animation names 2026-04-01 16:28:09 -04:00
soconnor e402c51483 chore: update robot-plugins submodule to latest with gesture actions 2026-04-01 16:05:03 -04:00
soconnor 7c360dc860 feat: add initial seed data migration and form builder components
- Created migration 0001_seed_data.sql to insert minimal seed data for users, accounts, and roles.
- Added meta journal for migration tracking.
- Implemented FormBuilder component for dynamic form field creation and management.
- Developed FormFieldRenderer component to render various types of form fields based on user input.
- Introduced constants for trust levels and status configurations.
- Defined types for form fields and trial data structures to enhance type safety and clarity.
2026-03-26 14:56:00 -04:00
soconnor 1c7f0297a6 feat(tutorials): add comprehensive tutorials for HRIStudio including Getting Started, Your First Study, Designing Experiments, Running Trials, Wizard Interface, Robot Integration, Forms & Surveys, Data & Analysis, and Simulation Mode 2026-03-25 22:48:42 -04:00
soconnor 3959cf23f7 feat(forms): add public form access and response submission for participants
- Implemented public access to forms with `getPublic` procedure.
- Added `submitPublic` procedure for participants to submit responses.
- Created a new participant form page to handle form display and submission.
- Enhanced form validation and error handling for required fields.
- Introduced CSV export functionality for form responses.
- Updated form listing and template creation procedures.
- Added README for homepage screenshots.
2026-03-23 11:07:02 -04:00
soconnor 3270e3f8fe docs: update documentation for forms system and role-based access
- Add forms system to README key features
- Update router/table counts to reflect new forms router
- Add forms section to quick-reference with types, templates, routes
- Clarify study-level user roles (owner/researcher/wizard/observer)
2026-03-22 18:11:42 -04:00
soconnor bfd1924897 fix: update forms pages to use proper page layout
- Remove EntityView wrapper, use standard space-y-6 pattern
- Full page width instead of container max-w
- Consistent with experiments/participants pages
2026-03-22 18:09:01 -04:00
soconnor 0827a791c6 fix: standardize MinIO bucket name to hristudio-data
- Update docker-compose to create hristudio-data bucket instead of hristudio
- Fix files.ts, storage.ts, trials.ts, lib/storage/minio.ts to use consistent bucket name
- All now default to hristudio-data matching compose bucket creation
2026-03-22 17:57:34 -04:00
soconnor ecf0ab9103 feat: add form templates
- Add isTemplate and templateName fields to forms
- Add listTemplates and createFromTemplate API endpoints
- Add template selection to new form page UI
- Add sample templates and forms to seed script:
  - Informed Consent template
  - Post-Session Survey template
  - Demographics questionnaire template
2026-03-22 17:53:16 -04:00
soconnor 49e0df016a feat: complete forms system overhaul
- Add new forms table with type (consent/survey/questionnaire)
- Add formResponses table for submissions
- Add forms API router with full CRUD:
  - list, get, create, update, delete
  - setActive, createVersion
  - getResponses, submitResponse
- Add forms list page with card-based UI
- Add form builder with field types (text, textarea, multiple_choice, checkbox, rating, yes_no, date, signature)
- Add form viewer with edit mode and preview
- Add responses viewing with participant info
2026-03-22 17:43:12 -04:00
soconnor 8529d0ef89 fix: add role-based permissions to forms page
- Hide Generate Default Template and Save Changes buttons for wizard/observer
- Backend already enforces owner/researcher for mutations
- UI now provides cleaner experience for read-only roles
2026-03-22 17:26:52 -04:00
soconnor 67ad904f62 feat: add role-based permissions and profile page improvements
- Add getMyMemberships API endpoint for user role lookup
- Add getMemberRole helper for profile page display
- Add role-based UI controls to study page (owner/researcher only)
- Add canManage checks to experiments, participants, trials pages
- Hide management actions for wizard/observer roles

Backend already enforces permissions; UI now provides cleaner UX
2026-03-22 17:25:04 -04:00
soconnor 519e6a2606 ui: complete profile page redesign
- Modern card-based layout with large avatar
- Inline editing for name/email with save/cancel
- Password change dialog with validation
- Security section with danger zone
- Studies access quick link
- Consistent teal theme colors
- Uses PageHeader pattern
- Better loading states
2026-03-22 17:08:50 -04:00
soconnor b353ef7c9f ui: dashboard redesign, member management, dark mode fixes
- Simplified dashboard using PageHeader/page-layout
- Fixed dark mode live session banner
- Added AddMemberDialog component for study team management
- Study page now has working member add/remove
- Fixed toast imports to use sonner
2026-03-22 17:05:28 -04:00
soconnor cbd31e9aa4 ui: complete dashboard redesign
- New modern dashboard layout with gradient background
- Quick action cards with teal glow effects
- Live trials banner with pulsing indicator
- Stats grid with colored icons
- Study list with bot icons
- Quick links sidebar
- Recent trials section with status badges
- Proper null safety and type checking
2026-03-22 16:55:41 -04:00
soconnor 37feea8df3 ui: fix dead links in dashboard, update theme to teal/cyan
- Fix broken /trials links to use study-scoped routes
- Fix /wizard/ link to proper wizard URL with studyId
- Add studyId to getLiveTrials API response
- Update theme colors to teal/cyan sci-fi style
- Add custom CSS utilities for glow effects and chamfered corners
- Consolidate docs and archive outdated files
2026-03-22 16:49:20 -04:00
soconnor cf3597881b docs: consolidate and archive documentation
- Move 30+ outdated docs to docs/_archive/
- Move obsolete root files to _archive/
- Update README.md (Better Auth, current features)
- Update docs/README.md (new architecture diagram)
- Update docs/quick-reference.md (consolidated)
- Update docs/project-status.md (March 2026 state)
- Update docs/nao6-quick-reference.md (14 actions, Docker services)
- Update docs/implementation-guide.md (Better Auth, git submodule)
- Update docs/proposal.tex (timeline updates)
- Archive errors.txt, plugin_dump.json, test HTML files
2026-03-22 16:38:28 -04:00
soconnor add3380307 fix: upgrade to Next.js 16.2.1 and resolve bundling issues
- Fixed client bundle contamination by moving child_process-dependent code
- Created standalone /api/robots/command route for SSH robot commands
- Created plugins router to replace robots.plugins for plugin management
- Added getStudyPlugins procedure to studies router
- Fixed trial.studyId references to trial.experiment.studyId
- Updated WizardInterface to use REST API for robot commands
2026-03-22 01:08:13 -04:00
soconnor 79bb298756 revert: stay on Next.js 16.1.6 due to bundling issues in 16.2.1 2026-03-22 00:50:29 -04:00
soconnor a5762ec935 feat: implement WebSocket for real-time trial updates
- Create standalone WebSocket server (ws-server.ts) on port 3001 using Bun
- Add ws_connections table to track active connections in database
- Create global WebSocket manager that persists across component unmounts
- Fix useWebSocket hook to prevent infinite re-renders and use refs
- Fix TrialForm Select components with proper default values
- Add trialId to WebSocket URL for server-side tracking
- Update package.json with dev:ws script for separate WS server
2026-03-22 00:48:43 -04:00
soconnor 20d6d3de1a migrate: replace NextAuth.js with Better Auth
- Install better-auth and @better-auth/drizzle-adapter
- Create src/lib/auth.ts with Better Auth configuration using bcrypt
- Update database schema: change auth table IDs from uuid to text
- Update route handler from /api/auth/[...nextauth] to /api/auth/[...all]
- Update tRPC context and middleware for Better Auth session handling
- Update client components to use Better Auth APIs (signIn, signOut)
- Update seed script with text-based IDs and correct account schema
- Fix type errors in wizard components (robotId, optional chaining)
- Fix API paths: api.robots.initialize -> api.robots.plugins.initialize
- Update auth router to use text IDs for Better Auth compatibility

Note: Auth tables were reset - users will need to re-register.
2026-03-21 23:03:55 -04:00
soconnor 4bed537943 Update docs: add March 2026 session summary, NAO6 Docker integration docs, and quick reference updates
- Add MARCH-2026-SESSION.md with complete summary of work done
- Update nao6-quick-reference.md for Docker-based deployment
- Update quick-reference.md with NAO6 Docker integration section
2026-03-21 20:51:08 -04:00
soconnor 73f70f6550 Add nextStepId conditions to Branch A and B to jump to Story Continues 2026-03-21 20:44:47 -04:00
soconnor 3fafd61553 Fix onClick handlers passing event object to handleNextStep
The issue was that onClick={onNextStep} passes the click event as the first argument,
making targetIndex an object instead of undefined. This caused handleNextStep to fall
through to linear progression instead of properly checking branching logic.

Fixed by wrapping with arrow function: onClick={() => onNextStep()}
2026-03-21 20:35:54 -04:00
soconnor 3491bf4463 Add debug logging for branching flow 2026-03-21 20:26:55 -04:00
soconnor cc58593891 Update robot-plugins submodule 2026-03-21 20:21:38 -04:00
soconnor bbbe397ba8 Various improvements: study forms, participant management, PDF generator, robot integration 2026-03-21 20:21:18 -04:00
soconnor bbc34921b5 Fix branching logic and robot action timing
- Remove onCompleted() call after branch selection to prevent count increment
- Add proper duration estimation for speech actions (word count + emotion overhead)
- Add say_with_emotion and wave_goodbye to built-in actions
- Add identifier field to admin.ts plugin creation
2026-03-21 20:15:39 -04:00
soconnor 8e647c958e Fix seed script to include identifier for system plugins 2026-03-21 20:04:46 -04:00
soconnor 4e86546311 Add identifier column to plugins table for cleaner plugin lookup
- Added 'identifier' column (unique) for machine-readable plugin ID
- 'name' now used for display name only
- Updated trial-execution to look up by identifier first, then name
- Added migration script for existing databases
2026-03-21 20:03:33 -04:00
soconnor e84c794962 Load plugin from local file first (not remote) 2026-03-21 19:32:13 -04:00
soconnor 70064f487e Fix say_with_emotion with proper NAOqi markup, add transform functions, update seed script for linear branching 2026-03-21 19:29:28 -04:00
soconnor 91d03a789d Redesign experiment structure and add pending trial
- Both branch choices now jump to Story Continues (convergence point)
- Add Story Continues step with expressive actions
- Add pre-seeded pending trial for immediate testing
- Fix duplicate comments in seed script
- Update step ordering (Conclusion now step6)
2026-03-21 19:15:41 -04:00
soconnor 31d2173703 Fix branching and add move_arm builtin
- Branching: mark source step as completed when jumping to prevent revisiting
- Add move_arm as builtin for arm control
2026-03-21 19:09:26 -04:00
soconnor 4a9abf4ff1 Restore builtins for standard ROS actions
- Re-add say_text, walk_forward, walk_backward, turn_left, turn_right, move_head, turn_head as builtins
- These use standard ROS topics (/speech, /cmd_vel, /joint_angles) that work with most robots
- Plugin-specific actions should still be defined in plugin config
2026-03-21 19:04:51 -04:00
soconnor 487f97c5c2 Update robot-plugins submodule 2026-03-21 18:58:29 -04:00
soconnor db147f2294 Update robot-plugins submodule 2026-03-21 18:57:00 -04:00
soconnor a705c720fb Make wizard-ros-service robot-agnostic
- Remove NAO-specific hardcoded action handlers
- Remove helper methods (executeMovementAction, executeTurnHead, executeMoveArm)
- Keep only generic emergency_stop as builtin
- All robot-specific actions should be defined in plugin config
2026-03-21 18:55:52 -04:00
soconnor e460c1b029 Update robot-plugins submodule 2026-03-21 18:54:18 -04:00
soconnor eb0d86f570 Clean up debug logs 2026-03-21 18:52:16 -04:00
soconnor e40c37cfd0 Fix branching logic and add combo robot actions
- Fix handleNextStep to handle both string and object options in conditions
- Add say_with_emotion, bow, wave, nod, shake_head, point combo actions
- Update seed data with nextStepId in wizard_wait_for_response options
2026-03-21 18:51:27 -04:00
soconnor f8e6fccae3 Update robot-plugins submodule 2026-03-21 18:28:07 -04:00
soconnor 3f87588fea fix: Update ROS topics and robot configuration
ROS Topic Fixes:
- wizard-ros-service.ts: Use correct ROS topics (/cmd_vel, /joint_angles, /speech)
- ros-bridge.ts: Update subscriptions to match naoqi_driver topics
- Fixes action execution (movement, speech, head control)

Robot Configuration:
- robots.ts: Use NAO_IP/NAO_ROBOT_IP env vars instead of hardcoded 'nao.local'
- robots.ts: Use NAO_PASSWORD env var for SSH authentication
- Improves Docker integration with NAO6

Wizard Interface:
- useWizardRos.ts: Enhanced wizard interface for robot control
- WizardInterface.tsx: Updated wizard controls
- Add comprehensive event listeners for robot actions
2026-03-21 17:58:29 -04:00
soconnor 18e5aab4a5 feat: Convert robot-plugins to proper git submodule
- Removes nested .git directory from robot-plugins
- Adds robot-plugins as a proper submodule of hristudio
- Points to main branch of soconnor0919/robot-plugins repository
- This enables proper version tracking and updates of robot plugins
2026-03-21 17:57:54 -04:00
soconnor c16d0d2565 feat: Add uuid package and its types to dependencies. 2026-03-19 17:40:39 -04:00
soconnor c37acad3d2 feat: Enforce study membership access for file uploads and integrate live system statistics. 2026-03-06 00:22:22 -05:00
soconnor 0051946bde feat: Implement digital signatures for participant consent and introduce study forms management. 2026-03-02 10:51:20 -05:00
soconnor 61af467cc8 feat: enhance experiment designer action definitions, refactor trial analysis UI, and update video playback controls 2026-03-01 19:00:23 -05:00
soconnor 60d4fae72c feat: Enhance trial event display with improved formatting and icons, refine trial wizard panels, and update dashboard page layouts. 2026-02-20 00:37:33 -05:00
soconnor 72971a4b49 feat(analytics): refine timeline visualization and add print support 2026-02-17 21:17:11 -05:00
soconnor 568d408587 feat: Add guided tour functionality for analytics and wizard components, including new tour steps and triggers. 2026-02-12 00:53:28 -05:00
soconnor 93de577939 feat: Add a new onboarding tour for participant creation and refactor consent form uploads to use sonner for toasts and XMLHttpRequest for progress tracking. 2026-02-11 23:49:51 -05:00
soconnor 85b951f742 refactor: restructure study and participant forms into logical sections with separators and enhance EntityForm's layout flexibility for sidebar presence. 2026-02-10 16:31:43 -05:00
soconnor a8c868ad3f feat: Implement trial event logging, archiving, experiment soft deletion, and new analytics/event data tables. 2026-02-10 16:14:31 -05:00
soconnor 0f535f6887 feat: introduce conditional steps and branching logic to the experiment wizard and designer, along with new core and WoZ plugins. 2026-02-10 10:24:09 -05:00
soconnor 388897c70e feat: Implement collapsible left and right panels with dynamic column spanning, updated styling, and integrated a bottom status bar in the DesignerRoot. 2026-02-03 13:58:47 -05:00
soconnor 0ec63b3c97 feat: Redesign the designer layout using a grid system, adding explicit left, center, and right panels with collapse functionality. 2026-02-02 15:48:17 -05:00
soconnor 89c44efcf7 feat: Implement responsive design for the experiment designer and enhance general UI components with hover effects and shadows. 2026-02-02 12:51:53 -05:00
314 changed files with 35355 additions and 11898 deletions
+10 -2
View File
@@ -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"
+5 -5
View File
@@ -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",
},
};
+4
View File
@@ -0,0 +1,4 @@
[submodule "robot-plugins"]
path = robot-plugins
url = git@github.com:soconnor0919/robot-plugins.git
branch = main
+38 -17
View File
@@ -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
+45
View File
@@ -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.
+941 -297
View File
File diff suppressed because it is too large Load Diff
+20 -8
View File
@@ -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
View File
@@ -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
+159
View File
@@ -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
+367
View File
@@ -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
+9 -2
View File
@@ -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 \
+182
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+151
View File
@@ -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)
+222
View File
@@ -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)
+341
View File
@@ -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)
+366
View File
@@ -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)
+393
View File
@@ -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)
+386
View File
@@ -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)
+505
View File
@@ -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)
+505
View File
@@ -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)
+389
View File
@@ -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)
+71
View File
@@ -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)
-279
View File
@@ -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.
+1
View File
@@ -9,4 +9,5 @@ export default {
url: env.DATABASE_URL,
},
tablesFilter: ["hs_*"],
out: "./migrations",
} satisfies Config;
+9 -37
View File
@@ -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)$).*)",
],
};
+605
View File
@@ -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");
+34
View File
@@ -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 $$;
+20
View File
@@ -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
View File
@@ -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;
+89 -60
View File
@@ -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"
}
}
+28
View File
@@ -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"
/>
```
+46
View File
@@ -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();
+66
View File
@@ -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);
});
+41
View File
@@ -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);
});
+46
View File
@@ -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);
});
+29
View File
@@ -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);
});
+29
View File
@@ -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);
});
+62
View File
@@ -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);
});
+32
View File
@@ -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);
});
+100
View File
@@ -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);
});
+87
View File
@@ -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.");
}
+136
View File
@@ -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.
+254
View File
@@ -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:
+274
View File
@@ -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();
+92
View File
@@ -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");
+59
View File
@@ -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);
}
+74
View File
@@ -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();
+46
View File
@@ -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);
});
+107
View File
@@ -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);
});
+86
View File
@@ -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);
});
+25
View File
@@ -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();
+25
View File
@@ -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();
+37
View File
@@ -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);
+18
View File
@@ -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
+21
View File
@@ -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