From b6c65805f85fb0845bfbc6ddc35b769842c9d5c4 Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 15 Sep 2023 12:39:30 +0200 Subject: [PATCH 01/14] header, font and color changes --- www/app/(auth)/userInfo.tsx | 41 +++++++++------------------- www/app/layout.tsx | 46 +++++++++++++++++++++----------- www/app/transcripts/new/page.tsx | 8 +++--- 3 files changed, 45 insertions(+), 50 deletions(-) diff --git a/www/app/(auth)/userInfo.tsx b/www/app/(auth)/userInfo.tsx index d0a8ad02..a8b918b0 100644 --- a/www/app/(auth)/userInfo.tsx +++ b/www/app/(auth)/userInfo.tsx @@ -10,34 +10,17 @@ export default function UserInfo() { const isAuthenticated = useFiefIsAuthenticated(); const userinfo = useFiefUserinfo(); - return ( -
- {/* Logo on the left */} - - Reflector - - - {/* Text link on the right */} - {!isAuthenticated && ( - - Log in or create account - - )} - {isAuthenticated && ( - - {userinfo?.email} ( - - Log out - - ) - - )} -
+ return !isAuthenticated ? ( + + Log in or create account + + ) : ( + + {userinfo?.email} ( + + Log out + + ) + ); } diff --git a/www/app/layout.tsx b/www/app/layout.tsx index c15974a2..3f5a846c 100644 --- a/www/app/layout.tsx +++ b/www/app/layout.tsx @@ -1,12 +1,13 @@ import "./styles/globals.scss"; -import { Roboto } from "next/font/google"; +import { Poppins } from "next/font/google"; import { Metadata } from "next"; import FiefWrapper from "./(auth)/fiefWrapper"; import UserInfo from "./(auth)/userInfo"; import { ErrorProvider } from "./(errors)/errorContext"; import ErrorMessage from "./(errors)/errorMessage"; +import Image from "next/image"; -const roboto = Roboto({ subsets: ["latin"], weight: "400" }); +const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] }); export const metadata: Metadata = { title: { @@ -55,24 +56,37 @@ export const metadata: Metadata = { export default function RootLayout({ children }) { return ( - + -
-
- - -
-

- Reflector -

-

- Capture The Signal, Not The Noise -

+ {/*TODO lvh or svh ? */} +
+
+ {/* Logo on the left */} +
+ Reflector +
+

Reflector

+

+ Capture The Signal, Not The Noise +

+
- {children} -
+ {/* Text link on the right */} + + + + {children}
diff --git a/www/app/transcripts/new/page.tsx b/www/app/transcripts/new/page.tsx index f993f93e..eef7d411 100644 --- a/www/app/transcripts/new/page.tsx +++ b/www/app/transcripts/new/page.tsx @@ -65,10 +65,8 @@ const TranscriptCreate = () => { ) : ( <> -
-

- Audio Permissions -

+
+

Audio Permissions

{loading ? (

Checking permission... @@ -83,7 +81,7 @@ const TranscriptCreate = () => { : "Please grant permission to continue."}

-   - - )} - - {hasRecorded && ( - <> - - - {props.transcriptId && ( - - - - )} - - {!props.transcriptId && ( - - - - )} - - )} -
-
-
- {isRecording && ( -
- )} - {timeLabel()} +
+
+
+
+ {isRecording && ( +
+ )} + {timeLabel()} +
+ + {hasRecorded && ( + <> + + + {props.transcriptId && ( + + + + )} + + {!props.transcriptId && ( + + + + )} + + )} + {!hasRecorded && ( + + )}
); } diff --git a/www/tailwind.config.js b/www/tailwind.config.js index 78ebc4e7..690437d8 100644 --- a/www/tailwind.config.js +++ b/www/tailwind.config.js @@ -1,4 +1,14 @@ /** @type {import('tailwindcss').Config} */ + +// 8 margin main container Top + 40 height header + 16 margin bottom header + 80 recorder +const dashboardStart = 144; + +// 8 margin main container Top + 40 height header + 16 margin bottom header + 80 recorder +const dashboardStartMd = 144; + +// 16 margin main container Top + 64 height header + 16 margin bottom header + 80 recorder +const dashboardStartLg = 176; + module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx,mdx}", @@ -7,10 +17,10 @@ module.exports = { ], theme: { extend: { - backgroundImage: { - "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", - "gradient-conic": - "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", + height: { + "outer-dashboard": `calc(100svh - ${dashboardStart}px)`, + "outer-dashboard-md": `calc(100svh - ${dashboardStartMd + 34}px)`, + "outer-dashboard-lg": `calc(100svh - ${dashboardStartLg}px)`, }, }, }, From f88a7bde56aca5c55881e4f7a66f4626b792fb31 Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 18 Sep 2023 16:31:23 +0200 Subject: [PATCH 03/14] layout changes --- www/app/layout.tsx | 6 +- www/app/styles/button.css | 26 ++-- www/app/styles/globals.scss | 2 - www/app/transcripts/[transcriptId]/page.tsx | 2 +- www/app/transcripts/audioInputsDropdown.tsx | 36 +++++ www/app/transcripts/dashboard.tsx | 121 ++++++++------- www/app/transcripts/finalSummary.tsx | 4 +- www/app/transcripts/liveTranscription.tsx | 7 +- www/app/transcripts/new/page.tsx | 14 +- www/app/transcripts/recorder.tsx | 160 ++++++++------------ www/tailwind.config.js | 18 ++- 11 files changed, 209 insertions(+), 187 deletions(-) create mode 100644 www/app/transcripts/audioInputsDropdown.tsx diff --git a/www/app/layout.tsx b/www/app/layout.tsx index 3f5a846c..65e9206f 100644 --- a/www/app/layout.tsx +++ b/www/app/layout.tsx @@ -56,16 +56,16 @@ export const metadata: Metadata = { export default function RootLayout({ children }) { return ( - + {/*TODO lvh or svh ? */}
-
+
{/* Logo on the left */}
-
+
{transcript?.loading === true || waveform?.loading == true || topics?.loading == true ? ( diff --git a/www/app/transcripts/audioInputsDropdown.tsx b/www/app/transcripts/audioInputsDropdown.tsx new file mode 100644 index 00000000..f2c8d187 --- /dev/null +++ b/www/app/transcripts/audioInputsDropdown.tsx @@ -0,0 +1,36 @@ +import React, { useRef, useEffect, useState } from "react"; + +import Dropdown, { Option } from "react-dropdown"; +import "react-dropdown/style.css"; + +const AudioInputsDropdown: React.FC<{ + audioDevices?: Option[]; + setDeviceId: React.Dispatch>; + disabled?: boolean; +}> = (props) => { + const [ddOptions, setDdOptions] = useState([]); + + useEffect(() => { + if (props.audioDevices) { + setDdOptions(props.audioDevices); + props.setDeviceId( + props.audioDevices.length > 0 ? props.audioDevices[0].value : null, + ); + } + }, [props.audioDevices]); + + const handleDropdownChange = (option: Option) => { + props.setDeviceId(option.value); + }; + + return ( + + ); +}; + +export default AudioInputsDropdown; diff --git a/www/app/transcripts/dashboard.tsx b/www/app/transcripts/dashboard.tsx index 4f2fdf64..3dfa814c 100644 --- a/www/app/transcripts/dashboard.tsx +++ b/www/app/transcripts/dashboard.tsx @@ -34,6 +34,7 @@ export function Dashboard({ useEffect(() => { if (autoscrollEnabled) scrollToBottom(); + console.log(topics); }, [topics.length]); const scrollToBottom = () => { @@ -55,68 +56,74 @@ export function Dashboard({ }; return ( - <> -
-
-

Meeting Notes

-
+
+ {/* Topic Section */} +
+ {topics.length > 0 ? ( + <> + - - -
- {topics.map((item, index) => ( -
-
- setActiveTopic(activeTopic?.id == item.id ? null : item) - } - > -
- - [{formatTime(item.timestamp)}] - {" "} - {item.title} - +
+ {topics.map((item, index) => ( +
+ setActiveTopic(activeTopic?.id == item.id ? null : item) + } + > +
+

+ + [{formatTime(item.timestamp)}] + +   + {item.title} +

+ +
+ {activeTopic?.id == item.id && ( +
+ {item.transcript} +
+ )}
-
- {activeTopic?.id == item.id && ( -
- {item.transcript} -
- )} + ))}
- ))} - {topics.length === 0 && ( -
- Discussion topics will appear here after you start recording. It - may take up to 5 minutes of conversation for the first topic to - appear. -
- )} -
+ + ) : ( +
+ Discussion topics will appear here after you start recording. It may + take up to 5 minutes of conversation for the first topic to appear. +
+ )} +
- {finalSummary.summary && } -
+
+ {finalSummary.summary ? ( + + ) : ( + + )} +
{disconnected && } - - - +
); } diff --git a/www/app/transcripts/finalSummary.tsx b/www/app/transcripts/finalSummary.tsx index d93fb6d4..24ff91c3 100644 --- a/www/app/transcripts/finalSummary.tsx +++ b/www/app/transcripts/finalSummary.tsx @@ -4,8 +4,8 @@ type FinalSummaryProps = { export default function FinalSummary(props: FinalSummaryProps) { return ( -
-

Final Summary

+
+

Final Summary

{props.text}

); diff --git a/www/app/transcripts/liveTranscription.tsx b/www/app/transcripts/liveTranscription.tsx index 6241cdb2..d5ba2373 100644 --- a/www/app/transcripts/liveTranscription.tsx +++ b/www/app/transcripts/liveTranscription.tsx @@ -4,8 +4,11 @@ type LiveTranscriptionProps = { export default function LiveTrancription(props: LiveTranscriptionProps) { return ( -
-  {props.text}  +
+

+ {/* Nous allons prendre quelques appels téléphoniques et répondre à quelques questions */} + {props.text} +

); } diff --git a/www/app/transcripts/new/page.tsx b/www/app/transcripts/new/page.tsx index eef7d411..3c5d2804 100644 --- a/www/app/transcripts/new/page.tsx +++ b/www/app/transcripts/new/page.tsx @@ -9,6 +9,7 @@ import useAudioDevice from "../useAudioDevice"; import "../../styles/button.css"; import { Topic } from "../webSocketTypes"; import getApi from "../../lib/getApi"; +import AudioInputsDropdown from "../audioInputsDropdown"; const TranscriptCreate = () => { const [stream, setStream] = useState(null); @@ -38,8 +39,14 @@ const TranscriptCreate = () => { getAudioStream, } = useAudioDevice(); + const getCurrentStream = async () => { + return audioDevices.length + ? await getAudioStream(audioDevices[0].value) + : null; + }; + return ( -
+
{permissionOk ? ( <> { setStream(null); }} topics={webSockets.topics} - getAudioStream={getAudioStream} - audioDevices={audioDevices} + getAudioStream={getCurrentStream} useActiveTopic={useActiveTopic} isPastMeeting={false} /> @@ -65,7 +71,7 @@ const TranscriptCreate = () => { ) : ( <> -
+

Audio Permissions

{loading ? (

diff --git a/www/app/transcripts/recorder.tsx b/www/app/transcripts/recorder.tsx index 6c24109e..f235ecaf 100644 --- a/www/app/transcripts/recorder.tsx +++ b/www/app/transcripts/recorder.tsx @@ -7,49 +7,15 @@ import CustomRegionsPlugin from "../lib/custom-plugins/regions"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faDownload } from "@fortawesome/free-solid-svg-icons"; -import Dropdown, { Option } from "react-dropdown"; -import "react-dropdown/style.css"; - import { formatTime } from "../lib/time"; import { Topic } from "./webSocketTypes"; import { AudioWaveform } from "../api"; -const AudioInputsDropdown: React.FC<{ - audioDevices?: Option[]; - setDeviceId: React.Dispatch>; - disabled: boolean; -}> = (props) => { - const [ddOptions, setDdOptions] = useState([]); - - useEffect(() => { - if (props.audioDevices) { - setDdOptions(props.audioDevices); - props.setDeviceId( - props.audioDevices.length > 0 ? props.audioDevices[0].value : null, - ); - } - }, [props.audioDevices]); - - const handleDropdownChange = (option: Option) => { - props.setDeviceId(option.value); - }; - - return ( - - ); -}; - type RecorderProps = { setStream?: React.Dispatch>; onStop?: () => void; topics: Topic[]; - getAudioStream?: (deviceId: string | null) => Promise; - audioDevices?: Option[]; + getAudioStream?: () => Promise; useActiveTopic: [ Topic | null, React.Dispatch>, @@ -66,7 +32,6 @@ export default function Recorder(props: RecorderProps) { const [isRecording, setIsRecording] = useState(false); const [hasRecorded, setHasRecorded] = useState(props.isPastMeeting); const [isPlaying, setIsPlaying] = useState(false); - const [deviceId, setDeviceId] = useState(null); const [currentTime, setCurrentTime] = useState(0); const [timeInterval, setTimeInterval] = useState(null); const [duration, setDuration] = useState(0); @@ -88,7 +53,7 @@ export default function Recorder(props: RecorderProps) { hideScrollbar: true, autoCenter: true, barWidth: 2, - height: 90, + height: "auto", url: props.transcriptId ? `${process.env.NEXT_PUBLIC_API_URL}/v1/transcripts/${props.transcriptId}/audio/mp3` : undefined, @@ -222,7 +187,7 @@ export default function Recorder(props: RecorderProps) { setIsRecording(false); setHasRecorded(true); } else if (props.getAudioStream) { - const stream = await props.getAudioStream(deviceId); + const stream = await props.getAudioStream(); if (props.setStream) props.setStream(stream); waveRegions?.clearRegions(); @@ -246,68 +211,65 @@ export default function Recorder(props: RecorderProps) { }; return ( -

-
- {!hasRecorded && ( - <> - -   - -   - - )} - - {hasRecorded && ( - <> - - - {props.transcriptId && ( - - - - )} - - {!props.transcriptId && ( - - - - )} - - )} -
-
-
- {isRecording && ( -
- )} - {timeLabel()} +
+
+
+
+ {isRecording && ( +
+ )} + {timeLabel()} +
+ + {hasRecorded && ( + <> + + + {props.transcriptId && ( + + + + )} + + {!props.transcriptId && ( + + + + )} + + )} + {!hasRecorded && ( + + )}
); } diff --git a/www/tailwind.config.js b/www/tailwind.config.js index 78ebc4e7..690437d8 100644 --- a/www/tailwind.config.js +++ b/www/tailwind.config.js @@ -1,4 +1,14 @@ /** @type {import('tailwindcss').Config} */ + +// 8 margin main container Top + 40 height header + 16 margin bottom header + 80 recorder +const dashboardStart = 144; + +// 8 margin main container Top + 40 height header + 16 margin bottom header + 80 recorder +const dashboardStartMd = 144; + +// 16 margin main container Top + 64 height header + 16 margin bottom header + 80 recorder +const dashboardStartLg = 176; + module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx,mdx}", @@ -7,10 +17,10 @@ module.exports = { ], theme: { extend: { - backgroundImage: { - "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", - "gradient-conic": - "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", + height: { + "outer-dashboard": `calc(100svh - ${dashboardStart}px)`, + "outer-dashboard-md": `calc(100svh - ${dashboardStartMd + 34}px)`, + "outer-dashboard-lg": `calc(100svh - ${dashboardStartLg}px)`, }, }, }, From 44f22664a17ff5990515ee287a1f2c90f8ef763d Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 18 Sep 2023 18:44:56 +0200 Subject: [PATCH 04/14] remove test data --- www/app/transcripts/dashboard.tsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/www/app/transcripts/dashboard.tsx b/www/app/transcripts/dashboard.tsx index a6b8c783..3dfa814c 100644 --- a/www/app/transcripts/dashboard.tsx +++ b/www/app/transcripts/dashboard.tsx @@ -55,22 +55,11 @@ export function Dashboard({ } }; - const faketopic = { - id: "641fbc68-dc2e-4c9d-aafd-89bdbf8cfc26", - summary: - "Explore the history of hypnotica music, a genre that has been deeply influential on modern music. From its origins in the 60s to its current status, this music has a unique and hypnotic quality that can be felt in the gut. Dive into the canon of modern hypnotica and discover its impact on music today.", - timestamp: 0, - title: "The Origins and Influence of Hypnotica Music", - transcript: - " vertically oriented music ultimately hypnotic So, that's what we're talking about. Uh, when does it start? I mean, technically, I think... It's always been here but Hypnotica, much like Exotica, which is also sort of a fraught genre. a sort of a western interpretive genre, a fetishizing genre. I would say, uh, it starts in the 60s when all these wh- weird things started, you know, and I started fucking around and... You can go into Woodstock or whatever, that's usually when these things start. Anything that ends with a at the end of a word usually started in By some dirty hippie. Yeah. By some dirty hippie. Yeah. It was like, uh. Okay. So. That's hypnotica, I don't care to explain it to be honest I think everyone can feel it in their gut. We're mostly gonna ex- Explore this kind of the canon of the modern canon of what what I might call hypnotic It's been deeply influential on me and, uh...", - }; - const faketopics = new Array(10).fill(faketopic); - return (
{/* Topic Section */}
- {faketopics.length > 0 ? ( + {topics.length > 0 ? ( <> - {faketopics.map((item, index) => ( + {topics.map((item, index) => (
Date: Wed, 20 Sep 2023 15:56:49 +0200 Subject: [PATCH 05/14] layout and design improvements --- www/app/(auth)/userInfo.tsx | 7 +- www/app/layout.tsx | 9 +- www/app/styles/button.css | 4 - www/app/styles/globals.scss | 18 +-- www/app/transcripts/[transcriptId]/page.tsx | 54 ++++---- www/app/transcripts/audioInputsDropdown.tsx | 46 ++++--- www/app/transcripts/dashboard.tsx | 129 -------------------- www/app/transcripts/finalSummary.tsx | 2 +- www/app/transcripts/liveTranscription.tsx | 2 +- www/app/transcripts/new/page.tsx | 51 +++++--- www/app/transcripts/recorder.tsx | 21 ++-- www/app/transcripts/scrollToBottom.tsx | 12 +- www/app/transcripts/topicList.tsx | 101 +++++++++++++++ www/tailwind.config.js | 15 +-- 14 files changed, 224 insertions(+), 247 deletions(-) delete mode 100644 www/app/transcripts/dashboard.tsx create mode 100644 www/app/transcripts/topicList.tsx diff --git a/www/app/(auth)/userInfo.tsx b/www/app/(auth)/userInfo.tsx index a8b918b0..5f56cb6e 100644 --- a/www/app/(auth)/userInfo.tsx +++ b/www/app/(auth)/userInfo.tsx @@ -4,20 +4,19 @@ import { useFiefUserinfo, } from "@fief/fief/nextjs/react"; import Link from "next/link"; -import Image from "next/image"; export default function UserInfo() { const isAuthenticated = useFiefIsAuthenticated(); const userinfo = useFiefUserinfo(); return !isAuthenticated ? ( - + Log in or create account ) : ( - + {userinfo?.email} ( - + Log out ) diff --git a/www/app/layout.tsx b/www/app/layout.tsx index 65e9206f..f865357f 100644 --- a/www/app/layout.tsx +++ b/www/app/layout.tsx @@ -6,6 +6,7 @@ import UserInfo from "./(auth)/userInfo"; import { ErrorProvider } from "./(errors)/errorContext"; import ErrorMessage from "./(errors)/errorMessage"; import Image from "next/image"; +import Link from "next/link"; const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] }); @@ -63,11 +64,11 @@ export default function RootLayout({ children }) { {/*TODO lvh or svh ? */}
-
+
{/* Logo on the left */} -
+
-
+ {/* Text link on the right */}
diff --git a/www/app/styles/button.css b/www/app/styles/button.css index 76d5596a..92c246a5 100644 --- a/www/app/styles/button.css +++ b/www/app/styles/button.css @@ -8,13 +8,9 @@ button { padding: 0; cursor: pointer; /* Visual */ - /* background-color: #3e68ff; */ - /* color: #fff; */ border-radius: 8px; - /* box-shadow: 0 3px 5px rgba(0, 0, 0, 0.18); */ /* Size */ padding: 0.4em 1em; - min-width: 10ch; min-height: 44px; /* Text */ text-align: center; diff --git a/www/app/styles/globals.scss b/www/app/styles/globals.scss index 53dee09d..b9570abb 100644 --- a/www/app/styles/globals.scss +++ b/www/app/styles/globals.scss @@ -12,18 +12,6 @@ body { background: white; } -.temp-transcription { - background: rgb(151 190 255); - border-radius: 5px; - border: solid 1px #808080; - margin: 1em 0; -} - -.temp-transcription h2 { - font-weight: bold; - font-size: 130%; -} - .Dropdown-placeholder { text-wrap: nowrap; } @@ -31,8 +19,6 @@ body { top: 47% !important; } -@media (max-width: 768px) { - .audio-source-dropdown .Dropdown-control { - max-width: 200px; - } +.Dropdown-control.Dropdown-disabled { + background-color: lightgray; } diff --git a/www/app/transcripts/[transcriptId]/page.tsx b/www/app/transcripts/[transcriptId]/page.tsx index e1a31a3b..15391357 100644 --- a/www/app/transcripts/[transcriptId]/page.tsx +++ b/www/app/transcripts/[transcriptId]/page.tsx @@ -4,11 +4,12 @@ import getApi from "../../lib/getApi"; import useTranscript from "../useTranscript"; import useTopics from "../useTopics"; import useWaveform from "../useWaveform"; -import { Dashboard } from "../dashboard"; +import { TopicList } from "../topicList"; import Recorder from "../recorder"; import { Topic } from "../webSocketTypes"; import React, { useEffect, useState } from "react"; import "../../styles/button.css"; +import FinalSummary from "../finalSummary"; type TranscriptDetails = { params: { @@ -34,34 +35,37 @@ export default function TranscriptDetails(details: TranscriptDetails) { return ( <> -
- {transcript?.loading === true || - waveform?.loading == true || - topics?.loading == true ? ( - + ) : ( + <> + - ) : ( - <> - + - - - - )} -
+
+
+ {transcript?.response?.longSummary && ( + + )} +
+
+
+ + )} ); } diff --git a/www/app/transcripts/audioInputsDropdown.tsx b/www/app/transcripts/audioInputsDropdown.tsx index f2c8d187..4b7b3cdc 100644 --- a/www/app/transcripts/audioInputsDropdown.tsx +++ b/www/app/transcripts/audioInputsDropdown.tsx @@ -1,36 +1,42 @@ -import React, { useRef, useEffect, useState } from "react"; - +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faMicrophone } from "@fortawesome/free-solid-svg-icons"; +import React, { useEffect, useState } from "react"; import Dropdown, { Option } from "react-dropdown"; import "react-dropdown/style.css"; const AudioInputsDropdown: React.FC<{ - audioDevices?: Option[]; + audioDevices: Option[]; + disabled: boolean; setDeviceId: React.Dispatch>; - disabled?: boolean; -}> = (props) => { +}> = ({ audioDevices, disabled, setDeviceId }) => { const [ddOptions, setDdOptions] = useState([]); useEffect(() => { - if (props.audioDevices) { - setDdOptions(props.audioDevices); - props.setDeviceId( - props.audioDevices.length > 0 ? props.audioDevices[0].value : null, - ); + if (audioDevices) { + setDdOptions(audioDevices); + setDeviceId(audioDevices.length > 0 ? audioDevices[0].value : null); } - }, [props.audioDevices]); + }, [audioDevices]); const handleDropdownChange = (option: Option) => { - props.setDeviceId(option.value); + setDeviceId(option.value); }; - return ( - - ); + if (audioDevices?.length > 0) { + return ( +
+ + +
+ ); + } + return null; }; export default AudioInputsDropdown; diff --git a/www/app/transcripts/dashboard.tsx b/www/app/transcripts/dashboard.tsx deleted file mode 100644 index 3dfa814c..00000000 --- a/www/app/transcripts/dashboard.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faChevronRight, - faChevronDown, -} from "@fortawesome/free-solid-svg-icons"; -import { formatTime } from "../lib/time"; -import ScrollToBottom from "./scrollToBottom"; -import DisconnectedIndicator from "./disconnectedIndicator"; -import LiveTrancription from "./liveTranscription"; -import FinalSummary from "./finalSummary"; -import { Topic, FinalSummary as FinalSummaryType } from "./webSocketTypes"; - -type DashboardProps = { - transcriptionText: string; - finalSummary: FinalSummaryType; - topics: Topic[]; - disconnected: boolean; - useActiveTopic: [ - Topic | null, - React.Dispatch>, - ]; -}; - -export function Dashboard({ - transcriptionText, - finalSummary, - topics, - disconnected, - useActiveTopic, -}: DashboardProps) { - const [activeTopic, setActiveTopic] = useActiveTopic; - const [autoscrollEnabled, setAutoscrollEnabled] = useState(true); - - useEffect(() => { - if (autoscrollEnabled) scrollToBottom(); - console.log(topics); - }, [topics.length]); - - const scrollToBottom = () => { - const topicsDiv = document.getElementById("topics-div"); - - if (!topicsDiv) - console.error("Could not find topics div to scroll to bottom"); - else topicsDiv.scrollTop = topicsDiv.scrollHeight; - }; - - const handleScroll = (e) => { - const bottom = - e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight; - if (!bottom && autoscrollEnabled) { - setAutoscrollEnabled(false); - } else if (bottom && !autoscrollEnabled) { - setAutoscrollEnabled(true); - } - }; - - return ( -
- {/* Topic Section */} -
- {topics.length > 0 ? ( - <> - - -
- {topics.map((item, index) => ( -
- setActiveTopic(activeTopic?.id == item.id ? null : item) - } - > -
-

- - [{formatTime(item.timestamp)}] - -   - {item.title} -

- -
- {activeTopic?.id == item.id && ( -
- {item.transcript} -
- )} -
- ))} -
- - ) : ( -
- Discussion topics will appear here after you start recording. It may - take up to 5 minutes of conversation for the first topic to appear. -
- )} -
- -
- {finalSummary.summary ? ( - - ) : ( - - )} -
- - {disconnected && } -
- ); -} diff --git a/www/app/transcripts/finalSummary.tsx b/www/app/transcripts/finalSummary.tsx index 24ff91c3..a9646cee 100644 --- a/www/app/transcripts/finalSummary.tsx +++ b/www/app/transcripts/finalSummary.tsx @@ -5,7 +5,7 @@ type FinalSummaryProps = { export default function FinalSummary(props: FinalSummaryProps) { return (
-

Final Summary

+

Final Summary

{props.text}

); diff --git a/www/app/transcripts/liveTranscription.tsx b/www/app/transcripts/liveTranscription.tsx index d5ba2373..2c913a91 100644 --- a/www/app/transcripts/liveTranscription.tsx +++ b/www/app/transcripts/liveTranscription.tsx @@ -5,7 +5,7 @@ type LiveTranscriptionProps = { export default function LiveTrancription(props: LiveTranscriptionProps) { return (
-

+

{/* Nous allons prendre quelques appels téléphoniques et répondre à quelques questions */} {props.text}

diff --git a/www/app/transcripts/new/page.tsx b/www/app/transcripts/new/page.tsx index 3c5d2804..eddf4fcc 100644 --- a/www/app/transcripts/new/page.tsx +++ b/www/app/transcripts/new/page.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useEffect, useState } from "react"; import Recorder from "../recorder"; -import { Dashboard } from "../dashboard"; +import { TopicList } from "../topicList"; import useWebRTC from "../useWebRTC"; import useTranscript from "../useTranscript"; import { useWebSockets } from "../useWebSockets"; @@ -10,11 +10,15 @@ import "../../styles/button.css"; import { Topic } from "../webSocketTypes"; import getApi from "../../lib/getApi"; import AudioInputsDropdown from "../audioInputsDropdown"; +import LiveTrancription from "../liveTranscription"; +import DisconnectedIndicator from "../disconnectedIndicator"; const TranscriptCreate = () => { const [stream, setStream] = useState(null); const [disconnected, setDisconnected] = useState(false); const useActiveTopic = useState(null); + const [deviceId, setDeviceId] = useState(null); + const [recordStarted, setRecordStarted] = useState(false); useEffect(() => { if (process.env.NEXT_PUBLIC_ENV === "development") { @@ -40,13 +44,18 @@ const TranscriptCreate = () => { } = useAudioDevice(); const getCurrentStream = async () => { - return audioDevices.length - ? await getAudioStream(audioDevices[0].value) - : null; + setRecordStarted(true); + return deviceId ? await getAudioStream(deviceId) : null; }; + useEffect(() => { + if (audioDevices.length > 0) { + setDeviceId[audioDevices[0].value]; + } + }, [audioDevices]); + return ( -
+ <> {permissionOk ? ( <> { useActiveTopic={useActiveTopic} isPastMeeting={false} /> +
+ +
+
+ +
+
+
+ +
+
+
- + {disconnected && } +
) : ( <> @@ -87,7 +110,7 @@ const TranscriptCreate = () => { : "Please grant permission to continue."}

)} -
+ ); }; diff --git a/www/app/transcripts/recorder.tsx b/www/app/transcripts/recorder.tsx index f235ecaf..87e9955a 100644 --- a/www/app/transcripts/recorder.tsx +++ b/www/app/transcripts/recorder.tsx @@ -213,10 +213,7 @@ export default function Recorder(props: RecorderProps) { return (
-
+
{isRecording && (
@@ -229,8 +226,10 @@ export default function Recorder(props: RecorderProps) { <> - - )} -
+

+ With real-time transcriptions, translations, and summaries, + Reflector captures and categorizes the details of your meetings + and events, all while keeping your data locked down tight on + your own infrastructure. Forget the scribbled notes, endless + recordings, or third-party apps. Discover Reflector, a powerful + new way to elevate knowledge management and accessibility for + all. +

+
+
+
+

Audio Permissions

+ {loading ? ( +

+ Checking permission... +

+ ) : ( + <> +

+ Reflector needs access to your microphone to work. +
+ {permissionDenied + ? "Please reset microphone permissions to continue." + : "Please grant permission to continue."} +

+ + + )} +
+
+ )} diff --git a/www/app/transcripts/recorder.tsx b/www/app/transcripts/recorder.tsx index 87e9955a..91cdcfac 100644 --- a/www/app/transcripts/recorder.tsx +++ b/www/app/transcripts/recorder.tsx @@ -5,17 +5,21 @@ import RecordPlugin from "../lib/custom-plugins/record"; import CustomRegionsPlugin from "../lib/custom-plugins/regions"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faMicrophone } from "@fortawesome/free-solid-svg-icons"; import { faDownload } from "@fortawesome/free-solid-svg-icons"; import { formatTime } from "../lib/time"; import { Topic } from "./webSocketTypes"; import { AudioWaveform } from "../api"; +import AudioInputsDropdown from "./audioInputsDropdown"; +import { Option } from "react-dropdown"; type RecorderProps = { setStream?: React.Dispatch>; onStop?: () => void; topics: Topic[]; - getAudioStream?: () => Promise; + getAudioStream?: (deviceId) => Promise; + audioDevices?: Option[]; useActiveTopic: [ Topic | null, React.Dispatch>, @@ -38,10 +42,11 @@ export default function Recorder(props: RecorderProps) { const [waveRegions, setWaveRegions] = useState( null, ); - + const [deviceId, setDeviceId] = useState(null); + const [recordStarted, setRecordStarted] = useState(false); const [activeTopic, setActiveTopic] = props.useActiveTopic; - const topicsRef = useRef(props.topics); + const [showDevices, setShowDevices] = useState(false); useEffect(() => { if (waveformRef.current) { @@ -186,8 +191,8 @@ export default function Recorder(props: RecorderProps) { record.stopRecording(); setIsRecording(false); setHasRecorded(true); - } else if (props.getAudioStream) { - const stream = await props.getAudioStream(); + } else { + const stream = await getCurrentStream(); if (props.setStream) props.setStream(stream); waveRegions?.clearRegions(); @@ -195,8 +200,6 @@ export default function Recorder(props: RecorderProps) { await record.startRecording(stream); setIsRecording(true); } - } else { - throw new Error("No getAudioStream function provided"); } }; @@ -210,8 +213,21 @@ export default function Recorder(props: RecorderProps) { return ""; }; + const getCurrentStream = async () => { + setRecordStarted(true); + return deviceId && props.getAudioStream + ? await props.getAudioStream(deviceId) + : null; + }; + + useEffect(() => { + if (props.audioDevices && props.audioDevices.length > 0) { + setDeviceId[props.audioDevices[0].value]; + } + }, [props.audioDevices]); + return ( -
+
@@ -259,17 +275,41 @@ export default function Recorder(props: RecorderProps) { )} {!hasRecorded && ( - + <> + + {props.audioDevices && props.audioDevices?.length > 0 && ( + <> + +
+ setShowDevices(false)} + /> +
+ + )} + )}
); From 3f2c3ddadcd495d35211def1d2d95de64ea27bf7 Mon Sep 17 00:00:00 2001 From: Sara Date: Wed, 20 Sep 2023 17:16:24 +0200 Subject: [PATCH 07/14] self-review --- www/app/layout.tsx | 1 - www/app/styles/button.css | 2 -- 2 files changed, 3 deletions(-) diff --git a/www/app/layout.tsx b/www/app/layout.tsx index f865357f..685f0821 100644 --- a/www/app/layout.tsx +++ b/www/app/layout.tsx @@ -61,7 +61,6 @@ export default function RootLayout({ children }) { - {/*TODO lvh or svh ? */}
Date: Thu, 21 Sep 2023 16:53:47 +0200 Subject: [PATCH 08/14] cuter scrollbars, better focus, small design improvements --- www/app/(auth)/userInfo.tsx | 12 ++++++---- www/app/(errors)/errorMessage.tsx | 6 ++--- www/app/layout.tsx | 9 ++++--- www/app/styles/button.css | 5 +++- www/app/styles/globals.scss | 13 +++++++++++ www/app/transcripts/[transcriptId]/page.tsx | 7 ++---- www/app/transcripts/modal.tsx | 2 +- www/app/transcripts/new/page.tsx | 4 ++-- www/app/transcripts/recorder.tsx | 14 +++++------ www/app/transcripts/topicList.tsx | 26 ++++++++++----------- www/tailwind.config.js | 1 + 11 files changed, 59 insertions(+), 40 deletions(-) diff --git a/www/app/(auth)/userInfo.tsx b/www/app/(auth)/userInfo.tsx index 5f56cb6e..0f01a85e 100644 --- a/www/app/(auth)/userInfo.tsx +++ b/www/app/(auth)/userInfo.tsx @@ -10,14 +10,18 @@ export default function UserInfo() { const userinfo = useFiefUserinfo(); return !isAuthenticated ? ( - - Log in or create account + + + Log in or create account + ) : ( {userinfo?.email} ( - - Log out + + + Log out + ) diff --git a/www/app/(errors)/errorMessage.tsx b/www/app/(errors)/errorMessage.tsx index f048cac5..8b410c4c 100644 --- a/www/app/(errors)/errorMessage.tsx +++ b/www/app/(errors)/errorMessage.tsx @@ -18,16 +18,16 @@ const ErrorMessage: React.FC = () => { if (!isVisible || !error) return null; return ( -
{ setIsVisible(false); setError(null); }} - className="max-w-xs z-50 fixed bottom-5 right-5 md:bottom-10 md:right-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded transition-opacity duration-300 ease-out opacity-100 hover:opacity-80 cursor-pointer transform hover:scale-105" + className="max-w-xs z-50 fixed bottom-5 right-5 md:bottom-10 md:right-10 border-solid bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded transition-opacity duration-300 ease-out opacity-100 hover:opacity-80 focus-visible:opacity-80 cursor-pointer transform hover:scale-105 focus-visible:scale-105" role="alert" > {error?.message} -
+ ); }; diff --git a/www/app/layout.tsx b/www/app/layout.tsx index 685f0821..9742f1d4 100644 --- a/www/app/layout.tsx +++ b/www/app/layout.tsx @@ -67,15 +67,18 @@ export default function RootLayout({ children }) { >
{/* Logo on the left */} - + Reflector -
+

Reflector

Capture The Signal, Not The Noise diff --git a/www/app/styles/button.css b/www/app/styles/button.css index 49adbb09..e8c41c47 100644 --- a/www/app/styles/button.css +++ b/www/app/styles/button.css @@ -6,7 +6,6 @@ button { background-color: transparent; font-family: inherit; padding: 0; - cursor: pointer; /* Visual */ border-radius: 8px; /* Size */ @@ -23,6 +22,10 @@ button { transition: 220ms all ease-in-out; } +button:focus-visible { + outline-style: none; +} + @media (max-width: 768px) { input[type="button"], button { diff --git a/www/app/styles/globals.scss b/www/app/styles/globals.scss index b9570abb..0acacc90 100644 --- a/www/app/styles/globals.scss +++ b/www/app/styles/globals.scss @@ -10,6 +10,8 @@ body { background: white; + scrollbar-color: rgb(96 165 250) transparent; + scrollbar-width: thin; } .Dropdown-placeholder { @@ -22,3 +24,14 @@ body { .Dropdown-control.Dropdown-disabled { background-color: lightgray; } + +::-webkit-scrollbar { + visibility: visible; + width: 5px; + opacity: 0.7; +} + +::-webkit-scrollbar-thumb { + border-radius: 10px; + background-color: rgb(96 165 250); +} diff --git a/www/app/transcripts/[transcriptId]/page.tsx b/www/app/transcripts/[transcriptId]/page.tsx index 15391357..967eab12 100644 --- a/www/app/transcripts/[transcriptId]/page.tsx +++ b/www/app/transcripts/[transcriptId]/page.tsx @@ -38,10 +38,7 @@ export default function TranscriptDetails(details: TranscriptDetails) { {transcript?.loading === true || waveform?.loading == true || topics?.loading == true ? ( - + ) : ( <>

-
+
{transcript?.response?.longSummary && ( )} diff --git a/www/app/transcripts/modal.tsx b/www/app/transcripts/modal.tsx index b3cb0446..55e74fbd 100644 --- a/www/app/transcripts/modal.tsx +++ b/www/app/transcripts/modal.tsx @@ -6,7 +6,7 @@ type ModalProps = { export default function Modal(props: ModalProps) { return ( <> -
+

{props.title}

{props.text}

diff --git a/www/app/transcripts/new/page.tsx b/www/app/transcripts/new/page.tsx index 4bbba050..10bfe789 100644 --- a/www/app/transcripts/new/page.tsx +++ b/www/app/transcripts/new/page.tsx @@ -58,7 +58,7 @@ const TranscriptCreate = () => { audioDevices={audioDevices} /> -
+
{ : "Please grant permission to continue."}

))}
diff --git a/www/tailwind.config.js b/www/tailwind.config.js index fbd7b0bf..7536965d 100644 --- a/www/tailwind.config.js +++ b/www/tailwind.config.js @@ -10,6 +10,7 @@ module.exports = { extend: { gridTemplateRows: { layout: "auto auto minmax(0, 1fr)", + "mobile-inner": "minmax(0, 2fr) minmax(0, 1fr)", }, }, }, From b989f2b54a8b76f58c5b46441d7db5121e19d2e3 Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 22 Sep 2023 15:50:38 +0200 Subject: [PATCH 09/14] adds loading indicator --- www/app/transcripts/new/page.tsx | 23 +++++++++++++++++++---- www/tailwind.config.js | 3 +++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/www/app/transcripts/new/page.tsx b/www/app/transcripts/new/page.tsx index 10bfe789..2a01abf0 100644 --- a/www/app/transcripts/new/page.tsx +++ b/www/app/transcripts/new/page.tsx @@ -9,9 +9,10 @@ import useAudioDevice from "../useAudioDevice"; import "../../styles/button.css"; import { Topic } from "../webSocketTypes"; import getApi from "../../lib/getApi"; -import AudioInputsDropdown from "../audioInputsDropdown"; import LiveTrancription from "../liveTranscription"; import DisconnectedIndicator from "../disconnectedIndicator"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faGear } from "@fortawesome/free-solid-svg-icons"; const TranscriptCreate = () => { const [stream, setStream] = useState(null); @@ -40,6 +41,7 @@ const TranscriptCreate = () => { requestPermission, getAudioStream, } = useAudioDevice(); + const [hasRecorded, setHasRecorded] = useState(false); return ( <> @@ -50,6 +52,7 @@ const TranscriptCreate = () => { onStop={() => { webRTC?.peer?.send(JSON.stringify({ cmd: "STOP" })); setStream(null); + setHasRecorded(true); }} topics={webSockets.topics} getAudioStream={getAudioStream} @@ -64,9 +67,21 @@ const TranscriptCreate = () => { useActiveTopic={useActiveTopic} />
-
- -
+ {!hasRecorded ? ( +
+ +
+ ) : ( +
+
+ +
+

Your final summary is being processed.

+
+ )}
diff --git a/www/tailwind.config.js b/www/tailwind.config.js index 7536965d..15f0dfb9 100644 --- a/www/tailwind.config.js +++ b/www/tailwind.config.js @@ -12,6 +12,9 @@ module.exports = { layout: "auto auto minmax(0, 1fr)", "mobile-inner": "minmax(0, 2fr) minmax(0, 1fr)", }, + animation: { + "spin-slow": "spin 3s linear infinite", + }, }, }, plugins: [], From cd648f428b9ac8c186131c6ebebd85bb3c432b6b Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 22 Sep 2023 17:01:17 +0200 Subject: [PATCH 10/14] fix copy --- www/app/transcripts/new/page.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/www/app/transcripts/new/page.tsx b/www/app/transcripts/new/page.tsx index 2a01abf0..15ce5ca9 100644 --- a/www/app/transcripts/new/page.tsx +++ b/www/app/transcripts/new/page.tsx @@ -73,13 +73,17 @@ const TranscriptCreate = () => {
) : (
-
+
-

Your final summary is being processed.

+

+ We are generating the final summary for you. This may take a + couple of minutes. Please do not navigate away from the page + during this time. +

)}
From d60a582347a5df74ab76bf43d0d3c8600cbc8947 Mon Sep 17 00:00:00 2001 From: andreas Date: Fri, 22 Sep 2023 17:03:00 +0200 Subject: [PATCH 11/14] Make reflector.media not indexable by search engines --- www/app/layout.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/app/layout.tsx b/www/app/layout.tsx index 9742f1d4..168ef820 100644 --- a/www/app/layout.tsx +++ b/www/app/layout.tsx @@ -52,6 +52,8 @@ export const metadata: Metadata = { initialScale: 1, maximumScale: 1, }, + + robots: { index: false, follow: false, noarchive: true, noimageindex: true } }; export default function RootLayout({ children }) { From 2cc98314d246794ef6a06e2da55fb5a0b256cfda Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 22 Sep 2023 16:52:38 +0200 Subject: [PATCH 12/14] fix scroll to bottom --- www/app/transcripts/scrollToBottom.tsx | 6 +-- www/app/transcripts/topicList.tsx | 22 ++++++++--- www/app/transcripts/useWebSockets.ts | 52 ++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/www/app/transcripts/scrollToBottom.tsx b/www/app/transcripts/scrollToBottom.tsx index a702ede9..5fa09039 100644 --- a/www/app/transcripts/scrollToBottom.tsx +++ b/www/app/transcripts/scrollToBottom.tsx @@ -9,15 +9,15 @@ type ScrollToBottomProps = { export default function ScrollToBottom(props: ScrollToBottomProps) { return (
{ props.handleScrollBottom(); return false; }} > - +
); } diff --git a/www/app/transcripts/topicList.tsx b/www/app/transcripts/topicList.tsx index 02bba6f7..ec78da9e 100644 --- a/www/app/transcripts/topicList.tsx +++ b/www/app/transcripts/topicList.tsx @@ -22,8 +22,7 @@ export function TopicList({ topics, useActiveTopic }: TopicListProps) { useEffect(() => { if (autoscrollEnabled) scrollToBottom(); - console.log(topics); - }, [topics.length]); + }, [topics]); const scrollToBottom = () => { const topicsDiv = document.getElementById("topics-div"); @@ -33,15 +32,28 @@ export function TopicList({ topics, useActiveTopic }: TopicListProps) { else topicsDiv.scrollTop = topicsDiv.scrollHeight; }; - const handleScroll = (e) => { + // scroll top is not rounded, heights are, so exact match won't work. + // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled + const toggleScroll = (element) => { const bottom = - e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight; + Math.abs( + element.scrollHeight - element.clientHeight - element.scrollTop, + ) < 2 || element.scrollHeight == element.clientHeight; if (!bottom && autoscrollEnabled) { setAutoscrollEnabled(false); } else if (bottom && !autoscrollEnabled) { setAutoscrollEnabled(true); } }; + const handleScroll = (e) => { + toggleScroll(e.target); + }; + + useEffect(() => { + const topicsDiv = document.getElementById("topics-div"); + + topicsDiv && toggleScroll(topicsDiv); + }, [activeTopic]); return (
@@ -60,7 +72,7 @@ export function TopicList({ topics, useActiveTopic }: TopicListProps) { {topics.map((topic, index) => (