diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 391d279..ed801d8 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -8,11 +8,11 @@ if echo "$branch" | grep -Eq "(dev|main)"; then fi # allowed pattern -if echo "$branch" | grep -Eq "^(feat|fix|refactor|perf|style|test|docs|build|chore|revert)\/\w+-\w+-\w+"; then +if echo "$branch" | grep -Eq "^(feat|fix|refactor|perf|style|test|docs|build|chore|revert)\/\w+(-\w+){0,5}$"; then echo "✅ Branch name valid: $branch" exit 0 else echo "❌ Invalid branch name: $branch" - echo "Branch must be named / (must have 2 stipes - -)" + echo "Branch must be named / (must have one to six words separated by a dash)" exit 1 fi \ No newline at end of file diff --git a/src/App.css b/src/App.css index dcb46cf..ab28aa0 100644 --- a/src/App.css +++ b/src/App.css @@ -1,24 +1,3 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - 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 { height: 8em; padding: 1.5em; @@ -32,27 +11,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 +36,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 +80,83 @@ button.movePage.right{ button.movePage:hover{ background-color: rgb(0, 176, 176); } + + + +header { + position: sticky; + top: 0; + left: 0; + right: 0; + + padding: 1rem; + + display: flex; + gap: 1rem; + align-items: center; + justify-content: center; + + backdrop-filter: blur(10px); + z-index: 1; /* Otherwise any translated elements render above the blur?? */ +} + +main { + padding: 1rem 0; +} + +.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 1e2ae17..803b84c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,17 +1,24 @@ -import { Routes, Route } from 'react-router' +import { Routes, Route, Link } from 'react-router' import './App.css' -import VisProg from './pages/VisProgPage/VisProg.tsx' +import TemplatePage from './pages/TemplatePage/Template.tsx' import Home from './pages/Home/Home.tsx' - +import Robot from './pages/Robot/Robot.tsx'; function App(){ - return ( - - } /> - } /> - +
+
+ Home +
+
+ + } /> + } /> + } /> + +
+
) } -export default App \ No newline at end of file +export default App diff --git a/src/components/components.tsx b/src/components/components.tsx index d323843..24dd429 100644 --- a/src/components/components.tsx +++ b/src/components/components.tsx @@ -1,14 +1,11 @@ -// src/components/Counter.tsx import { useState } from 'react' -//import style from './Counter.module.css' // optional, if you want a CSS module for reset button -import '../App.css' function Counter() { const [count, setCount] = useState(0) return (
- - - -

- Edit src/App.tsx and save to test HMR -

- -

- Click on the Vite and React logos to learn more -

- +
+ Robot Interaction → + Template → +
+
) } diff --git a/src/pages/Robot/Robot.tsx b/src/pages/Robot/Robot.tsx new file mode 100644 index 0000000..0038dd9 --- /dev/null +++ b/src/pages/Robot/Robot.tsx @@ -0,0 +1,94 @@ +import { useState, useEffect, useRef } from 'react' + +export default function Robot() { + const [message, setMessage] = 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 { + const response = await fetch("http://localhost:8000/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); + } + }; + + useEffect(() => { + const eventSource = new EventSource("http://localhost:8000/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]); + + useEffect(() => { + if (!conversationRef || !conversationRef.current) return; + conversationRef.current.scrollTop = conversationRef.current.scrollHeight; + }, [conversation]); + + return ( + <> +

Robot interaction

+

Force robot speech

+
+ setMessage(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && sendMessage().then(() => setMessage(""))} + placeholder="Enter a message" + /> + +
+
+

Conversation

+

Listening {listening ? "🟢" : "🔴"}

+
+ {conversation.map((item, i) => ( +

{item["content"]}

+ ))} +
+
+ + +
+
+ + ); +} diff --git a/src/pages/TemplatePage/Template.module.css b/src/pages/TemplatePage/Template.module.css deleted file mode 100644 index 8526661..0000000 --- a/src/pages/TemplatePage/Template.module.css +++ /dev/null @@ -1,4 +0,0 @@ -button.reset:hover { - background-color: yellow; -} - diff --git a/src/pages/TemplatePage/Template.tsx b/src/pages/TemplatePage/Template.tsx index 0f08c25..dc24adf 100644 --- a/src/pages/TemplatePage/Template.tsx +++ b/src/pages/TemplatePage/Template.tsx @@ -1,21 +1,9 @@ - -import { Link } from 'react-router' import Counter from '../../components/components.tsx' -//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 TemplatePage() { - - return ( <> - {/* here you link to the homepage, in App.tsx you can link new pages */} - - ) } diff --git a/vite.config.ts b/vite.config.ts index 8b0f57b..aa7de4f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,4 +4,9 @@ import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + css: { + modules: { + localsConvention: "camelCase", + } + }, })