adds three-letter language picker

This commit is contained in:
Sara
2023-10-13 23:35:19 +02:00
parent 3555cc3a3c
commit 47fc52af11
8 changed files with 593 additions and 78 deletions

View File

@@ -0,0 +1,496 @@
import Script from "next/script";
// type Script = 'Latn' | 'Ethi' | 'Arab' | 'Beng' | 'Cyrl' | 'Taml' | 'Hant' | 'Hans' | 'Grek' | 'Gujr' | 'Hebr'| 'Deva'| 'Armn' | 'Jpan' | 'Knda' | 'Geor';
type LanguageOption = {
value: string | undefined;
name: string;
script?: string;
};
const supportedLanguages: LanguageOption[] = [
{ value: "afr", name: "Afrikaans", script: "Latn" },
{
value: "amh",
name: "Amharic",
script: "Ethi",
},
{
value: "arb",
name: "Modern Standard Arabic",
script: "Arab",
},
{
value: "ary",
name: "Moroccan Arabic",
script: "Arab",
},
{
value: "arz",
name: "Egyptian Arabic",
script: "Arab",
},
{
value: "asm",
name: "Assamese",
script: "Beng",
},
{
value: "azj",
name: "North Azerbaijani",
script: "Latn",
},
{
value: "bel",
name: "Belarusian",
script: "Cyrl",
},
{
value: "ben",
name: "Bengali",
script: "Beng",
},
{
value: "bos",
name: "Bosnian",
script: "Latn",
},
{
value: "bul",
name: "Bulgarian",
script: "Cyrl",
},
{
value: "cat",
name: "Catalan",
script: "Latn",
},
{
value: "ceb",
name: "Cebuano",
script: "Latn",
},
{
value: "ces",
name: "Czech",
script: "Latn",
},
{
value: "ckb",
name: "Central Kurdish",
script: "Arab",
},
{
value: "cmn",
name: "Mandarin Chinese",
script: "Hans",
},
{
value: "cmn_Ha",
name: "Mandarin Chinese",
script: "Hant",
},
{
value: "cym",
name: "Welsh",
script: "Latn",
},
{
value: "dan",
name: "Danish",
script: "Latn",
},
{
value: "deu",
name: "German",
script: "Latn",
},
{
value: "ell",
name: "Greek",
script: "Grek",
},
{
value: "eng",
name: "English",
script: "Latn",
},
{
value: "est",
name: "Estonian",
script: "Latn",
},
{
value: "eus",
name: "Basque",
script: "Latn",
},
{
value: "fin",
name: "Finnish",
script: "Latn",
},
{
value: "fra",
name: "French",
script: "Latn",
},
{
value: "gaz",
name: "West Central Oromo",
script: "Latn",
},
{
value: "gle",
name: "Irish",
script: "Latn",
},
{
value: "glg",
name: "Galician",
script: "Latn",
},
{
value: "guj",
name: "Gujarati",
script: "Gujr",
},
{
value: "heb",
name: "Hebrew",
script: "Hebr",
},
{
value: "hin",
name: "Hindi",
script: "Deva",
},
{
value: "hrv",
name: "Croatian",
script: "Latn",
},
{
value: "hun",
name: "Hungarian",
script: "Latn",
},
{
value: "hye",
name: "Armenian",
script: "Armn",
},
{
value: "ibo",
name: "Igbo",
script: "Latn",
},
{
value: "ind",
name: "Indonesian",
script: "Latn",
},
{
value: "isl",
name: "Icelandic",
script: "Latn",
},
{
value: "ita",
name: "Italian",
script: "Latn",
},
{
value: "jav",
name: "Javanese",
script: "Latn",
},
{
value: "jpn",
name: "Japanese",
script: "Jpan",
},
{
value: "kan",
name: "Kannada",
script: "Knda",
},
{
value: "kat",
name: "Georgian",
script: "Geor",
},
{
value: "kaz",
name: "Kazakh",
script: "Cyrl",
},
{
value: "khk",
name: "Halh Mongolian",
script: "Cyrl",
},
{
value: "khm",
name: "Khmer",
script: "Khmr",
},
{
value: "kir",
name: "Kyrgyz",
script: "Cyrl",
},
{
value: "kor",
name: "Korean",
script: "Kore",
},
{
value: "lao",
name: "Lao",
script: "Laoo",
},
{
value: "lit",
name: "Lithuanian",
script: "Latn",
},
{
value: "lug",
name: "Ganda",
script: "Latn",
},
{
value: "luo",
name: "Luo",
script: "Latn",
},
{
value: "lvs",
name: "Standard Latvian",
script: "Latn",
},
{
value: "mai",
name: "Maithili",
script: "Deva",
},
{
value: "mal",
name: "Malayalam",
script: "Mlym",
},
{
value: "mar",
name: "Marathi",
script: "Deva",
},
{
value: "mkd",
name: "Macedonian",
script: "Cyrl",
},
{
value: "mlt",
name: "Maltese",
script: "Latn",
},
{
value: "mni",
name: "Meitei",
script: "Beng",
},
{
value: "mya",
name: "Burmese",
script: "Mymr",
},
{
value: "nld",
name: "Dutch",
script: "Latn",
},
{
value: "nno",
name: "Norwegian Nynorsk",
script: "Latn",
},
{
value: "nob",
name: "Norwegian Bokmål",
script: "Latn",
},
{
value: "npi",
name: "Nepali",
script: "Deva",
},
{
value: "nya",
name: "Nyanja",
script: "Latn",
},
{
value: "ory",
name: "Odia",
script: "Orya",
},
{
value: "pan",
name: "Punjabi",
script: "Guru",
},
{
value: "pbt",
name: "Southern Pashto",
script: "Arab",
},
{
value: "pes",
name: "Western Persian",
script: "Arab",
},
{
value: "pol",
name: "Polish",
script: "Latn",
},
{
value: "por",
name: "Portuguese",
script: "Latn",
},
{
value: "ron",
name: "Romanian",
script: "Latn",
},
{
value: "rus",
name: "Russian",
script: "Cyrl",
},
{
value: "slk",
name: "Slovak",
script: "Latn",
},
{
value: "slv",
name: "Slovenian",
script: "Latn",
},
{
value: "sna",
name: "Shona",
script: "Latn",
},
{
value: "snd",
name: "Sindhi",
script: "Arab",
},
{
value: "som",
name: "Somali",
script: "Latn",
},
{
value: "spa",
name: "Spanish",
script: "Latn",
},
{
value: "srp",
name: "Serbian",
script: "Cyrl",
},
{
value: "swe",
name: "Swedish",
script: "Latn",
},
{
value: "swh",
name: "Swahili",
script: "Latn",
},
{
value: "tam",
name: "Tamil",
script: "Taml",
},
{
value: "tel",
name: "Telugu",
script: "Telu",
},
{
value: "tgk",
name: "Tajik",
script: "Cyrl",
},
{
value: "tgl",
name: "Tagalog",
script: "Latn",
},
{
value: "tha",
name: "Thai",
script: "Thai",
},
{
value: "tur",
name: "Turkish",
script: "Latn",
},
{
value: "ukr",
name: "Ukrainian",
script: "Cyrl",
},
{
value: "urd",
name: "Urdu",
script: "Arab",
},
{
value: "uzn",
name: "Northern Uzbek",
script: "Latn",
},
{
value: "vie",
name: "Vietnamese",
script: "Latn",
},
{
value: "yor",
name: "Yoruba",
script: "Latn",
},
{
value: "yue",
name: "Cantonese",
script: "Hant",
},
{
value: "zsm",
name: "Standard Malay",
script: "Latn",
},
{
value: "zul",
name: "Zulu",
script: "Latn",
},
];
const supportedLatinLanguages = supportedLanguages.filter(
(lan) => lan.script == "Latn",
);
supportedLatinLanguages.push({ value: undefined, name: "None" });
export { supportedLatinLanguages };
export default supportedLanguages;

