Translate pilot monitoring and learn pages

This commit is contained in:
Remi
2025-09-19 08:54:57 +02:00
parent 43f2f42238
commit d4521c87c6
4 changed files with 107 additions and 110 deletions

View File

@@ -10,28 +10,27 @@
>
<section class="cookie-consent-panel">
<header class="cookie-consent-header">
<h2 id="cookie-consent-title">Cookies &amp; Analysen</h2>
<h2 id="cookie-consent-title">Cookies &amp; Analytics</h2>
<p id="cookie-consent-description">
Wir verwenden notwendige Cookies, damit die Website zuverlässig funktioniert. Darüber hinaus würden wir gern optionale
Analyse-Cookies von Hotjar einsetzen, um das Nutzungserlebnis zu verbessern. Sie entscheiden, ob Sie diese zulassen
alle Details finden Sie in unserer
<a href="/datenschutz" target="_blank" rel="noopener">Datenschutzerklärung</a>.
We use essential cookies to keep the site reliable. We would also like to enable optional Hotjar analytics cookies to
improve the experience. You decide whether to allow themfull details are available in our
<a href="/datenschutz" target="_blank" rel="noopener">privacy policy</a>.
</p>
</header>
<div class="cookie-consent-options" role="list">
<article class="cookie-option is-required" role="listitem">
<div class="cookie-option-text">
<h3>Notwendige Cookies</h3>
<p>Speichern Ihre Auswahl und sorgen für sichere Anmeldung sowie eine stabile Grundfunktionalität.</p>
<h3>Necessary cookies</h3>
<p>Store your preferences and keep sign-in secure while ensuring the core experience remains stable.</p>
</div>
<span class="cookie-option-badge" aria-hidden="true">Immer aktiv</span>
<span class="cookie-option-badge" aria-hidden="true">Always active</span>
</article>
<article class="cookie-option" role="listitem">
<div class="cookie-option-text">
<h3>Analyse (Hotjar)</h3>
<p>Hilft uns, Nutzungsverhalten anonym auszuwerten und unsere Inhalte gezielt zu verbessern.</p>
<h3>Analytics (Hotjar)</h3>
<p>Helps us analyse anonymised usage patterns and improve our content in a focused way.</p>
</div>
<button
type="button"
@@ -44,21 +43,20 @@
<span class="cookie-toggle-thumb" />
</span>
<span class="sr-only">
Analyse-Cookies {{ analyticsSelection ? 'deaktivieren' : 'aktivieren' }}
{{ analyticsSelection ? 'Disable analytics cookies' : 'Enable analytics cookies' }}
</span>
</button>
</article>
</div>
<p class="cookie-consent-note">
Optionales Tracking wird erst nach Ihrer Zustimmung geladen. Sie können Ihre Entscheidung jederzeit über Cookie-Einstellungen
unten links widerrufen.
Optional tracking loads only after you agree. You can change your decision any time via "Cookie settings" in the lower left.
</p>
<div class="cookie-consent-actions">
<button type="button" class="cookie-button ghost" @click="handleRejectAll">Nur notwendige</button>
<button type="button" class="cookie-button secondary" @click="handleSave">Auswahl speichern</button>
<button type="button" class="cookie-button primary" @click="handleAcceptAll">Alle akzeptieren</button>
<button type="button" class="cookie-button ghost" @click="handleRejectAll">Essential only</button>
<button type="button" class="cookie-button secondary" @click="handleSave">Save selection</button>
<button type="button" class="cookie-button primary" @click="handleAcceptAll">Accept all</button>
</div>
</section>
</div>
@@ -66,7 +64,7 @@
<transition name="cookie-manage">
<button v-if="showManageButton" type="button" class="cookie-manage-button" @click="openManager">
Cookie-Einstellungen
Cookie settings
</button>
</transition>
</template>

View File

