diff --git a/package-lock.json b/package-lock.json index 3951476..81c111b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@emotion/styled": "^11.6.0", "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", - "@mui/icons-material": "^5.16.14", + "@mui/icons-material": "^5.18.0", "@mui/material": "^5.16.14", "@reduxjs/toolkit": "^1.9.3", "@testing-library/jest-dom": "^5.16.5", @@ -29,7 +29,6 @@ "@types/webaudioapi": "^0.0.27", "@types/webvtt-parser": "^2.2.0", "axios": "^1.4.0", - "caniuse-lite": "^1.0.30001489", "linspace": "^1.0.2", "mathjs": "^11.5.1", "meyda": "^5.5.1", @@ -3278,9 +3277,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "5.16.14", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.14.tgz", - "integrity": "sha512-heL4S+EawrP61xMXBm59QH6HODsu0gxtZi5JtnXF2r+rghzyU/3Uftlt1ij8rmJh+cFdKTQug1L9KkZB5JgpMQ==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.18.0.tgz", + "integrity": "sha512-1s0vEZj5XFXDMmz3Arl/R7IncFqJ+WQ95LDp1roHWGDE2oCO3IS4/hmiOv1/8SD9r6B7tv9GLiqVZYHo+6PkTg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9" @@ -3780,19 +3779,6 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, "node_modules/@rushstack/eslint-patch": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz", @@ -4392,9 +4378,10 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" }, "node_modules/@types/express": { "version": "4.17.13", @@ -6294,9 +6281,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001489", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz", - "integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==", + "version": "1.0.30001743", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", + "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==", "funding": [ { "type": "opencollective", @@ -6310,7 +6297,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -13182,42 +13170,6 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, - "node_modules/meyda/node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", - "optional": true, - "peer": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", - "fsevents": "~2.3.2" - } - }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", diff --git a/package.json b/package.json index c3973db..ad2c51f 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@emotion/styled": "^11.6.0", "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", - "@mui/icons-material": "^5.16.14", + "@mui/icons-material": "^5.18.0", "@mui/material": "^5.16.14", "@reduxjs/toolkit": "^1.9.3", "@testing-library/jest-dom": "^5.16.5", @@ -25,7 +25,6 @@ "@types/webaudioapi": "^0.0.27", "@types/webvtt-parser": "^2.2.0", "axios": "^1.4.0", - "caniuse-lite": "^1.0.30001489", "linspace": "^1.0.2", "mathjs": "^11.5.1", "meyda": "^5.5.1", diff --git a/src/components/api/recogComponent.tsx b/src/components/api/recogComponent.tsx index e4b2ce9..3e7b103 100644 --- a/src/components/api/recogComponent.tsx +++ b/src/components/api/recogComponent.tsx @@ -11,6 +11,7 @@ import { } from '../../react-redux&middleware/redux/typesImports'; import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import WhisperIcon from '../icons/WhisperIcon'; import { RootState } from '../../store'; import { STTRenderer } from '../sttRenderer'; diff --git a/src/components/icons/WhisperIcon.tsx b/src/components/icons/WhisperIcon.tsx new file mode 100644 index 0000000..c54e020 --- /dev/null +++ b/src/components/icons/WhisperIcon.tsx @@ -0,0 +1,16 @@ +// src/components/icons/WhisperIcon.tsx +import * as React from 'react'; +import { useMediaQuery, useTheme } from '@mui/material'; + + +import GraphicEqIcon from '@mui/icons-material/GraphicEq'; +// import ClosedCaptionIcon from '@mui/icons-material/ClosedCaption'; +// import SettingsVoiceIcon from '@mui/icons-material/SettingsVoice'; + +export default function WhisperIcon() { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + const fontSize: 'small' | 'medium' = isMobile ? 'small' : 'medium'; + + return ; +} diff --git a/src/components/navbars/topbar/api/WhisperDropdown.tsx b/src/components/navbars/topbar/api/WhisperDropdown.tsx index afcdafb..c18229b 100644 --- a/src/components/navbars/topbar/api/WhisperDropdown.tsx +++ b/src/components/navbars/topbar/api/WhisperDropdown.tsx @@ -1,88 +1,80 @@ -import React, { useState } from 'react'; -import { List, ListItem, Button } from '../../../../muiImports' +import React, { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +// Pull UI pieces from the same aggregator used elsewhere +import { + IconButton, + Tooltip, + Menu, + MenuItem, + SettingsIcon, // this should be re-exported by your muiImports like other icons +} from '../../../../muiImports'; -export default function WhisperDropdown(props) { +import { selectSelectedModel } from + '../../../../react-redux&middleware/redux/reducers/modelSelectionReducers'; - // const [isTrue, setIsTrue] = useState(false); - // const [isClearCache, setIsClearCache] = useState(false); - const [isDownloadTiny, setIsDownloadTiny] = useState(false); - const [isDownloadBase, setIsDownloadBase] = useState(false); - // const [progress, setProgress] = useState(null); - // const [showModal, setShowModal] = useState(false); +type Props = { onPicked?: () => void }; +type ModelKey = 'tiny' | 'base'; - const handleTinyDownload = () => { - console.log("clicked download tiny") - setIsDownloadTiny(prevIsDownloadTiny => !prevIsDownloadTiny); - sessionStorage.setItem('isDownloadTiny', (!isDownloadTiny).toString()); - }; +// Helper to normalize current selection into 'tiny' | 'base' | undefined +function normalizeSelected(selected: unknown): ModelKey | undefined { + if (typeof selected === 'string') { + return selected === 'tiny' || selected === 'base' ? selected : undefined; + } + if (selected && typeof selected === 'object' && 'key' in (selected as any)) { + const k = (selected as any).key; + return k === 'tiny' || k === 'base' ? k : undefined; + } + return undefined; +} - const handleBaseDownload = () => { - console.log("clicked download tiny") - setIsDownloadBase(prevIsDownloadBase => !prevIsDownloadBase); - sessionStorage.setItem('isDownloadBase', (!isDownloadBase).toString()); +export default function WhisperDropdown({ onPicked }: Props) { + const dispatch = useDispatch(); + const selected = useSelector(selectSelectedModel); + const selectedKey = normalizeSelected(selected); - const intervalId = setInterval(() => { - let p = sessionStorage.getItem('whisper_progress'); - if (p === '100') { - alert("Base model downloaded! trigger the mike by stopping and starting again"); - clearInterval(intervalId); // Stop checking once it's 100% - } - }, 2000); // Check every 2000 milliseconds, or 2 seconds - }; + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); - // --------------- Using a modal to show the progress + const handleOpen = (e: React.MouseEvent) => setAnchorEl(e.currentTarget); + const handleClose = () => setAnchorEl(null); - // const handleBaseDownload = () => { - // console.log("clicked download base"); - // setIsDownloadBase(prevIsDownloadBase => !prevIsDownloadBase); - // sessionStorage.setItem('isDownloadBase', (!isDownloadBase).toString()); - - // // Initially show the modal - // setShowModal(true); - - // const intervalId = setInterval(() => { - // let p = sessionStorage.getItem('whisper_progress'); - // setProgress(p); // Update the progress - // if (p === '100%') { - // clearInterval(intervalId); // Stop checking once it's 100% - // } - // }, 2000); - // }; - - // // Close the modal when progress reaches 100% - // useEffect(() => { - // if (progress === '100%') { - // setShowModal(false); - // } - // }, [progress]); + const pick = (which: ModelKey) => { + // Keep the existing flags your loader is watching + sessionStorage.setItem('isDownloadTiny', String(which === 'tiny')); + sessionStorage.setItem('isDownloadBase', String(which === 'base')); - + // Update Redux – if you have a real action creator, use it here + dispatch({ type: 'SET_SELECTED_MODEL', payload: which as any }); - // const handleClickClearCache = () => { - // console.log("clicked cache") - // setIsClearCache(prevIsClearCache => !prevIsClearCache); - // sessionStorage.setItem('isClearCache', (!isClearCache).toString()); - // }; + handleClose(); + onPicked?.(); + }; + return ( + <> + {/* Right-aligned gear like other providers */} + + + + + - return ( - <> -
- - - - - - - - {/* TODO: add clear cache to delete the database with whisper.cpp models */} - {/* - - */} - -
- - ) -} \ No newline at end of file + + pick('tiny')}> + TINY (75 MB) + + pick('base')}> + BASE (145 MB) + + + + ); +} diff --git a/src/components/navbars/topbar/api/WhisperSettings.tsx b/src/components/navbars/topbar/api/WhisperSettings.tsx new file mode 100644 index 0000000..e50dd0f --- /dev/null +++ b/src/components/navbars/topbar/api/WhisperSettings.tsx @@ -0,0 +1,104 @@ +// src/components/navbars/topbar/api/WhisperSettings.tsx +import * as React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { + Box, + Menu, + List, + ListItem, + IconButton, + SettingsIcon, + Button, +} from '../../../../muiImports'; + +import { selectSelectedModel } from + '../../../../react-redux&middleware/redux/reducers/modelSelectionReducers'; + +type ModelKey = 'tiny' | 'base'; + +export default function WhisperSettings() { + const [anchorEl, setAnchorEl] = React.useState(null); + const isShown = Boolean(anchorEl); + + const dispatch = useDispatch(); + const selected = useSelector(selectSelectedModel); + + // normalize current selection to 'tiny' | 'base' | undefined + const selectedKey: ModelKey | undefined = React.useMemo(() => { + if (typeof selected === 'string') { + return selected === 'tiny' || selected === 'base' ? selected : undefined; + } + if (selected && typeof selected === 'object' && 'key' in (selected as any)) { + const k = (selected as any).key; + return k === 'tiny' || k === 'base' ? k : undefined; + } + return undefined; + }, [selected]); + + const showPopup = (e: React.MouseEvent) => setAnchorEl(e.currentTarget); + const closePopup = () => setAnchorEl(null); + + const pick = (which: ModelKey) => { + // keep existing flags your loader watches + sessionStorage.setItem('isDownloadTiny', (which === 'tiny').toString()); + sessionStorage.setItem('isDownloadBase', (which === 'base').toString()); + + // update redux + dispatch({ type: 'SET_SELECTED_MODEL', payload: which as any }); + + closePopup(); + }; + + return ( + <> + {/* Right-side gear, same pattern as Azure/ScribeAR Server */} + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/navbars/topbar/api/pickApi.tsx b/src/components/navbars/topbar/api/pickApi.tsx index 7848118..ca4172b 100644 --- a/src/components/navbars/topbar/api/pickApi.tsx +++ b/src/components/navbars/topbar/api/pickApi.tsx @@ -1,287 +1,270 @@ import * as React from 'react'; -import { useDispatch, useSelector } from "react-redux"; +import { useDispatch, useSelector } from 'react-redux'; import { - API, - ApiStatus, - ApiType, - AzureStatus, - ControlStatus, - RootState, - STATUS, - StreamTextStatus, - ScribearServerStatus, - PlaybackStatus + API, + ApiStatus, + ApiType, + AzureStatus, + ControlStatus, + RootState, + STATUS, + StreamTextStatus, + ScribearServerStatus, + PlaybackStatus, } from '../../../../react-redux&middleware/redux/typesImports'; -import { CancelIcon, CheckCircleIcon, Collapse, DoNotDisturbOnIcon, ErrorIcon, ExpandLess, ExpandMore, IconButton, ListItemButton, ListItemIcon, ListItemText, ThemeProvider, createTheme } from '../../../../muiImports' + +import { + CancelIcon, + CheckCircleIcon, + Collapse, + DoNotDisturbOnIcon, + ErrorIcon, + ExpandLess, + ExpandMore, + IconButton, + ListItemButton, + ListItemIcon, + ListItemText, + ThemeProvider, + createTheme, +} from '../../../../muiImports'; import { ListItem } from '@mui/material'; import AzureSettings from './AzureSettings'; import StreamTextSettings from './StreamTextSettings'; -import ScribearServerSettings from './ScribearServerSettings' +import ScribearServerSettings from './ScribearServerSettings'; import PlaybackSettings from './PlaybackSettings'; -import WhisperDropdown from './WhisperDropdown'; +import WhisperSettings from './WhisperSettings'; // <-- use settings-gear dialog for Whisper + import swal from 'sweetalert'; import { testAzureTranslRecog } from '../../../api/azure/azureTranslRecog'; const currTheme = createTheme({ - palette: { - primary: { - main: '#ffffff', - }, - success: { - main: '#4caf50' - }, - warning: { - main: '#f44336' - }, - error: { - main: '#C8C224' - } - } + palette: { + primary: { main: '#ffffff' }, + success: { main: '#4caf50' }, + warning: { main: '#f44336' }, + error: { main: '#C8C224' }, + }, }); -/** - * Icon component whose symbol and color represent the current availability of a given API - * @param currentApi the API to represent - * @returns The icon component - */ +/** Status icon shown on the left of each API row */ const IconStatus = (currentApi: any) => { - const myTheme = currTheme; - switch (currentApi.currentApi) { - case STATUS.AVAILABLE: - return ( - - - - ) - - case STATUS.TRANSCRIBING: - return ( - - - - ) - case STATUS.UNAVAILABLE: - return ( - - - - ) - } - return ( - - - - ) -} + const myTheme = currTheme; + switch (currentApi.currentApi) { + case STATUS.AVAILABLE: + return ( + + + + ); + case STATUS.TRANSCRIBING: + return ( + + + + ); + case STATUS.UNAVAILABLE: + return ( + + + + ); + default: + return ( + + + + ); + } +}; -// Switch to azure -> keep api menu open -> cannot switch to webspeech -// Switch to azure -> reopen api menu -> can switch to webspeech -> webspeech doesn't work until microphone restarted -export default function PickApi(props) { - const dispatch = useDispatch(); - const myTheme = currTheme; +export default function PickApi() { + const dispatch = useDispatch(); + const myTheme = currTheme; - const apiStatus = useSelector((state: RootState) => { - return state.APIStatusReducer as ApiStatus; - }) - const controlStatus = useSelector((state: RootState) => { - return state.ControlReducer as ControlStatus; - }) - const azureStatus = useSelector((state: RootState) => { - return state.AzureReducer as AzureStatus; - }) - const streamTextStatus = useSelector((state: RootState) => { - return state.StreamTextReducer as StreamTextStatus; - }) - const scribearServerStatus = useSelector((state: RootState) => { - return state.ScribearServerReducer as ScribearServerStatus; - }) + const apiStatus = useSelector((state: RootState) => state.APIStatusReducer as ApiStatus); + const controlStatus = useSelector((state: RootState) => state.ControlReducer as ControlStatus); + const azureStatus = useSelector((state: RootState) => state.AzureReducer as AzureStatus); + const streamTextStatus = useSelector((state: RootState) => state.StreamTextReducer as StreamTextStatus); + const scribearServerStatus = useSelector((state: RootState) => state.ScribearServerReducer as ScribearServerStatus); + const playbackStatus = useSelector((state: RootState) => state.PlaybackReducer as PlaybackStatus); + const [state, setState] = React.useState({ + showAzureDropdown: false, + showWhisperDropdown: false, + }); - const playbackStatus = useSelector((state: RootState) => { - return state.PlaybackReducer as PlaybackStatus; - }) + const switchToAzure = async () => { + dispatch({ type: 'FLIP_RECORDING', payload: controlStatus }); + const copyStatus = { ...apiStatus }; + testAzureTranslRecog(controlStatus, azureStatus) + .then(() => { + localStorage.setItem('azureStatus', JSON.stringify(azureStatus)); - const [state, setState] = React.useState({ - showAzureDropdown: false, - showWhisperDropdown: false, - }); + copyStatus.currentApi = API.AZURE_TRANSLATION; + copyStatus.azureTranslStatus = STATUS.TRANSCRIBING; + copyStatus.webspeechStatus = STATUS.AVAILABLE; + copyStatus.azureConvoStatus = STATUS.AVAILABLE; + copyStatus.whisperStatus = STATUS.AVAILABLE; + copyStatus.streamTextStatus = STATUS.AVAILABLE; + copyStatus.playbackStatus = STATUS.AVAILABLE; - // TODO: change Whisper dropdown to pop-up as well - // TODO: merge switchToAzure and toggleDrawer into one switch-to function - const switchToAzure = async () => { - dispatch({type: 'FLIP_RECORDING', payload: controlStatus}); - let copyStatus = Object.assign({}, apiStatus); - testAzureTranslRecog(controlStatus, azureStatus).then(() => { - // fullfill (test good) - localStorage.setItem("azureStatus", JSON.stringify(azureStatus)); - - copyStatus.currentApi = API.AZURE_TRANSLATION; - // Ugh - copyStatus.azureTranslStatus = STATUS.TRANSCRIBING; - copyStatus.webspeechStatus = STATUS.AVAILABLE; - copyStatus.azureConvoStatus = STATUS.AVAILABLE; - copyStatus.whisperStatus = STATUS.AVAILABLE; - copyStatus.streamTextStatus = STATUS.AVAILABLE; - copyStatus.playbackStatus = STATUS.AVAILABLE; + swal({ + title: 'Success!', + text: 'Switching to Microsoft Azure', + icon: 'success', + timer: 1500, + }); - swal({ - title: "Success!", - text: "Switching to Microsoft Azure", - icon: "success", - timer: 1500, - }) - - dispatch({type: 'CHANGE_API_STATUS', payload: copyStatus}); - }, (error)=> { - // reject (test bad) - console.log("error"); - copyStatus.azureTranslStatus = STATUS.ERROR; - swal({ - title: "Warning!", - text: `${error}`, - icon: "warning", - }) - }).finally(() => { - dispatch({type: 'FLIP_RECORDING', payload: controlStatus}) + dispatch({ type: 'CHANGE_API_STATUS', payload: copyStatus }); + }) + .catch((error) => { + copyStatus.azureTranslStatus = STATUS.ERROR; + swal({ title: 'Warning!', text: `${error}`, icon: 'warning' }); }) - } + .finally(() => { + dispatch({ type: 'FLIP_RECORDING', payload: controlStatus }); + }); + }; - const switchToStreamText = (event: React.KeyboardEvent | React.MouseEvent) => { - localStorage.setItem("streamTextStatus", JSON.stringify(streamTextStatus)); - return toggleDrawer("streamTextStatus", API.STREAM_TEXT, false)(event) - } - const switchToScribearServer = (event: React.KeyboardEvent | React.MouseEvent) => { - localStorage.setItem("scribearServerStatus", JSON.stringify(scribearServerStatus)); - return toggleDrawer("scribearServerStatus", API.SCRIBEAR_SERVER, false)(event) - } - - const switchToPlayback = (event: React.KeyboardEvent | React.MouseEvent) => { - localStorage.setItem("playbackStatus", JSON.stringify(playbackStatus)); - return toggleDrawer("playbackStatus", API.PLAYBACK, false)(event) - } - + const switchToStreamText = (event: React.KeyboardEvent | React.MouseEvent) => { + localStorage.setItem('streamTextStatus', JSON.stringify(streamTextStatus)); + return toggleDrawer('streamTextStatus', API.STREAM_TEXT, false)(event); + }; - const toggleDrawer = - (apiStat: string, api:ApiType, isArrow:boolean) => - (event: React.KeyboardEvent | React.MouseEvent) => { - if (apiStatus.currentApi !== api) { - if (!isArrow) { - let copyStatus = Object.assign({}, apiStatus); - copyStatus.currentApi = api; - let apiName = ""; - //Ugh - copyStatus.azureTranslStatus = STATUS.AVAILABLE; - copyStatus.webspeechStatus = STATUS.AVAILABLE; - copyStatus.azureConvoStatus = STATUS.AVAILABLE; - copyStatus.whisperStatus = STATUS.AVAILABLE; - copyStatus.streamTextStatus = STATUS.AVAILABLE; - copyStatus.scribearServerStatus = STATUS.AVAILABLE; - copyStatus.playbackStatus = STATUS.AVAILABLE; + const switchToScribearServer = (event: React.KeyboardEvent | React.MouseEvent) => { + localStorage.setItem('scribearServerStatus', JSON.stringify(scribearServerStatus)); + return toggleDrawer('scribearServerStatus', API.SCRIBEAR_SERVER, false)(event); + }; - if (api === API.AZURE_TRANSLATION) { - apiName = "Microsoft Azure"; - copyStatus.azureTranslStatus = STATUS.TRANSCRIBING; - } else if (api === API.WHISPER) { - apiName = "Whisper"; - copyStatus.whisperStatus = STATUS.TRANSCRIBING - } else if (api === API.WEBSPEECH) { - copyStatus.webspeechStatus = STATUS.TRANSCRIBING - } else if (api === API.STREAM_TEXT) { - apiName = "StreamText"; - copyStatus.streamTextStatus = STATUS.TRANSCRIBING - } else if (api === API.SCRIBEAR_SERVER) { - apiName = "ScribeAR Server"; - copyStatus.scribearServerStatus = STATUS.TRANSCRIBING - } else if (api === API.PLAYBACK) { - apiName = "Playback"; - copyStatus.playbackStatus = STATUS.TRANSCRIBING - } - console.log(88, copyStatus); - dispatch({type: 'CHANGE_API_STATUS', payload: copyStatus}); - swal({ - title: "Success!", - text: "Switching to " + apiName, - icon: "success", - timer: 2500, - - }) - } else { - setState({ ...state, [apiStat]: !state[apiStat] }); - } - } else if (isArrow) { - setState({ ...state, [apiStat]: !state[apiStat] }); - } - } + const switchToPlayback = (event: React.KeyboardEvent | React.MouseEvent) => { + localStorage.setItem('playbackStatus', JSON.stringify(playbackStatus)); + return toggleDrawer('playbackStatus', API.PLAYBACK, false)(event); + }; - return ( -
- - - - - - - - + const toggleDrawer = + (apiStat: string, api: ApiType, isArrow: boolean) => + (_event: React.KeyboardEvent | React.MouseEvent) => { + if (apiStatus.currentApi !== api) { + if (!isArrow) { + const copyStatus = { ...apiStatus }; + copyStatus.currentApi = api; - - - - - - - + // reset all + copyStatus.azureTranslStatus = STATUS.AVAILABLE; + copyStatus.webspeechStatus = STATUS.AVAILABLE; + copyStatus.azureConvoStatus = STATUS.AVAILABLE; + copyStatus.whisperStatus = STATUS.AVAILABLE; + copyStatus.streamTextStatus = STATUS.AVAILABLE; + copyStatus.scribearServerStatus = STATUS.AVAILABLE; + copyStatus.playbackStatus = STATUS.AVAILABLE; - - + let apiName = ''; + if (api === API.AZURE_TRANSLATION) { + apiName = 'Microsoft Azure'; + copyStatus.azureTranslStatus = STATUS.TRANSCRIBING; + } else if (api === API.WHISPER) { + apiName = 'Whisper'; + copyStatus.whisperStatus = STATUS.TRANSCRIBING; + } else if (api === API.WEBSPEECH) { + copyStatus.webspeechStatus = STATUS.TRANSCRIBING; + } else if (api === API.STREAM_TEXT) { + apiName = 'StreamText'; + copyStatus.streamTextStatus = STATUS.TRANSCRIBING; + } else if (api === API.SCRIBEAR_SERVER) { + apiName = 'ScribeAR Server'; + copyStatus.scribearServerStatus = STATUS.TRANSCRIBING; + } else if (api === API.PLAYBACK) { + apiName = 'Playback'; + copyStatus.playbackStatus = STATUS.TRANSCRIBING; + } - - - - - - - - - + dispatch({ type: 'CHANGE_API_STATUS', payload: copyStatus }); + swal({ + title: 'Success!', + text: 'Switching to ' + apiName, + icon: 'success', + timer: 2500, + }); + } else { + setState((s) => ({ ...s, [apiStat]: !s[apiStat] })); + } + } else if (isArrow) { + setState((s) => ({ ...s, [apiStat]: !s[apiStat] })); + } + }; - - - - - - - + return ( +
+ {/* Webspeech */} + + + + + + + + - - + {/* Microsoft Azure */} + + + + + + + + + - - - - - - - + {/* ScribeAR Server */} + + + + + + + + + - - + {/* Playback */} + + + + + + + + + - - - - - - - {state.showWhisperDropdown ? : } - - + {/* StreamText */} + + + + + + + + + - - - -
- ); -} \ No newline at end of file + {/* Whisper (now matches others: button + gear on the right) */} + + + + + + + + + +
+ ); +} diff --git a/src/components/navbars/topbar/apiDropdown.tsx b/src/components/navbars/topbar/apiDropdown.tsx index c5c5737..8d76c2b 100644 --- a/src/components/navbars/topbar/apiDropdown.tsx +++ b/src/components/navbars/topbar/apiDropdown.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useEffect } from "react"; import { Box, Menu, ExpandLess, ExpandMore, ThemeProvider, IconButton, Tooltip } from '../../../muiImports'; import { createTheme, useMediaQuery } from '@mui/material'; +import WhisperIcon from '../../icons/WhisperIcon'; import PickApi from './api/pickApi'; import { @@ -90,6 +91,7 @@ export default function ApiDropdown(props) { }, [accessToken, dispatch, mode, kioskServerAddress, serverAddress, sourceToken]); const isMobile = useMediaQuery(currTheme.breakpoints.down('sm')); + const isWhisperActive = apiStatus?.currentApi === API.WHISPER || (typeof props.apiDisplayName === 'string' && props.apiDisplayName.toLowerCase().includes('whisper')); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); @@ -101,8 +103,9 @@ export default function ApiDropdown(props) { // Make this a dropdown menu with the current api as the menu title return ( - - {props.apiDisplayName} + + {isWhisperActive ? : null} + {props.apiDisplayName}