Skip to content

Commit

Permalink
implement auto silence detection to stop recording
Browse files Browse the repository at this point in the history
  • Loading branch information
Mahmoudz committed Apr 21, 2024
1 parent b695781 commit b763bd9
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 213 deletions.
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"@types/howler": "^2.2.11",
"@types/react": "^18.2.79",
"@types/react-dom": "^17.0.0",
"@types/recorder-js": "^1.0.4",
"nodemon": "^3.1.0",
"typescript": "^5.4.5"
},
Expand All @@ -50,7 +49,7 @@
],
"dependencies": {
"howler": "^2.2.4",
"react-icons": "^5.0.1",
"recorder-js": "^1.0.7"
"msr": "^1.3.4",
"react-icons": "^5.0.1"
}
}
256 changes: 128 additions & 128 deletions src/AiAssistantButton.tsx
Original file line number Diff line number Diff line change
@@ -1,144 +1,144 @@
import React, { useState, useEffect } from 'react';
import { useAiAssistant } from './AiAssistantContext';
import { FaMicrophone, FaVolumeUp } from 'react-icons/fa';
import { GiBrainFreeze } from 'react-icons/gi';
import React, { useState, useEffect } from 'react';
import { useAiAssistant } from './AiAssistantContext';
import { FaMicrophone, FaVolumeUp } from 'react-icons/fa';
import { GiBrainFreeze } from 'react-icons/gi';

const injectStyles = () => {
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes bounce {
0%, 100% { transform: scale(0.7); }
50% { transform: scale(1.2); }
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.6); }
100% { box-shadow: 0 0 0 30px rgba(255, 255, 255, 0); }
}
.ai-assistant-button {
width: 75px;
height: 75px;
font-size: 35px;
color: #ffffff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border: none;
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.7);
transition: background-color 0.3s ease-in-out, box-shadow 0.3s ease-in-out;
position: fixed;
bottom: 75px;
right: 75px;
z-index: 9999;
}
@media (max-width: 768px) {
const injectStyles = () => {
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes bounce {
0%, 100% { transform: scale(0.7); }
50% { transform: scale(1.2); }
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.6); }
100% { box-shadow: 0 0 0 30px rgba(255, 255, 255, 0); }
}
.ai-assistant-button {
width: 65px;
height: 65px;
font-size: 30px;
bottom: 25px;
right: 25px;
width: 75px;
height: 75px;
font-size: 35px;
color: #ffffff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
border: none;
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.7);
transition: background-color 0.3s ease-in-out, box-shadow 0.3s ease-in-out;
position: fixed;
bottom: 75px;
right: 75px;
z-index: 9999;
}
}
`;
document.head.appendChild(style);
};
@media (max-width: 768px) {
.ai-assistant-button {
width: 65px;
height: 65px;
font-size: 30px;
bottom: 25px;
right: 25px;
}
}
`;
document.head.appendChild(style);
};

type RecordingState =
| 'STATE_IDLE'
| 'STATE_LISTENING_START'
| 'STATE_THINKING_START'
| 'STATE_SPEAKING_START';
type RecordingState =
| 'STATE_IDLE'
| 'STATE_LISTENING_START'
| 'STATE_THINKING_START'
| 'STATE_SPEAKING_START';

type StateColors = { [key in RecordingState]: string };
type StateColors = { [key in RecordingState]: string };

interface AiAssistantButtonProps {
stateColors?: StateColors;
style?: React.CSSProperties;
[key: string]: any;
}
interface AiAssistantButtonProps {
stateColors?: StateColors;
style?: React.CSSProperties;
[key: string]: any;
}

