mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-05 08:55:54 +08:00
Translate pilot monitoring and learn pages
This commit is contained in:
@@ -10,28 +10,27 @@
|
||||
>
|
||||
<section class="cookie-consent-panel">
|
||||
<header class="cookie-consent-header">
|
||||
<h2 id="cookie-consent-title">Cookies & Analysen</h2>
|
||||
<h2 id="cookie-consent-title">Cookies & 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 them—full 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>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</button>
|
||||
<span class="brand">OpenSquawk</span>
|
||||
<span class="sep">|</span>
|
||||
<span class="mode">Lernwelt</span>
|
||||
<span class="mode">Training Hub</span>
|
||||
</div>
|
||||
|
||||
<div class="hud-right">
|
||||
@@ -26,16 +26,16 @@
|
||||
|
||||
<button class="btn ghost" @click="panel='progress'">
|
||||
<v-icon size="18">mdi-chart-line</v-icon>
|
||||
Fortschritt
|
||||
Progress
|
||||
</button>
|
||||
|
||||
<!-- ATC Einstellungen -->
|
||||
<button class="btn ghost" @click="showSettings=true" title="ATC-Einstellungen">
|
||||
<button class="btn ghost" @click="showSettings=true" title="ATC settings">
|
||||
<v-icon size="18">mdi-tune</v-icon>
|
||||
ATC
|
||||
</button>
|
||||
|
||||
<button class="btn ghost" @click="resetAll" title="Zurücksetzen">
|
||||
<button class="btn ghost" @click="resetAll" title="Reset">
|
||||
<v-icon size="18">mdi-refresh</v-icon>
|
||||
Reset
|
||||
</button>
|
||||
@@ -49,16 +49,16 @@
|
||||
<div class="panel hero-panel" :style="worldTiltStyle" @mousemove="tilt">
|
||||
<div class="hero-left">
|
||||
<div class="eyebrow">ALPHA BUILD · TRAINING</div>
|
||||
<h1 class="h1">ATC Lernwelt</h1>
|
||||
<p class="muted">Geführte Pfade · Missionen · XP · Abzeichen. Lokal gespeichert.</p>
|
||||
<h1 class="h1">ATC Learning Hub</h1>
|
||||
<p class="muted">Guided paths · missions · XP · badges. Stored locally.</p>
|
||||
<div class="actions">
|
||||
<button class="btn primary" @click="panel='hub'">
|
||||
<v-icon>mdi-play</v-icon>
|
||||
Losfliegen
|
||||
Start training
|
||||
</button>
|
||||
<button class="btn ghost" @click="panel='progress'">
|
||||
<v-icon>mdi-progress-check</v-icon>
|
||||
Fortschritt
|
||||
Progress
|
||||
</button>
|
||||
<button class="btn ghost" @click="testBeep">
|
||||
<v-icon>mdi-volume-high</v-icon>
|
||||
@@ -86,7 +86,7 @@
|
||||
</div>
|
||||
<div class="card-title">{{ d.title }}</div>
|
||||
<div class="card-sub muted">{{ d.sub }}</div>
|
||||
<button class="btn mini soft" @click="startDaily(d)">Annehmen</button>
|
||||
<button class="btn mini soft" @click="startDaily(d)">Accept</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,7 +98,7 @@
|
||||
<main v-if="panel==='hub'" class="container" role="main">
|
||||
<div class="hub-head">
|
||||
<h2 class="h2">Mission Hub</h2>
|
||||
<div class="muted">Starte mit ICAO Alphabet & Zahlen, dann Basics, Ground …</div>
|
||||
<div class="muted">Start with the ICAO alphabet & numbers, then basics, ground, and more.</div>
|
||||
</div>
|
||||
|
||||
<div class="tiles">
|
||||
@@ -128,7 +128,7 @@
|
||||
</button>
|
||||
<button class="btn soft" @click="quickContinue(m.id)">
|
||||
<v-icon size="18">mdi-skip-next</v-icon>
|
||||
Weiter
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,7 +137,7 @@
|
||||
</main>
|
||||
|
||||
<!-- MODULE -->
|
||||
<section v-if="panel==='module' && current" class="container play" aria-label="Modul">
|
||||
<section v-if="panel==='module' && current" class="container play" aria-label="Module">
|
||||
<div class="play-head">
|
||||
<div class="crumbs">
|
||||
<button class="link" @click="panel='hub'">
|
||||
@@ -167,8 +167,8 @@
|
||||
<v-icon size="18">mdi-headset</v-icon>
|
||||
{{ l.title }}
|
||||
</div>
|
||||
<div class="chip" :class="{ ok: bestScore(current.id,l.id)>=80 }">
|
||||
{{ bestScore(current.id, l.id) ? bestScore(current.id, l.id) + '%' : 'neu' }}
|
||||
<div class="chip" :class="{ ok: bestScore(current.id,l.id)>=80 }">
|
||||
{{ bestScore(current.id, l.id) ? bestScore(current.id, l.id) + '%' : 'new' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="muted small">{{ l.desc }}</div>
|
||||
@@ -186,12 +186,12 @@
|
||||
<div class="scenario-sub">{{ scenario.radioCall }}</div>
|
||||
</div>
|
||||
<div class="scenario-item">
|
||||
<span class="scenario-label">Flugplatz</span>
|
||||
<span class="scenario-label">Airport</span>
|
||||
<div class="scenario-value">{{ scenario.airport.icao }}</div>
|
||||
<div class="scenario-sub">{{ scenario.airport.name }}</div>
|
||||
</div>
|
||||
<div class="scenario-item wide">
|
||||
<span class="scenario-label">Frequenzen</span>
|
||||
<span class="scenario-label">Frequencies</span>
|
||||
<div class="freq-chips">
|
||||
<button
|
||||
v-for="freq in scenario.frequencies"
|
||||
@@ -224,7 +224,7 @@
|
||||
{{ targetPhrase }}
|
||||
</div>
|
||||
<div v-if="audioContentHidden" class="audio-note muted small">
|
||||
Audio Challenge aktiv – zuerst anhören.
|
||||
Audio challenge active – listen first.
|
||||
</div>
|
||||
</div>
|
||||
<div class="row wrap">
|
||||
@@ -245,11 +245,11 @@
|
||||
@click="revealAudioContent"
|
||||
>
|
||||
<v-icon size="16">mdi-eye</v-icon>
|
||||
Anzeigen
|
||||
Reveal text
|
||||
</button>
|
||||
<button class="btn ghost mini" type="button" @click="rollScenario(true)">
|
||||
<v-icon size="16">mdi-dice-5</v-icon>
|
||||
Würfeln
|
||||
Roll
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -272,7 +272,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="label">Deine Readback</div>
|
||||
<div class="label">Your readback</div>
|
||||
<div class="panel readback-panel">
|
||||
<div class="cloze">
|
||||
<template v-for="(segment, idx) in activeLesson.readback" :key="segment.type === 'field' ? `f-${segment.key}` : `t-${idx}`">
|
||||
@@ -303,7 +303,7 @@
|
||||
<div class="row wrap controls">
|
||||
<button class="btn primary" type="button" :disabled="evaluating" @click="evaluate">
|
||||
<v-icon size="18">mdi-check</v-icon>
|
||||
Prüfen
|
||||
Check
|
||||
</button>
|
||||
<button class="btn soft" type="button" @click="clearAnswers">
|
||||
<v-icon size="18">mdi-eraser</v-icon>
|
||||
@@ -311,20 +311,20 @@
|
||||
</button>
|
||||
<button class="btn ghost" type="button" @click="fillSolution">
|
||||
<v-icon size="18">mdi-auto-fix</v-icon>
|
||||
Autotext
|
||||
Auto-fill
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="result" class="score">
|
||||
<div class="score-num">{{ result.score }}%</div>
|
||||
<div class="muted small">
|
||||
Felder korrekt: {{ result.hits }}/{{ activeLesson.fields.length }} · Ähnlichkeit: {{ Math.round(result.sim * 100) }}%
|
||||
Fields correct: {{ result.hits }}/{{ activeLesson.fields.length }} · Similarity: {{ Math.round(result.sim * 100) }}%
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="result" class="field-checks">
|
||||
<div v-for="field in result.fields" :key="field.key" class="field-check" :class="{ ok: field.pass }">
|
||||
<div class="field-name">{{ field.label }}</div>
|
||||
<div class="field-answer">{{ field.answer || '—' }}</div>
|
||||
<div class="field-expected">Soll: {{ field.expected }}</div>
|
||||
<div class="field-expected">Expected: {{ field.expected }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -332,8 +332,8 @@
|
||||
</section>
|
||||
|
||||
<!-- PROGRESS -->
|
||||
<section v-if="panel==='progress'" class="container" aria-label="Fortschritt">
|
||||
<h2 class="h2">Gesamtfortschritt</h2>
|
||||
<section v-if="panel==='progress'" class="container" aria-label="Progress">
|
||||
<h2 class="h2">Overall progress</h2>
|
||||
<div class="progress-grid">
|
||||
<div v-for="m in modules" :key="m.id" class="panel">
|
||||
<div class="row between">
|
||||
@@ -353,27 +353,27 @@
|
||||
<!-- SETTINGS DIALOG -->
|
||||
<v-dialog v-model="showSettings" max-width="720">
|
||||
<div class="panel dialog">
|
||||
<h3 class="h3">ATC Einstellungen</h3>
|
||||
<h3 class="h3">ATC settings</h3>
|
||||
|
||||
<div class="settings">
|
||||
<div class="set-row">
|
||||
<div class="set-info">
|
||||
<span>Browser-TTS (Web Speech)</span>
|
||||
<small class="muted">Läuft offline, startet schneller und spart unsere API-Anfragen.</small>
|
||||
<span>Browser TTS (Web Speech)</span>
|
||||
<small class="muted">Runs offline, starts faster, and saves our API calls.</small>
|
||||
</div>
|
||||
<v-switch v-model="cfg.tts" color="cyan" hide-details inset/>
|
||||
</div>
|
||||
|
||||
<div class="set-row">
|
||||
<div class="set-info">
|
||||
<span>Audio Challenge</span>
|
||||
<small class="muted">Blendet Zieltext & Infos, bis du sie manuell einblendest.</small>
|
||||
<span>Audio challenge</span>
|
||||
<small class="muted">Hides target text & info until you reveal it manually.</small>
|
||||
</div>
|
||||
<v-switch v-model="cfg.audioChallenge" color="cyan" hide-details inset/>
|
||||
</div>
|
||||
|
||||
<div class="set-row">
|
||||
<span>Funk-Level (1..5)</span>
|
||||
<span>Radio level (1..5)</span>
|
||||
<v-slider v-model="cfg.radioLevel" :min="1" :max="5" :step="1" color="cyan" thumb-label/>
|
||||
</div>
|
||||
|
||||
@@ -383,22 +383,22 @@
|
||||
</div>
|
||||
|
||||
<div class="set-row">
|
||||
<span>Probe-TTS</span>
|
||||
<span>Test TTS</span>
|
||||
<div class="row">
|
||||
<button class="btn soft" @click="say('Frankfurt Ground, Lufthansa one two three, request taxi.')">
|
||||
<v-icon>mdi-volume-high</v-icon>
|
||||
Probe
|
||||
Test
|
||||
</button>
|
||||
<button class="btn ghost" @click="stopAudio">
|
||||
<v-icon>mdi-stop</v-icon>
|
||||
Stopp
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row end">
|
||||
<button class="btn soft" @click="showSettings=false">Schließen</button>
|
||||
<button class="btn soft" @click="showSettings=false">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</v-dialog>
|
||||
@@ -668,7 +668,7 @@ const airportsData: AirportData[] = [
|
||||
},
|
||||
{
|
||||
icao: 'EDDM',
|
||||
name: 'München',
|
||||
name: 'Munich',
|
||||
city: 'Munich',
|
||||
runways: ['26R', '26L', '08R', '08L'],
|
||||
stands: ['211', '214', '302', 'N16', 'H45'],
|
||||
@@ -1014,17 +1014,17 @@ const modules = ref<ModuleDef[]>([
|
||||
lessons: [
|
||||
{
|
||||
id: 'icao-alphabet',
|
||||
title: 'ICAO Alphabet & Zahlen',
|
||||
desc: 'Buchstaben und Ziffern sauber buchstabieren',
|
||||
title: 'ICAO Alphabet & Numbers',
|
||||
desc: 'Spell letters and digits clearly',
|
||||
keywords: ['Alphabet', 'Numbers', 'Normalize'],
|
||||
hints: [
|
||||
'Buchstabiere jeden Buchstaben mit dem ICAO-Namen (z. B. Delta, Lima, Hotel).',
|
||||
'Ziffern im Funk: tree, fower, fife, niner.'
|
||||
'Spell each letter with its ICAO name (e.g. Delta, Lima, Hotel).',
|
||||
'Use ATC numbers: tree, fower, fife, niner.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
key: 'letters',
|
||||
label: 'Buchstaben',
|
||||
label: 'Letters',
|
||||
expected: scenario => scenario.callsignNato,
|
||||
placeholder: 'Delta Lima Hotel',
|
||||
width: 'xl',
|
||||
@@ -1032,7 +1032,7 @@ const modules = ref<ModuleDef[]>([
|
||||
},
|
||||
{
|
||||
key: 'digits',
|
||||
label: 'Zahlen',
|
||||
label: 'Numbers',
|
||||
expected: scenario => scenario.flightNumberWords,
|
||||
placeholder: 'one two three',
|
||||
width: 'lg',
|
||||
@@ -1040,7 +1040,7 @@ const modules = ref<ModuleDef[]>([
|
||||
}
|
||||
],
|
||||
readback: [
|
||||
{ type: 'text', text: 'Rufezeichen: ' },
|
||||
{ type: 'text', text: 'Call sign: ' },
|
||||
{ type: 'field', key: 'letters', width: 'lg' },
|
||||
{ type: 'text', text: ' · ' },
|
||||
{ type: 'field', key: 'digits', width: 'md' }
|
||||
@@ -1048,21 +1048,21 @@ const modules = ref<ModuleDef[]>([
|
||||
defaultFrequency: 'DEL',
|
||||
phrase: scenario => `${scenario.callsignNato} ${scenario.flightNumberWords}`,
|
||||
info: scenario => [
|
||||
`Kennzeichen: ${scenario.callsign}`,
|
||||
`Airline Call: ${scenario.radioCall}`,
|
||||
`ICAO: ${scenario.callsignNato}`,
|
||||
`Flightnummer gesprochen: ${scenario.flightNumberWords}`
|
||||
`Callsign: ${scenario.callsign}`,
|
||||
`Radio call: ${scenario.radioCall}`,
|
||||
`ICAO spelling: ${scenario.callsignNato}`,
|
||||
`Flight number spoken: ${scenario.flightNumberWords}`
|
||||
],
|
||||
generate: createBaseScenario
|
||||
},
|
||||
{
|
||||
id: 'atis',
|
||||
title: 'ATIS verstehen',
|
||||
desc: 'Kennung und Kerndaten aus der ATIS ziehen',
|
||||
keywords: ['ATIS', 'Wetter'],
|
||||
title: 'Understand the ATIS',
|
||||
desc: 'Extract the identifier and key data from the ATIS',
|
||||
keywords: ['ATIS', 'Weather'],
|
||||
hints: [
|
||||
'ATIS-Kennung als einzelnen Buchstaben merken.',
|
||||
'Reihenfolge: Runway – Wind – Sicht – Temperatur – Taupunkt – QNH.'
|
||||
'Remember the ATIS identifier as a single letter.',
|
||||
'Order: runway – wind – visibility – temperature – dew point – QNH.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1074,7 +1074,7 @@ const modules = ref<ModuleDef[]>([
|
||||
scenario.atisCode.toLowerCase(),
|
||||
`Information ${scenario.atisCode}`
|
||||
],
|
||||
placeholder: 'Buchstabe',
|
||||
placeholder: 'Letter',
|
||||
width: 'xs',
|
||||
threshold: 0.9
|
||||
},
|
||||
@@ -1098,21 +1098,21 @@ const modules = ref<ModuleDef[]>([
|
||||
},
|
||||
{
|
||||
key: 'atis-visibility',
|
||||
label: 'Sicht',
|
||||
label: 'Visibility',
|
||||
expected: scenario => scenario.atisSummary.visibility,
|
||||
alternatives: scenario => [scenario.visibility, scenario.visibilityWords],
|
||||
width: 'sm'
|
||||
},
|
||||
{
|
||||
key: 'atis-temp',
|
||||
label: 'Temperatur',
|
||||
label: 'Temperature',
|
||||
expected: scenario => scenario.atisSummary.temperature,
|
||||
alternatives: scenario => [scenario.temperatureWords],
|
||||
width: 'sm'
|
||||
},
|
||||
{
|
||||
key: 'atis-dew',
|
||||
label: 'Taupunkt',
|
||||
label: 'Dew point',
|
||||
expected: scenario => scenario.atisSummary.dewpoint,
|
||||
alternatives: scenario => [scenario.dewpointWords],
|
||||
width: 'sm'
|
||||
@@ -1144,19 +1144,19 @@ const modules = ref<ModuleDef[]>([
|
||||
defaultFrequency: 'ATIS',
|
||||
phrase: scenario => scenario.atisText,
|
||||
info: () => [
|
||||
'Notiere Kennung, Runway, Wind, Sichtweite, Temperatur, Taupunkt und QNH.',
|
||||
'Sicht: vier Ziffern oder 9999 für ≥10 km · QNH als vierstellige Zahl.'
|
||||
'Note the identifier, runway, wind, visibility, temperature, dew point, and QNH.',
|
||||
'Visibility: four digits or 9999 for ≥10 km; QNH as a four-digit value.'
|
||||
],
|
||||
generate: createBaseScenario
|
||||
},
|
||||
{
|
||||
id: 'metar',
|
||||
title: 'METAR entschlüsseln',
|
||||
desc: 'Rohe METAR-Werte extrahieren',
|
||||
title: 'Decode the METAR',
|
||||
desc: 'Extract the raw METAR values',
|
||||
keywords: ['METAR', 'Weather'],
|
||||
hints: [
|
||||
'METAR blockweise lesen: Wind – Sicht – Wolken – Temperatur – QNH.',
|
||||
'Der Temperaturblock hat die Form 18/10, Minuswerte beginnen mit M.'
|
||||
'Read the METAR in blocks: wind – visibility – clouds – temperature – QNH.',
|
||||
'The temperature block looks like 18/10; negative values start with M.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1172,7 +1172,7 @@ const modules = ref<ModuleDef[]>([
|
||||
},
|
||||
{
|
||||
key: 'metar-vis',
|
||||
label: 'Sicht',
|
||||
label: 'Visibility',
|
||||
expected: scenario => scenario.metarSegments.visibility,
|
||||
alternatives: scenario => [scenario.visibility],
|
||||
placeholder: '9999',
|
||||
@@ -1199,9 +1199,9 @@ const modules = ref<ModuleDef[]>([
|
||||
readback: [
|
||||
{ type: 'text', text: 'Wind ' },
|
||||
{ type: 'field', key: 'metar-wind', width: 'md' },
|
||||
{ type: 'text', text: ', Sicht ' },
|
||||
{ type: 'text', text: ', visibility ' },
|
||||
{ type: 'field', key: 'metar-vis', width: 'sm' },
|
||||
{ type: 'text', text: ', Temperatur ' },
|
||||
{ type: 'text', text: ', temperature ' },
|
||||
{ type: 'field', key: 'metar-temp', width: 'md' },
|
||||
{ type: 'text', text: ', QNH ' },
|
||||
{ type: 'field', key: 'metar-qnh', width: 'sm' }
|
||||
@@ -1210,18 +1210,18 @@ const modules = ref<ModuleDef[]>([
|
||||
phrase: scenario => scenario.metar,
|
||||
info: scenario => [
|
||||
`METAR: ${scenario.metar}`,
|
||||
`Interpretation: Wind ${scenario.metarSegments.wind}, Sicht ${scenario.visibilityWords}, Temperatur ${scenario.temperature}°C, QNH ${scenario.qnh}`
|
||||
`Interpretation: Wind ${scenario.metarSegments.wind}, visibility ${scenario.visibilityWords}, temperature ${scenario.temperature}°C, QNH ${scenario.qnh}`
|
||||
],
|
||||
generate: createBaseScenario
|
||||
},
|
||||
{
|
||||
id: 'radio-check',
|
||||
title: 'Radio Check',
|
||||
desc: 'Lesbarkeit melden',
|
||||
desc: 'Report readability',
|
||||
keywords: ['Ground', 'Comms'],
|
||||
hints: [
|
||||
'Antwort besteht aus "Readability" plus Zahl.',
|
||||
'Beende den Check immer mit deinem Callsign.'
|
||||
'Reply with "Readability" followed by the number.',
|
||||
'Always finish the check with your call sign.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1238,7 +1238,7 @@ const modules = ref<ModuleDef[]>([
|
||||
},
|
||||
{
|
||||
key: 'rc-readability',
|
||||
label: 'Lesbarkeit',
|
||||
label: 'Readability',
|
||||
expected: scenario => scenario.readabilityWord,
|
||||
alternatives: scenario => [scenario.readability.toString(), scenario.readabilityWord],
|
||||
placeholder: 'five',
|
||||
@@ -1254,8 +1254,8 @@ const modules = ref<ModuleDef[]>([
|
||||
defaultFrequency: 'GND',
|
||||
phrase: scenario => `${scenario.airport.name} Ground, ${scenario.radioCall}, radio check on ${scenario.groundFreq}.`,
|
||||
info: scenario => [
|
||||
`Frequenz: ${scenario.groundFreq} (${scenario.frequencyWords.GND})`,
|
||||
`Erwartete Antwort: Readability ${scenario.readability} (${scenario.readabilityWord})`
|
||||
`Frequency: ${scenario.groundFreq} (${scenario.frequencyWords.GND})`,
|
||||
`Expected response: Readability ${scenario.readability} (${scenario.readabilityWord})`
|
||||
],
|
||||
generate: createBaseScenario
|
||||
}
|
||||
@@ -1264,17 +1264,17 @@ const modules = ref<ModuleDef[]>([
|
||||
{
|
||||
id: 'arc',
|
||||
title: 'ARC Decision Tree',
|
||||
subtitle: 'Vom Clearance Call bis Departure',
|
||||
subtitle: 'From clearance call to departure',
|
||||
art: gradientArt(['#f97316', '#fb923c', '#0f172a']),
|
||||
lessons: [
|
||||
{
|
||||
id: 'clearance-contact',
|
||||
title: 'Delivery: Erstkontakt',
|
||||
desc: 'Clearance Delivery kontaktieren',
|
||||
title: 'Delivery: Initial contact',
|
||||
desc: 'Contact clearance delivery',
|
||||
keywords: ['Delivery', 'Clearance'],
|
||||
hints: [
|
||||
'Reihenfolge: Einheit, Callsign, ATIS, Ziel, Stand, Request.',
|
||||
'ATIS als einzelner Buchstabe sprechen.'
|
||||
'Order: unit, call sign, ATIS, destination, stand, request.',
|
||||
'Say the ATIS as a single letter.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1298,7 +1298,7 @@ const modules = ref<ModuleDef[]>([
|
||||
},
|
||||
{
|
||||
key: 'cd-stand',
|
||||
label: 'Stand',
|
||||
label: 'Stand/Gate',
|
||||
expected: scenario => scenario.stand,
|
||||
width: 'sm'
|
||||
}
|
||||
@@ -1318,20 +1318,20 @@ const modules = ref<ModuleDef[]>([
|
||||
defaultFrequency: 'DEL',
|
||||
phrase: scenario => `${scenario.airport.city} Delivery, ${scenario.radioCall}, information ${scenario.atisCode}, IFR to ${scenario.destination.city}, stand ${scenario.stand}, request clearance.`,
|
||||
info: scenario => [
|
||||
`ATIS ${scenario.atisCode}`,
|
||||
`Ziel: ${scenario.destination.city} (${scenario.destination.icao})`,
|
||||
`Stand: ${scenario.stand}`
|
||||
`ATIS: ${scenario.atisCode}`,
|
||||
`Destination: ${scenario.destination.city} (${scenario.destination.icao})`,
|
||||
`Stand/Gate: ${scenario.stand}`
|
||||
],
|
||||
generate: createBaseScenario
|
||||
},
|
||||
{
|
||||
id: 'clearance-readback',
|
||||
title: 'Clearance Readback',
|
||||
desc: 'Freigabe komplett zurücklesen',
|
||||
desc: 'Read back the clearance in full',
|
||||
keywords: ['Delivery', 'Readback'],
|
||||
hints: [
|
||||
'Reihenfolge merken: Ziel – SID – Runway – Altitude – Squawk.',
|
||||
'Zahlen bei Altitude und Squawk sauber ausschreiben.'
|
||||
'Remember the order: destination – SID – runway – altitude – squawk.',
|
||||
'Speak altitude and squawk digits clearly.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1393,11 +1393,11 @@ const modules = ref<ModuleDef[]>([
|
||||
{
|
||||
id: 'pushback',
|
||||
title: 'Push & Start Readback',
|
||||
desc: 'Pushback-Freigabe bestätigen',
|
||||
desc: 'Acknowledge the pushback clearance',
|
||||
keywords: ['Ground', 'Pushback'],
|
||||
hints: [
|
||||
'Bahnrichtung und QNH übernehmen, Callsign ans Ende.',
|
||||
'QNH darf als Zahl oder "QNH xxxx" stehen.'
|
||||
'Repeat the runway direction and QNH, call sign at the end.',
|
||||
'QNH may be just the number or "QNH xxxx".'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1437,7 +1437,7 @@ const modules = ref<ModuleDef[]>([
|
||||
defaultFrequency: 'GND',
|
||||
phrase: scenario => `${scenario.radioCall}, push and start approved, facing runway ${scenario.runway}. QNH ${scenario.qnh}.`,
|
||||
info: scenario => [
|
||||
`Stand: ${scenario.stand}`,
|
||||
`Stand/Gate: ${scenario.stand}`,
|
||||
`Ground: ${scenario.groundFreq} (${scenario.frequencyWords.GND})`
|
||||
],
|
||||
generate: createBaseScenario
|
||||
@@ -1445,11 +1445,11 @@ const modules = ref<ModuleDef[]>([
|
||||
{
|
||||
id: 'taxi',
|
||||
title: 'Taxi Readback',
|
||||
desc: 'Taxi-Freigabe mit Hold Short',
|
||||
desc: 'Read back the taxi clearance with hold short',
|
||||
keywords: ['Ground', 'Taxi'],
|
||||
hints: [
|
||||
'Route wie gehört wiederholen, inklusive Hold Short.',
|
||||
'Am Ende wieder Callsign.'
|
||||
'Repeat the route exactly as received, including the hold short.',
|
||||
'Finish with the call sign.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1498,19 +1498,19 @@ const modules = ref<ModuleDef[]>([
|
||||
defaultFrequency: 'GND',
|
||||
phrase: scenario => `${scenario.radioCall}, taxi to runway ${scenario.runway} via ${scenario.taxiRoute}, hold short runway ${scenario.runway}.`,
|
||||
info: scenario => [
|
||||
`Taxi-Route: ${scenario.taxiRoute}`,
|
||||
`Hold Short: ${scenario.runway}`
|
||||
`Taxi route: ${scenario.taxiRoute}`,
|
||||
`Hold short: ${scenario.runway}`
|
||||
],
|
||||
generate: createBaseScenario
|
||||
},
|
||||
{
|
||||
id: 'lineup',
|
||||
title: 'Line-up Clearance',
|
||||
desc: 'Aufrollen und warten bestätigen',
|
||||
desc: 'Acknowledge line up and wait',
|
||||
keywords: ['Tower', 'Line Up'],
|
||||
hints: [
|
||||
'Runway wiederholen, dann "line up and wait".',
|
||||
'Callsign am Ende setzen.'
|
||||
'Repeat the runway, then say "line up and wait".',
|
||||
'Place the call sign at the end.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1549,11 +1549,11 @@ const modules = ref<ModuleDef[]>([
|
||||
{
|
||||
id: 'takeoff',
|
||||
title: 'Takeoff Clearance',
|
||||
desc: 'Takeoff-Freigabe bestätigen',
|
||||
desc: 'Acknowledge the takeoff clearance',
|
||||
keywords: ['Tower', 'Departure'],
|
||||
hints: [
|
||||
'Reihenfolge: Runway – cleared for takeoff – Callsign.',
|
||||
'Windangabe kann entfallen, wenn nicht gefordert.'
|
||||
'Order: runway – cleared for takeoff – call sign.',
|
||||
'Wind information can be omitted if it was not given.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1592,11 +1592,11 @@ const modules = ref<ModuleDef[]>([
|
||||
{
|
||||
id: 'departure-handoff',
|
||||
title: 'Departure Handoff',
|
||||
desc: 'Auf Departure wechseln',
|
||||
desc: 'Switch to departure',
|
||||
keywords: ['Departure', 'Handoff'],
|
||||
hints: [
|
||||
'Frequenz exakt wiederholen, optional als Zahl oder gesprochen.',
|
||||
'Callsign am Ende anhängen.'
|
||||
'Repeat the frequency exactly, either as digits or spoken form.',
|
||||
'Append the call sign at the end.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1632,18 +1632,18 @@ const modules = ref<ModuleDef[]>([
|
||||
phrase: scenario => `${scenario.radioCall}, contact departure ${scenario.departureFreq}.`,
|
||||
info: scenario => [
|
||||
`Departure: ${scenario.departureFreq} (${scenario.frequencyWords.DEP})`,
|
||||
`Tower Handoff nach Start.`
|
||||
`Tower handoff after departure.`
|
||||
],
|
||||
generate: createBaseScenario
|
||||
},
|
||||
{
|
||||
id: 'departure-checkin',
|
||||
title: 'Departure Check-in',
|
||||
desc: 'Erstmeldung bei Departure',
|
||||
desc: 'Initial call to departure',
|
||||
keywords: ['Departure', 'Check-in'],
|
||||
hints: [
|
||||
'Einheit ansprechen, dann Callsign nennen.',
|
||||
'Altitude und SID exakt wie gehört wiederholen.'
|
||||
'Address the unit first, then state the call sign.',
|
||||
'Repeat the altitude and SID exactly as received.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1694,11 +1694,11 @@ const modules = ref<ModuleDef[]>([
|
||||
{
|
||||
id: 'climb-instruction',
|
||||
title: 'Climb & Direct',
|
||||
desc: 'Steigauftrag komplett zurücklesen',
|
||||
desc: 'Read back the climb instruction in full',
|
||||
keywords: ['Departure', 'Climb'],
|
||||
hints: [
|
||||
'Mit "Climb" beginnen, dann Höhe und gegebenenfalls Direct.',
|
||||
'Callsign am Ende wiederholen.'
|
||||
'Start with "Climb", then the altitude and any direct clearance.',
|
||||
'Repeat the call sign at the end.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1749,11 +1749,11 @@ const modules = ref<ModuleDef[]>([
|
||||
{
|
||||
id: 'center-handoff',
|
||||
title: 'Center Handoff',
|
||||
desc: 'Übergabe an Center bestätigen',
|
||||
desc: 'Acknowledge the handoff to center',
|
||||
keywords: ['Center', 'Handoff'],
|
||||
hints: [
|
||||
'Frequenz exakt wiedergeben (mit oder ohne Dezimalpunkt).',
|
||||
'Callsign am Ende platzieren.'
|
||||
'Repeat the frequency exactly (with or without the decimal point).',
|
||||
'Place the call sign at the end.'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
@@ -1789,7 +1789,7 @@ const modules = ref<ModuleDef[]>([
|
||||
phrase: scenario => `${scenario.radioCall}, contact center ${scenario.centerFreq}.`,
|
||||
info: scenario => [
|
||||
`Center: ${scenario.centerFreq} (${scenario.frequencyWords.CTR})`,
|
||||
`Nächste Einheit: Center`
|
||||
`Next unit: Center`
|
||||
],
|
||||
generate: createBaseScenario
|
||||
}
|
||||
@@ -2003,7 +2003,7 @@ function blankStateClass(key: string): string {
|
||||
}
|
||||
|
||||
function fieldLabel(key: string): string {
|
||||
return fieldMap.value[key]?.label ?? 'Feld'
|
||||
return fieldMap.value[key]?.label ?? 'Field'
|
||||
}
|
||||
|
||||
function fieldPlaceholder(key: string): string {
|
||||
@@ -2185,9 +2185,9 @@ function avgScore(modId: string) {
|
||||
}
|
||||
|
||||
const dailies = ref([
|
||||
{ id: 'd1', title: '3 Readbacks ≥80%', sub: 'Belohnung: +50 XP', reward: 50 },
|
||||
{ id: 'd2', title: '1 Modul starten', sub: 'Belohnung: +20 XP', reward: 20 },
|
||||
{ id: 'd3', title: 'Zielphrase abspielen', sub: 'Belohnung: +10 XP', reward: 10 }
|
||||
{ id: 'd1', title: '3 readbacks ≥80%', sub: 'Reward: +50 XP', reward: 50 },
|
||||
{ id: 'd2', title: 'Start 1 module', sub: 'Reward: +20 XP', reward: 20 },
|
||||
{ id: 'd3', title: 'Play the target phrase', sub: 'Reward: +10 XP', reward: 10 }
|
||||
])
|
||||
|
||||
function startDaily(daily: { id: string; reward: number; title: string }) {
|
||||
|
||||
105
app/pages/pm.vue
105
app/pages/pm.vue
@@ -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 EDDF→EDDM)
|
||||
Start demo (DLH39A EDDF→EDDM)
|
||||
</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 fü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 fü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 fü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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 A–M',
|
||||
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 N–Z',
|
||||
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']
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user