@@ -22,7 +22,7 @@
<v-card-text class="space-y-4">
<div>
<h2 class="text-lg font-semibold">VATSIM Integration</h2>
<p class="text-sm text-white/70">Gib deine CID ein um deine gefilterten Flugpläne zu laden</p>
<p class="text-sm text-white/70">Enter your CID to load your filtered flight plans.</p>
</div>
<v-text-field
v-model="vatsimId"
@@ -41,7 +41,7 @@
:loading="loading"
@click="loadFlightPlans"
>
Flugpläne abrufen
Load flight plans
</v-btn>
<v-alert
v-if="error"
@@ -60,7 +60,7 @@
<v-card-text class="space-y-4">
<div>
<h2 class="text-lg font-semibold">Demo Mode</h2>
<p class="text-sm text-white/70">Starte mit einem Beispiel-Flugplan zum Testen</p>
<p class="text-sm text-white/70">Start with a sample flight plan for testing.</p>
</div>
<v-btn
block
@@ -68,7 +68,7 @@
variant="outlined"
@click="startDemoFlight"
>
Demo starten (DLH39A EDDFEDDM)
Start demo (DLH39A EDDFEDDM)
</v-btn>
</v-card-text>
</v-card>
@@ -80,20 +80,20 @@
<v-btn icon @click="currentScreen = 'login'" class="text-cyan-300">
<v-icon>mdi-arrow-left</v-icon>
</v-btn>
<h2 class="text-lg font-semibold">Verfügbare Flugpläne</h2>
<h2 class="text-lg font-semibold">Available flight plans</h2>
<v-chip color="cyan" variant="outlined" size="small">{{ vatsimId }}</v-chip>
</div>
<div v-if="loading" class="text-center py-8">
<v-progress-circular indeterminate color="cyan" class="mb-4" />
<p class="text-sm text-white/70">Lade Flugpläne von VATSIM...</p>
<p class="text-sm text-white/70">Loading flight plans from VATSIM</p>
</div>
<div v-else-if="flightPlans.length === 0" class="text-center py-8">
<v-icon size="48" class="text-white/30 mb-4">mdi-airplane-off</v-icon>
<p class="text-white/70">Keine Flugpläne gefunden</p>
<p class="text-white/70">No flight plans found</p>
<v-btn color="cyan" variant="outlined" class="mt-4" @click="currentScreen = 'login'">
Zurück
Back
</v-btn>
</div>
@@ -135,7 +135,7 @@
<v-card-text class="space-y-4">
<div class="flex items-start justify-between gap-3">
<div>
<p class="text-xs uppercase tracking-[0.3em] text-white/50">Aktiver Flug</p>
<p class="text-xs uppercase tracking-[0.3em] text-white/50">Active flight</p>
<h2 class="text-2xl font-semibold">{{ flightContext.callsign || 'N/A' }}</h2>
<p class="text-sm text-white/70">{{ flightContext.dep }} {{ flightContext.dest }}</p>
</div>
@@ -203,7 +203,7 @@
@click="backToSetup"
prepend-icon="mdi-airplane-off"
>
Neuen Flug wählen
Select new flight
</v-btn>
</v-card-text>
</v-card>
@@ -247,7 +247,7 @@
@click="swapFrequencies"
:class="{ 'swap-animation': swapAnimation }"
>
Frequenzen tauschen
Swap frequencies
</v-btn>
<!-- Signal Quality -->
@@ -270,7 +270,7 @@
<v-card v-if="currentStep" class="bg-white/5 border border-white/10">
<v-card-text class="space-y-3">
<div class="flex items-center justify-between">
<p class="text-xs uppercase tracking-[0.3em] text-white/40">Erwartete Kommunikation</p>
<p class="text-xs uppercase tracking-[0.3em] text-white/40">Expected communication</p>
<v-chip color="orange" variant="tonal" size="small">{{ currentStep.frequencyName }}</v-chip>
</div>
@@ -285,7 +285,7 @@
<div v-if="currentStep.pilot" class="space-y-2 rounded-2xl bg-blue-500/10 border border-blue-500/20 p-3 text-sm">
<div class="flex items-center gap-2 text-blue-300">
<v-icon size="16">mdi-account-pilot</v-icon>
<span class="text-xs uppercase font-semibold">Pilot (Du)</span>
<span class="text-xs uppercase font-semibold">Pilot (You)</span>
</div>
<p class="font-mono text-white">{{ normalizeExpectedText(currentStep.pilot) }}</p>
</div>
@@ -314,9 +314,9 @@
class="bg-cyan-500/10 text-cyan-100 mb-4"
density="compact"
>
Mikrofonberechtigung erforderlich r Push-To-Talk.
Microphone permission required for push-to-talk.
<template #append>
<v-btn color="cyan" size="small" variant="flat" @click="requestMicAccess">Erlauben</v-btn>
<v-btn color="cyan" size="small" variant="flat" @click="requestMicAccess">Allow</v-btn>
</template>
</v-alert>
@@ -344,7 +344,7 @@
{{ radioMode === 'atc' ? 'ATC' : 'INTERCOM' }}
</p>
<p class="mt-2 text-sm text-white/60">
{{ radioMode === 'atc' ? `Sendet auf ${frequencies.active}` : 'Crew Intercom • Checklist' }}
{{ radioMode === 'atc' ? `Transmitting on ${frequencies.active}` : 'Crew Intercom • Checklist' }}
</p>
</div>
</div>
@@ -376,7 +376,7 @@
prepend-icon="mdi-information-variant"
density="comfortable"
>
ATIS abhören
Listen to ATIS
</v-btn>
<v-btn
color="cyan"
@@ -386,7 +386,7 @@
prepend-icon="mdi-progress-check"
density="comfortable"
>
Voller Flug (Simulation)
Full flight (simulation)
</v-btn>
</div>
</v-card-text>
@@ -396,7 +396,7 @@
<v-card class="bg-white/5 border border-white/10">
<v-card-text class="space-y-3">
<div class="flex flex-wrap items-center justify-between gap-3">
<h3 class="text-lg font-semibold">Frequenzübersicht</h3>
<h3 class="text-lg font-semibold">Frequency overview</h3>
<div class="flex items-center gap-2">
<v-progress-circular
v-if="airportFrequencyLoading"
@@ -421,7 +421,7 @@
<v-expansion-panels variant="accordion" class="bg-transparent">
<v-expansion-panel>
<v-expansion-panel-title class="text-sm text-white/70">
Frequenzen r {{ flightContext.dep || 'Abflug' }}
Frequencies for {{ flightContext.dep || 'departure' }}
</v-expansion-panel-title>
<v-expansion-panel-text>
<div
@@ -429,13 +429,13 @@
class="flex items-center gap-2 text-sm text-white/60"
>
<v-progress-circular indeterminate size="20" color="cyan" />
<span>Lade Frequenzen vom Netzwerk</span>
<span>Loading frequencies from the network</span>
</div>
<div
v-else-if="airportFrequencies.length === 0"
class="text-xs text-white/50"
>
Keine Frequenzen verfügbar. Bitte später erneut versuchen.
No frequencies available. Please try again later.
</div>
<div v-else class="space-y-3">
<div
@@ -527,7 +527,7 @@
@click:append-inner="sendPilotText"
/>
<p class="text-xs text-white/50">
Für Notfälle wenn PTT nicht funktioniert oder r Tests
For emergencies when PTT fails or for testing
</p>
</v-card-text>
</v-card>
@@ -546,7 +546,7 @@
</v-chip>
</div>
<v-chip size="small" :color="simulationRunning ? 'orange' : 'grey'" variant="tonal">
{{ simulationRunning ? 'Läuft' : 'Bereit' }}
{{ simulationRunning ? 'Running' : 'Ready' }}
</v-chip>
</div>
@@ -564,7 +564,7 @@
v-if="simulationRunning && simulationTrace.length === 0"
class="text-sm text-white/60"
>
Simulation initialisiert...
Simulation initializing...
</div>
<div
@@ -602,22 +602,22 @@
</div>
<div class="space-y-2 rounded-2xl border border-white/10 bg-black/30 p-3">
<p class="text-xs uppercase tracking-[0.3em] text-white/40">Aktueller Knoten</p>
<p class="text-xs uppercase tracking-[0.3em] text-white/40">Current node</p>
<p class="font-mono text-sm text-white">{{ debugState?.id || '—' }}</p>
<p class="text-[11px] text-white/50">
{{ debugState ? `${debugState.role}${debugState.phase}` : 'N/A' }}
<span v-if="debugState?.frequencyName" class="ml-1 text-white/40">({{ debugState.frequencyName }})</span>
</p>
<p v-if="debugState?.sayPlain" class="text-xs text-white/70">
Auto: <span class="font-mono text-white">{{ debugState.sayPlain }}</span>
Auto (LLM): <span class="font-mono text-white">{{ debugState.sayPlain }}</span>
</p>
<p v-if="debugState?.sayNormalized" class="text-[11px] text-white/40">
Funk: {{ debugState.sayNormalized }}
Radio: {{ debugState.sayNormalized }}
</p>
</div>
<div>
<p class="text-xs uppercase tracking-[0.3em] text-white/40 mb-2">Nächste Entscheidungen</p>
<p class="text-xs uppercase tracking-[0.3em] text-white/40 mb-2">Upcoming decisions</p>
<div v-if="debugNextStates.length" class="space-y-2">
<div
v-for="state in debugNextStates"
@@ -646,11 +646,11 @@
ATC: <span class="font-mono text-white">{{ state.sayPlain }}</span>
</p>
<p v-if="state.sayNormalized" class="text-[11px] text-white/40">
Funk: {{ state.sayNormalized }}
Radio: {{ state.sayNormalized }}
</p>
</div>
</div>
<p v-else class="text-xs text-white/50">Keine weiteren Entscheidungen verfügbar.</p>
<p v-else class="text-xs text-white/50">No further decisions available.</p>
</div>
</v-card-text>
</v-card>
@@ -671,7 +671,7 @@
:disabled="log.length === 0"
@click="clearLog"
>
Leeren
Clear
</v-btn>
</div>
@@ -702,7 +702,7 @@
</div>
<p v-if="log.length === 0" class="text-xs text-white/50 text-center py-4">
Noch keine Kommunikation aufgezeichnet.
No communications recorded yet.
</p>
</div>
</v-card-text>
@@ -730,7 +730,7 @@
class="text-xs uppercase tracking-[0.3em]"
:class="lastTransmissionFaulty ? 'text-red-300' : 'text-cyan-300'"
>
Letzte Übertragung
Last transmission
</span>
</div>
<div class="flex flex-wrap items-center gap-1">
@@ -740,7 +740,7 @@
color="red"
variant="flat"
>
Fehlerhaft
Faulty
</v-chip>
<v-btn
variant="text"
@@ -752,7 +752,7 @@
<template #prepend>
<v-icon size="16">mdi-alert-circle-outline</v-icon>
</template>
{{ lastTransmissionFaulty ? 'Fehler bearbeiten' : 'Fehler markieren' }}
{{ lastTransmissionFaulty ? 'Edit issue' : 'Mark as faulty' }}
</v-btn>
<v-btn
v-if="lastTransmissionFaulty"
@@ -765,7 +765,7 @@
<template #prepend>
<v-icon size="16">mdi-restore</v-icon>
</template>
Zurücksetzen
Reset
</v-btn>
<v-btn
variant="text"
@@ -777,7 +777,7 @@
<template #prepend>
<v-icon size="16">mdi-close</v-icon>
</template>
Löschen
Delete
</v-btn>
</div>
</div>
@@ -786,12 +786,12 @@
v-if="lastTransmissionFaulty"
class="space-y-2 rounded-xl border border-red-400/30 bg-red-500/10 px-3 py-2"
>
<p class="text-xs uppercase tracking-[0.25em] text-red-200/80">Fehlerbeschreibung</p>
<p class="text-xs uppercase tracking-[0.25em] text-red-200/80">Issue description</p>
<p v-if="lastTransmissionFaultNote" class="text-sm text-red-100">
{{ lastTransmissionFaultNote }}
</p>
<p v-else class="text-xs text-red-200/70">
Als fehlerhaft markiert.
Marked as faulty.
</p>
</div>
</v-card-text>
@@ -801,16 +801,15 @@
<v-card class="bg-[#0b101d] border border-white/10 text-white">
<v-card-title class="flex items-center gap-2 text-base font-semibold">
<v-icon icon="mdi-alert-circle-outline" color="#f87171" size="20" />
Übertragung als fehlerhaft markieren
Mark transmission as faulty
</v-card-title>
<v-card-text class="space-y-4 text-sm text-white/70">
<p>
Hinterlasse optional eine kurze Beschreibung, damit das Dev Team die
Übertragung nachvollziehen kann.
Optionally leave a short note so the dev team can follow up on the transmission.
</p>
<v-textarea
v-model="transmissionIssueNote"
label="Fehlerbeschreibung (optional)"
label="Issue description (optional)"
variant="outlined"
color="red"
rows="3"
@@ -819,7 +818,7 @@
</v-card-text>
<v-card-actions class="justify-end gap-2">
<v-btn variant="text" color="grey" @click="cancelTransmissionIssue">
Abbrechen
Cancel
</v-btn>
<v-btn
v-if="lastTransmissionFaulty"
@@ -827,10 +826,10 @@
color="cyan"
@click="removeTransmissionIssue"
>
Markierung entfernen
Remove flag
</v-btn>
<v-btn color="red" variant="flat" @click="confirmTransmissionIssue">
Speichern
Save
</v-btn>
</v-card-actions>
</v-card>
@@ -844,7 +843,7 @@
<div class="space-y-4">
<div>
<label class="text-xs uppercase tracking-[0.3em] text-white/40 block mb-2">
Signal Stärke
Signal strength
</label>
<v-slider
v-model="signalStrength"
@@ -863,14 +862,14 @@
v-model="radioEffectsEnabled"
color="cyan"
inset
label="Radio-Effekte"
label="Radio effects"
hide-details
/>
<v-switch
v-model="readbackEnabled"
color="cyan"
inset
label="Readback Stimme"
label="Readback voice"
hide-details
/>
<v-switch
@@ -1579,11 +1578,11 @@ const loadFlightPlans = async () => {
flightPlans.value = response.slice(0, 10)
currentScreen.value = 'flightselect'
} else {
error.value = 'Keine Flugpläne für diese VATSIM ID gefunden'
error.value = 'No flight plans found for this VATSIM ID'
}
} catch (err) {
console.error('Error loading flight plans:', err)
error.value = 'Fehler beim Laden der Flugpläne. Bitte VATSIM ID prüfen.'
error.value = 'Error loading flight plans. Please check the VATSIM ID.'
} finally {
loading.value = false
}
@@ -1947,7 +1946,7 @@ const playAtisBroadcast = async () => {
}
if (!content) {
setLastTransmission('ATIS: Keine aktuellen Informationen verfügbar')
setLastTransmission('ATIS: No current information available')
return
}