const AiAssistantButton: React.FC<AiAssistantButtonProps> = ({
stateColors = {},
style = {},
...props
}) => {
const { aiAssistant } = useAiAssistant();
const [recordingState, setRecordingState] = useState<RecordingState>('STATE_IDLE');
const [isButtonDisabled, setButtonDisabled] = useState<boolean>(false);
const [hover, setHover] = useState<boolean>(false);
const AiAssistantButton: React.FC<AiAssistantButtonProps> = ({
stateColors = {},
style = {},
...props
}) => {
const { aiAssistant } = useAiAssistant();
const [recordingState, setRecordingState] = useState<RecordingState>('STATE_IDLE');
const [isButtonDisabled, setButtonDisabled] = useState<boolean>(false);
const [hover, setHover] = useState<boolean>(false);

useEffect(() => {
injectStyles();
}, []);
useEffect(() => {
injectStyles();
}, []);

const defaultStateColors: StateColors = {
STATE_IDLE: '#4a6cf6',
STATE_LISTENING_START: '#F64A7B',
STATE_THINKING_START: '#015589',
STATE_SPEAKING_START: '#019a9a',
};
const defaultStateColors: StateColors = {
STATE_IDLE: '#4a6cf6',
STATE_LISTENING_START: '#F64A7B',
STATE_THINKING_START: '#015589',
STATE_SPEAKING_START: '#019a9a',
};

const colors: StateColors = { ...defaultStateColors, ...stateColors };
const colors: StateColors = { ...defaultStateColors, ...stateColors };

const handleButtonClick = () => {
if (aiAssistant) {
aiAssistant.startProcessing();
}
};
const handleButtonClick = () => {
if (aiAssistant) {
aiAssistant.startProcessing();
}
};

useEffect(() => {
if (aiAssistant) {
const handleStateChange = (newState: string) => {
setRecordingState(newState as RecordingState);
setButtonDisabled(newState !== 'STATE_IDLE');
};
useEffect(() => {
if (aiAssistant) {
const handleStateChange = (newState: string) => {
setRecordingState(newState as RecordingState);
setButtonDisabled(newState !== 'STATE_IDLE');
};

aiAssistant.on('stateChange', handleStateChange);
aiAssistant.on('stateChange', handleStateChange);

return () => {
aiAssistant.off('stateChange', handleStateChange);
};
}
}, [aiAssistant]);
return () => {
aiAssistant.off('stateChange', handleStateChange);
};
}
}, [aiAssistant]);

return (
<button
className="ai-assistant-button"
onClick={handleButtonClick}
disabled={isButtonDisabled}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
backgroundColor: colors[recordingState],
boxShadow: hover
? '0px 0px 10px #4a6cf6'
: '0px 0px 15px rgba(0, 0, 0, 0.9)',
animation:
recordingState === 'STATE_LISTENING_START'
? 'spin 2s infinite'
: recordingState === 'STATE_THINKING_START'
? 'bounce 2s infinite'
: recordingState === 'STATE_SPEAKING_START'
? 'pulse 1.5s infinite'
: 'none',
...style,
}}
{...props}
>
{recordingState === 'STATE_THINKING_START' ? (
<GiBrainFreeze />
) : recordingState === 'STATE_SPEAKING_START' ? (
<FaVolumeUp />
) : (
<FaMicrophone />
)}
</button>
);
};
return (
<button
className="ai-assistant-button"
onClick={handleButtonClick}
disabled={isButtonDisabled}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
backgroundColor: colors[recordingState],
boxShadow: hover
? '0px 0px 10px #4a6cf6'
: '0px 0px 15px rgba(0, 0, 0, 0.9)',
animation:
recordingState === 'STATE_LISTENING_START'
? 'spin 2s infinite'
: recordingState === 'STATE_THINKING_START'
? 'bounce 2s infinite'
: recordingState === 'STATE_SPEAKING_START'
? 'pulse 1.5s infinite'
: 'none',
...style,
}}
{...props}
>
{recordingState === 'STATE_THINKING_START' ? (
<GiBrainFreeze />
) : recordingState === 'STATE_SPEAKING_START' ? (
<FaVolumeUp />
) : (
<FaMicrophone />
)}
</button>
);
};

export { AiAssistantButton };
export { AiAssistantButton };
2 changes: 1 addition & 1 deletion src/core/AiAssistantEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class AiAssistantEngine extends EventEmitter {
Logger.setDebugMode(debugMode);
this.sdkVersion = pkg.version;
Logger.log(
`--[SISTA]-- Initializing AiAssistantEngine Version: ${this.sdkVersion}`,
`--[SISTA]-- Initializing AiAssistantEngine Version: ${this.sdkVersion} + L 1`,
);
this.scrapeContent = scrapeContent;
this.apiKey = apiKey;
Expand Down
Loading

0 comments on commit b763bd9

Please sign in to comment.