mirror of
https://github.com/soconnor0919/hristudio.git
synced 2026-02-04 15:36:32 -05:00
feat: Implement dynamic plugin definition loading from remote/local sources and standardize action IDs using plugin metadata.
This commit is contained in:
27
bun.lock
27
bun.lock
@@ -49,7 +49,7 @@
|
|||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"lucide-react": "^0.536.0",
|
"lucide-react": "^0.536.0",
|
||||||
"minio": "^8.0.6",
|
"minio": "^8.0.6",
|
||||||
"next": "^16.0.10",
|
"next": "^16.1.6",
|
||||||
"next-auth": "^5.0.0-beta.29",
|
"next-auth": "^5.0.0-beta.29",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"postgres": "^3.4.4",
|
"postgres": "^3.4.4",
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@tailwindcss/postcss": "^4.0.15",
|
"@tailwindcss/postcss": "^4.0.15",
|
||||||
"@types/bcryptjs": "^3.0.0",
|
"@types/bcryptjs": "^3.0.0",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/node": "^20.14.10",
|
"@types/node": "^20.14.10",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
@@ -347,25 +348,25 @@
|
|||||||
|
|
||||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
|
||||||
|
|
||||||
"@next/env": ["@next/env@16.0.10", "", {}, "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang=="],
|
"@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="],
|
||||||
|
|
||||||
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.4.5", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-YhbrlbEt0m4jJnXHMY/cCUDBAWgd5SaTa5mJjzOt82QwflAFfW/h3+COp2TfVSzhmscIZ5sg2WXt3MLziqCSCw=="],
|
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.4.5", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-YhbrlbEt0m4jJnXHMY/cCUDBAWgd5SaTa5mJjzOt82QwflAFfW/h3+COp2TfVSzhmscIZ5sg2WXt3MLziqCSCw=="],
|
||||||
|
|
||||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg=="],
|
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="],
|
||||||
|
|
||||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw=="],
|
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw=="],
|
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw=="],
|
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.10", "", { "os": "linux", "cpu": "x64" }, "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA=="],
|
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.10", "", { "os": "linux", "cpu": "x64" }, "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g=="],
|
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg=="],
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg=="],
|
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw=="],
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.10", "", { "os": "win32", "cpu": "x64" }, "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q=="],
|
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
@@ -633,6 +634,8 @@
|
|||||||
|
|
||||||
"@types/bcryptjs": ["@types/bcryptjs@3.0.0", "", { "dependencies": { "bcryptjs": "*" } }, "sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg=="],
|
"@types/bcryptjs": ["@types/bcryptjs@3.0.0", "", { "dependencies": { "bcryptjs": "*" } }, "sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg=="],
|
||||||
|
|
||||||
|
"@types/crypto-js": ["@types/crypto-js@4.2.2", "", {}, "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ=="],
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
"@types/js-cookie": ["@types/js-cookie@3.0.6", "", {}, "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ=="],
|
"@types/js-cookie": ["@types/js-cookie@3.0.6", "", {}, "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ=="],
|
||||||
@@ -759,6 +762,8 @@
|
|||||||
|
|
||||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||||
|
|
||||||
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
||||||
|
|
||||||
"bcryptjs": ["bcryptjs@3.0.2", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog=="],
|
"bcryptjs": ["bcryptjs@3.0.2", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog=="],
|
||||||
|
|
||||||
"bl": ["bl@5.1.0", "", { "dependencies": { "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ=="],
|
"bl": ["bl@5.1.0", "", { "dependencies": { "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ=="],
|
||||||
@@ -1191,7 +1196,7 @@
|
|||||||
|
|
||||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||||
|
|
||||||
"next": ["next@16.0.10", "", { "dependencies": { "@next/env": "16.0.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.10", "@next/swc-darwin-x64": "16.0.10", "@next/swc-linux-arm64-gnu": "16.0.10", "@next/swc-linux-arm64-musl": "16.0.10", "@next/swc-linux-x64-gnu": "16.0.10", "@next/swc-linux-x64-musl": "16.0.10", "@next/swc-win32-arm64-msvc": "16.0.10", "@next/swc-win32-x64-msvc": "16.0.10", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA=="],
|
"next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="],
|
||||||
|
|
||||||
"next-auth": ["next-auth@5.0.0-beta.29", "", { "dependencies": { "@auth/core": "0.40.0" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0-0", "nodemailer": "^6.6.5", "react": "^18.2.0 || ^19.0.0-0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-Ukpnuk3NMc/LiOl32njZPySk7pABEzbjhMUFd5/n10I0ZNC7NCuVv8IY2JgbDek2t/PUOifQEoUiOOTLy4os5A=="],
|
"next-auth": ["next-auth@5.0.0-beta.29", "", { "dependencies": { "@auth/core": "0.40.0" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0-0", "nodemailer": "^6.6.5", "react": "^18.2.0 || ^19.0.0-0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-Ukpnuk3NMc/LiOl32njZPySk7pABEzbjhMUFd5/n10I0ZNC7NCuVv8IY2JgbDek2t/PUOifQEoUiOOTLy4os5A=="],
|
||||||
|
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -10,10 +10,10 @@
|
|||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:push": "drizzle-kit push",
|
"db:push": "drizzle-kit push",
|
||||||
"db:studio": "drizzle-kit studio",
|
"db:studio": "drizzle-kit studio",
|
||||||
"db:seed": "bun scripts/seed-dev.ts",
|
"db:seed": "bun db:push && bun scripts/seed-dev.ts",
|
||||||
"dev": "next dev --turbo",
|
"dev": "next dev --turbo",
|
||||||
"docker:up": "colima start && docker-compose up -d",
|
"docker:up": "if [ \"$(uname)\" = \"Darwin\" ]; then colima start; fi && docker compose up -d",
|
||||||
"docker:down": "docker-compose down && colima stop",
|
"docker:down": "docker compose down && if [ \"$(uname)\" = \"Darwin\" ]; then colima stop; fi",
|
||||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"lucide-react": "^0.536.0",
|
"lucide-react": "^0.536.0",
|
||||||
"minio": "^8.0.6",
|
"minio": "^8.0.6",
|
||||||
"next": "^16.0.10",
|
"next": "^16.1.6",
|
||||||
"next-auth": "^5.0.0-beta.29",
|
"next-auth": "^5.0.0-beta.29",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"postgres": "^3.4.4",
|
"postgres": "^3.4.4",
|
||||||
@@ -88,6 +88,7 @@
|
|||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@tailwindcss/postcss": "^4.0.15",
|
"@tailwindcss/postcss": "^4.0.15",
|
||||||
"@types/bcryptjs": "^3.0.0",
|
"@types/bcryptjs": "^3.0.0",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/node": "^20.14.10",
|
"@types/node": "^20.14.10",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
@@ -113,4 +114,4 @@
|
|||||||
"sharp",
|
"sharp",
|
||||||
"unrs-resolver"
|
"unrs-resolver"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Submodule robot-plugins updated: f3db314c8a...c6310d3144
@@ -3,32 +3,52 @@ import { drizzle } from "drizzle-orm/postgres-js";
|
|||||||
import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
import * as schema from "../src/server/db/schema";
|
import * as schema from "../src/server/db/schema";
|
||||||
|
import { createHash } from "crypto";
|
||||||
|
|
||||||
// Database connection
|
// Database connection
|
||||||
const connectionString = process.env.DATABASE_URL!;
|
const connectionString = process.env.DATABASE_URL!;
|
||||||
const connection = postgres(connectionString);
|
const connection = postgres(connectionString);
|
||||||
const db = drizzle(connection, { schema });
|
const db = drizzle(connection, { schema });
|
||||||
|
|
||||||
// --- NAO6 Plugin Definitions (Synced from seed-nao6-plugin.ts) ---
|
// --- NAO6 Plugin Definitions ---
|
||||||
const NAO_PLUGIN_DEF = {
|
import * as fs from "fs";
|
||||||
name: "NAO6 Robot (Enhanced ROS2 Integration)",
|
import * as path from "path";
|
||||||
version: "2.0.0",
|
|
||||||
description: "Comprehensive NAO6 robot integration for HRIStudio experiments via ROS2.",
|
// Function to load plugin definition (Remote -> Local Fallback)
|
||||||
actions: [
|
async function loadNaoPluginDef() {
|
||||||
{ id: "nao_speak", name: "Speak Text", category: "speech", parametersSchema: { type: "object", properties: { text: { type: "string" }, volume: { type: "number", default: 0.7 } }, required: ["text"] } },
|
const REMOTE_URL = "https://repo.hristudio.com/plugins/nao6-ros2.json";
|
||||||
{ id: "nao_gesture", name: "Perform Gesture", category: "interaction", parametersSchema: { type: "object", properties: { gesture: { type: "string", enum: ["wave", "bow", "point"] }, speed: { type: "number", default: 0.8 } } } },
|
const LOCAL_PATH = path.join(__dirname, "../robot-plugins/plugins/nao6-ros2.json");
|
||||||
{ id: "nao_look_at", name: "Look At", category: "movement", parametersSchema: { type: "object", properties: { target: { type: "string", enum: ["participant", "screen", "away"] }, duration: { type: "number", default: 2.0 } } } },
|
|
||||||
{ id: "nao_nod", name: "Nod Head", category: "interaction", parametersSchema: { type: "object", properties: { speed: { type: "number", default: 1.0 } } } },
|
try {
|
||||||
{ id: "nao_shake_head", name: "Shake Head", category: "interaction", parametersSchema: { type: "object", properties: { speed: { type: "number", default: 1.0 } } } },
|
console.log(`🌐 Attempting to fetch plugin definition from ${REMOTE_URL}...`);
|
||||||
{ id: "nao_bow", name: "Bow", category: "interaction", parametersSchema: { type: "object", properties: {} } },
|
const response = await fetch(REMOTE_URL, { signal: AbortSignal.timeout(3000) }); // 3s timeout
|
||||||
{ id: "nao_open_hand", name: "Present (Open Hand)", category: "interaction", parametersSchema: { type: "object", properties: { hand: { type: "string", enum: ["left", "right", "both"], default: "right" } } } }
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
]
|
const data = await response.json();
|
||||||
};
|
console.log("✅ Successfully fetched plugin definition from remote.");
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`⚠️ Remote fetch failed (${err instanceof Error ? err.message : String(err)}). Falling back to local file.`);
|
||||||
|
const rawPlugin = fs.readFileSync(LOCAL_PATH, "utf-8");
|
||||||
|
return JSON.parse(rawPlugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global variable to hold the loaded definition
|
||||||
|
let NAO_PLUGIN_DEF: any;
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
console.log("🌱 Starting realistic seed script...");
|
console.log("🌱 Starting realistic seed script...");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
NAO_PLUGIN_DEF = await loadNaoPluginDef();
|
||||||
|
|
||||||
|
// Ensure legacy 'actions' property maps to 'actionDefinitions' if needed, though schema supports both if we map it
|
||||||
|
if (NAO_PLUGIN_DEF.actions && !NAO_PLUGIN_DEF.actionDefinitions) {
|
||||||
|
NAO_PLUGIN_DEF.actionDefinitions = NAO_PLUGIN_DEF.actions;
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Clean existing data (Full Wipe)
|
// 1. Clean existing data (Full Wipe)
|
||||||
console.log("🧹 Cleaning existing data...");
|
console.log("🧹 Cleaning existing data...");
|
||||||
await db.delete(schema.mediaCaptures).where(sql`1=1`);
|
await db.delete(schema.mediaCaptures).where(sql`1=1`);
|
||||||
@@ -51,12 +71,14 @@ async function main() {
|
|||||||
console.log("👥 Creating users...");
|
console.log("👥 Creating users...");
|
||||||
const hashedPassword = await bcrypt.hash("password123", 12);
|
const hashedPassword = await bcrypt.hash("password123", 12);
|
||||||
|
|
||||||
|
const gravatarUrl = (email: string) => `https://www.gravatar.com/avatar/${createHash("md5").update(email.toLowerCase().trim()).digest("hex")}?d=identicon`;
|
||||||
|
|
||||||
const [adminUser] = await db.insert(schema.users).values({
|
const [adminUser] = await db.insert(schema.users).values({
|
||||||
name: "Sean O'Connor",
|
name: "Sean O'Connor",
|
||||||
email: "sean@soconnor.dev",
|
email: "sean@soconnor.dev",
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
emailVerified: new Date(),
|
emailVerified: new Date(),
|
||||||
image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Sean",
|
image: gravatarUrl("sean@soconnor.dev"),
|
||||||
}).returning();
|
}).returning();
|
||||||
|
|
||||||
const [researcherUser] = await db.insert(schema.users).values({
|
const [researcherUser] = await db.insert(schema.users).values({
|
||||||
@@ -98,12 +120,12 @@ async function main() {
|
|||||||
version: NAO_PLUGIN_DEF.version,
|
version: NAO_PLUGIN_DEF.version,
|
||||||
description: NAO_PLUGIN_DEF.description,
|
description: NAO_PLUGIN_DEF.description,
|
||||||
author: "HRIStudio Team",
|
author: "HRIStudio Team",
|
||||||
trustLevel: "official",
|
repositoryUrl: "https://github.com/hristudio/plugins/tree/main/nao6",
|
||||||
|
trustLevel: "verified",
|
||||||
|
actionDefinitions: NAO_PLUGIN_DEF.actionDefinitions,
|
||||||
|
metadata: NAO_PLUGIN_DEF,
|
||||||
status: "active",
|
status: "active",
|
||||||
repositoryUrl: naoRepo!.url,
|
createdAt: new Date(),
|
||||||
actionDefinitions: NAO_PLUGIN_DEF.actions,
|
|
||||||
configurationSchema: { type: "object", properties: { robotIp: { type: "string", default: "192.168.1.100" } } },
|
|
||||||
metadata: { category: "robot_control" }
|
|
||||||
}).returning();
|
}).returning();
|
||||||
|
|
||||||
// 4. Create Study & Experiment - Comparative WoZ Study
|
// 4. Create Study & Experiment - Comparative WoZ Study
|
||||||
@@ -157,21 +179,29 @@ async function main() {
|
|||||||
{
|
{
|
||||||
stepId: step1!.id,
|
stepId: step1!.id,
|
||||||
name: "Greet Participant",
|
name: "Greet Participant",
|
||||||
type: "nao6.nao_speak",
|
type: "nao6-ros2.say_with_emotion",
|
||||||
orderIndex: 0,
|
orderIndex: 0,
|
||||||
parameters: { text: "Hello there! I have a wonderful story to share with you today.", volume: 0.8 },
|
parameters: { text: "Hello there! I have a wonderful story to share with you today.", emotion: "happy", speed: 1.0 },
|
||||||
pluginId: naoPlugin!.id,
|
pluginId: naoPlugin!.id,
|
||||||
category: "speech",
|
category: "interaction",
|
||||||
retryable: true
|
retryable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stepId: step1!.id,
|
stepId: step1!.id,
|
||||||
name: "Wave Greeting",
|
name: "Wave Greeting",
|
||||||
type: "nao6.nao_gesture",
|
type: "nao6-ros2.move_arm",
|
||||||
orderIndex: 1,
|
orderIndex: 1,
|
||||||
parameters: { gesture: "wave" },
|
// Raising right arm to wave position
|
||||||
|
parameters: {
|
||||||
|
arm: "right",
|
||||||
|
shoulder_pitch: -1.0,
|
||||||
|
shoulder_roll: -0.3,
|
||||||
|
elbow_yaw: 1.5,
|
||||||
|
elbow_roll: 0.5,
|
||||||
|
speed: 0.5
|
||||||
|
},
|
||||||
pluginId: naoPlugin!.id,
|
pluginId: naoPlugin!.id,
|
||||||
category: "interaction",
|
category: "movement",
|
||||||
retryable: true
|
retryable: true
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -191,20 +221,20 @@ async function main() {
|
|||||||
{
|
{
|
||||||
stepId: step2!.id,
|
stepId: step2!.id,
|
||||||
name: "Tell Story Part 1",
|
name: "Tell Story Part 1",
|
||||||
type: "nao6.nao_speak",
|
type: "nao6-ros2.say_text",
|
||||||
orderIndex: 0,
|
orderIndex: 0,
|
||||||
parameters: { text: "Once upon a time, in a land far away, there lived a curious robot named Alpha.", volume: 0.8 },
|
parameters: { text: "Once upon a time, in a land far away, there lived a curious robot named Alpha." },
|
||||||
pluginId: naoPlugin!.id,
|
pluginId: naoPlugin!.id,
|
||||||
category: "speech"
|
category: "interaction"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stepId: step2!.id,
|
stepId: step2!.id,
|
||||||
name: "Present Gesture",
|
name: "Look at Audience",
|
||||||
type: "nao6.nao_open_hand",
|
type: "nao6-ros2.move_head",
|
||||||
orderIndex: 1,
|
orderIndex: 1,
|
||||||
parameters: { hand: "right" },
|
parameters: { yaw: 0.0, pitch: -0.2, speed: 0.5 },
|
||||||
pluginId: naoPlugin!.id,
|
pluginId: naoPlugin!.id,
|
||||||
category: "interaction"
|
category: "movement"
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -225,18 +255,22 @@ async function main() {
|
|||||||
{
|
{
|
||||||
stepId: step3!.id,
|
stepId: step3!.id,
|
||||||
name: "Ask Question",
|
name: "Ask Question",
|
||||||
type: "nao6.nao_speak",
|
type: "nao6-ros2.say_with_emotion",
|
||||||
orderIndex: 0,
|
orderIndex: 0,
|
||||||
parameters: { text: "What was the robot's name?", volume: 0.8 },
|
parameters: { text: "Did you understand the story so far?", emotion: "happy", speed: 1.0 },
|
||||||
pluginId: naoPlugin!.id,
|
pluginId: naoPlugin!.id,
|
||||||
category: "speech"
|
category: "interaction"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stepId: step3!.id,
|
stepId: step3!.id,
|
||||||
name: "Wait for Wizard Input",
|
name: "Wait for Wizard Input",
|
||||||
type: "core.wait_for_response",
|
type: "wizard_wait_for_response",
|
||||||
orderIndex: 1,
|
orderIndex: 1,
|
||||||
parameters: { prompt: "Did participant answer 'Alpha'?", options: ["Yes", "No"] },
|
parameters: {
|
||||||
|
prompt_text: "Did participant answer 'Alpha'?",
|
||||||
|
response_type: "verbal",
|
||||||
|
timeout: 60
|
||||||
|
},
|
||||||
sourceKind: "core",
|
sourceKind: "core",
|
||||||
category: "wizard"
|
category: "wizard"
|
||||||
}
|
}
|
||||||
@@ -257,21 +291,21 @@ async function main() {
|
|||||||
await db.insert(schema.actions).values([
|
await db.insert(schema.actions).values([
|
||||||
{
|
{
|
||||||
stepId: step4!.id,
|
stepId: step4!.id,
|
||||||
name: "Nod Affirmation",
|
name: "Express Agreement",
|
||||||
type: "nao6.nao_nod",
|
type: "nao6-ros2.say_with_emotion",
|
||||||
orderIndex: 0,
|
orderIndex: 0,
|
||||||
parameters: { speed: 1.0 },
|
parameters: { text: "Yes, exactly!", emotion: "happy", speed: 1.0 },
|
||||||
pluginId: naoPlugin!.id,
|
pluginId: naoPlugin!.id,
|
||||||
category: "interaction"
|
category: "interaction"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stepId: step4!.id,
|
stepId: step4!.id,
|
||||||
name: "Say Correct",
|
name: "Say Correct",
|
||||||
type: "nao6.nao_speak",
|
type: "nao6-ros2.say_text",
|
||||||
orderIndex: 1,
|
orderIndex: 1,
|
||||||
parameters: { text: "That is correct! Well done.", volume: 0.8 },
|
parameters: { text: "That is correct! Well done." },
|
||||||
pluginId: naoPlugin!.id,
|
pluginId: naoPlugin!.id,
|
||||||
category: "speech"
|
category: "interaction"
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -290,18 +324,18 @@ async function main() {
|
|||||||
{
|
{
|
||||||
stepId: step5!.id,
|
stepId: step5!.id,
|
||||||
name: "Finish Story",
|
name: "Finish Story",
|
||||||
type: "nao6.nao_speak",
|
type: "nao6-ros2.say_text",
|
||||||
orderIndex: 0,
|
orderIndex: 0,
|
||||||
parameters: { text: "Alpha explored the world and learned many things. The end.", volume: 0.8 },
|
parameters: { text: "Alpha explored the world and learned many things. The end." },
|
||||||
pluginId: naoPlugin!.id,
|
pluginId: naoPlugin!.id,
|
||||||
category: "speech"
|
category: "interaction"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stepId: step5!.id,
|
stepId: step5!.id,
|
||||||
name: "Bow Goodbye",
|
name: "Say Goodbye",
|
||||||
type: "nao6.nao_bow",
|
type: "nao6-ros2.say_with_emotion",
|
||||||
orderIndex: 1,
|
orderIndex: 1,
|
||||||
parameters: {},
|
parameters: { text: "Goodbye everyone!", emotion: "happy", speed: 1.0 },
|
||||||
pluginId: naoPlugin!.id,
|
pluginId: naoPlugin!.id,
|
||||||
category: "interaction"
|
category: "interaction"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,10 +47,13 @@ async function verify() {
|
|||||||
// Verify Step Names
|
// Verify Step Names
|
||||||
const expectedSteps = ["The Hook", "The Narrative - Part 1", "Comprehension Check", "Positive Feedback", "Conclusion"];
|
const expectedSteps = ["The Hook", "The Narrative - Part 1", "Comprehension Check", "Positive Feedback", "Conclusion"];
|
||||||
for (let i = 0; i < expectedSteps.length; i++) {
|
for (let i = 0; i < expectedSteps.length; i++) {
|
||||||
if (steps[i].name !== expectedSteps[i]) {
|
const step = steps[i];
|
||||||
console.error(`❌ Step mismatch at index ${i}. Expected '${expectedSteps[i]}', got '${steps[i].name}'`);
|
if (!step) continue;
|
||||||
|
|
||||||
|
if (step.name !== expectedSteps[i]) {
|
||||||
|
console.error(`❌ Step mismatch at index ${i}. Expected '${expectedSteps[i]}', got '${step.name}'`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`✅ Step ${i + 1}: ${steps[i].name}`);
|
console.log(`✅ Step ${i + 1}: ${step.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export default function NaoTestPage() {
|
|||||||
data.topic?.includes("touch") ||
|
data.topic?.includes("touch") ||
|
||||||
data.topic?.includes("sonar")
|
data.topic?.includes("sonar")
|
||||||
) {
|
) {
|
||||||
setSensorData((prev) => ({
|
setSensorData((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[data.topic]: data.msg,
|
[data.topic]: data.msg,
|
||||||
}));
|
}));
|
||||||
@@ -196,14 +196,14 @@ export default function NaoTestPage() {
|
|||||||
|
|
||||||
const walkForward = () => {
|
const walkForward = () => {
|
||||||
publishMessage("/cmd_vel", "geometry_msgs/Twist", {
|
publishMessage("/cmd_vel", "geometry_msgs/Twist", {
|
||||||
linear: { x: walkSpeed[0], y: 0, z: 0 },
|
linear: { x: walkSpeed[0] ?? 0, y: 0, z: 0 },
|
||||||
angular: { x: 0, y: 0, z: 0 },
|
angular: { x: 0, y: 0, z: 0 },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const walkBackward = () => {
|
const walkBackward = () => {
|
||||||
publishMessage("/cmd_vel", "geometry_msgs/Twist", {
|
publishMessage("/cmd_vel", "geometry_msgs/Twist", {
|
||||||
linear: { x: -walkSpeed[0], y: 0, z: 0 },
|
linear: { x: -(walkSpeed[0] ?? 0), y: 0, z: 0 },
|
||||||
angular: { x: 0, y: 0, z: 0 },
|
angular: { x: 0, y: 0, z: 0 },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -211,14 +211,14 @@ export default function NaoTestPage() {
|
|||||||
const turnLeft = () => {
|
const turnLeft = () => {
|
||||||
publishMessage("/cmd_vel", "geometry_msgs/Twist", {
|
publishMessage("/cmd_vel", "geometry_msgs/Twist", {
|
||||||
linear: { x: 0, y: 0, z: 0 },
|
linear: { x: 0, y: 0, z: 0 },
|
||||||
angular: { x: 0, y: 0, z: turnSpeed[0] },
|
angular: { x: 0, y: 0, z: turnSpeed[0] ?? 0 },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const turnRight = () => {
|
const turnRight = () => {
|
||||||
publishMessage("/cmd_vel", "geometry_msgs/Twist", {
|
publishMessage("/cmd_vel", "geometry_msgs/Twist", {
|
||||||
linear: { x: 0, y: 0, z: 0 },
|
linear: { x: 0, y: 0, z: 0 },
|
||||||
angular: { x: 0, y: 0, z: -turnSpeed[0] },
|
angular: { x: 0, y: 0, z: -(turnSpeed[0] ?? 0) },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ export default function NaoTestPage() {
|
|||||||
const moveHead = () => {
|
const moveHead = () => {
|
||||||
publishMessage("/joint_angles", "naoqi_bridge_msgs/JointAnglesWithSpeed", {
|
publishMessage("/joint_angles", "naoqi_bridge_msgs/JointAnglesWithSpeed", {
|
||||||
joint_names: ["HeadYaw", "HeadPitch"],
|
joint_names: ["HeadYaw", "HeadPitch"],
|
||||||
joint_angles: [headYaw[0], headPitch[0]],
|
joint_angles: [headYaw[0] ?? 0, headPitch[0] ?? 0],
|
||||||
speed: 0.3,
|
speed: 0.3,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -365,7 +365,7 @@ export default function NaoTestPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Walk Speed: {walkSpeed[0].toFixed(2)} m/s</Label>
|
<Label>Walk Speed: {(walkSpeed[0] ?? 0).toFixed(2)} m/s</Label>
|
||||||
<Slider
|
<Slider
|
||||||
value={walkSpeed}
|
value={walkSpeed}
|
||||||
onValueChange={setWalkSpeed}
|
onValueChange={setWalkSpeed}
|
||||||
@@ -375,7 +375,7 @@ export default function NaoTestPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Turn Speed: {turnSpeed[0].toFixed(2)} rad/s</Label>
|
<Label>Turn Speed: {(turnSpeed[0] ?? 0).toFixed(2)} rad/s</Label>
|
||||||
<Slider
|
<Slider
|
||||||
value={turnSpeed}
|
value={turnSpeed}
|
||||||
onValueChange={setTurnSpeed}
|
onValueChange={setTurnSpeed}
|
||||||
@@ -415,7 +415,7 @@ export default function NaoTestPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Head Yaw: {headYaw[0].toFixed(2)} rad</Label>
|
<Label>Head Yaw: {(headYaw[0] ?? 0).toFixed(2)} rad</Label>
|
||||||
<Slider
|
<Slider
|
||||||
value={headYaw}
|
value={headYaw}
|
||||||
onValueChange={setHeadYaw}
|
onValueChange={setHeadYaw}
|
||||||
@@ -425,7 +425,7 @@ export default function NaoTestPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Head Pitch: {headPitch[0].toFixed(2)} rad</Label>
|
<Label>Head Pitch: {(headPitch[0] ?? 0).toFixed(2)} rad</Label>
|
||||||
<Slider
|
<Slider
|
||||||
value={headPitch}
|
value={headPitch}
|
||||||
onValueChange={setHeadPitch}
|
onValueChange={setHeadPitch}
|
||||||
|
|||||||
@@ -260,7 +260,6 @@ export default function StudyAnalyticsPage() {
|
|||||||
setSelectedTrialId={setSelectedTrialId}
|
setSelectedTrialId={setSelectedTrialId}
|
||||||
trialsList={trialsList ?? []}
|
trialsList={trialsList ?? []}
|
||||||
isLoadingList={isLoadingList}
|
isLoadingList={isLoadingList}
|
||||||
studyId={studyId}
|
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,21 +26,17 @@ import {
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { api } from "~/trpc/react";
|
import { api } from "~/trpc/react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { type Experiment } from "~/lib/experiments/types";
|
import { type experiments, experimentStatusEnum } from "~/server/db/schema";
|
||||||
|
import { type InferSelectModel } from "drizzle-orm";
|
||||||
|
|
||||||
|
type Experiment = InferSelectModel<typeof experiments>;
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(2, {
|
name: z.string().min(2, {
|
||||||
message: "Name must be at least 2 characters.",
|
message: "Name must be at least 2 characters.",
|
||||||
}),
|
}),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
status: z.enum([
|
status: z.enum(experimentStatusEnum.enumValues),
|
||||||
"draft",
|
|
||||||
"ready",
|
|
||||||
"data_collection",
|
|
||||||
"analysis",
|
|
||||||
"completed",
|
|
||||||
"archived",
|
|
||||||
]),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ExperimentFormProps {
|
interface ExperimentFormProps {
|
||||||
@@ -133,11 +129,9 @@ export function ExperimentForm({ experiment }: ExperimentFormProps) {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="draft">Draft</SelectItem>
|
<SelectItem value="draft">Draft</SelectItem>
|
||||||
|
<SelectItem value="testing">Testing</SelectItem>
|
||||||
<SelectItem value="ready">Ready</SelectItem>
|
<SelectItem value="ready">Ready</SelectItem>
|
||||||
<SelectItem value="data_collection">Data Collection</SelectItem>
|
<SelectItem value="deprecated">Deprecated</SelectItem>
|
||||||
<SelectItem value="analysis">Analysis</SelectItem>
|
|
||||||
<SelectItem value="completed">Completed</SelectItem>
|
|
||||||
<SelectItem value="archived">Archived</SelectItem>
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { type Experiment } from "~/lib/experiments/types";
|
import { type experiments } from "~/server/db/schema";
|
||||||
|
import { type InferSelectModel } from "drizzle-orm";
|
||||||
|
|
||||||
|
type Experiment = InferSelectModel<typeof experiments>;
|
||||||
import { api } from "~/trpc/server";
|
import { api } from "~/trpc/server";
|
||||||
import { ExperimentForm } from "./experiment-form";
|
import { ExperimentForm } from "./experiment-form";
|
||||||
import {
|
import {
|
||||||
@@ -43,14 +46,6 @@ export default async function ExperimentEditPage({
|
|||||||
title="Edit Experiment"
|
title="Edit Experiment"
|
||||||
subtitle={`Update settings for ${experiment.name}`}
|
subtitle={`Update settings for ${experiment.name}`}
|
||||||
icon="Edit"
|
icon="Edit"
|
||||||
backButton={
|
|
||||||
<Button variant="ghost" size="sm" asChild className="-ml-2 mb-2">
|
|
||||||
<Link href={`/studies/${studyId}/experiments/${experimentId}`}>
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
Back to Experiment
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="max-w-2xl">
|
<div className="max-w-2xl">
|
||||||
|
|||||||
@@ -39,11 +39,10 @@ export default async function ParticipantDetailPage({
|
|||||||
title={participant.participantCode}
|
title={participant.participantCode}
|
||||||
subtitle={participant.name ?? "Unnamed Participant"}
|
subtitle={participant.name ?? "Unnamed Participant"}
|
||||||
icon="Users"
|
icon="Users"
|
||||||
badge={
|
status={{
|
||||||
<Badge variant={participant.consentGiven ? "default" : "secondary"}>
|
label: participant.consentGiven ? "Consent Given" : "No Consent",
|
||||||
{participant.consentGiven ? "Consent Given" : "No Consent"}
|
variant: participant.consentGiven ? "default" : "secondary"
|
||||||
</Badge>
|
}}
|
||||||
}
|
|
||||||
actions={
|
actions={
|
||||||
<Button asChild variant="outline" size="sm">
|
<Button asChild variant="outline" size="sm">
|
||||||
<Link href={`/studies/${studyId}/participants/${participantId}/edit`}>
|
<Link href={`/studies/${studyId}/participants/${participantId}/edit`}>
|
||||||
|
|||||||
@@ -318,20 +318,11 @@ export class ActionRegistry {
|
|||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
metadata?: Record<string, any>;
|
||||||
};
|
};
|
||||||
}>,
|
}>,
|
||||||
): void {
|
): void {
|
||||||
console.log("ActionRegistry.loadPluginActions called with:", {
|
// console.log("ActionRegistry.loadPluginActions called with:", { studyId, pluginCount: studyPlugins?.length ?? 0 });
|
||||||
studyId,
|
|
||||||
pluginCount: studyPlugins?.length ?? 0,
|
|
||||||
plugins: studyPlugins?.map((sp) => ({
|
|
||||||
id: sp.plugin.id,
|
|
||||||
actionCount: Array.isArray(sp.plugin.actionDefinitions)
|
|
||||||
? sp.plugin.actionDefinitions.length
|
|
||||||
: 0,
|
|
||||||
hasActionDefs: !!sp.plugin.actionDefinitions,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.pluginActionsLoaded && this.loadedStudyId === studyId) return;
|
if (this.pluginActionsLoaded && this.loadedStudyId === studyId) return;
|
||||||
|
|
||||||
@@ -347,11 +338,7 @@ export class ActionRegistry {
|
|||||||
? plugin.actionDefinitions
|
? plugin.actionDefinitions
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
console.log(`Plugin ${plugin.id}:`, {
|
// console.log(`Plugin ${plugin.id}:`, { actionCount: actionDefs?.length ?? 0 });
|
||||||
actionDefinitions: plugin.actionDefinitions,
|
|
||||||
isArray: Array.isArray(plugin.actionDefinitions),
|
|
||||||
actionCount: actionDefs?.length ?? 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!actionDefs) return;
|
if (!actionDefs) return;
|
||||||
|
|
||||||
@@ -399,9 +386,13 @@ export class ActionRegistry {
|
|||||||
retryable: action.retryable,
|
retryable: action.retryable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Extract semantic ID from metadata if available, otherwise fall back to database IDs (which typically causes mismatch if seed uses semantic)
|
||||||
|
// Ideally, plugin.metadata.robotId should populate this.
|
||||||
|
const semanticRobotId = plugin.metadata?.robotId || plugin.robotId || plugin.id;
|
||||||
|
|
||||||
const actionDef: ActionDefinition = {
|
const actionDef: ActionDefinition = {
|
||||||
id: `${plugin.robotId ?? plugin.id}.${action.id}`,
|
id: `${semanticRobotId}.${action.id}`,
|
||||||
type: `${plugin.robotId ?? plugin.id}.${action.id}`,
|
type: `${semanticRobotId}.${action.id}`,
|
||||||
name: action.name,
|
name: action.name,
|
||||||
description: action.description ?? "",
|
description: action.description ?? "",
|
||||||
category,
|
category,
|
||||||
@@ -412,7 +403,7 @@ export class ActionRegistry {
|
|||||||
),
|
),
|
||||||
source: {
|
source: {
|
||||||
kind: "plugin",
|
kind: "plugin",
|
||||||
pluginId: plugin.robotId ?? plugin.id,
|
pluginId: semanticRobotId, // Use semantic ID here too
|
||||||
robotId: plugin.robotId,
|
robotId: plugin.robotId,
|
||||||
pluginVersion: plugin.version ?? undefined,
|
pluginVersion: plugin.version ?? undefined,
|
||||||
baseActionId: action.id,
|
baseActionId: action.id,
|
||||||
@@ -439,15 +430,8 @@ export class ActionRegistry {
|
|||||||
console.log(
|
console.log(
|
||||||
`ActionRegistry: Loaded ${totalActionsLoaded} plugin actions for study ${studyId}`,
|
`ActionRegistry: Loaded ${totalActionsLoaded} plugin actions for study ${studyId}`,
|
||||||
);
|
);
|
||||||
console.log("Current action registry state:", {
|
// console.log("Current action registry state:", { totalActions: this.actions.size });
|
||||||
totalActions: this.actions.size,
|
|
||||||
actionsByCategory: {
|
|
||||||
wizard: this.getActionsByCategory("wizard").length,
|
|
||||||
robot: this.getActionsByCategory("robot").length,
|
|
||||||
control: this.getActionsByCategory("control").length,
|
|
||||||
observation: this.getActionsByCategory("observation").length,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pluginActionsLoaded = true;
|
this.pluginActionsLoaded = true;
|
||||||
this.loadedStudyId = studyId;
|
this.loadedStudyId = studyId;
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ export function DesignerRoot({
|
|||||||
robotId: sp.plugin.robotId ?? "",
|
robotId: sp.plugin.robotId ?? "",
|
||||||
name: sp.plugin.name,
|
name: sp.plugin.name,
|
||||||
version: sp.plugin.version,
|
version: sp.plugin.version,
|
||||||
|
actionDefinitions: sp.plugin.actionDefinitions as any[],
|
||||||
|
metadata: sp.plugin.metadata as Record<string, any>,
|
||||||
})),
|
})),
|
||||||
[studyPluginsRaw],
|
[studyPluginsRaw],
|
||||||
);
|
);
|
||||||
@@ -272,11 +274,11 @@ export function DesignerRoot({
|
|||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
if (loadingExperiment && !initialDesign) return;
|
if (loadingExperiment && !initialDesign) return;
|
||||||
|
|
||||||
console.log('[DesignerRoot] 🚀 INITIALIZING', {
|
// console.log('[DesignerRoot] 🚀 INITIALIZING', {
|
||||||
hasExperiment: !!experiment,
|
// hasExperiment: !!experiment,
|
||||||
hasInitialDesign: !!initialDesign,
|
// hasInitialDesign: !!initialDesign,
|
||||||
loadingExperiment,
|
// loadingExperiment,
|
||||||
});
|
// });
|
||||||
|
|
||||||
const adapted =
|
const adapted =
|
||||||
initialDesign ??
|
initialDesign ??
|
||||||
@@ -304,7 +306,7 @@ export function DesignerRoot({
|
|||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
// NOTE: We don't call recomputeHash() here because the automatic
|
// NOTE: We don't call recomputeHash() here because the automatic
|
||||||
// hash recomputation useEffect will trigger when setSteps() updates the steps array
|
// hash recomputation useEffect will trigger when setSteps() updates the steps array
|
||||||
console.log('[DesignerRoot] 🚀 Initialization complete, steps set');
|
// console.log('[DesignerRoot] 🚀 Initialization complete, steps set');
|
||||||
}, [
|
}, [
|
||||||
initialized,
|
initialized,
|
||||||
loadingExperiment,
|
loadingExperiment,
|
||||||
@@ -346,7 +348,7 @@ export function DesignerRoot({
|
|||||||
// Small delay to ensure all components have rendered
|
// Small delay to ensure all components have rendered
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setIsReady(true);
|
setIsReady(true);
|
||||||
console.log('[DesignerRoot] ✅ Designer ready (plugins loaded), fading in');
|
// console.log('[DesignerRoot] ✅ Designer ready (plugins loaded), fading in');
|
||||||
}, 150);
|
}, 150);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
@@ -357,19 +359,13 @@ export function DesignerRoot({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialized) return;
|
if (!initialized) return;
|
||||||
|
|
||||||
console.log('[DesignerRoot] Steps changed, scheduling hash recomputation', {
|
// console.log('[DesignerRoot] Steps changed, scheduling hash recomputation');
|
||||||
stepsCount: steps.length,
|
|
||||||
actionsCount: steps.reduce((sum, s) => sum + s.actions.length, 0),
|
|
||||||
});
|
|
||||||
|
|
||||||
const timeoutId = setTimeout(async () => {
|
const timeoutId = setTimeout(async () => {
|
||||||
console.log('[DesignerRoot] Executing debounced hash recomputation');
|
// console.log('[DesignerRoot] Executing debounced hash recomputation');
|
||||||
const result = await recomputeHash();
|
const result = await recomputeHash();
|
||||||
if (result) {
|
if (result) {
|
||||||
console.log('[DesignerRoot] Hash recomputed:', {
|
// console.log('[DesignerRoot] Hash recomputed:', result.designHash.slice(0, 16));
|
||||||
newHash: result.designHash.slice(0, 16),
|
|
||||||
fullHash: result.designHash,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, 300); // Debounce 300ms
|
}, 300); // Debounce 300ms
|
||||||
|
|
||||||
@@ -383,13 +379,12 @@ export function DesignerRoot({
|
|||||||
|
|
||||||
// Debug logging to track hash updates and save button state
|
// Debug logging to track hash updates and save button state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('[DesignerRoot] Hash State:', {
|
// console.log('[DesignerRoot] Hash State:', {
|
||||||
currentDesignHash: currentDesignHash?.slice(0, 10),
|
// currentDesignHash: currentDesignHash?.slice(0, 10),
|
||||||
lastPersistedHash: lastPersistedHash?.slice(0, 10),
|
// lastPersistedHash: lastPersistedHash?.slice(0, 10),
|
||||||
hasUnsavedChanges,
|
// hasUnsavedChanges,
|
||||||
stepsCount: steps.length,
|
// });
|
||||||
});
|
}, [currentDesignHash, lastPersistedHash, hasUnsavedChanges]);
|
||||||
}, [currentDesignHash, lastPersistedHash, hasUnsavedChanges, steps.length]);
|
|
||||||
|
|
||||||
/* ------------------------------- Step Ops -------------------------------- */
|
/* ------------------------------- Step Ops -------------------------------- */
|
||||||
const createNewStep = useCallback(() => {
|
const createNewStep = useCallback(() => {
|
||||||
@@ -426,13 +421,28 @@ export function DesignerRoot({
|
|||||||
});
|
});
|
||||||
// Debug: log validation results for troubleshooting
|
// Debug: log validation results for troubleshooting
|
||||||
|
|
||||||
console.debug("[DesignerRoot] validation", {
|
// Debug: Improved structured logging for validation results
|
||||||
valid: result.valid,
|
console.group("🧪 Experiment Validation Results");
|
||||||
errors: result.errorCount,
|
if (result.valid) {
|
||||||
warnings: result.warningCount,
|
console.log(`%c✓ VALID (0 errors, ${result.warningCount} warnings, ${result.infoCount} hints)`, "color: green; font-weight: bold; font-size: 12px;");
|
||||||
infos: result.infoCount,
|
} else {
|
||||||
issues: result.issues,
|
console.log(`%c✗ INVALID (${result.errorCount} errors, ${result.warningCount} warnings)`, "color: red; font-weight: bold; font-size: 12px;");
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (result.issues.length > 0) {
|
||||||
|
console.table(
|
||||||
|
result.issues.map(i => ({
|
||||||
|
Severity: i.severity.toUpperCase(),
|
||||||
|
Category: i.category,
|
||||||
|
Message: i.message,
|
||||||
|
Suggest: i.suggestion,
|
||||||
|
Location: i.actionId ? `Action ${i.actionId}` : (i.stepId ? `Step ${i.stepId}` : 'Global')
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("No issues found. Design is perfectly compliant.");
|
||||||
|
}
|
||||||
|
console.groupEnd();
|
||||||
// Persist issues to store for inspector rendering
|
// Persist issues to store for inspector rendering
|
||||||
const grouped = groupIssuesByEntity(result.issues);
|
const grouped = groupIssuesByEntity(result.issues);
|
||||||
clearAllValidationIssues();
|
clearAllValidationIssues();
|
||||||
|
|||||||
@@ -232,9 +232,9 @@ export function PropertiesPanelBase({
|
|||||||
</Badge>
|
</Badge>
|
||||||
{/* internal plugin identifiers hidden from UI */}
|
{/* internal plugin identifiers hidden from UI */}
|
||||||
<Badge variant="outline" className="h-4 text-[10px]">
|
<Badge variant="outline" className="h-4 text-[10px]">
|
||||||
{selectedAction.execution.transport}
|
{selectedAction.execution?.transport}
|
||||||
</Badge>
|
</Badge>
|
||||||
{selectedAction.execution.retryable && (
|
{selectedAction.execution?.retryable && (
|
||||||
<Badge variant="outline" className="h-4 text-[10px]">
|
<Badge variant="outline" className="h-4 text-[10px]">
|
||||||
retryable
|
retryable
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -473,7 +473,7 @@ const ParameterEditor = React.memo(function ParameterEditor({
|
|||||||
}: ParameterEditorProps) {
|
}: ParameterEditorProps) {
|
||||||
// Local state for immediate feedback
|
// Local state for immediate feedback
|
||||||
const [localValue, setLocalValue] = useState<unknown>(rawValue);
|
const [localValue, setLocalValue] = useState<unknown>(rawValue);
|
||||||
const debounceRef = useRef<NodeJS.Timeout | undefined>();
|
const debounceRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||||
|
|
||||||
// Sync from prop if it changes externally
|
// Sync from prop if it changes externally
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -207,6 +207,18 @@ export function InspectorPanel({
|
|||||||
/* ------------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------------ */
|
||||||
/* Render */
|
/* Render */
|
||||||
/* ------------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------------ */
|
||||||
|
const designObject = useMemo(
|
||||||
|
() => ({
|
||||||
|
id: "design",
|
||||||
|
name: "Design",
|
||||||
|
description: "",
|
||||||
|
version: 1,
|
||||||
|
steps,
|
||||||
|
lastSaved: new Date(),
|
||||||
|
}),
|
||||||
|
[steps],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -284,17 +296,7 @@ export function InspectorPanel({
|
|||||||
<div className="flex-1 overflow-x-hidden overflow-y-auto">
|
<div className="flex-1 overflow-x-hidden overflow-y-auto">
|
||||||
<div className="w-full px-0 py-2 break-words whitespace-normal">
|
<div className="w-full px-0 py-2 break-words whitespace-normal">
|
||||||
<PropertiesPanel
|
<PropertiesPanel
|
||||||
design={useMemo(
|
design={designObject}
|
||||||
() => ({
|
|
||||||
id: "design",
|
|
||||||
name: "Design",
|
|
||||||
description: "",
|
|
||||||
version: 1,
|
|
||||||
steps,
|
|
||||||
lastSaved: new Date(),
|
|
||||||
}),
|
|
||||||
[steps],
|
|
||||||
)}
|
|
||||||
selectedStep={selectedStep}
|
selectedStep={selectedStep}
|
||||||
selectedAction={selectedAction}
|
selectedAction={selectedAction}
|
||||||
onActionUpdate={handleActionUpdate}
|
onActionUpdate={handleActionUpdate}
|
||||||
|
|||||||
@@ -659,8 +659,8 @@ export function validateExecution(
|
|||||||
const robotActions = steps.flatMap((step) =>
|
const robotActions = steps.flatMap((step) =>
|
||||||
step.actions.filter(
|
step.actions.filter(
|
||||||
(action) =>
|
(action) =>
|
||||||
action.execution.transport === "ros2" ||
|
action.execution?.transport === "ros2" ||
|
||||||
action.execution.transport === "rest",
|
action.execution?.transport === "rest",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -505,7 +505,7 @@ export function ParticipantForm({
|
|||||||
error={error}
|
error={error}
|
||||||
onDelete={mode === "edit" ? onDelete : undefined}
|
onDelete={mode === "edit" ? onDelete : undefined}
|
||||||
isDeleting={isDeleting}
|
isDeleting={isDeleting}
|
||||||
isDeleting={isDeleting}
|
|
||||||
// sidebar={sidebar} // Removed for cleaner UI per user request
|
// sidebar={sidebar} // Removed for cleaner UI per user request
|
||||||
submitText={mode === "create" ? "Register Participant" : "Save Changes"}
|
submitText={mode === "create" ? "Register Participant" : "Save Changes"}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export function EventTimeline() {
|
|||||||
<div className="text-[10px] font-mono opacity-70 mb-1">
|
<div className="text-[10px] font-mono opacity-70 mb-1">
|
||||||
{new Date(event.timestamp).toLocaleTimeString()}
|
{new Date(event.timestamp).toLocaleTimeString()}
|
||||||
</div>
|
</div>
|
||||||
{event.data && (
|
{!!event.data && (
|
||||||
<div className="bg-muted/50 p-1 rounded font-mono text-[9px] max-w-[200px] break-all">
|
<div className="bg-muted/50 p-1 rounded font-mono text-[9px] max-w-[200px] break-all">
|
||||||
{JSON.stringify(event.data as object).slice(0, 100)}
|
{JSON.stringify(event.data as object).slice(0, 100)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -114,8 +114,8 @@ export function PlaybackPlayer({ src }: PlaybackPlayerProps) {
|
|||||||
step={0.1}
|
step={0.1}
|
||||||
onValueChange={([val]) => {
|
onValueChange={([val]) => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
videoRef.current.currentTime = val;
|
videoRef.current.currentTime = val ?? 0;
|
||||||
setCurrentTime(val);
|
setCurrentTime(val ?? 0);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ export function TrialAnalysisView({ trial }: TrialAnalysisViewProps) {
|
|||||||
{event.eventType.replace(/_/g, " ")}
|
{event.eventType.replace(/_/g, " ")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{event.data && (
|
{!!event.data && (
|
||||||
<div className="text-[10px] text-muted-foreground bg-muted p-1.5 rounded border font-mono whitespace-pre-wrap break-all opacity-80 group-hover:opacity-100">
|
<div className="text-[10px] text-muted-foreground bg-muted p-1.5 rounded border font-mono whitespace-pre-wrap break-all opacity-80 group-hover:opacity-100">
|
||||||
{JSON.stringify(event.data as object, null, 1).replace(/"/g, '').replace(/[{}]/g, '').trim()}
|
{JSON.stringify(event.data as object, null, 1).replace(/"/g, '').replace(/[{}]/g, '').trim()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -255,10 +255,10 @@ export function RobotActionsPanel({
|
|||||||
// Look for ROS2 configuration in the action definition
|
// Look for ROS2 configuration in the action definition
|
||||||
const actionConfig = (actionDef as any).ros2
|
const actionConfig = (actionDef as any).ros2
|
||||||
? {
|
? {
|
||||||
topic: (actionDef as any).ros2.topic,
|
topic: (actionDef as any).ros2.topic,
|
||||||
messageType: (actionDef as any).ros2.messageType,
|
messageType: (actionDef as any).ros2.messageType,
|
||||||
payloadMapping: (actionDef as any).ros2.payloadMapping,
|
payloadMapping: (actionDef as any).ros2.payloadMapping,
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
await executeRosAction(
|
await executeRosAction(
|
||||||
@@ -635,7 +635,7 @@ export function RobotActionsPanel({
|
|||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{/* Parameters */}
|
{/* Parameters */}
|
||||||
{selectedAction.parameters &&
|
{selectedAction.parameters &&
|
||||||
selectedAction.parameters.length > 0 ? (
|
selectedAction.parameters.length > 0 ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Label className="text-base">Parameters</Label>
|
<Label className="text-base">Parameters</Label>
|
||||||
{selectedAction.parameters.map((param, index) =>
|
{selectedAction.parameters.map((param, index) =>
|
||||||
@@ -662,9 +662,9 @@ export function RobotActionsPanel({
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{selectedPluginData &&
|
{selectedPluginData &&
|
||||||
executingActions.has(
|
executingActions.has(
|
||||||
`${selectedPluginData.plugin.name}.${selectedAction.id}`,
|
`${selectedPluginData.plugin.name}.${selectedAction.id}`,
|
||||||
) ? (
|
) ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
Executing...
|
Executing...
|
||||||
@@ -900,50 +900,50 @@ export function RobotActionsPanel({
|
|||||||
{selectedPluginData &&
|
{selectedPluginData &&
|
||||||
Object.entries(
|
Object.entries(
|
||||||
groupActionsByCategory(
|
groupActionsByCategory(
|
||||||
(selectedPluginData.plugin
|
(selectedPluginData?.plugin
|
||||||
.actionDefinitions as RobotAction[]) ?? [],
|
.actionDefinitions as RobotAction[]) ?? [],
|
||||||
),
|
),
|
||||||
).map(([category, actions]) => {
|
).map(([category, actions]) => {
|
||||||
const CategoryIcon = getCategoryIcon(category);
|
const CategoryIcon = getCategoryIcon(category);
|
||||||
const isExpanded = expandedCategories.has(category);
|
const isExpanded = expandedCategories.has(category);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
key={category}
|
key={category}
|
||||||
open={isExpanded}
|
open={isExpanded}
|
||||||
onOpenChange={() => toggleCategory(category)}
|
onOpenChange={() => toggleCategory(category)}
|
||||||
>
|
>
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="w-full justify-start p-2"
|
|
||||||
>
|
|
||||||
<CategoryIcon className="mr-2 h-4 w-4" />
|
|
||||||
{category.charAt(0).toUpperCase() + category.slice(1)}
|
|
||||||
<Badge variant="secondary" className="ml-auto">
|
|
||||||
{actions.length}
|
|
||||||
</Badge>
|
|
||||||
</Button>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent className="ml-6 space-y-1">
|
|
||||||
{actions.map((action) => (
|
|
||||||
<Button
|
<Button
|
||||||
key={action.id}
|
variant="ghost"
|
||||||
variant={
|
className="w-full justify-start p-2"
|
||||||
selectedAction?.id === action.id
|
|
||||||
? "default"
|
|
||||||
: "ghost"
|
|
||||||
}
|
|
||||||
className="w-full justify-start text-sm"
|
|
||||||
onClick={() => setSelectedAction(action)}
|
|
||||||
>
|
>
|
||||||
{action.name}
|
<CategoryIcon className="mr-2 h-4 w-4" />
|
||||||
|
{category.charAt(0).toUpperCase() + category.slice(1)}
|
||||||
|
<Badge variant="secondary" className="ml-auto">
|
||||||
|
{actions.length}
|
||||||
|
</Badge>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
</CollapsibleTrigger>
|
||||||
</CollapsibleContent>
|
<CollapsibleContent className="ml-6 space-y-1">
|
||||||
</Collapsible>
|
{actions.map((action) => (
|
||||||
);
|
<Button
|
||||||
})}
|
key={action.id}
|
||||||
|
variant={
|
||||||
|
selectedAction?.id === action.id
|
||||||
|
? "default"
|
||||||
|
: "ghost"
|
||||||
|
}
|
||||||
|
className="w-full justify-start text-sm"
|
||||||
|
onClick={() => setSelectedAction(action)}
|
||||||
|
>
|
||||||
|
{action.name}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
@@ -962,7 +962,7 @@ export function RobotActionsPanel({
|
|||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{/* Parameters */}
|
{/* Parameters */}
|
||||||
{selectedAction?.parameters &&
|
{selectedAction?.parameters &&
|
||||||
(selectedAction.parameters?.length ?? 0) > 0 ? (
|
(selectedAction?.parameters?.length ?? 0) > 0 ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Label className="text-base">Parameters</Label>
|
<Label className="text-base">Parameters</Label>
|
||||||
{selectedAction?.parameters?.map((param, index) =>
|
{selectedAction?.parameters?.map((param, index) =>
|
||||||
@@ -990,10 +990,10 @@ export function RobotActionsPanel({
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{selectedPluginData &&
|
{selectedPluginData &&
|
||||||
selectedAction &&
|
selectedAction &&
|
||||||
executingActions.has(
|
executingActions.has(
|
||||||
`${selectedPluginData?.plugin.name}.${selectedAction?.id}`,
|
`${selectedPluginData?.plugin.name}.${selectedAction?.id}`,
|
||||||
) ? (
|
) ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
Executing...
|
Executing...
|
||||||
|
|||||||
@@ -149,10 +149,10 @@ export function convertActionToDatabase(
|
|||||||
pluginVersion: action.source.pluginVersion,
|
pluginVersion: action.source.pluginVersion,
|
||||||
robotId: action.source.robotId,
|
robotId: action.source.robotId,
|
||||||
baseActionId: action.source.baseActionId,
|
baseActionId: action.source.baseActionId,
|
||||||
transport: action.execution.transport,
|
transport: action.execution?.transport,
|
||||||
ros2: action.execution.ros2,
|
ros2: action.execution?.ros2,
|
||||||
rest: action.execution.rest,
|
rest: action.execution?.rest,
|
||||||
retryable: action.execution.retryable,
|
retryable: action.execution?.retryable,
|
||||||
parameterSchemaRaw: action.parameterSchemaRaw,
|
parameterSchemaRaw: action.parameterSchemaRaw,
|
||||||
sourceKind: action.source.kind,
|
sourceKind: action.source.kind,
|
||||||
category: action.category,
|
category: action.category,
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ export function parseVisualDesignSteps(raw: unknown): {
|
|||||||
if (!act.source.kind) {
|
if (!act.source.kind) {
|
||||||
issues.push(`Action "${act.id}" missing source.kind`);
|
issues.push(`Action "${act.id}" missing source.kind`);
|
||||||
}
|
}
|
||||||
if (!act.execution.transport) {
|
if (!act.execution?.transport) {
|
||||||
issues.push(`Action "${act.id}" missing execution transport`);
|
issues.push(`Action "${act.id}" missing execution transport`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -471,6 +471,7 @@ export const robotsRouter = createTRPCRouter({
|
|||||||
actionDefinitions: plugins.actionDefinitions,
|
actionDefinitions: plugins.actionDefinitions,
|
||||||
createdAt: plugins.createdAt,
|
createdAt: plugins.createdAt,
|
||||||
updatedAt: plugins.updatedAt,
|
updatedAt: plugins.updatedAt,
|
||||||
|
metadata: plugins.metadata,
|
||||||
},
|
},
|
||||||
installation: {
|
installation: {
|
||||||
id: studyPlugins.id,
|
id: studyPlugins.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user