From 10522b71c368301a6b8949def72214dfbb6b7b4b Mon Sep 17 00:00:00 2001
From: Twirre Meulenbelt <43213592+TwirreM@users.noreply.github.com>
Date: Wed, 1 Oct 2025 22:56:03 +0200
Subject: [PATCH] chore: combined some branches, improved style
This demo branch contains code from multiple different branches. DO NOT MERGE this branch because it looks like I'm the author of all these changes.
---
package-lock.json | 241 +++++++++++-
package.json | 2 +
src/App.css | 109 ++++--
src/App.tsx | 20 +-
src/assets/data.ts | 361 ++++++++++++++++++
src/pages/Home/Home.module.css | 6 +
src/pages/Home/Home.tsx | 45 +--
src/pages/Logging/Logging.module.css | 17 +
src/pages/Logging/Logging.tsx | 78 ++++
src/pages/ServerComms/ServerComms.css | 0
src/pages/ServerComms/ServerComms.tsx | 86 +++--
src/pages/VisProgPage/VisProg.tsx | 11 +
src/visualProgrammingUI/VisProgUI.css | 7 +
src/visualProgrammingUI/VisProgUI.tsx | 132 +++++++
.../components/DragDropSidebar.tsx | 141 +++++++
.../components/NodeDefinitions.tsx | 111 ++++++
16 files changed, 1251 insertions(+), 116 deletions(-)
create mode 100644 src/assets/data.ts
create mode 100644 src/pages/Logging/Logging.module.css
create mode 100644 src/pages/Logging/Logging.tsx
delete mode 100644 src/pages/ServerComms/ServerComms.css
create mode 100644 src/pages/VisProgPage/VisProg.tsx
create mode 100644 src/visualProgrammingUI/VisProgUI.css
create mode 100644 src/visualProgrammingUI/VisProgUI.tsx
create mode 100644 src/visualProgrammingUI/components/DragDropSidebar.tsx
create mode 100644 src/visualProgrammingUI/components/NodeDefinitions.tsx
diff --git a/package-lock.json b/package-lock.json
index 0b92f5c..db54d49 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,8 @@
"name": "pepperplus-ui",
"version": "0.0.0",
"dependencies": {
+ "@neodrag/react": "^2.3.1",
+ "@xyflow/react": "^12.8.6",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router": "^7.9.3"
@@ -1006,6 +1008,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@neodrag/react": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@neodrag/react/-/react-2.3.1.tgz",
+ "integrity": "sha512-mOVefo3mFmaVLs9PB5F5wMXnnclG81qjOaPHyf8YZUnw/Ciz0pAqyJDwDJk0nPTIK5I2x1JdjXSchGNdCxZNRQ==",
+ "license": "MIT"
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1404,6 +1412,55 @@
"@babel/types": "^7.28.2"
}
},
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1422,7 +1479,7 @@
"version": "19.1.13",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
@@ -1730,6 +1787,38 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
+ "node_modules/@xyflow/react": {
+ "version": "12.8.6",
+ "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.6.tgz",
+ "integrity": "sha512-SksAm2m4ySupjChphMmzvm55djtgMDPr+eovPDdTnyGvShf73cvydfoBfWDFllooIQ4IaiUL5yfxHRwU0c37EA==",
+ "license": "MIT",
+ "dependencies": {
+ "@xyflow/system": "0.0.70",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.0"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@xyflow/system": {
+ "version": "0.0.70",
+ "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.70.tgz",
+ "integrity": "sha512-PpC//u9zxdjj0tfTSmZrg3+sRbTz6kop/Amky44U2Dl51sxzDTIUfXMwETOYpmr2dqICWXBIJwXL2a9QWtX2XA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-drag": "^3.0.7",
+ "@types/d3-interpolate": "^3.0.4",
+ "@types/d3-selection": "^3.0.10",
+ "@types/d3-transition": "^3.0.8",
+ "@types/d3-zoom": "^3.0.8",
+ "d3-drag": "^3.0.0",
+ "d3-interpolate": "^3.0.1",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0"
+ }
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -1916,6 +2005,12 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/classcat": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
+ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
+ "license": "MIT"
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1978,9 +2073,114 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -3287,6 +3487,15 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/vite": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
@@ -3438,6 +3647,34 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/package.json b/package.json
index 6070bd8..252cf4f 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
+ "@neodrag/react": "^2.3.1",
+ "@xyflow/react": "^12.8.6",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router": "^7.9.3"
diff --git a/src/App.css b/src/App.css
index dcb46cf..0c641e7 100644
--- a/src/App.css
+++ b/src/App.css
@@ -5,18 +5,6 @@
text-align: center;
}
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
.logopepper {
@@ -32,27 +20,21 @@
filter: drop-shadow(0 0 10em #4eff14aa);
}
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-
@keyframes logo-pepper-spin {
- from {
- transform: rotate(-20deg);
+ 0% {
+ transform: rotate(0);
}
- to {
+ 25% {
transform: rotate(20deg);
}
+ 75% {
+ transform: rotate(-20deg);
+ }
+ 100% {
+ transform: rotate(0);
+ }
}
-
-
@keyframes logo-pepper-scale {
from {
transform: scale(1,1);
@@ -63,19 +45,13 @@
}
@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
+ .logopepper:hover {
+ animation: logo-pepper-spin infinite 1s linear;
}
}
@media (prefers-reduced-motion: no-preference) {
- .logopepper {
- animation: logo-pepper-spin infinite 1s linear alternate;
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .logoPepperScaling {
+ .logoPepperScaling:hover {
animation: logo-pepper-scale infinite 1s linear alternate;
}
}
@@ -113,3 +89,64 @@ button.movePage.right{
button.movePage:hover{
background-color: rgb(0, 176, 176);
}
+
+
+
+
+
+.flex-row {
+ display: flex;
+ flex-direction: row;
+}
+.flex-col {
+ display: flex;
+ flex-direction: column;
+}
+
+.flex-1 {
+ flex: 1;
+}
+
+.flex-wrap {
+ flex-wrap: wrap;
+}
+
+.align-center {
+ align-items: center;
+}
+.justify-center {
+ justify-content: center;
+}
+.justify-between {
+ justify-content: space-between;
+}
+
+.gap-sm {
+ gap: .25rem;
+}
+.gap-md {
+ gap: .5rem;
+}
+.gap-lg {
+ gap: 1rem;
+}
+
+.padding-sm {
+ padding: .25rem;
+}
+.padding-md {
+ padding: .5rem;
+}
+.padding-lg {
+ padding: 1rem;
+}
+
+.round-sm {
+ border-radius: .25rem;
+}
+.round-md {
+ border-radius: .5rem;
+}
+.round-lg {
+ border-radius: 1rem;
+}
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 757e769..0833bdb 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,17 +1,25 @@
-import { Routes, Route } from 'react-router'
+import { Routes, Route, Link } from 'react-router'
import './App.css'
import TemplatePage from './pages/TemplatePage/Template.tsx'
import Home from './pages/Home/Home.tsx'
import ServerComms from './pages/ServerComms/ServerComms.tsx'
+import Logging from './pages/Logging/Logging.tsx'
+import VisProg from "./pages/VisProgPage/VisProg.tsx";
function App(){
return (
-
- } />
- } />
- } />
-
+
+ {/* Should not use inline styles like this */}
+ Home
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
)
}
diff --git a/src/assets/data.ts b/src/assets/data.ts
new file mode 100644
index 0000000..c1eacfb
--- /dev/null
+++ b/src/assets/data.ts
@@ -0,0 +1,361 @@
+export const DATA: LogEntry[] = [
+ {
+ id: "1",
+ timestamp: "2025-10-01T12:00:00Z",
+ level: "info",
+ msg: "User said: Hello, Pepper!",
+ type: "speech",
+ },
+ {
+ id: "2",
+ timestamp: "2025-10-01T12:00:05Z",
+ level: "debug",
+ msg: "Proximity sensor value: 0.85",
+ type: "sensor",
+ },
+ {
+ id: "3",
+ timestamp: "2025-10-01T12:00:10Z",
+ level: "warn",
+ msg: "Battery level low: 15%",
+ type: "system",
+ },
+ {
+ id: "4",
+ timestamp: "2025-10-01T12:00:15Z",
+ level: "info",
+ msg: "User requested weather update.",
+ type: "speech",
+ },
+ {
+ id: "5",
+ timestamp: "2025-10-01T12:00:20Z",
+ level: "debug",
+ msg: "Microphone activated.",
+ type: "system",
+ },
+ {
+ id: "6",
+ timestamp: "2025-10-01T12:00:25Z",
+ level: "warn",
+ msg: "Obstacle detected in front.",
+ type: "sensor",
+ },
+ {
+ id: "7",
+ timestamp: "2025-10-01T12:00:30Z",
+ level: "info",
+ msg: "User said: Thank you!",
+ type: "speech",
+ },
+ {
+ id: "8",
+ timestamp: "2025-10-01T12:00:35Z",
+ level: "debug",
+ msg: "Network latency: 120ms",
+ type: "system",
+ },
+ {
+ id: "9",
+ timestamp: "2025-10-01T12:00:40Z",
+ level: "warn",
+ msg: "High CPU usage detected.",
+ type: "system",
+ },
+ {
+ id: "10",
+ timestamp: "2025-10-01T12:00:45Z",
+ level: "info",
+ msg: "User started a new session.",
+ type: "system",
+ },
+ {
+ id: "11",
+ timestamp: "2025-10-01T12:01:00Z",
+ level: "info",
+ msg: "User asked: What's the weather?",
+ type: "speech",
+ },
+ {
+ id: "12",
+ timestamp: "2025-10-01T12:01:05Z",
+ level: "debug",
+ msg: "Camera initialized.",
+ type: "system",
+ },
+ {
+ id: "13",
+ timestamp: "2025-10-01T12:01:10Z",
+ level: "warn",
+ msg: "Temperature sensor disconnected.",
+ type: "sensor",
+ },
+ {
+ id: "14",
+ timestamp: "2025-10-01T12:01:15Z",
+ level: "info",
+ msg: "User said: Play some music.",
+ type: "speech",
+ },
+ {
+ id: "15",
+ timestamp: "2025-10-01T12:01:20Z",
+ level: "debug",
+ msg: "Audio output device selected: Speaker.",
+ type: "system",
+ },
+ {
+ id: "16",
+ timestamp: "2025-10-01T12:01:25Z",
+ level: "warn",
+ msg: "Low light detected in room.",
+ type: "sensor",
+ },
+ {
+ id: "17",
+ timestamp: "2025-10-01T12:01:30Z",
+ level: "info",
+ msg: "User said: Turn on the lights.",
+ type: "speech",
+ },
+ {
+ id: "18",
+ timestamp: "2025-10-01T12:01:35Z",
+ level: "debug",
+ msg: "Light control signal sent.",
+ type: "system",
+ },
+ {
+ id: "19",
+ timestamp: "2025-10-01T12:01:40Z",
+ level: "warn",
+ msg: "Light bulb not responding.",
+ type: "system",
+ },
+ {
+ id: "20",
+ timestamp: "2025-10-01T12:01:45Z",
+ level: "info",
+ msg: "User said: Good night.",
+ type: "speech",
+ },
+ {
+ id: "21",
+ timestamp: "2025-10-01T12:02:00Z",
+ level: "info",
+ msg: "User asked: What's the time?",
+ type: "speech",
+ },
+ {
+ id: "22",
+ timestamp: "2025-10-01T12:02:05Z",
+ level: "debug",
+ msg: "Time module loaded.",
+ type: "system",
+ },
+ {
+ id: "23",
+ timestamp: "2025-10-01T12:02:10Z",
+ level: "warn",
+ msg: "WiFi signal weak.",
+ type: "system",
+ },
+ {
+ id: "24",
+ timestamp: "2025-10-01T12:02:15Z",
+ level: "info",
+ msg: "User said: Set an alarm for 7 AM.",
+ type: "speech",
+ },
+ {
+ id: "25",
+ timestamp: "2025-10-01T12:02:20Z",
+ level: "debug",
+ msg: "Alarm scheduled for 7:00 AM.",
+ type: "system",
+ },
+ {
+ id: "26",
+ timestamp: "2025-10-01T12:02:25Z",
+ level: "warn",
+ msg: "Alarm module not responding.",
+ type: "system",
+ },
+ {
+ id: "27",
+ timestamp: "2025-10-01T12:02:30Z",
+ level: "info",
+ msg: "User said: Cancel the alarm.",
+ type: "speech",
+ },
+ {
+ id: "28",
+ timestamp: "2025-10-01T12:02:35Z",
+ level: "debug",
+ msg: "Alarm cancellation requested.",
+ type: "system",
+ },
+ {
+ id: "29",
+ timestamp: "2025-10-01T12:02:40Z",
+ level: "warn",
+ msg: "Alarm cancellation failed.",
+ type: "system",
+ },
+ {
+ id: "30",
+ timestamp: "2025-10-01T12:02:45Z",
+ level: "info",
+ msg: "User said: Open the window.",
+ type: "speech",
+ },
+ {
+ id: "31",
+ timestamp: "2025-10-01T12:03:00Z",
+ level: "info",
+ msg: "User asked: What's on my calendar?",
+ type: "speech",
+ },
+ {
+ id: "32",
+ timestamp: "2025-10-01T12:03:05Z",
+ level: "debug",
+ msg: "Calendar module loaded.",
+ type: "system",
+ },
+ {
+ id: "33",
+ timestamp: "2025-10-01T12:03:10Z",
+ level: "warn",
+ msg: "Calendar sync failed.",
+ type: "system",
+ },
+ {
+ id: "34",
+ timestamp: "2025-10-01T12:03:15Z",
+ level: "info",
+ msg: "User said: Remind me to call John.",
+ type: "speech",
+ },
+ {
+ id: "35",
+ timestamp: "2025-10-01T12:03:20Z",
+ level: "debug",
+ msg: "Reminder set for John.",
+ type: "system",
+ },
+ {
+ id: "36",
+ timestamp: "2025-10-01T12:03:25Z",
+ level: "warn",
+ msg: "Reminder module not available.",
+ type: "system",
+ },
+ {
+ id: "37",
+ timestamp: "2025-10-01T12:03:30Z",
+ level: "info",
+ msg: "User said: What's the news?",
+ type: "speech",
+ },
+ {
+ id: "38",
+ timestamp: "2025-10-01T12:03:35Z",
+ level: "debug",
+ msg: "News API request sent.",
+ type: "system",
+ },
+ {
+ id: "39",
+ timestamp: "2025-10-01T12:03:40Z",
+ level: "warn",
+ msg: "News API rate limit reached.",
+ type: "system",
+ },
+ {
+ id: "40",
+ timestamp: "2025-10-01T12:03:45Z",
+ level: "info",
+ msg: "User said: Tell me a joke.",
+ type: "speech",
+ },
+ {
+ id: "41",
+ timestamp: "2025-10-01T12:04:00Z",
+ level: "info",
+ msg: "User asked: What's the temperature?",
+ type: "speech",
+ },
+ {
+ id: "42",
+ timestamp: "2025-10-01T12:04:05Z",
+ level: "debug",
+ msg: "Temperature sensor reading: 22°C.",
+ type: "sensor",
+ },
+ {
+ id: "43",
+ timestamp: "2025-10-01T12:04:10Z",
+ level: "warn",
+ msg: "Temperature sensor calibration needed.",
+ type: "sensor",
+ },
+ {
+ id: "44",
+ timestamp: "2025-10-01T12:04:15Z",
+ level: "info",
+ msg: "User said: Start cleaning.",
+ type: "speech",
+ },
+ {
+ id: "45",
+ timestamp: "2025-10-01T12:04:20Z",
+ level: "debug",
+ msg: "Vacuum motor started.",
+ type: "system",
+ },
+ {
+ id: "46",
+ timestamp: "2025-10-01T12:04:25Z",
+ level: "warn",
+ msg: "Vacuum bin full.",
+ type: "system",
+ },
+ {
+ id: "47",
+ timestamp: "2025-10-01T12:04:30Z",
+ level: "info",
+ msg: "User said: Stop cleaning.",
+ type: "speech",
+ },
+ {
+ id: "48",
+ timestamp: "2025-10-01T12:04:35Z",
+ level: "debug",
+ msg: "Vacuum motor stopped.",
+ type: "system",
+ },
+ {
+ id: "49",
+ timestamp: "2025-10-01T12:04:40Z",
+ level: "warn",
+ msg: "Obstacle detected during cleaning.",
+ type: "sensor",
+ },
+ {
+ id: "50",
+ timestamp: "2025-10-01T12:04:45Z",
+ level: "info",
+ msg: "User said: Goodbye!",
+ type: "speech",
+ },
+];
+
+interface LogEntry {
+ id: string;
+ type?: string;
+ timestamp: string;
+ level: "info" | "debug" | "warn";
+ msg?: string;
+}
+
diff --git a/src/pages/Home/Home.module.css b/src/pages/Home/Home.module.css
index aed0f27..d609fe9 100644
--- a/src/pages/Home/Home.module.css
+++ b/src/pages/Home/Home.module.css
@@ -4,3 +4,9 @@
.card {
padding: 2em;
}
+
+.links {
+ display: flex;
+ flex-direction: column;
+ gap: 1em;
+}
\ No newline at end of file
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx
index 8767db3..582357b 100644
--- a/src/pages/Home/Home.tsx
+++ b/src/pages/Home/Home.tsx
@@ -1,47 +1,20 @@
-//import { useState } from 'react'
import { Link } from 'react-router'
-import reactLogo from '../../assets/react.svg'
-import viteLogo from '../../assets/vite.svg'
import pepperLogo from '../../assets/pepper_transp2_small.svg'
-import style from './Home.module.css'
-import Counter from '../../components/components.tsx'
+import styles from './Home.module.css'
function Home() {
-
-
-
return (
<>
-
-
-
-
-
-
-
-
+
- Vite + React
-
-
-
-
-
-
-
- Edit src/App.tsx and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
+
+ Robot interaction →
+ Node editor →
+ Logs →
+
>
)
}
diff --git a/src/pages/Logging/Logging.module.css b/src/pages/Logging/Logging.module.css
new file mode 100644
index 0000000..52191c3
--- /dev/null
+++ b/src/pages/Logging/Logging.module.css
@@ -0,0 +1,17 @@
+.DivToScroll{
+ background-color: color-mix(in srgb, canvas, #000 5%);
+ border: 1px solid color-mix(in srgb, canvas, #000 15%);
+ border-radius: 4px 0 4px 0;
+ color: #3B3C3E;
+ font-size: 12px;
+ font-weight: bold;
+ left: -1px;
+ padding: 10px 7px 5px;
+}
+
+.DivWithScroll{
+ height:50vh;
+ width:100vh;
+ overflow:scroll;
+ overflow-x:hidden;
+}
diff --git a/src/pages/Logging/Logging.tsx b/src/pages/Logging/Logging.tsx
new file mode 100644
index 0000000..31945c1
--- /dev/null
+++ b/src/pages/Logging/Logging.tsx
@@ -0,0 +1,78 @@
+import { useState } from 'react';
+import { DATA } from "../../assets/data";
+import styles from './Logging.module.css';
+
+
+// const dataType = DATA as { id: string; level: "debug"|"info"|"warn"|"error"; msg: string; timestamp?: string };
+type Level = "debug" | "info" | "warn" | "error";
+
+// make optional fields optional
+type LogEntry = {
+ id: string;
+ level: Level;
+ timestamp?: string;
+ msg?: string;
+ type?: "speech" | "sensor" | "system" | string;
+
+};
+
+function getLevelColor(level: Level) {
+ switch (level) {
+ case "debug":
+ return "gray";
+ case "info":
+ return "blue";
+ case "warn":
+ return "red";
+ case "error":
+ return "red";
+ default:
+ return "black";
+ }
+}
+
+function Logging() {
+ const [logs, setLogs] = useState([]);
+
+ const logDiv = (
+
+
+ {logs.map((log) => (
+
+
+ [{log.timestamp}]
+
+
+ {log.msg ? log.msg : "No message"}
+
+
+ ({log.level})
+
+
+ ))}
+
+
+ )
+ return (
+ <>
+ Log Screen
+ { logDiv }
+
+
+
+
+ >
+ )
+}
+
+export default Logging
diff --git a/src/pages/ServerComms/ServerComms.css b/src/pages/ServerComms/ServerComms.css
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pages/ServerComms/ServerComms.tsx b/src/pages/ServerComms/ServerComms.tsx
index c16ec81..6d15524 100644
--- a/src/pages/ServerComms/ServerComms.tsx
+++ b/src/pages/ServerComms/ServerComms.tsx
@@ -1,15 +1,12 @@
-import { useState, useEffect } from 'react'
-import { Link } from 'react-router'
-//import Counter from '../../components/components.tsx'
+import { useState, useEffect, useRef } from 'react'
-
-//this is your css file where you can style your buttons and such
-//you can still use css parts from App.css, but also overwrite them
-
-function ServerComms() {
+export default function ServerComms() {
const [message, setMessage] = useState('');
- const [sseMessage, setSseMessage] = useState('');
- const [spoken, setSpoken] = useState("");
+
+ const [listening, setListening] = useState(false);
+ const [conversation, setConversation] = useState<{"role": "user" | "assistant", "content": string}[]>([])
+ const conversationRef = useRef(null);
+ const [conversationIndex, setConversationIndex] = useState(0);
const sendMessage = async () => {
try {
@@ -31,22 +28,31 @@ function ServerComms() {
const eventSource = new EventSource("http://localhost:8000/sse");
eventSource.onmessage = (event) => {
- setSseMessage(event.data);
-
try {
const data = JSON.parse(event.data);
- if (data.speech) setSpoken(data.speech);
- } catch {}
+ if ("voice_active" in data) setListening(data.voice_active);
+ if ("speech" in data) setConversation(conversation => [...conversation, {"role": "user", "content": data.speech}]);
+ if ("llm_response" in data) setConversation(conversation => [...conversation, {"role": "assistant", "content": data.llm_response}]);
+ } catch {
+ console.log("Unparsable SSE message:", event.data);
+ }
};
return () => {
eventSource.close();
};
- }, []);
+ }, [conversationIndex]);
+
+ useEffect(() => {
+ if (!conversationRef || !conversationRef.current) return;
+ conversationRef.current.scrollTop = conversationRef.current.scrollHeight;
+ }, [conversation]);
return (
-
-
+
+
Robot interaction
+
Force robot speech
+
e.key === "Enter" && sendMessage().then(() => setMessage(""))}
placeholder="Enter a message"
/>
-
+
-
-
Message from Server (SSE):
-
{sseMessage}
-
-
-
Spoken text (SSE):
-
{spoken}
-
-
-
{/* here you link to the homepage, in App.tsx you can link new pages */}
-
-
+
+
Conversation
+
Listening {listening ? "🟢" : "🔴"}
+
+ {conversation.map((item) => (
+
{item["content"]}
+ ))}
+
+
+
+
+
-
-
);
}
-
-export default ServerComms
\ No newline at end of file
diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx
new file mode 100644
index 0000000..ec0055a
--- /dev/null
+++ b/src/pages/VisProgPage/VisProg.tsx
@@ -0,0 +1,11 @@
+import VisProgUI from "../../visualProgrammingUI/VisProgUI.tsx";
+
+function VisProgPage() {
+ return (
+ <>
+
+ >
+ )
+}
+
+export default VisProgPage
\ No newline at end of file
diff --git a/src/visualProgrammingUI/VisProgUI.css b/src/visualProgrammingUI/VisProgUI.css
new file mode 100644
index 0000000..8d82d09
--- /dev/null
+++ b/src/visualProgrammingUI/VisProgUI.css
@@ -0,0 +1,7 @@
+.default-node {
+ padding: 10px 20px;
+ background-color: canvas;
+ outline-style: solid;
+ border-radius: 5pt;
+ outline-width: 1pt;
+}
\ No newline at end of file
diff --git a/src/visualProgrammingUI/VisProgUI.tsx b/src/visualProgrammingUI/VisProgUI.tsx
new file mode 100644
index 0000000..a2b1e9c
--- /dev/null
+++ b/src/visualProgrammingUI/VisProgUI.tsx
@@ -0,0 +1,132 @@
+import './VisProgUI.css'
+
+import {
+ useCallback,
+ useRef
+} from 'react';
+import {
+ Background,
+ Controls,
+ ReactFlow,
+ ReactFlowProvider,
+ useNodesState,
+ useEdgesState,
+ reconnectEdge,
+ addEdge,
+ MarkerType,
+ type Edge,
+ type Connection,
+} from '@xyflow/react';
+import '@xyflow/react/dist/style.css';
+import {
+ StartNode,
+ EndNode,
+ PhaseNode,
+ NormNode
+} from "./components/NodeDefinitions.tsx";
+
+import { Sidebar } from './components/DragDropSidebar.tsx';
+
+const nodeTypes = {
+ start: StartNode,
+ end: EndNode,
+ phase: PhaseNode,
+ norm: NormNode
+};
+
+const initialNodes = [
+ {
+ id: 'start',
+ type: 'start',
+ position: {x: 0, y: 0},
+ data: {label: 'start'}
+ },
+ {
+ id: 'genericPhase',
+ type: 'phase',
+ position: {x: 0, y: 150},
+ data: {label: 'Generic Phase', number: 1},
+ },
+ {
+ id: 'end',
+ type: 'end',
+ position: {x: 0, y: 300},
+ data: {label: 'End'}
+ }
+];
+const initialEdges = [{id: 'start-end', source: 'start', target: 'end'}];
+
+const defaultEdgeOptions = {
+ type: 'floating',
+ markerEnd: {
+ type: MarkerType.ArrowClosed,
+ color: '#505050',
+ },
+};
+
+const VisProgUI = ()=> {
+ const edgeReconnectSuccessful = useRef(true);
+ const [nodes, , onNodesChange] = useNodesState(initialNodes);
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
+
+ const onConnect = useCallback(
+ (params: Edge | Connection) => setEdges((els) => addEdge(params, els)),
+ [setEdges],
+ );
+
+ const onReconnectStart = useCallback(() => {
+ edgeReconnectSuccessful.current = false;
+ }, []);
+
+ const onReconnect = useCallback((oldEdge: Edge, newConnection: Connection) => {
+ edgeReconnectSuccessful.current = true;
+ setEdges((els) => reconnectEdge(oldEdge, newConnection, els));
+ }, [setEdges]);
+
+ const onReconnectEnd = useCallback((_: unknown, edge: { id: string; }) => {
+ if (!edgeReconnectSuccessful.current) {
+ setEdges((eds) => eds.filter((e) => e.id !== edge.id));
+ }
+
+ edgeReconnectSuccessful.current = true;
+ }, [setEdges]);
+
+ return (
+
+
+ );
+};
+
+function VisualProgrammingUI(){
+ return (
+
+
+
+ );
+}
+
+export default VisualProgrammingUI;
\ No newline at end of file
diff --git a/src/visualProgrammingUI/components/DragDropSidebar.tsx b/src/visualProgrammingUI/components/DragDropSidebar.tsx
new file mode 100644
index 0000000..b3926d9
--- /dev/null
+++ b/src/visualProgrammingUI/components/DragDropSidebar.tsx
@@ -0,0 +1,141 @@
+import { useDraggable } from '@neodrag/react';
+import {
+ useReactFlow,
+ type XYPosition
+} from '@xyflow/react';
+import {
+ type ReactNode,
+ useCallback,
+ useRef,
+ useState
+} from 'react';
+
+
+// improve later to create better automatic IDs
+let id = 0;
+const getId = () => `dndnode_${id++}`;
+
+
+interface DraggableNodeProps {
+ className?: string;
+ children: ReactNode;
+ nodeType: string;
+ onDrop: (nodeType: string, position: XYPosition) => void;
+}
+
+function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeProps) {
+ const draggableRef = useRef
(null);
+ const [position, setPosition] = useState({ x: 0, y: 0 });
+
+
+ // @ts-ignore
+ useDraggable(draggableRef, {
+ position: position,
+ onDrag: ({ offsetX, offsetY }) => {
+ // Calculate position relative to the viewport
+ setPosition({
+ x: offsetX,
+ y: offsetY,
+ });
+ },
+ onDragEnd: ({ event }) => {
+ setPosition({ x: 0, y: 0 });
+ onDrop(nodeType, {
+ x: event.clientX,
+ y: event.clientY,
+ });
+ },
+ });
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function Sidebar() {
+ const { setNodes, screenToFlowPosition } = useReactFlow();
+
+ const handleNodeDrop = useCallback(
+ (nodeType: string, screenPosition: XYPosition) => {
+ const flow = document.querySelector('.react-flow');
+ const flowRect = flow?.getBoundingClientRect();
+ const isInFlow =
+ flowRect &&
+ screenPosition.x >= flowRect.left &&
+ screenPosition.x <= flowRect.right &&
+ screenPosition.y >= flowRect.top &&
+ screenPosition.y <= flowRect.bottom;
+
+ // Create a new node and add it to the flow
+ if (isInFlow) {
+ const position = screenToFlowPosition(screenPosition);
+
+ const newNode = () => {
+ switch (nodeType) {
+ case "phase":
+ return {
+ id: getId(),
+ type: nodeType,
+ position,
+ data: {label: `"new"`, number: (-1)},
+ };
+ case "start":
+ return {
+ id: getId(),
+ type: nodeType,
+ position,
+ data: {label: `new start node`},
+ };
+ case "end":
+ return {
+ id: getId(),
+ type: nodeType,
+ position,
+ data: {label: `new end node`},
+ };
+ case "norm":
+ return {
+ id: getId(),
+ type: nodeType,
+ position,
+ data: {label: `new norm node`},
+ };
+ default: {
+ return {
+ id: getId(),
+ type: nodeType,
+ position,
+ data: {label: `new default node`},
+ };
+ }
+ }
+ }
+
+ setNodes((nds) => nds.concat(newNode()));
+ }
+ },
+ [setNodes, screenToFlowPosition],
+ );
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/visualProgrammingUI/components/NodeDefinitions.tsx b/src/visualProgrammingUI/components/NodeDefinitions.tsx
new file mode 100644
index 0000000..b4547b2
--- /dev/null
+++ b/src/visualProgrammingUI/components/NodeDefinitions.tsx
@@ -0,0 +1,111 @@
+import {Handle, NodeToolbar, Position, useReactFlow} from '@xyflow/react';
+import '@xyflow/react/dist/style.css';
+import '../VisProgUI.css';
+
+// Datatypes for NodeTypes
+
+type defaultNodeData = {
+ label: string;
+};
+
+type startNodeData = defaultNodeData;
+type endNodeData = defaultNodeData;
+type normNodeData = defaultNodeData;
+type phaseNodeData = defaultNodeData & {
+ number: number;
+};
+
+export type nodeData = defaultNodeData | startNodeData | phaseNodeData | endNodeData;
+
+// Node Toolbar definition
+
+type ToolbarProps= {
+ nodeId: string;
+};
+
+export function Toolbar({nodeId}:ToolbarProps) {
+ const { setNodes, setEdges } = useReactFlow();
+
+ const handleDelete = () => {
+ setNodes((nds) => nds.filter((n) => n.id !== nodeId));
+ setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId));
+ };
+ return (
+
+
+ );
+}
+
+
+// Definitions of Nodes
+
+type StartNodeProps = {
+ id: string;
+ data: startNodeData;
+};
+
+export const StartNode= ({ id, data }: StartNodeProps) => {
+ return (
+ <>
+
+
+
data test {data.label}
+
+
+ >
+ );
+};
+
+type EndNodeProps = {
+ id: string;
+ data: endNodeData;
+};
+
+export const EndNode= ({ id, data }: EndNodeProps) => {
+ return (
+ <>
+
+
+ >
+ );
+};
+
+
+type PhaseNodeProps = {
+ id: string;
+ data: phaseNodeData;
+};
+
+export const PhaseNode= ({ id, data }: PhaseNodeProps) => {
+ return (
+ <>
+
+
+
phase {data.number} {data.label}
+
+
+
+
+ >
+ );
+};
+
+type NormNodeProps = {
+ id: string;
+ data: normNodeData;
+};
+
+export const NormNode= ({ id, data }: NormNodeProps) => {
+ return (
+ <>
+
+
+ >
+ );
+};
\ No newline at end of file