View File

@@ -41,14 +41,7 @@ const TranscriptRecord = (details: TranscriptDetails) => {
const webRTC = useWebRTC(stream, details.params.transcriptId, api); const webRTC = useWebRTC(stream, details.params.transcriptId, api);
const webSockets = useWebSockets(details.params.transcriptId); const webSockets = useWebSockets(details.params.transcriptId);
const { const { audioDevices, getAudioStream } = useAudioDevice();
loading,
permissionOk,
permissionDenied,
audioDevices,
requestPermission,
getAudioStream,
} = useAudioDevice();
const [hasRecorded, setHasRecorded] = useState(false); const [hasRecorded, setHasRecorded] = useState(false);
const [transcriptStarted, setTranscriptStarted] = useState(false); const [transcriptStarted, setTranscriptStarted] = useState(false);
@@ -115,7 +108,10 @@ const TranscriptRecord = (details: TranscriptDetails) => {
you start recording. you start recording.
</div> </div>
) : ( ) : (
<LiveTrancription text={webSockets.transcriptText} /> <LiveTrancription
text={webSockets.transcriptText}
translateText={webSockets.translateText}
/>
)} )}
</div> </div>
</div> </div>

View File

@@ -25,7 +25,7 @@ const useCreateTranscript = (): CreateTranscript => {
const requestParameters: V1TranscriptsCreateRequest = { const requestParameters: V1TranscriptsCreateRequest = {
createTranscript: { createTranscript: {
name: params.name || "Weekly All-Hands", // Default name: params.name || "Weekly All-Hands", // Default
targetLanguage: params.targetLanguage || "en", // Default targetLanguage: params.targetLanguage || "eng", // Default
}, },
}; };

View File

@@ -1,14 +1,19 @@
type LiveTranscriptionProps = { type LiveTranscriptionProps = {
text: string; text: string;
translateText: string;
}; };
export default function LiveTrancription(props: LiveTranscriptionProps) { export default function LiveTrancription(props: LiveTranscriptionProps) {
return ( return (
<div className="text-center p-4"> <div className="text-center p-4">
<p className="text-lg md:text-xl font-bold line-clamp-4"> <p className="text-lg md:text-xl font-bold line-clamp-4">
{/* Nous allons prendre quelques appels téléphoniques et répondre à quelques questions */} {props.translateText ? props.translateText : props.text}
{props.text}
</p> </p>
{props.translateText && (
<p className="text-base md:textlg font-bold line-clamp-4">
{props.text}
</p>
)}
</div> </div>
); );
} }

View File

@@ -1,20 +1,15 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import useWebRTC from "../useWebRTC";
import useTranscript from "../useTranscript";
import { useWebSockets } from "../useWebSockets";
import useAudioDevice from "../useAudioDevice"; import useAudioDevice from "../useAudioDevice";
import "../../styles/button.css"; import "../../styles/button.css";
import { Topic } from "../webSocketTypes";
import getApi from "../../lib/getApi"; import getApi from "../../lib/getApi";
import About from "../../(aboutAndPrivacy)/about"; import About from "../../(aboutAndPrivacy)/about";
import Privacy from "../../(aboutAndPrivacy)/privacy"; import Privacy from "../../(aboutAndPrivacy)/privacy";
import { lockWakeState, releaseWakeState } from "../../lib/wakeLock";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import createTranscript from "../createTranscript";
import { GetTranscript } from "../../api";
import { Router } from "next/router";
import useCreateTranscript from "../createTranscript"; import useCreateTranscript from "../createTranscript";
import SelectSearch from "react-select-search";
import { supportedLatinLanguages } from "../../supportedLanguages";
import "react-select-search/style.css";
const TranscriptCreate = () => { const TranscriptCreate = () => {
// const transcript = useTranscript(stream, api); // const transcript = useTranscript(stream, api);
@@ -27,6 +22,10 @@ const TranscriptCreate = () => {
}; };
const [targetLanguage, setTargetLanguage] = useState<string>(); const [targetLanguage, setTargetLanguage] = useState<string>();
const onLanguageChange = (newval) => {
typeof newval === "string" && setTargetLanguage(newval);
};
const createTranscript = useCreateTranscript(); const createTranscript = useCreateTranscript();
const send = () => { const send = () => {
@@ -38,70 +37,75 @@ const TranscriptCreate = () => {
router.push(`/transcripts/${createTranscript.response.id}/record`); router.push(`/transcripts/${createTranscript.response.id}/record`);
}, [createTranscript.response]); }, [createTranscript.response]);
const { const { loading, permissionOk, permissionDenied, requestPermission } =
loading, useAudioDevice();
permissionOk,
permissionDenied,
audioDevices,
requestPermission,
getAudioStream,
} = useAudioDevice();
return ( return (
<> <>
<div></div> <div></div>
<div className="max-h-full overflow-auto"> <div className="grid grid-cols-1 lg:grid-cols-2 grid-rows-mobile-inner lg:grid-rows-1 gap-2 lg:gap-4 h-full">
<section className="flex flex-col w-full h-full items-center justify-evenly p-4 md:px-6 md:py-8"> <section className="flex flex-col w-full h-full items-center justify-evenly p-4 md:px-6 md:py-8">
<div> <div className="flex flex-col max-w-xl items-center justify-center">
<div className="flex flex-col max-w-xl items-center justify-center"> <h1 className="text-2xl font-bold mb-2">
<h1 className="text-2xl font-bold mb-2"> Welcome to reflector.media
Welcome to reflector.media </h1>
</h1> <p>
<p> Reflector is a transcription and summarization pipeline that
Reflector is a transcription and summarization pipeline that transforms audio into knowledge. The output is meeting minutes and
transforms audio into knowledge. The output is meeting minutes topic summaries enabling topic-specific analyses stored in your
and topic summaries enabling topic-specific analyses stored in systems of record. This is accomplished on your infrastructure
your systems of record. This is accomplished on your without 3rd parties keeping your data private, secure, and
infrastructure without 3rd parties keeping your data organized.
private, secure, and organized. </p>
</p> <About buttonText="Learn more" />
<About buttonText="Learn more" />
<input type="text" onChange={nameChange} />
<button onClick={() => setTargetLanguage("fr")}>Language</button>
<h2 className="text-2xl font-bold mt-4 mb-2">
Audio Permissions
</h2>
{loading ? (
<p className="text-center">Checking permission...</p>
) : permissionOk ? (
<> Microphone permission granted </>
) : (
<>
<p className="text-center">
In order to use Reflector, we kindly request permission to
access your microphone during meetings and events.
<br />
<Privacy buttonText="Privacy policy" />
<br />
{permissionDenied
? "Permission to use your microphone was denied, please change the permission setting in your browser and refresh this page."
: "Please grant permission to continue."}
</p>
<button
className="mt-4 bg-blue-400 hover:bg-blue-500 focus-visible:bg-blue-500 text-white font-bold py-2 px-4 rounded m-auto"
onClick={requestPermission}
disabled={permissionDenied}
>
{permissionDenied ? "Access denied" : "Grant Permission"}
</button>
</>
)}
</div>
<button onClick={send} disabled={!permissionOk}>
{createTranscript.loading ? "loading" : "Send"}
</button>
</div> </div>
</section> </section>
<section className="rounded-xl md:bg-blue-200 flex flex-col justify-start p-6">
<h2 className="text-2xl font-bold mt-4 mb-2"> Try Reflector</h2>
<label className="mb-3">
<p>What is this meeting about ?</p>
<input type="text" onChange={nameChange} />
</label>
<label className="mb-3">
<p>Do you need live translation ?</p>
<SelectSearch
search
options={supportedLatinLanguages}
value={targetLanguage}
onChange={onLanguageChange}
placeholder="Choose your language"
/>
</label>
{loading ? (
<p className="">Checking permission...</p>
) : permissionOk ? (
<> Microphone permission granted </>
) : (
<>
<p className="">
In order to use Reflector, we kindly request permission to
access your microphone during meetings and events.
<br />
<Privacy buttonText="Privacy policy" />
<br />
{permissionDenied &&
"Permission to use your microphone was denied, please change the permission setting in your browser and refresh this page."}
</p>
<button
className="mt-4 bg-blue-400 hover:bg-blue-500 focus-visible:bg-blue-500 text-white font-bold py-2 px-4 rounded m-auto"
onClick={requestPermission}
disabled={permissionDenied}
>
{permissionDenied ? "Access denied" : "Grant Permission"}
</button>
</>
)}
<button onClick={send} disabled={!permissionOk}>
{createTranscript.loading ? "loading" : "Send"}
</button>
</section>
</div> </div>
</> </>
); );

View File

@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
type UseWebSockets = { type UseWebSockets = {
transcriptText: string; transcriptText: string;
translateText: string;
topics: Topic[]; topics: Topic[];
finalSummary: FinalSummary; finalSummary: FinalSummary;
status: Status; status: Status;
@@ -12,7 +13,9 @@ type UseWebSockets = {
export const useWebSockets = (transcriptId: string | null): UseWebSockets => { export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
const [transcriptText, setTranscriptText] = useState<string>(""); const [transcriptText, setTranscriptText] = useState<string>("");
const [translateText, setTranslateText] = useState<string>("");
const [textQueue, setTextQueue] = useState<string[]>([]); const [textQueue, setTextQueue] = useState<string[]>([]);
const [translationQueue, setTranslationQueue] = useState<string[]>([]);
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = useState(false);
const [topics, setTopics] = useState<Topic[]>([]); const [topics, setTopics] = useState<Topic[]>([]);
const [finalSummary, setFinalSummary] = useState<FinalSummary>({ const [finalSummary, setFinalSummary] = useState<FinalSummary>({
@@ -30,6 +33,8 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
setIsProcessing(true); setIsProcessing(true);
const text = textQueue[0]; const text = textQueue[0];
setTranscriptText(text); setTranscriptText(text);
setTranslateText(translationQueue[0]);
console.log("displaying " + translateText);
const WPM_READING = 200 + textQueue.length * 10; // words per minute to read const WPM_READING = 200 + textQueue.length * 10; // words per minute to read
const wordCount = text.split(/\s+/).length; const wordCount = text.split(/\s+/).length;
@@ -38,6 +43,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
setTimeout(() => { setTimeout(() => {
setIsProcessing(false); setIsProcessing(false);
setTextQueue((prevQueue) => prevQueue.slice(1)); setTextQueue((prevQueue) => prevQueue.slice(1));
setTranslationQueue((prevQueue) => prevQueue.slice(1));
}, delay); }, delay);
}, [textQueue, isProcessing]); }, [textQueue, isProcessing]);
@@ -158,11 +164,13 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
switch (message.event) { switch (message.event) {
case "TRANSCRIPT": case "TRANSCRIPT":
const newText = (message.data.text ?? "").trim(); const newText = (message.data.text ?? "").trim();
const newTranslation = (message.data.translation ?? "").trim();
if (!newText) break; if (!newText) break;
console.debug("TRANSCRIPT event:", newText); console.debug("TRANSCRIPT event:", newText);
setTextQueue((prevQueue) => [...prevQueue, newText]); setTextQueue((prevQueue) => [...prevQueue, newText]);
setTranslationQueue((prevQueue) => [...prevQueue, newTranslation]);
break; break;
case "TOPIC": case "TOPIC":
@@ -233,5 +241,5 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
}; };
}, [transcriptId]); }, [transcriptId]);
return { transcriptText, topics, finalSummary, status }; return { transcriptText, translateText, topics, finalSummary, status };
}; };

View File

@@ -27,6 +27,7 @@
"react-dropdown": "^1.11.0", "react-dropdown": "^1.11.0",
"react-markdown": "^9.0.0", "react-markdown": "^9.0.0",
"react-qr-code": "^2.0.12", "react-qr-code": "^2.0.12",
"react-select-search": "^4.1.7",
"sass": "^1.63.6", "sass": "^1.63.6",
"simple-peer": "^9.11.1", "simple-peer": "^9.11.1",
"superagent": "^8.0.9", "superagent": "^8.0.9",

View File

@@ -2088,6 +2088,11 @@ react-qr-code@^2.0.12:
prop-types "^15.8.1" prop-types "^15.8.1"
qr.js "0.0.0" qr.js "0.0.0"
react-select-search@^4.1.7:
version "4.1.7"
resolved "https://registry.yarnpkg.com/react-select-search/-/react-select-search-4.1.7.tgz#5662729b9052282bde52e1352006d495d9c5ed6e"
integrity sha512-pU7ONAdK+bmz2tbhBWYQv9m5mnXOn8yImuiy+5UhimIG80d5iKv3nSYJIjJWjDbdrrdoXiCRwQm8xbA8llTjmQ==
react@^18.2.0: react@^18.2.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"