View File

@@ -5,7 +5,7 @@ export type Lesson = { id: string; title: string; desc: string; target: string;
export type ModuleDef = { id: string; title: string; subtitle: string; art: string; lessons: Lesson[] }
const modules = ref<ModuleDef[]>([
// NEW: ICAO Kapitel
// New: ICAO chapter
{
id: 'icao',
title: 'ICAO Alphabet',
@@ -15,33 +15,33 @@ const modules = ref<ModuleDef[]>([
{
id: 'alpha',
title: 'Alphabet AM',
desc: 'Alpha bis Mike sprechen.',
desc: 'Say Alpha through Mike.',
target: 'Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike.',
hints: ['konstant sprechen', 'deutlich trennen'],
hints: ['Keep a steady pace', 'Separate each word clearly'],
keywords: ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf', 'Hotel', 'India', 'Juliett', 'Kilo', 'Lima', 'Mike']
},
{
id: 'alpha2',
title: 'Alphabet NZ',
desc: 'November bis Zulu.',
desc: 'November through Zulu.',
target: 'November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu.',
hints: ['X-ray mit hyphen', 'Juliett mit zwei t'],
hints: ['Spell X-ray with a hyphen', "Juliett has two t's"],
keywords: ['November', 'Oscar', 'Papa', 'Quebec', 'Romeo', 'Sierra', 'Tango', 'Uniform', 'Victor', 'Whiskey', 'X-ray', 'Yankee', 'Zulu']
},
{
id: 'numbers',
title: 'Zahlen',
desc: 'ICAO-Nummernlesen.',
title: 'Numbers',
desc: 'Read ICAO numbers.',
target: 'Tree Fower Fife Six Seven Eight Niner Zero.',
hints: ['Nine → Niner', 'Three → Tree', 'Four → Fower', 'Five → Fife'],
keywords: ['Tree', 'Fower', 'Fife', 'Niner']
},
{
id: 'callsign-icao',
title: 'Callsign Buchstabieren',
desc: 'Beispiel-Callsign.',
title: 'Spell the call sign',
desc: 'Sample call sign.',
target: 'DLH one two three, Lufthansa one two three.',
hints: ['DLH → Lufthansa', 'Nummern ICAO'],
hints: ['DLH → Lufthansa', 'Use ICAO numbers'],
keywords: ['Lufthansa', 'DLH', 'one', 'two', 'three']
}
]
@@ -49,19 +49,19 @@ const modules = ref<ModuleDef[]>([
{
id: 'basics',
title: 'Basics',
subtitle: 'Callsign · Struktur · Zahlen',
subtitle: 'Call sign · Structure · Numbers',
art: 'https://images.unsplash.com/photo-1541392822270-85b2ff6c4577?q=80&w=1600&auto=format&fit=crop',
lessons: [
{
id: 'checkin',
title: 'Check-in',
desc: 'Erster Call korrekt.',
desc: 'Make the first call correctly.',
target: 'Frankfurt Ground, Lufthansa one two three at stand A12, request taxi.',
hints: ['Station • Callsign • Position • Intent'],
keywords: ['Frankfurt Ground', 'Lufthansa', 'stand', 'request taxi']
},
{
id: 'readback', title: 'Short Readback', desc: 'Kurz bestätigen.',
id: 'readback', title: 'Short Readback', desc: 'Give a short acknowledgement.',
target: 'Lufthansa one two three, roger.',
hints: ['Callsign + roger/affirm'], keywords: ['roger', 'affirm']
}
@@ -79,7 +79,7 @@ const modules = ref<ModuleDef[]>([
hints: ['Taxi to runway • via • hold short'], keywords: ['taxi to runway', 'via', 'hold short']
},
{
id: 'handoff', title: 'Handoff', desc: 'Frequenzwechsel.',
id: 'handoff', title: 'Handoff', desc: 'Frequency change.',
target: 'Contact Tower on one one niner decimal five, Lufthansa one two three.',
hints: ['Contact Tower on … • decimal'], keywords: ['Contact Tower', 'decimal']
}
@@ -92,7 +92,7 @@ const modules = ref<ModuleDef[]>([
art: 'https://images.unsplash.com/photo-1494412685616-a5d310fbb07d?q=80&w=1600&auto=format&fit=crop',
lessons: [
{
id: 'lineup', title: 'Line up', desc: 'Aufrollen und warten.',
id: 'lineup', title: 'Line up', desc: 'Line up and wait.',
target: 'Lufthansa one two three, line up and wait runway two five.',
hints: ['line up and wait'], keywords: ['line up and wait']
}
@@ -105,7 +105,7 @@ const modules = ref<ModuleDef[]>([
art: 'https://images.unsplash.com/photo-1542089363-07b2d92aacc3?q=80&w=1600&auto=format&fit=crop',
lessons: [
{
id: 'vacate', title: 'Vacate', desc: 'Verlasse Bahn, melde frei.',
id: 'vacate', title: 'Vacate', desc: 'Exit the runway and report clear.',
target: 'Lufthansa one two three, vacated runway two five via A six.',
hints: ['vacated runway • via taxiway'], keywords: ['vacated', 'runway']
}
@@ -118,7 +118,7 @@ const modules = ref<ModuleDef[]>([
art: 'https://images.unsplash.com/photo-1508264769638-658b34d79f6e?q=80&w=1600&auto=format&fit=crop',
lessons: [
{
id: 'checkin', title: 'IFR Check-in', desc: 'Erster Online-Call.',
id: 'checkin', title: 'IFR Check-in', desc: 'First online call.',
target: 'Frankfurt Ground, Lufthansa one two three, A320 at stand A12, IFR to Munich, information Bravo, request clearance.',
hints: ['IFR/VFR • ATIS Info • Request'], keywords: ['IFR', 'information', 'request clearance']
}

View File

@@ -1,8 +1,8 @@
// composables/communicationsEngine.ts
// communicationsEngine composable
import { ref, computed, readonly } from 'vue'
import atcDecisionTree from "../data/atcDecisionTree";
// --- DecisionTree-Types (aus ~/data/atcDecisionTree.json abgeleitet) ---
// --- DecisionTree types (derived from ~/data/atcDecisionTree.json) ---
type Role = 'pilot' | 'atc' | 'system'
type Phase =
| 'Preflight' | 'Clearance' | 'PushStart' | 'TaxiOut' | 'Departure'
@@ -31,8 +31,8 @@ interface DTState {
auto?: 'check_readback' | 'monitor' | 'end' | 'pop_stack_or_route_by_intent'
readback_required?: string[]
actions?: (DTActionSet | string)[]
frequency?: string // Für einfachere Freq-Zugriffe
frequencyName?: string // Name der Frequenz (DEL, GND, TWR, etc.)
frequency?: string // Shortcut for accessing frequencies
frequencyName?: string // Frequency label (DEL, GND, TWR, etc.)
}
interface DecisionTree {
@@ -47,8 +47,8 @@ interface DecisionTree {
emergency_active: boolean
current_unit: string
stack: string[]
off_schema_count: number // Zähler für Off-Schema Antworten
radio_checks_done: number // Zähler für Radio Checks
off_schema_count: number // Counter for off-schema responses
radio_checks_done: number // Counter for radio checks
}
policies: {
timeouts: {
@@ -67,7 +67,7 @@ interface DecisionTree {
states: Record<string, DTState>
}
// Flight Phases und Communication Steps für bessere Integration
// Flight phases and communication steps for better integration
export const FLIGHT_PHASES = [
{ id: 'clearance', name: 'Clearance Delivery', frequency: '121.900', action: 'Request IFR clearance' },
{ id: 'ground', name: 'Ground Control', frequency: '121.700', action: 'Request pushback and taxi' },
@@ -90,7 +90,7 @@ export const COMMUNICATION_STEPS = [
atc: '{callsign}, cleared to {dest} via {sid} departure, runway {runway}, climb {initial_altitude_ft} feet, squawk {squawk}.',
pilotResponse: '{callsign} cleared {dest} via {sid}, runway {runway}, climb {initial_altitude_ft}, squawk {squawk}.'
}
// Weitere Steps können hier definiert werden
// Additional steps can be defined here
]
type FrequencyVariableKey = 'atis_freq'
@@ -101,7 +101,7 @@ type FrequencyVariableKey = 'atis_freq'
| 'approach_freq'
| 'handoff_freq'
// --- ATC Decision Tree laden ---
// --- Load ATC decision tree ---
export interface FlightContext {
callsign: string
aircraft: string
@@ -140,7 +140,7 @@ export interface EngineLog {
offSchema?: boolean
}
// NATO/ICAO Normalizer (gekürzt für bessere Performance)
// NATO/ICAO normalizer trimmed for better performance
const NATO_PHONETIC: Record<string, string> = {
A: 'Alpha', B: 'Bravo', C: 'Charlie', D: 'Delta', E: 'Echo', F: 'Foxtrot',
G: 'Golf', H: 'Hotel', I: 'India', J: 'Juliett', K: 'Kilo', L: 'Lima',
@@ -212,7 +212,7 @@ export default function useCommunicationsEngine() {
const currentStateId = ref<string>(tree.value.start_state)
const communicationLog = ref<EngineLog[]>([])
// Flight Context für bessere Integration mit pm_alt.vue Stil
// Flight context used for pm_alt.vue integration
const flightContext = ref<FlightContext>({
callsign: '',
aircraft: 'A320',
@@ -264,7 +264,7 @@ export default function useCommunicationsEngine() {
}
})
// Current Step für pm_alt.vue Integration
// Current step for pm_alt.vue integration
const currentStep = computed(() => {
const phase = flightContext.value.phase
const step = COMMUNICATION_STEPS.find(s => s.phase === phase)
@@ -280,7 +280,7 @@ export default function useCommunicationsEngine() {
})
function initializeFlight(fpl: any) {
// Variablen setzen
// Set variables
variables.value = {
...variables.value,
callsign: fpl.callsign || fpl.callsign,
@@ -313,7 +313,7 @@ export default function useCommunicationsEngine() {
time_now: new Date().toISOString()
}
// Flight Context aktualisieren
// Update flight context
Object.assign(flightContext.value, {
...variables.value,
phase: 'clearance'
@@ -364,11 +364,11 @@ export default function useCommunicationsEngine() {
}
function applyLLMDecision(decision: any) {
// Updates anwenden
// Apply updates
if (decision.updates) Object.assign(variables.value, decision.updates)
if (decision.flags) Object.assign(flags.value, decision.flags)
// Off-Schema und Radio Check Tracking
// Track off-schema responses and radio checks
if (decision.off_schema) {
flags.value.off_schema_count++
console.log(`[Engine] Off-schema response #${flags.value.off_schema_count}`)
@@ -378,7 +378,7 @@ export default function useCommunicationsEngine() {
console.log(`[Engine] Radio check #${flags.value.radio_checks_done}`)
}
// Controller Response
// Controller response
if (decision.controller_say_tpl) {
speak('atc', decision.controller_say_tpl, currentStateId.value, {
radioCheck: decision.radio_check,
@@ -386,7 +386,7 @@ export default function useCommunicationsEngine() {
})
}
// State Transition (nur wenn nicht Radio Check)
// State transition (only when not a radio check)
if (!decision.radio_check) {
moveTo(decision.next_state)
}
@@ -396,7 +396,7 @@ export default function useCommunicationsEngine() {
// Log pilot input
speak('pilot', transcript, currentStateId.value)
// Radio Check Detection (fallback falls LLM es nicht erkennt)
// Radio check detection (fallback if the LLM misses it)
const t = transcript.toLowerCase()
if (t.includes('radio check') || (t.includes('read') && t.includes('check'))) {
const callsign = variables.value.callsign || ''
@@ -417,7 +417,7 @@ export default function useCommunicationsEngine() {
return null
}
return null // LLM soll entscheiden
return null // Let the LLM decide
}
function processUserTransmission(transcript: string): string | null {
@@ -437,7 +437,7 @@ export default function useCommunicationsEngine() {
currentStateId.value = stateId
const s = currentState.value
// Actions ausführen
// Execute actions
for (const act of s.actions ?? []) {
if (typeof act === 'string') continue
if (act.if && !safeEvalBoolean(act.if)) continue
@@ -457,7 +457,7 @@ export default function useCommunicationsEngine() {
speak(s.role, s.say_tpl, s.id!)
}
// Phase Update für Flight Context
// Update flight context phase
updateFlightPhase(s.phase)
}
@@ -599,7 +599,7 @@ export default function useCommunicationsEngine() {
communicationLog: readonly(communicationLog),
clearCommunicationLog: () => { communicationLog.value = [] },
// pm_alt.vue Integration
// pm_alt.vue integration
flightContext: readonly(flightContext),
currentStep,