Agricrop / components /ControlTray.tsx
Rohanbagulwar
Commitingcode
7c2c194
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cn from 'classnames';
// FIX: Added missing React imports.
import React, { memo, useEffect, useRef, useState, FormEvent, Ref } from 'react';
import { useLogStore, useUI, useSettings } from '@/lib/state';
import { useLiveAPIContext } from '../contexts/LiveAPIContext';
// Hook to detect screen size for responsive component rendering
const useMediaQuery = (query: string) => {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => {
setMatches(media.matches);
};
media.addEventListener('change', listener);
return () => media.removeEventListener('change', listener);
}, [matches, query]);
return matches;
};
export type ControlTrayProps = {
trayRef?: Ref<HTMLElement>;
};
function ControlTray({trayRef}: ControlTrayProps) {
const [textPrompt, setTextPrompt] = useState('');
const connectButtonRef = useRef<HTMLButtonElement>(null);
const { toggleSidebar } = useUI();
const { activateEasterEggMode } = useSettings();
const settingsClickTimestamps = useRef<number[]>([]);
const isMobile = useMediaQuery('(max-width: 768px), (orientation: landscape) and (max-height: 768px)');
const [isTextEntryVisible, setIsTextEntryVisible] = useState(false);
const isLandscape = useMediaQuery('(orientation: landscape) and (max-height: 768px)');
const { client, connected, connect, disconnect } = useLiveAPIContext();
useEffect(() => {
if (!connected && connectButtonRef.current) {
connectButtonRef.current.focus();
}
}, [connected]);
const handleTextSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!textPrompt.trim()) return;
useLogStore.getState().addTurn({
role: 'user',
text: textPrompt,
isFinal: true,
});
const currentPrompt = textPrompt;
setTextPrompt(''); // Clear input immediately
if (!connected) {
console.warn("Cannot send text message: not connected to live stream.");
useLogStore.getState().addTurn({
role: 'system',
text: `Cannot send message. Please connect to the stream first.`,
isFinal: true,
});
return;
}
client.sendRealtimeText(currentPrompt);
};
const handleSettingsClick = () => {
toggleSidebar();
const now = Date.now();
settingsClickTimestamps.current.push(now);
// Filter out clicks older than 3 seconds
settingsClickTimestamps.current = settingsClickTimestamps.current.filter(
timestamp => now - timestamp < 3000
);
if (settingsClickTimestamps.current.length >= 6) {
activateEasterEggMode();
useLogStore.getState().addTurn({
role: 'system',
text: "You've unlocked Scavenger Hunt mode!.",
isFinal: true,
});
// Reset after triggering
settingsClickTimestamps.current = [];
}
};
const connectButtonTitle = connected ? 'Stop session' : 'Start session';
return (
<section className="control-tray" ref={trayRef}>
<nav className={cn('actions-nav', { 'text-entry-visible-landscape': isLandscape && isTextEntryVisible })}>
<button
ref={connectButtonRef}
className={cn('action-button connect-toggle', { connected })}
onClick={connected ? disconnect : connect}
title={connectButtonTitle}
>
<span className="material-symbols-outlined filled">
{connected ? 'pause' : 'play_arrow'}
</span>
</button>
<button
className={cn('action-button keyboard-toggle-button')}
onClick={() => setIsTextEntryVisible(!isTextEntryVisible)}
title="Toggle text input"
>
<span className="icon">
{isTextEntryVisible ? 'keyboard_hide' : 'keyboard'}
</span>
</button>
{(!isMobile || isTextEntryVisible) && (
<form className="prompt-form" onSubmit={handleTextSubmit}>
<input
type="text"
className="prompt-input"
placeholder={
connected ? 'Type a message...' : 'Connect to start typing...'
}
value={textPrompt}
onChange={e => setTextPrompt(e.target.value)}
aria-label="Text prompt"
disabled={!connected}
/>
<button
type="submit"
className="send-button"
disabled={!textPrompt.trim() || !connected}
aria-label="Send message"
>
<span className="icon">send</span>
</button>
</form>
)}
<button
className={cn('action-button')}
onClick={handleSettingsClick}
title="Settings"
aria-label="Settings"
>
<span className="icon">tune</span>
</button>
</nav>
</section>
);
}
export default memo(ControlTray);