mirror of
https://github.com/soconnor0919/hristudio.git
synced 2026-03-24 03:37:51 -04:00
Compare commits
48 Commits
85b951f742
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3959cf23f7 | ||
| 3270e3f8fe | |||
| bfd1924897 | |||
| 0827a791c6 | |||
| ecf0ab9103 | |||
| 49e0df016a | |||
| 8529d0ef89 | |||
| 67ad904f62 | |||
| 519e6a2606 | |||
| b353ef7c9f | |||
| cbd31e9aa4 | |||
| 37feea8df3 | |||
| cf3597881b | |||
|
|
add3380307 | ||
|
|
79bb298756 | ||
|
|
a5762ec935 | ||
|
|
20d6d3de1a | ||
| 4bed537943 | |||
| 73f70f6550 | |||
| 3fafd61553 | |||
| 3491bf4463 | |||
| cc58593891 | |||
| bbbe397ba8 | |||
| bbc34921b5 | |||
| 8e647c958e | |||
| 4e86546311 | |||
| e84c794962 | |||
| 70064f487e | |||
| 91d03a789d | |||
| 31d2173703 | |||
| 4a9abf4ff1 | |||
| 487f97c5c2 | |||
| db147f2294 | |||
| a705c720fb | |||
| e460c1b029 | |||
| eb0d86f570 | |||
| e40c37cfd0 | |||
| f8e6fccae3 | |||
| 3f87588fea | |||
| 18e5aab4a5 | |||
| c16d0d2565 | |||
| c37acad3d2 | |||
| 0051946bde | |||
| 61af467cc8 | |||
| 60d4fae72c | |||
| 72971a4b49 | |||
| 568d408587 | |||
| 93de577939 |
@@ -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
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "robot-plugins"]
|
||||
path = robot-plugins
|
||||
url = git@github.com:soconnor0919/robot-plugins.git
|
||||
branch = main
|
||||
54
README.md
54
README.md
@@ -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,11 @@ 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)
|
||||
- **[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 +230,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
_archive/errors.txt
Normal file
45
_archive/errors.txt
Normal 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.
|
||||
@@ -29,6 +29,18 @@ services:
|
||||
- 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 minioadmin minioadmin;
|
||||
/usr/bin/mc mb myminio/hristudio-data;
|
||||
/usr/bin/mc anonymous set public myminio/hristudio-data;
|
||||
exit 0;
|
||||
"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
minio_data:
|
||||
|
||||
437
docs/README.md
437
docs/README.md
@@ -1,305 +1,174 @@
|
||||
# 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 |
|
||||
|----------|-------------|
|
||||
| **[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**
|
||||
|
||||
**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
|
||||
|
||||
### **📋 Core Documentation** (8 Files)
|
||||
|
||||
#### **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
|
||||
|
||||
### Technical Documentation
|
||||
- `docs/implementation-guide.md` - Full technical implementation
|
||||
- `docs/project-status.md` - Development status
|
||||
|
||||
### 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
docs/_archive/MARCH-2026-SESSION.md
Normal file
159
docs/_archive/MARCH-2026-SESSION.md
Normal 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
docs/_archive/wizard-interface-guide.md
Executable file
367
docs/_archive/wizard-interface-guide.md
Executable 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
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
3908
drizzle/meta/0000_snapshot.json
Normal file
3908
drizzle/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1774137504617,
|
||||
"tag": "0000_old_tattoo",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)$).*)",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
137
package.json
137
package.json
@@ -11,7 +11,8 @@
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:seed": "bun db:push && bun scripts/seed-dev.ts",
|
||||
"dev": "next dev --turbo",
|
||||
"dev": "bun run ws-server.ts & next dev --turbo",
|
||||
"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",
|
||||
@@ -23,87 +24,107 @@
|
||||
"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 +134,9 @@
|
||||
"esbuild",
|
||||
"sharp",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
],
|
||||
"overrides": {
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
28
public/images/screenshots/README.md
Normal file
28
public/images/screenshots/README.md
Normal 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"
|
||||
/>
|
||||
```
|
||||
Submodule robot-plugins updated: d554891dab...d772aecc54
46
scripts/archive/check-db-actions.ts
Normal file
46
scripts/archive/check-db-actions.ts
Normal 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
scripts/archive/check-db.ts
Normal file
66
scripts/archive/check-db.ts
Normal 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);
|
||||
60
scripts/archive/debug-experiment-structure.ts
Normal file
60
scripts/archive/debug-experiment-structure.ts
Normal file
@@ -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
scripts/archive/inspect-all-steps.ts
Normal file
41
scripts/archive/inspect-all-steps.ts
Normal 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
scripts/archive/inspect-branch-action.ts
Normal file
46
scripts/archive/inspect-branch-action.ts
Normal 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
scripts/archive/inspect-branch-steps.ts
Normal file
29
scripts/archive/inspect-branch-steps.ts
Normal 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
scripts/archive/inspect-db.ts
Normal file
29
scripts/archive/inspect-db.ts
Normal 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
scripts/archive/inspect-step.ts
Normal file
62
scripts/archive/inspect-step.ts
Normal 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
scripts/archive/inspect-visual-design.ts
Normal file
32
scripts/archive/inspect-visual-design.ts
Normal 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);
|
||||
});
|
||||
74
scripts/archive/patch-branch-action-params.ts
Normal file
74
scripts/archive/patch-branch-action-params.ts
Normal file
@@ -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
scripts/archive/patch-branch-steps.ts
Normal file
100
scripts/archive/patch-branch-steps.ts
Normal 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
scripts/archive/reproduce-hydration.ts
Normal file
87
scripts/archive/reproduce-hydration.ts
Normal 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
scripts/archive/seed-control-demo-draft.ts
Normal file
136
scripts/archive/seed-control-demo-draft.ts
Normal 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
scripts/archive/seed-control-demo.ts
Normal file
254
scripts/archive/seed-control-demo.ts
Normal 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
scripts/archive/seed-story-red-rock.ts
Normal file
274
scripts/archive/seed-story-red-rock.ts
Normal 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
scripts/archive/simulate-branch-logic.ts
Normal file
92
scripts/archive/simulate-branch-logic.ts
Normal 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
scripts/archive/test-converter.ts
Normal file
59
scripts/archive/test-converter.ts
Normal 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
scripts/archive/test-trpc-client.ts
Normal file
74
scripts/archive/test-trpc-client.ts
Normal 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
scripts/archive/verify-conversion.ts
Normal file
46
scripts/archive/verify-conversion.ts
Normal 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
scripts/archive/verify-study-readiness.ts
Normal file
107
scripts/archive/verify-study-readiness.ts
Normal 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
scripts/archive/verify-trpc-logic.ts
Normal file
86
scripts/archive/verify-trpc-logic.ts
Normal 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);
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
|
||||
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);
|
||||
@@ -1,58 +0,0 @@
|
||||
|
||||
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);
|
||||
});
|
||||
25
scripts/get-demo-id.ts
Normal file
25
scripts/get-demo-id.ts
Normal 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
scripts/get-user-id.ts
Normal file
25
scripts/get-user-id.ts
Normal 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();
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
|
||||
|
||||
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}`);
|
||||
console.log(`NextStepId: ${step.nextStepId}`);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
|
||||
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);
|
||||
});
|
||||
37
scripts/migrate-add-identifier.ts
Normal file
37
scripts/migrate-add-identifier.ts
Normal 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);
|
||||
@@ -1,69 +0,0 @@
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1,92 +0,0 @@
|
||||
|
||||
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);
|
||||
});
|
||||
1164
scripts/seed-dev.ts
1164
scripts/seed-dev.ts
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
|
||||
// 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];
|
||||
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");
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1,90 +0,0 @@
|
||||
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);
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
|
||||
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];
|
||||
console.log("Step 4 (Branch A):", branchAStep.name);
|
||||
console.log(" Type:", branchAStep.type);
|
||||
console.log(" Trigger:", JSON.stringify(branchAStep.trigger, null, 2));
|
||||
|
||||
// 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];
|
||||
console.log("Step 5 (Branch B):", branchBStep.name);
|
||||
console.log(" Trigger:", JSON.stringify(branchBStep.trigger, null, 2));
|
||||
}
|
||||
|
||||
verifyTrpcLogic()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -46,7 +46,10 @@ export default function DebugPage() {
|
||||
|
||||
const ROS_BRIDGE_URL = "ws://134.82.159.25:9090";
|
||||
|
||||
const addLog = (message: string, type: "info" | "error" | "success" = "info") => {
|
||||
const addLog = (
|
||||
message: string,
|
||||
type: "info" | "error" | "success" = "info",
|
||||
) => {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const logEntry = `[${timestamp}] [${type.toUpperCase()}] ${message}`;
|
||||
setLogs((prev) => [...prev.slice(-99), logEntry]);
|
||||
@@ -79,7 +82,9 @@ export default function DebugPage() {
|
||||
setConnectionStatus("connecting");
|
||||
setConnectionAttempts((prev) => prev + 1);
|
||||
setLastError(null);
|
||||
addLog(`Attempting connection #${connectionAttempts + 1} to ${ROS_BRIDGE_URL}`);
|
||||
addLog(
|
||||
`Attempting connection #${connectionAttempts + 1} to ${ROS_BRIDGE_URL}`,
|
||||
);
|
||||
|
||||
const socket = new WebSocket(ROS_BRIDGE_URL);
|
||||
|
||||
@@ -96,7 +101,10 @@ export default function DebugPage() {
|
||||
setConnectionStatus("connected");
|
||||
setRosSocket(socket);
|
||||
setLastError(null);
|
||||
addLog("✅ WebSocket connection established successfully", "success");
|
||||
addLog(
|
||||
"[SUCCESS] WebSocket connection established successfully",
|
||||
"success",
|
||||
);
|
||||
|
||||
// Test basic functionality by advertising
|
||||
const advertiseMsg = {
|
||||
@@ -138,16 +146,20 @@ export default function DebugPage() {
|
||||
addLog(`Connection closed normally: ${event.reason || reason}`);
|
||||
} else if (event.code === 1006) {
|
||||
reason = "Connection lost/refused";
|
||||
setLastError("ROS Bridge server not responding - check if rosbridge_server is running");
|
||||
addLog(`❌ Connection failed: ${reason} (${event.code})`, "error");
|
||||
setLastError(
|
||||
"ROS Bridge server not responding - check if rosbridge_server is running",
|
||||
);
|
||||
addLog(`[ERROR] Connection failed: ${reason} (${event.code})`, "error");
|
||||
} else if (event.code === 1011) {
|
||||
reason = "Server error";
|
||||
setLastError("ROS Bridge server encountered an error");
|
||||
addLog(`❌ Server error: ${reason} (${event.code})`, "error");
|
||||
addLog(`[ERROR] Server error: ${reason} (${event.code})`, "error");
|
||||
} else {
|
||||
reason = `Code ${event.code}`;
|
||||
setLastError(`Connection closed with code ${event.code}: ${event.reason || "No reason given"}`);
|
||||
addLog(`❌ Connection closed: ${reason}`, "error");
|
||||
setLastError(
|
||||
`Connection closed with code ${event.code}: ${event.reason || "No reason given"}`,
|
||||
);
|
||||
addLog(`[ERROR] Connection closed: ${reason}`, "error");
|
||||
}
|
||||
|
||||
if (wasConnected) {
|
||||
@@ -160,7 +172,7 @@ export default function DebugPage() {
|
||||
setConnectionStatus("error");
|
||||
const errorMsg = "WebSocket error occurred";
|
||||
setLastError(errorMsg);
|
||||
addLog(`❌ ${errorMsg}`, "error");
|
||||
addLog(`[ERROR] ${errorMsg}`, "error");
|
||||
console.error("WebSocket error details:", error);
|
||||
};
|
||||
};
|
||||
@@ -298,7 +310,7 @@ export default function DebugPage() {
|
||||
>
|
||||
{connectionStatus.toUpperCase()}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Attempts: {connectionAttempts}
|
||||
</span>
|
||||
</div>
|
||||
@@ -306,7 +318,9 @@ export default function DebugPage() {
|
||||
{lastError && (
|
||||
<Alert variant="destructive">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription className="text-sm">{lastError}</AlertDescription>
|
||||
<AlertDescription className="text-sm">
|
||||
{lastError}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -318,7 +332,9 @@ export default function DebugPage() {
|
||||
className="flex-1"
|
||||
>
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
{connectionStatus === "connecting" ? "Connecting..." : "Connect"}
|
||||
{connectionStatus === "connecting"
|
||||
? "Connecting..."
|
||||
: "Connect"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -479,27 +495,32 @@ export default function DebugPage() {
|
||||
key={index}
|
||||
className={`rounded p-2 text-xs ${
|
||||
msg.direction === "sent"
|
||||
? "bg-blue-50 border-l-2 border-blue-400"
|
||||
: "bg-green-50 border-l-2 border-green-400"
|
||||
? "border-l-2 border-blue-400 bg-blue-50"
|
||||
: "border-l-2 border-green-400 bg-green-50"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="mb-1 flex items-center justify-between">
|
||||
<Badge
|
||||
variant={msg.direction === "sent" ? "default" : "secondary"}
|
||||
variant={
|
||||
msg.direction === "sent" ? "default" : "secondary"
|
||||
}
|
||||
className="text-xs"
|
||||
>
|
||||
{msg.direction === "sent" ? "SENT" : "RECEIVED"}
|
||||
</Badge>
|
||||
<span className="text-muted-foreground">{msg.timestamp}</span>
|
||||
<span className="text-muted-foreground">
|
||||
{msg.timestamp}
|
||||
</span>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap text-xs">
|
||||
<pre className="text-xs whitespace-pre-wrap">
|
||||
{JSON.stringify(msg.data, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
))}
|
||||
{messages.length === 0 && (
|
||||
<div className="text-center text-muted-foreground py-8">
|
||||
No messages yet. Connect and send a test message to see data here.
|
||||
<div className="text-muted-foreground py-8 text-center">
|
||||
No messages yet. Connect and send a test message to see data
|
||||
here.
|
||||
</div>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user