Spaces:
Running
Running
Updated to only use enhanced commentary
Browse files- reachy_f1_commentator/main.py +3 -7
- reachy_f1_commentator/src/config.py +5 -1
- reachy_f1_commentator/static/index.html +2 -10
- reachy_f1_commentator/static/main.js +68 -37
- test_ui.html +64 -0
reachy_f1_commentator/main.py
CHANGED
|
@@ -161,18 +161,14 @@ class ReachyF1Commentator(ReachyMiniApp):
|
|
| 161 |
logger.info(f"Starting playback - Reachy instance available: {self.reachy_mini_instance is not None}")
|
| 162 |
self.playback_status.state = 'playing'
|
| 163 |
|
| 164 |
-
# Initialize commentary generator
|
| 165 |
from .src.config import Config
|
| 166 |
gen_config = Config()
|
| 167 |
gen_config.elevenlabs_api_key = config.elevenlabs_api_key
|
| 168 |
gen_config.elevenlabs_voice_id = config.elevenlabs_voice_id
|
|
|
|
| 169 |
|
| 170 |
-
|
| 171 |
-
gen_config.enhanced_mode = True
|
| 172 |
-
self.commentary_generator = EnhancedCommentaryGenerator(gen_config, self.state_tracker)
|
| 173 |
-
else:
|
| 174 |
-
gen_config.enhanced_mode = False
|
| 175 |
-
self.commentary_generator = CommentaryGenerator(gen_config, self.state_tracker)
|
| 176 |
|
| 177 |
# Initialize speech synthesizer if API key provided
|
| 178 |
speech_synthesizer = None
|
|
|
|
| 161 |
logger.info(f"Starting playback - Reachy instance available: {self.reachy_mini_instance is not None}")
|
| 162 |
self.playback_status.state = 'playing'
|
| 163 |
|
| 164 |
+
# Initialize commentary generator (always use Enhanced mode)
|
| 165 |
from .src.config import Config
|
| 166 |
gen_config = Config()
|
| 167 |
gen_config.elevenlabs_api_key = config.elevenlabs_api_key
|
| 168 |
gen_config.elevenlabs_voice_id = config.elevenlabs_voice_id
|
| 169 |
+
gen_config.enhanced_mode = True
|
| 170 |
|
| 171 |
+
self.commentary_generator = EnhancedCommentaryGenerator(gen_config, self.state_tracker)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
# Initialize speech synthesizer if API key provided
|
| 174 |
speech_synthesizer = None
|
reachy_f1_commentator/src/config.py
CHANGED
|
@@ -93,7 +93,11 @@ class Config:
|
|
| 93 |
perspective_weight_historical: float = 0.10
|
| 94 |
|
| 95 |
# Template Selection Configuration (Requirement 17.2, 17.5)
|
| 96 |
-
template_file: str =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
template_repetition_window: int = 10
|
| 98 |
max_sentence_length: int = 40
|
| 99 |
|
|
|
|
| 93 |
perspective_weight_historical: float = 0.10
|
| 94 |
|
| 95 |
# Template Selection Configuration (Requirement 17.2, 17.5)
|
| 96 |
+
template_file: str = field(default_factory=lambda: os.path.join(
|
| 97 |
+
os.path.dirname(os.path.dirname(__file__)),
|
| 98 |
+
"config",
|
| 99 |
+
"enhanced_templates.json"
|
| 100 |
+
))
|
| 101 |
template_repetition_window: int = 10
|
| 102 |
max_sentence_length: int = 40
|
| 103 |
|
reachy_f1_commentator/static/index.html
CHANGED
|
@@ -45,20 +45,12 @@
|
|
| 45 |
</div>
|
| 46 |
</div>
|
| 47 |
|
| 48 |
-
<div class="form-group">
|
| 49 |
-
<label for="commentaryMode">Commentary Mode:</label>
|
| 50 |
-
<select id="commentaryMode" class="form-control">
|
| 51 |
-
<option value="enhanced" selected>Enhanced (Recommended)</option>
|
| 52 |
-
<option value="basic">Basic</option>
|
| 53 |
-
</select>
|
| 54 |
-
</div>
|
| 55 |
-
|
| 56 |
<div class="form-group">
|
| 57 |
<label for="playbackSpeed">Playback Speed:</label>
|
| 58 |
<select id="playbackSpeed" class="form-control">
|
| 59 |
-
<option value="1">1x (Real-time)</option>
|
| 60 |
<option value="5">5x</option>
|
| 61 |
-
<option value="10"
|
| 62 |
<option value="20">20x</option>
|
| 63 |
</select>
|
| 64 |
</div>
|
|
|
|
| 45 |
</div>
|
| 46 |
</div>
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
<div class="form-group">
|
| 49 |
<label for="playbackSpeed">Playback Speed:</label>
|
| 50 |
<select id="playbackSpeed" class="form-control">
|
| 51 |
+
<option value="1" selected>1x (Real-time - Recommended)</option>
|
| 52 |
<option value="5">5x</option>
|
| 53 |
+
<option value="10">10x</option>
|
| 54 |
<option value="20">20x</option>
|
| 55 |
</select>
|
| 56 |
</div>
|
reachy_f1_commentator/static/main.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
// Reachy F1 Commentator - Web UI JavaScript
|
|
|
|
| 2 |
|
| 3 |
// LocalStorage keys
|
| 4 |
const STORAGE_KEYS = {
|
|
@@ -11,36 +12,46 @@ const state = {
|
|
| 11 |
mode: 'quick_demo',
|
| 12 |
selectedYear: null,
|
| 13 |
selectedRace: null,
|
| 14 |
-
|
| 15 |
-
playbackSpeed: 10,
|
| 16 |
elevenLabsApiKey: '',
|
| 17 |
elevenLabsVoiceId: 'HSSEHuB5EziJgTfCVmC6',
|
| 18 |
status: 'idle',
|
| 19 |
statusPollInterval: null
|
| 20 |
};
|
| 21 |
|
| 22 |
-
// DOM elements
|
| 23 |
-
|
| 24 |
-
mode: document.getElementById('mode'),
|
| 25 |
-
year: document.getElementById('year'),
|
| 26 |
-
race: document.getElementById('race'),
|
| 27 |
-
commentaryMode: document.getElementById('commentaryMode'),
|
| 28 |
-
playbackSpeed: document.getElementById('playbackSpeed'),
|
| 29 |
-
apiKey: document.getElementById('apiKey'),
|
| 30 |
-
voiceId: document.getElementById('voiceId'),
|
| 31 |
-
startBtn: document.getElementById('startBtn'),
|
| 32 |
-
stopBtn: document.getElementById('stopBtn'),
|
| 33 |
-
raceSelection: document.getElementById('raceSelection'),
|
| 34 |
-
statusIndicator: document.getElementById('statusIndicator'),
|
| 35 |
-
statusText: document.getElementById('statusText'),
|
| 36 |
-
progressPanel: document.getElementById('progressPanel'),
|
| 37 |
-
currentLap: document.getElementById('currentLap'),
|
| 38 |
-
totalLaps: document.getElementById('totalLaps'),
|
| 39 |
-
elapsedTime: document.getElementById('elapsedTime')
|
| 40 |
-
};
|
| 41 |
|
| 42 |
// Initialize
|
| 43 |
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
loadSavedCredentials();
|
| 45 |
setupEventListeners();
|
| 46 |
loadYears();
|
|
@@ -125,29 +136,36 @@ async function saveServerConfig(apiKey, voiceId) {
|
|
| 125 |
}
|
| 126 |
|
| 127 |
function setupEventListeners() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
elements.mode.addEventListener('change', handleModeChange);
|
| 129 |
elements.year.addEventListener('change', handleYearChange);
|
| 130 |
elements.startBtn.addEventListener('click', handleStart);
|
| 131 |
elements.stopBtn.addEventListener('click', handleStop);
|
| 132 |
|
| 133 |
// Save form values to state
|
| 134 |
-
elements.
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
state.playbackSpeed = parseInt(e.target.value);
|
| 140 |
-
});
|
| 141 |
|
| 142 |
-
elements.apiKey
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
| 146 |
|
| 147 |
-
elements.voiceId
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
| 151 |
}
|
| 152 |
|
| 153 |
function handleModeChange(e) {
|
|
@@ -161,9 +179,15 @@ function handleModeChange(e) {
|
|
| 161 |
}
|
| 162 |
|
| 163 |
async function loadYears() {
|
|
|
|
|
|
|
|
|
|
| 164 |
try {
|
| 165 |
const response = await fetch('/api/races/years');
|
|
|
|
|
|
|
| 166 |
const data = await response.json();
|
|
|
|
| 167 |
|
| 168 |
if (data.years && data.years.length > 0) {
|
| 169 |
elements.year.innerHTML = '<option value="">Select year...</option>';
|
|
@@ -173,6 +197,7 @@ async function loadYears() {
|
|
| 173 |
option.textContent = year;
|
| 174 |
elements.year.appendChild(option);
|
| 175 |
});
|
|
|
|
| 176 |
}
|
| 177 |
} catch (error) {
|
| 178 |
console.error('Failed to load years:', error);
|
|
@@ -184,6 +209,8 @@ async function handleYearChange(e) {
|
|
| 184 |
const year = e.target.value;
|
| 185 |
state.selectedYear = year;
|
| 186 |
|
|
|
|
|
|
|
| 187 |
if (!year) {
|
| 188 |
elements.race.innerHTML = '<option value="">Select year first</option>';
|
| 189 |
return;
|
|
@@ -193,7 +220,10 @@ async function handleYearChange(e) {
|
|
| 193 |
|
| 194 |
try {
|
| 195 |
const response = await fetch(`/api/races/${year}`);
|
|
|
|
|
|
|
| 196 |
const data = await response.json();
|
|
|
|
| 197 |
|
| 198 |
if (data.races && data.races.length > 0) {
|
| 199 |
elements.race.innerHTML = '<option value="">Select race...</option>';
|
|
@@ -209,6 +239,7 @@ async function handleYearChange(e) {
|
|
| 209 |
|
| 210 |
elements.race.appendChild(option);
|
| 211 |
});
|
|
|
|
| 212 |
} else {
|
| 213 |
elements.race.innerHTML = '<option value="">No races found</option>';
|
| 214 |
}
|
|
@@ -234,7 +265,7 @@ async function handleStart() {
|
|
| 234 |
const config = {
|
| 235 |
mode: state.mode,
|
| 236 |
session_key: state.mode === 'full_race' ? parseInt(elements.race.value) : null,
|
| 237 |
-
commentary_mode:
|
| 238 |
playback_speed: state.playbackSpeed,
|
| 239 |
elevenlabs_api_key: state.elevenLabsApiKey,
|
| 240 |
elevenlabs_voice_id: state.elevenLabsVoiceId
|
|
|
|
| 1 |
// Reachy F1 Commentator - Web UI JavaScript
|
| 2 |
+
// Version: 2.0 - Fixed DOM initialization
|
| 3 |
|
| 4 |
// LocalStorage keys
|
| 5 |
const STORAGE_KEYS = {
|
|
|
|
| 12 |
mode: 'quick_demo',
|
| 13 |
selectedYear: null,
|
| 14 |
selectedRace: null,
|
| 15 |
+
playbackSpeed: 1,
|
|
|
|
| 16 |
elevenLabsApiKey: '',
|
| 17 |
elevenLabsVoiceId: 'HSSEHuB5EziJgTfCVmC6',
|
| 18 |
status: 'idle',
|
| 19 |
statusPollInterval: null
|
| 20 |
};
|
| 21 |
|
| 22 |
+
// DOM elements (will be initialized after DOM loads)
|
| 23 |
+
let elements = {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
// Initialize
|
| 26 |
document.addEventListener('DOMContentLoaded', () => {
|
| 27 |
+
console.log('DOM Content Loaded');
|
| 28 |
+
|
| 29 |
+
// Initialize DOM elements
|
| 30 |
+
elements = {
|
| 31 |
+
mode: document.getElementById('mode'),
|
| 32 |
+
year: document.getElementById('year'),
|
| 33 |
+
race: document.getElementById('race'),
|
| 34 |
+
playbackSpeed: document.getElementById('playbackSpeed'),
|
| 35 |
+
apiKey: document.getElementById('apiKey'),
|
| 36 |
+
voiceId: document.getElementById('voiceId'),
|
| 37 |
+
startBtn: document.getElementById('startBtn'),
|
| 38 |
+
stopBtn: document.getElementById('stopBtn'),
|
| 39 |
+
raceSelection: document.getElementById('raceSelection'),
|
| 40 |
+
statusIndicator: document.getElementById('statusIndicator'),
|
| 41 |
+
statusText: document.getElementById('statusText'),
|
| 42 |
+
progressPanel: document.getElementById('progressPanel'),
|
| 43 |
+
currentLap: document.getElementById('currentLap'),
|
| 44 |
+
totalLaps: document.getElementById('totalLaps'),
|
| 45 |
+
elapsedTime: document.getElementById('elapsedTime')
|
| 46 |
+
};
|
| 47 |
+
|
| 48 |
+
// Check for missing elements
|
| 49 |
+
for (const [key, element] of Object.entries(elements)) {
|
| 50 |
+
if (!element) {
|
| 51 |
+
console.error(`Element not found: ${key}`);
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
loadSavedCredentials();
|
| 56 |
setupEventListeners();
|
| 57 |
loadYears();
|
|
|
|
| 136 |
}
|
| 137 |
|
| 138 |
function setupEventListeners() {
|
| 139 |
+
if (!elements.mode || !elements.year || !elements.startBtn || !elements.stopBtn) {
|
| 140 |
+
console.error('Required elements not found for event listeners');
|
| 141 |
+
return;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
elements.mode.addEventListener('change', handleModeChange);
|
| 145 |
elements.year.addEventListener('change', handleYearChange);
|
| 146 |
elements.startBtn.addEventListener('click', handleStart);
|
| 147 |
elements.stopBtn.addEventListener('click', handleStop);
|
| 148 |
|
| 149 |
// Save form values to state
|
| 150 |
+
if (elements.playbackSpeed) {
|
| 151 |
+
elements.playbackSpeed.addEventListener('change', (e) => {
|
| 152 |
+
state.playbackSpeed = parseInt(e.target.value);
|
| 153 |
+
});
|
| 154 |
+
}
|
|
|
|
|
|
|
| 155 |
|
| 156 |
+
if (elements.apiKey) {
|
| 157 |
+
elements.apiKey.addEventListener('change', (e) => {
|
| 158 |
+
state.elevenLabsApiKey = e.target.value;
|
| 159 |
+
saveCredentials(); // Save when API key changes
|
| 160 |
+
});
|
| 161 |
+
}
|
| 162 |
|
| 163 |
+
if (elements.voiceId) {
|
| 164 |
+
elements.voiceId.addEventListener('change', (e) => {
|
| 165 |
+
state.elevenLabsVoiceId = e.target.value;
|
| 166 |
+
saveCredentials(); // Save when voice ID changes
|
| 167 |
+
});
|
| 168 |
+
}
|
| 169 |
}
|
| 170 |
|
| 171 |
function handleModeChange(e) {
|
|
|
|
| 179 |
}
|
| 180 |
|
| 181 |
async function loadYears() {
|
| 182 |
+
console.log('loadYears() called');
|
| 183 |
+
console.log('elements.year:', elements.year);
|
| 184 |
+
|
| 185 |
try {
|
| 186 |
const response = await fetch('/api/races/years');
|
| 187 |
+
console.log('Years API response:', response.status);
|
| 188 |
+
|
| 189 |
const data = await response.json();
|
| 190 |
+
console.log('Years data:', data);
|
| 191 |
|
| 192 |
if (data.years && data.years.length > 0) {
|
| 193 |
elements.year.innerHTML = '<option value="">Select year...</option>';
|
|
|
|
| 197 |
option.textContent = year;
|
| 198 |
elements.year.appendChild(option);
|
| 199 |
});
|
| 200 |
+
console.log('Years loaded successfully:', data.years);
|
| 201 |
}
|
| 202 |
} catch (error) {
|
| 203 |
console.error('Failed to load years:', error);
|
|
|
|
| 209 |
const year = e.target.value;
|
| 210 |
state.selectedYear = year;
|
| 211 |
|
| 212 |
+
console.log('Year changed to:', year);
|
| 213 |
+
|
| 214 |
if (!year) {
|
| 215 |
elements.race.innerHTML = '<option value="">Select year first</option>';
|
| 216 |
return;
|
|
|
|
| 220 |
|
| 221 |
try {
|
| 222 |
const response = await fetch(`/api/races/${year}`);
|
| 223 |
+
console.log('Races API response:', response.status);
|
| 224 |
+
|
| 225 |
const data = await response.json();
|
| 226 |
+
console.log('Races data:', data);
|
| 227 |
|
| 228 |
if (data.races && data.races.length > 0) {
|
| 229 |
elements.race.innerHTML = '<option value="">Select race...</option>';
|
|
|
|
| 239 |
|
| 240 |
elements.race.appendChild(option);
|
| 241 |
});
|
| 242 |
+
console.log('Races loaded successfully:', data.races.length);
|
| 243 |
} else {
|
| 244 |
elements.race.innerHTML = '<option value="">No races found</option>';
|
| 245 |
}
|
|
|
|
| 265 |
const config = {
|
| 266 |
mode: state.mode,
|
| 267 |
session_key: state.mode === 'full_race' ? parseInt(elements.race.value) : null,
|
| 268 |
+
commentary_mode: 'enhanced', // Always use enhanced mode
|
| 269 |
playback_speed: state.playbackSpeed,
|
| 270 |
elevenlabs_api_key: state.elevenLabsApiKey,
|
| 271 |
elevenlabs_voice_id: state.elevenLabsVoiceId
|
test_ui.html
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Test UI Fix</title>
|
| 5 |
+
</head>
|
| 6 |
+
<body>
|
| 7 |
+
<h1>Testing DOM Element Loading</h1>
|
| 8 |
+
<select id="year"><option>Loading...</option></select>
|
| 9 |
+
<select id="race"><option>Select year first</option></select>
|
| 10 |
+
<div id="output"></div>
|
| 11 |
+
|
| 12 |
+
<script>
|
| 13 |
+
// Test the fix
|
| 14 |
+
let elements = {};
|
| 15 |
+
|
| 16 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 17 |
+
console.log('DOM loaded');
|
| 18 |
+
|
| 19 |
+
elements = {
|
| 20 |
+
year: document.getElementById('year'),
|
| 21 |
+
race: document.getElementById('race'),
|
| 22 |
+
output: document.getElementById('output')
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
console.log('Elements:', elements);
|
| 26 |
+
|
| 27 |
+
// Test if elements are found
|
| 28 |
+
let output = '<h2>Element Check:</h2>';
|
| 29 |
+
for (const [key, element] of Object.entries(elements)) {
|
| 30 |
+
output += `<p>${key}: ${element ? 'FOUND ✓' : 'NOT FOUND ✗'}</p>`;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
elements.output.innerHTML = output;
|
| 34 |
+
|
| 35 |
+
// Test loading years
|
| 36 |
+
loadYears();
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
+
async function loadYears() {
|
| 40 |
+
try {
|
| 41 |
+
const response = await fetch('http://localhost:8080/api/races/years');
|
| 42 |
+
const data = await response.json();
|
| 43 |
+
|
| 44 |
+
console.log('Years data:', data);
|
| 45 |
+
|
| 46 |
+
if (data.years && data.years.length > 0) {
|
| 47 |
+
elements.year.innerHTML = '<option value="">Select year...</option>';
|
| 48 |
+
data.years.forEach(year => {
|
| 49 |
+
const option = document.createElement('option');
|
| 50 |
+
option.value = year;
|
| 51 |
+
option.textContent = year;
|
| 52 |
+
elements.year.appendChild(option);
|
| 53 |
+
});
|
| 54 |
+
|
| 55 |
+
elements.output.innerHTML += '<p>Years loaded: ' + data.years.join(', ') + '</p>';
|
| 56 |
+
}
|
| 57 |
+
} catch (error) {
|
| 58 |
+
console.error('Error:', error);
|
| 59 |
+
elements.output.innerHTML += '<p>Error loading years: ' + error.message + '</p>';
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
</script>
|
| 63 |
+
</body>
|
| 64 |
+
</html>
|