mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
Merge branch 'main' into jose/ui
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
# Project Timeline
|
||||
|
||||
Here's a structured timeline for our project completion:
|
||||
|
||||
| Day | Objective |
|
||||
| --------- | ------------------------------------------------------ |
|
||||
| Tuesday | Front-end and Back-end integration |
|
||||
| Wednesday | Project will be polished and tested by Adam |
|
||||
| Thursday | Project completion. Additional tests will be performed |
|
||||
| Friday | Big demo presentation |
|
||||
|
||||
Let's stay focused and get our tasks done on time for a successful demo on Friday. Let's have a successful week!
|
||||
@@ -18,81 +18,81 @@ class CustomRecordPlugin extends RecordPlugin {
|
||||
return new CustomRecordPlugin(options || {});
|
||||
}
|
||||
render(stream) {
|
||||
if (!this.wavesurfer) return () => undefined
|
||||
if (!this.wavesurfer) return () => undefined;
|
||||
|
||||
const container = this.wavesurfer.getWrapper()
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = container.clientWidth
|
||||
canvas.height = container.clientHeight
|
||||
canvas.style.zIndex = '10'
|
||||
container.appendChild(canvas)
|
||||
const container = this.wavesurfer.getWrapper();
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = container.clientWidth;
|
||||
canvas.height = container.clientHeight;
|
||||
canvas.style.zIndex = "10";
|
||||
container.appendChild(canvas);
|
||||
|
||||
const canvasCtx = canvas.getContext('2d')
|
||||
const audioContext = new AudioContext()
|
||||
const source = audioContext.createMediaStreamSource(stream)
|
||||
const analyser = audioContext.createAnalyser()
|
||||
analyser.fftSize = 2 ** 5
|
||||
source.connect(analyser)
|
||||
const bufferLength = analyser.frequencyBinCount
|
||||
const dataArray = new Uint8Array(bufferLength)
|
||||
const canvasCtx = canvas.getContext("2d");
|
||||
const audioContext = new AudioContext();
|
||||
const source = audioContext.createMediaStreamSource(stream);
|
||||
const analyser = audioContext.createAnalyser();
|
||||
analyser.fftSize = 2 ** 5;
|
||||
source.connect(analyser);
|
||||
const bufferLength = analyser.frequencyBinCount;
|
||||
const dataArray = new Uint8Array(bufferLength);
|
||||
|
||||
let animationId, previousTimeStamp;
|
||||
const BUFFER_SIZE = 2 ** 8
|
||||
const dataBuffer = new Array(BUFFER_SIZE).fill(canvas.height)
|
||||
const BUFFER_SIZE = 2 ** 8;
|
||||
const dataBuffer = new Array(BUFFER_SIZE).fill(canvas.height);
|
||||
|
||||
const drawWaveform = (timeStamp) => {
|
||||
if (!canvasCtx) return
|
||||
if (!canvasCtx) return;
|
||||
|
||||
analyser.getByteTimeDomainData(dataArray)
|
||||
canvasCtx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
canvasCtx.fillStyle = '#cc3347'
|
||||
analyser.getByteTimeDomainData(dataArray);
|
||||
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
canvasCtx.fillStyle = "#cc3347";
|
||||
|
||||
if (previousTimeStamp === undefined) {
|
||||
previousTimeStamp = timeStamp
|
||||
dataBuffer.push(Math.min(...dataArray))
|
||||
dataBuffer.splice(0, 1)
|
||||
previousTimeStamp = timeStamp;
|
||||
dataBuffer.push(Math.min(...dataArray));
|
||||
dataBuffer.splice(0, 1);
|
||||
}
|
||||
const elapsed = timeStamp - previousTimeStamp;
|
||||
if (elapsed > 10) {
|
||||
previousTimeStamp = timeStamp
|
||||
dataBuffer.push(Math.min(...dataArray))
|
||||
dataBuffer.splice(0, 1)
|
||||
previousTimeStamp = timeStamp;
|
||||
dataBuffer.push(Math.min(...dataArray));
|
||||
dataBuffer.splice(0, 1);
|
||||
}
|
||||
|
||||
// Drawing
|
||||
const sliceWidth = canvas.width / dataBuffer.length
|
||||
let x = 0
|
||||
const sliceWidth = canvas.width / dataBuffer.length;
|
||||
let x = 0;
|
||||
|
||||
for (let i = 0; i < dataBuffer.length; i++) {
|
||||
const valueNormalized = dataBuffer[i] / canvas.height
|
||||
const y = valueNormalized * canvas.height / 2
|
||||
const sliceHeight = canvas.height + 1 - y * 2
|
||||
const valueNormalized = dataBuffer[i] / canvas.height;
|
||||
const y = (valueNormalized * canvas.height) / 2;
|
||||
const sliceHeight = canvas.height + 1 - y * 2;
|
||||
|
||||
canvasCtx.fillRect(x, y, sliceWidth * 2 / 3, sliceHeight)
|
||||
x += sliceWidth
|
||||
canvasCtx.fillRect(x, y, (sliceWidth * 2) / 3, sliceHeight);
|
||||
x += sliceWidth;
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(drawWaveform)
|
||||
}
|
||||
animationId = requestAnimationFrame(drawWaveform);
|
||||
};
|
||||
|
||||
drawWaveform()
|
||||
drawWaveform();
|
||||
|
||||
return () => {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId)
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
|
||||
if (source) {
|
||||
source.disconnect()
|
||||
source.mediaStream.getTracks().forEach((track) => track.stop())
|
||||
source.disconnect();
|
||||
source.mediaStream.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
|
||||
if (audioContext) {
|
||||
audioContext.close()
|
||||
audioContext.close();
|
||||
}
|
||||
|
||||
canvas?.remove()
|
||||
}
|
||||
canvas?.remove();
|
||||
};
|
||||
}
|
||||
startRecording(stream) {
|
||||
this.preventInteraction();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Mulberry32 } from "../utils.js";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faChevronRight, faChevronDown } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faChevronRight,
|
||||
faChevronDown,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function Dashboard({
|
||||
isRecording,
|
||||
@@ -24,7 +27,8 @@ export function Dashboard({
|
||||
};
|
||||
|
||||
const handleScroll = (e) => {
|
||||
const bottom = e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight;
|
||||
const bottom =
|
||||
e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight;
|
||||
if (!bottom && autoscrollEnabled) {
|
||||
setAutoscrollEnabled(false);
|
||||
} else if (bottom && !autoscrollEnabled) {
|
||||
@@ -44,10 +48,18 @@ export function Dashboard({
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`absolute top-7 right-5 w-10 h-10 ${autoscrollEnabled ? 'hidden' : 'flex'} justify-center items-center text-2xl cursor-pointer opacity-70 hover:opacity-100 transition-opacity duration-200 animate-bounce rounded-xl border-slate-400 bg-[#3c82f638] text-[#3c82f6ed]`}
|
||||
className={`absolute top-7 right-5 w-10 h-10 ${
|
||||
autoscrollEnabled ? "hidden" : "flex"
|
||||
} justify-center items-center text-2xl cursor-pointer opacity-70 hover:opacity-100 transition-opacity duration-200 animate-bounce rounded-xl border-slate-400 bg-[#3c82f638] text-[#3c82f6ed]`}
|
||||
onClick={scrollToBottom}
|
||||
>⬇</div>
|
||||
<div id="topics-div" className="py-2 overflow-y-auto" onScroll={handleScroll}>
|
||||
>
|
||||
⬇
|
||||
</div>
|
||||
<div
|
||||
id="topics-div"
|
||||
className="py-2 overflow-y-auto"
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
{topics.map((item, index) => (
|
||||
<div key={index} className="border-b-2 py-2 hover:bg-[#8ec5fc30]">
|
||||
<div
|
||||
@@ -64,7 +76,9 @@ export function Dashboard({
|
||||
</div>
|
||||
</div>
|
||||
{openIndex === index && (
|
||||
<div className="p-2 mt-2 -mb-2 bg-slate-50 rounded">{item.transcript}</div>
|
||||
<div className="p-2 mt-2 -mb-2 bg-slate-50 rounded">
|
||||
{item.transcript}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -7,27 +7,28 @@ import "react-dropdown/style.css";
|
||||
|
||||
import CustomRecordPlugin from "./CustomRecordPlugin";
|
||||
|
||||
|
||||
const AudioInputsDropdown = (props) => {
|
||||
const [ddOptions, setDdOptions] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
// Request permission to use audio inputs
|
||||
await navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => stream.getTracks().forEach((t) => t.stop()))
|
||||
await navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
.then((stream) => stream.getTracks().forEach((t) => t.stop()));
|
||||
|
||||
const devices = await navigator.mediaDevices.enumerateDevices()
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const audioDevices = devices
|
||||
.filter((d) => d.kind === "audioinput" && d.deviceId != "")
|
||||
.map((d) => ({ value: d.deviceId, label: d.label }))
|
||||
.map((d) => ({ value: d.deviceId, label: d.label }));
|
||||
|
||||
if (audioDevices.length < 1) return console.log("no audio input devices")
|
||||
if (audioDevices.length < 1) return console.log("no audio input devices");
|
||||
|
||||
setDdOptions(audioDevices)
|
||||
props.setDeviceId(audioDevices[0].value)
|
||||
}
|
||||
init()
|
||||
}, [])
|
||||
setDdOptions(audioDevices);
|
||||
props.setDeviceId(audioDevices[0].value);
|
||||
};
|
||||
init();
|
||||
}, []);
|
||||
|
||||
const handleDropdownChange = (e) => {
|
||||
props.setDeviceId(e.value);
|
||||
@@ -40,8 +41,8 @@ const AudioInputsDropdown = (props) => {
|
||||
value={ddOptions[0]}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default function Recorder(props) {
|
||||
const waveformRef = useRef();
|
||||
|
||||
@@ -64,7 +64,7 @@ const useWebRTC = (stream) => {
|
||||
duration: serverData.duration,
|
||||
summary: serverData.summary,
|
||||
},
|
||||
text: ''
|
||||
text: "",
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -17,9 +17,8 @@ export default function RootLayout({ children }) {
|
||||
<title>Test</title>
|
||||
</Head>
|
||||
<body className={roboto.className + " flex flex-col min-h-screen"}>
|
||||
{children}
|
||||
</body>
|
||||
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ const App = () => {
|
||||
// transcription, summary, etc
|
||||
const serverData = useWebRTC(stream);
|
||||
|
||||
const sendStopCmd = () => serverData?.peer?.send(JSON.stringify({ cmd: "STOP" }))
|
||||
const sendStopCmd = () =>
|
||||
serverData?.peer?.send(JSON.stringify({ cmd: "STOP" }));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center h-[100svh] bg-gradient-to-r from-[#8ec5fc30] to-[#e0c3fc42]">
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
|
||||
// Sentry content below
|
||||
|
||||
const { withSentryConfig } = require("@sentry/nextjs");
|
||||
@@ -40,5 +39,5 @@ module.exports = withSentryConfig(
|
||||
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
},
|
||||
"main": "index.js",
|
||||
"repository": "https://github.com/Monadical-SAS/reflector-ui.git",
|
||||
"author": "Koper <andreas@monadical.com>",
|
||||
"license": "MIT",
|
||||
"author": "Andreas <andreas@monadical.com>",
|
||||
"license": "All Rights Reserved",
|
||||
"devDependencies": {
|
||||
"prettier": "^3.0.0"
|
||||
}
|
||||
|
||||
@@ -70,7 +70,10 @@ export default function Home() {
|
||||
|
||||
<p>
|
||||
Next, look for the error on the{" "}
|
||||
<a href="https://monadical.sentry.io/issues/?project=4505634666577920">Issues Page</a>.
|
||||
<a href="https://monadical.sentry.io/issues/?project=4505634666577920">
|
||||
Issues Page
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p style={{ marginTop: "24px" }}>
|
||||
For more information, see{" "}
|
||||
|
||||
Reference in New Issue
Block a user