Files
OpenSquawk/app/composables/useRadioBackend.ts
leubeem b80feb80d6 feat(stt): seed Whisper prompt with expected readback + per-field debug UI
Whisper prompt seeding (per request):
- ptt.post.ts builds the prompt as generic ICAO bias + this state's expected
  readback appended LAST (survives the 224-token truncation), in both raw token
  form and spoken ICAO form via new radioSpeech.speakToken().
- pm.vue passes the expected phrase + active variable values; classroom.vue
  passes the lesson's expected field values.

Per-field readback debug:
- sttMatch.matchTranscriptionToFields returns fields[] (matched/missing + which
  view matched) plus normalized/denormalized transcription views.
- useRadioBackend types readback_report on the transmit response.
- pm.vue renders a "Readback check" panel in the right log rail; classroom.vue
  renders per-field rows under the STT panel.

Radio-pronunciation fixes (radioSpeech.ts):
- callsign expander handles multi-letter suffixes (DLH6RK -> Lufthansa six Romeo
  Kilo).
- toRadioSpeech now expands airports (EDDC -> Echo Delta Delta Charlie).
- bare altitudes >=1000 in a clearance context are spoken ("climb initially
  5000" -> "climb initially five thousand feet"); speeds/headings untouched.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:12:54 +02:00

78 lines
2.5 KiB
TypeScript

export interface RadioSessionResponse {
session_id: string
flow_slug: string
current_state: string
variables: Record<string, any>
flags: Record<string, boolean>
expected_pilot_template: string | null
}
export interface RadioTransmitResponse {
session_id: string
next_state_id: string
active_flow: string
controller_say_template: string | null
controller_say_rendered: string | null
expected_pilot_template: string | null
variables: Record<string, any>
flags: Record<string, boolean>
trace: Array<{ type: string; message: string }>
fallback_used: boolean
fallback_reason: string | null
auto_advanced_states?: string[]
/** True when the full chain is done — show the completion screen. */
session_complete?: boolean
/** Per-field readback diagnostic for the pilot state just evaluated. */
readback_report?: ReadbackFieldDetail[]
}
export interface ReadbackFieldDetail {
field: string
expected: string
matched: boolean
/** Which accepted spoken form matched ("two five right", "icao_phonetic"), or null. */
matched_via: string | null
/** All spoken forms that would have matched this field. */
accepted_forms: string[]
note?: string | null
}
export function useRadioBackend() {
const config = useRuntimeConfig()
function baseUrl(): string {
return (config.public.radioBackendUrl as string) || 'http://127.0.0.1:8000'
}
async function createSession(
flowSlug: string,
variables?: Record<string, any>,
noChain: boolean = false,
): Promise<RadioSessionResponse> {
return await $fetch<RadioSessionResponse>(`${baseUrl()}/api/radio/session`, {
method: 'POST',
body: { flow_slug: flowSlug, variables: variables ?? null, no_chain: noChain },
})
}
async function transmit(sessionId: string, pilotUtterance: string): Promise<RadioTransmitResponse> {
return await $fetch<RadioTransmitResponse>(
`${baseUrl()}/api/radio/session/${sessionId}/transmissions`,
{
method: 'POST',
body: { pilot_utterance: pilotUtterance },
},
)
}
async function deleteSession(sessionId: string): Promise<void> {
await $fetch(`${baseUrl()}/api/radio/session/${sessionId}`, { method: 'DELETE' })
}
async function fetchFlows(): Promise<any> {
return await $fetch(`${baseUrl()}/api/decision-flows/runtime`)
}
return { createSession, transmit, deleteSession, fetchFlows }
}