Agricrop / components /Sidebar.tsx
Rohanbagulwar
Commitingcode
7c2c194
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
// FIX: Added missing React imports.
import React, { useEffect, useMemo } from 'react';
import { useSettings, useUI, useLogStore, useTools, personas } from '@/lib/state';
import c from 'classnames';
import {
AVAILABLE_VOICES_FULL,
AVAILABLE_VOICES_LIMITED,
MODELS_WITH_LIMITED_VOICES,
DEFAULT_VOICE,
} from '@/lib/constants';
import { useLiveAPIContext } from '@/contexts/LiveAPIContext';
const AVAILABLE_MODELS = [
'gemini-2.5-flash-native-audio-preview-09-2025',
'gemini-2.5-flash-native-audio-latest',
'gemini-live-2.5-flash-preview',
'gemini-2.0-flash-live-001'
];
export default function Sidebar() {
const {
isSidebarOpen,
toggleSidebar,
showSystemMessages,
toggleShowSystemMessages,
} = useUI();
const {
systemPrompt,
model,
voice,
setSystemPrompt,
setModel,
setVoice,
isEasterEggMode,
activePersona,
setPersona,
} = useSettings();
const { connected } = useLiveAPIContext();
const availableVoices = useMemo(() => {
return MODELS_WITH_LIMITED_VOICES.includes(model)
? AVAILABLE_VOICES_LIMITED
: AVAILABLE_VOICES_FULL;
}, [model]);
useEffect(() => {
if (!availableVoices.some(v => v.name === voice)) {
setVoice(DEFAULT_VOICE);
}
}, [availableVoices, voice, setVoice]);
const handleExportLogs = () => {
const { systemPrompt, model } = useSettings.getState();
const { tools } = useTools.getState();
const { turns } = useLogStore.getState();
const logData = {
configuration: {
model,
systemPrompt,
},
tools,
conversation: turns.map(turn => ({
...turn,
// Convert Date object to ISO string for JSON serialization
timestamp: turn.timestamp.toISOString(),
})),
};
const jsonString = JSON.stringify(logData, null, 2);
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
a.href = url;
a.download = `live-api-logs-${timestamp}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
return (
<>
<aside className={c('sidebar', { open: isSidebarOpen })}>
<div className="sidebar-header">
<h3>Settings</h3>
<button onClick={toggleSidebar} className="close-button">
<span className="icon">close</span>
</button>
</div>
<div className="sidebar-content">
<div className="sidebar-section">
<fieldset disabled={connected}>
{isEasterEggMode && (
<label>
Persona
<select
value={activePersona}
onChange={e => setPersona(e.target.value)}
>
{Object.keys(personas).map(personaName => (
<option key={personaName} value={personaName}>
{personaName}
</option>
))}
</select>
</label>
)}
<label>
System Prompt
<textarea
value={systemPrompt}
onChange={e => setSystemPrompt(e.target.value)}
rows={10}
placeholder="Describe the role and personality of the AI..."
disabled={isEasterEggMode}
/>
</label>
<label>
Model
<select
value={model}
onChange={e => setModel(e.target.value)}
disabled={!isEasterEggMode}
>
{/* This is an experimental model name that should not be removed from the options. */}
{AVAILABLE_MODELS.map(m => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
</label>
<label>
Voice
<select
value={voice}
onChange={e => setVoice(e.target.value)}
>
{availableVoices.map(v => (
<option key={v.name} value={v.name}>
{v.name} ({v.description})
</option>
))}
</select>
</label>
</fieldset>
<div className="settings-toggle-item">
<label className="tool-checkbox-wrapper">
<input
type="checkbox"
id="system-message-toggle"
checked={showSystemMessages}
onChange={toggleShowSystemMessages}
/>
<span className="checkbox-visual"></span>
</label>
<label
htmlFor="system-message-toggle"
className="settings-toggle-label"
>
Show system messages
</label>
</div>
</div>
<div className="sidebar-actions">
<button onClick={handleExportLogs} title="Export session logs">
<span className="icon">download</span>
Export Logs
</button>
<button
onClick={useLogStore.getState().clearTurns}
title="Reset session logs"
>
<span className="icon">refresh</span>
Reset Session
</button>
</div>
</div>
</aside>
</>
);
}