mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-11 20:15:32 +08:00
Address classroom tester feedback (Detlef / FSC e.V.)
Fixes the most impactful issues from ~1.5h of testing: - TTS: spell SID prefixes phonetically (ANEKI → Alfa November Echo Kilo India) so unfamiliar waypoint names are intelligible without prior briefing context. - TTS: expand standalone uppercase waypoints after via/direct (with a skip list for common ATC English tokens like MAYDAY, CLEARED, …). - TTS: join taxi route tokens with ", " so pauses land between taxiways (C5, Z5, U10, …) instead of running together. - TTS: handle "ILS Z 25C" variant before the runway → "ILS Zulu runway two five center" (was previously read as "Zee twentyfive cee"). - Scenarios: derive arrivalRunway from the chosen approach so the controller no longer clears a flight for ILS 25C onto runway 18. - Radio check: accept any readability 1–5 (numeric or spoken), shorten placeholder so it fits the sm-width field. - Line-up readback: clearer hint about the runway-first ICAO order. - Classroom UI: disable browser autocomplete/autocorrect on readback inputs (Edge autofill was injecting unrelated values). - Classroom UI: "Speak answer" button replays the expected readback as TTS so students can hear the correct phrasing. Tests adjusted for the new SID and taxi-route phonetics. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1077,6 +1077,10 @@
|
||||
:placeholder="fieldPlaceholder(segment.key)"
|
||||
:inputmode="fieldInputmode(segment.key)"
|
||||
:ref="el => assignReadbackFieldRef(segment.key, el as HTMLInputElement | null)"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<v-icon v-if="fieldPass(segment.key)" size="16" class="blank-status ok">mdi-check</v-icon>
|
||||
<v-icon v-else-if="fieldHasAnswer(segment.key)" size="16" class="blank-status warn">mdi-alert
|
||||
@@ -1101,6 +1105,17 @@
|
||||
<v-icon size="18">mdi-auto-fix</v-icon>
|
||||
Auto-fill
|
||||
</button>
|
||||
<button
|
||||
v-if="correctReadbackText"
|
||||
class="btn ghost"
|
||||
type="button"
|
||||
:disabled="ttsLoading"
|
||||
@click="speakCorrectReadback"
|
||||
title="Speak correct readback"
|
||||
>
|
||||
<v-icon size="18">mdi-volume-high</v-icon>
|
||||
Speak answer
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="result" class="score">
|
||||
<div class="score-num">{{ result.score }}%</div>
|
||||
@@ -3351,6 +3366,22 @@ const targetPhrase = computed(() => {
|
||||
if (!activeLesson.value || !scenario.value) return ''
|
||||
return displayCallsign(activeLesson.value.phrase(scenario.value), scenario.value)
|
||||
})
|
||||
|
||||
const correctReadbackText = computed(() => {
|
||||
if (!activeLesson.value || !scenario.value) return ''
|
||||
return activeLesson.value.readback.map(seg => {
|
||||
if (seg.type === 'text') return typeof seg.text === 'function' ? seg.text(scenario.value!) : seg.text
|
||||
const field = activeLesson.value!.fields.find(f => f.key === seg.key)
|
||||
return field ? field.expected(scenario.value!) : ''
|
||||
}).join('').trim()
|
||||
})
|
||||
|
||||
async function speakCorrectReadback() {
|
||||
const text = correctReadbackText.value
|
||||
if (!text || ttsLoading.value) return
|
||||
await say(text)
|
||||
}
|
||||
|
||||
const lessonInfo = computed(() => (activeLesson.value && scenario.value ? activeLesson.value.info(scenario.value) : []))
|
||||
|
||||
const lessonReference = computed(() => {
|
||||
|
||||
Reference in New Issue
Block a user