chore: cleaned up front page
note: pdfs are not added yet.
This commit is contained in:
@@ -4,8 +4,7 @@
|
||||
import { Routes, Route, Link } from 'react-router'
|
||||
import './App.css'
|
||||
import Home from './pages/Home/Home.tsx'
|
||||
import Robot from './pages/Robot/Robot.tsx';
|
||||
import ConnectedRobots from './pages/ConnectedRobots/ConnectedRobots.tsx'
|
||||
import UserManual from './pages/Manuals/Manuals.tsx';
|
||||
import VisProg from "./pages/VisProgPage/VisProg.tsx";
|
||||
import {useState} from "react";
|
||||
import Logging from "./components/Logging/Logging.tsx";
|
||||
@@ -26,8 +25,7 @@ function App(){
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/editor" element={<VisProg />} />
|
||||
<Route path="/robot" element={<Robot />} />
|
||||
<Route path="/ConnectedRobots" element={<ConnectedRobots />} />
|
||||
<Route path="/user_manual" element={<UserManual />} />
|
||||
</Routes>
|
||||
</main>
|
||||
{showLogs && <Logging />}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// This program has been developed by students from the bachelor Computer Science at Utrecht
|
||||
// University within the Software Project course.
|
||||
// © Copyright Utrecht University (Department of Information and Computing Sciences)
|
||||
import { useEffect, useState } from 'react'
|
||||
import { API_BASE_URL } from '../../config/api.ts';
|
||||
|
||||
/**
|
||||
* Displays the current connection status of a robot in real time.
|
||||
*
|
||||
* Opens an SSE connection to the backend (`/robot/ping_stream`) that emits
|
||||
* simple boolean JSON messages (`true` or `false`). Updates automatically when
|
||||
* the robot connects or disconnects.
|
||||
*
|
||||
* @returns A React element showing the current robot connection status.
|
||||
*/
|
||||
export default function ConnectedRobots() {
|
||||
|
||||
/**
|
||||
* The current connection state:
|
||||
* - `true`: Robot is connected.
|
||||
* - `false`: Robot is not connected.
|
||||
* - `null`: Connection status is unknown (initial check in progress).
|
||||
*/
|
||||
const [connected, setConnected] = useState<boolean | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Open a Server-Sent Events (SSE) connection to receive live ping updates.
|
||||
// We're expecting a stream of data like that looks like this: `data = False` or `data = True`
|
||||
const eventSource = new EventSource(`${API_BASE_URL}/robot/ping_stream`);
|
||||
eventSource.onmessage = (event) => {
|
||||
|
||||
// Expecting messages in JSON format: `true` or `false`
|
||||
console.log("received message:", event.data);
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
try {
|
||||
setConnected(data)
|
||||
}
|
||||
catch {
|
||||
console.log("couldnt extract connected from incoming ping data")
|
||||
}
|
||||
|
||||
} catch {
|
||||
console.log("Ping message not in correct format:", event.data);
|
||||
}
|
||||
};
|
||||
|
||||
// Clean up the SSE connection when the component unmounts.
|
||||
return () => eventSource.close();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Is robot currently connected?</h1>
|
||||
<div>
|
||||
<h2>Robot is currently: {connected == null ? "checking..." : (connected ? "connected! 🟢" : "not connected... 🔴")} </h2>
|
||||
<h3>
|
||||
{connected == null ? "If checking continues, make sure CB is properly loaded with robot at least once." : ""}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -26,4 +26,52 @@ University within the Software Project course.
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
flex-direction: row; /* Horizontal layout looks more like a dashboard */
|
||||
gap: 1.5rem;
|
||||
justify-content: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.navCard {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem 2rem;
|
||||
min-width: 180px;
|
||||
background-color: #ffffff;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e0e0e0;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Hover effects */
|
||||
.navCard:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
border-color: #ffcd00; /* UU Yellow accent */
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Specific styling for the logo container */
|
||||
.logoPepperScaling {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.logoPepperScaling:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.logopepper {
|
||||
height: 120px;
|
||||
width: auto;
|
||||
}
|
||||
@@ -16,15 +16,20 @@ import styles from './Home.module.css'
|
||||
function Home() {
|
||||
return (
|
||||
<div className={`flex-col ${styles.gapXl}`}>
|
||||
<div className="logoPepperScaling">
|
||||
<a href="https://git.science.uu.nl/ics/sp/2025/n25b" target="_blank">
|
||||
<img src={pepperLogo} className="logopepper" alt="Pepper logo" />
|
||||
<div className={styles.logoPepperScaling}>
|
||||
<a href="https://git.science.uu.nl/ics/sp/2025/n25b" target="_blank" rel="noreferrer">
|
||||
<img src={pepperLogo} className={styles.logopepper} alt="Pepper logo" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className={styles.links}>
|
||||
<Link to={"/robot"}>Robot Interaction →</Link>
|
||||
<Link to={"/editor"}>Editor →</Link>
|
||||
<Link to={"/ConnectedRobots"}>Connected Robots →</Link>
|
||||
{/* Program Editor is now first */}
|
||||
<Link to="/editor" className={styles.navCard}>
|
||||
Program Editor
|
||||
</Link>
|
||||
<Link to="/user_manual" className={styles.navCard}>
|
||||
User and Developer Manual
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
81
src/pages/Manuals/Manuals.module.css
Normal file
81
src/pages/Manuals/Manuals.module.css
Normal file
@@ -0,0 +1,81 @@
|
||||
/* This program has been developed by students from the bachelor Computer Science at Utrecht
|
||||
University within the Software Project course.
|
||||
© Copyright Utrecht University (Department of Information and Computing Sciences)
|
||||
*/
|
||||
|
||||
.manualContainer {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
margin: 4rem auto;
|
||||
padding: 2rem;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.manualHeader h1 {
|
||||
font-size: 2.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.buttonStack {
|
||||
display: flex;
|
||||
flex-direction: column; /* Stacks the manual sections vertically */
|
||||
gap: 3rem;
|
||||
margin-top: 3rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.manualEntry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.manualEntry h3 {
|
||||
margin: 0;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.manualEntry p {
|
||||
color: #666;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.downloadBtn {
|
||||
display: inline-block;
|
||||
background-color: #ffffff; /* White background as requested */
|
||||
color: #000;
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 50px;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
width: 280px; /* Fixed width for uniform appearance */
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.2s ease, background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.downloadBtn:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
|
||||
background-color: #247284; /* Teal hover as requested */
|
||||
color: #ffffff; /* Text turns white on teal for better contrast */
|
||||
}
|
||||
|
||||
.dateBadge {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
padding: 0.4rem 1rem;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin-top: 4rem;
|
||||
border: 0;
|
||||
border-top: 1px solid #eee;
|
||||
width: 60%;
|
||||
}
|
||||
39
src/pages/Manuals/Manuals.tsx
Normal file
39
src/pages/Manuals/Manuals.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
// This program has been developed by students from the bachelor Computer Science at Utrecht
|
||||
// University within the Software Project course.
|
||||
// © Copyright Utrecht University (Department of Information and Computing Sciences)
|
||||
import styles from './Manuals.module.css';
|
||||
|
||||
export default function Manuals() {
|
||||
const userManualPath = "/UserManual.pdf";
|
||||
const developerManualPath = "/DeveloperManual.pdf";
|
||||
|
||||
return (
|
||||
<div className={styles.manualContainer}>
|
||||
<header className={styles.manualHeader}>
|
||||
<h1>Documentation & Manuals</h1>
|
||||
|
||||
<span className={styles.dateBadge}>Last Updated: January 2026</span>
|
||||
</header>
|
||||
|
||||
<div className={styles.buttonStack}>
|
||||
<div className={styles.manualEntry}>
|
||||
<h3>User Manual</h3>
|
||||
<p>Manual for Users of the Pepper+ Software </p>
|
||||
<a href={userManualPath} download="UserManual.pdf" className={styles.downloadBtn}>
|
||||
Download User Manual
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className={styles.manualEntry}>
|
||||
<h3>Developer Manual</h3>
|
||||
<p>Technical documentation for future developers.</p>
|
||||
<a href={developerManualPath} download="DeveloperManual.pdf" className={styles.downloadBtn}>
|
||||
Download Developer Manual
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className={styles.divider} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
// This program has been developed by students from the bachelor Computer Science at Utrecht
|
||||
// University within the Software Project course.
|
||||
// © Copyright Utrecht University (Department of Information and Computing Sciences)
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { API_BASE_URL } from '../../config/api.ts';
|
||||
|
||||
/**
|
||||
* Displays a live robot interaction panel with user input, conversation history,
|
||||
* and real-time updates from the robot backend via Server-Sent Events (SSE).
|
||||
*
|
||||
* @returns A React element rendering the interactive robot UI.
|
||||
*/
|
||||
export default function Robot() {
|
||||
/** The text message currently entered by the user. */
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
/** Whether the robot’s microphone or listening mode is currently active. */
|
||||
const [listening, setListening] = useState(false);
|
||||
/** The ongoing conversation history as a sequence of user/assistant messages. */
|
||||
const [conversation, setConversation] = useState<
|
||||
{"role": "user" | "assistant", "content": string}[]>([])
|
||||
/** Reference to the scrollable conversation container for auto-scrolling. */
|
||||
const conversationRef = useRef<HTMLDivElement | null>(null);
|
||||
/**
|
||||
* Index used to force refresh the SSE connection or clear conversation.
|
||||
* Incrementing this value triggers a reset of the live data stream.
|
||||
*/
|
||||
const [conversationIndex, setConversationIndex] = useState(0);
|
||||
|
||||
/**
|
||||
* Sends a message to the robot backend.
|
||||
*
|
||||
* Makes a POST request to `/message` with the user’s text.
|
||||
* The backend may respond with confirmation or error information.
|
||||
*/
|
||||
const sendMessage = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/message`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ message }),
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.error("Error sending message: ", error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Establishes a persistent Server-Sent Events (SSE) connection
|
||||
* to receive real-time updates from the robot backend.
|
||||
*
|
||||
* Handles three event types:
|
||||
* - `voice_active`: whether the robot is currently listening.
|
||||
* - `speech`: recognized user speech input.
|
||||
* - `llm_response`: the robot’s language model-generated reply.
|
||||
*
|
||||
* The connection resets whenever `conversationIndex` changes.
|
||||
*/
|
||||
useEffect(() => {
|
||||
const eventSource = new EventSource(`${API_BASE_URL}/sse`);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
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]);
|
||||
|
||||
/**
|
||||
* Automatically scrolls the conversation view to the bottom
|
||||
* whenever a new message is added.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!conversationRef || !conversationRef.current) return;
|
||||
conversationRef.current.scrollTop = conversationRef.current.scrollHeight;
|
||||
}, [conversation]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Robot interaction</h1>
|
||||
<h2>Force robot speech</h2>
|
||||
<div className={"flex-row gap-md justify-center"}>
|
||||
<input
|
||||
type="text"
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && sendMessage().then(() => setMessage(""))}
|
||||
placeholder="Enter a message"
|
||||
/>
|
||||
<button onClick={sendMessage}>Speak</button>
|
||||
</div>
|
||||
<div className={"flex-col gap-lg align-center"}>
|
||||
<h2>Conversation</h2>
|
||||
<p>Listening {listening ? "🟢" : "🔴"}</p>
|
||||
<div style={{ maxHeight: "200px", maxWidth: "600px", overflowY: "auto"}} ref={conversationRef}>
|
||||
{conversation.map((item, i) => (
|
||||
<p key={i}
|
||||
style={{
|
||||
backgroundColor: item["role"] == "user"
|
||||
? "color-mix(in oklab, canvas, blue 20%)"
|
||||
: "color-mix(in oklab, canvas, gray 20%)",
|
||||
whiteSpace: "pre-line",
|
||||
}}
|
||||
className={"round-md padding-md"}
|
||||
>{item["content"]}</p>
|
||||
))}
|
||||
</div>
|
||||
<div className={"flex-row gap-md justify-center"}>
|
||||
<button onClick={() => {
|
||||
setConversationIndex((conversationIndex) => conversationIndex + 1)
|
||||
setConversation([])
|
||||
}}>Reset</button>
|
||||
<button onClick={() => {
|
||||
setConversationIndex((conversationIndex) => conversationIndex == -1 ? 0 : -1)
|
||||
setConversation([])
|
||||
}}>{conversationIndex == -1 ? "Start" : "Stop"}</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user