brand renaming, scenario reloads flight

This commit is contained in:
2026-04-12 08:39:12 -03:00
parent 9dbf89da02
commit 5c82703ebe
25 changed files with 433 additions and 482 deletions

View File

@@ -1,5 +1,5 @@
{
"name": "united-ops-ui",
"name": "stellar-ops-ui",
"version": "0.1.0",
"private": true,
"type": "module",

View File

@@ -1,10 +1,17 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ref, provide } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import ScenarioSelector from './components/ScenarioSelector.vue'
const router = useRouter()
const route = useRoute()
const scenarioVersion = ref(0)
function onScenarioChange() {
scenarioVersion.value++
}
provide('scenarioVersion', scenarioVersion)
</script>
<template>
@@ -18,8 +25,9 @@ const route = useRoute()
<router-link to="/" :class="{ active: route.path === '/' }">Operations</router-link>
<router-link to="/internals" :class="{ active: route.path === '/internals' }">Internals</router-link>
<router-link to="/data" :class="{ active: route.path === '/data' }">Data</router-link>
<a href="/docs/" class="docs-link" target="_blank">Docs</a>
</nav>
<ScenarioSelector />
<ScenarioSelector @change="onScenarioChange" />
</header>
<main class="app-main">
<router-view />
@@ -85,6 +93,11 @@ const route = useRoute()
border-color: var(--accent);
}
.app-nav .docs-link {
color: var(--text-dim);
font-size: 11px;
}
.app-main {
flex: 1;
overflow: auto;

View File

@@ -1,6 +1,8 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const emit = defineEmits<{ change: [id: string] }>()
const scenarios = ref<any[]>([])
const active = ref('')
@@ -19,6 +21,7 @@ async function switchScenario(id: string) {
body: JSON.stringify({ scenario_id: id }),
})
active.value = id
emit('change', id)
}
onMounted(loadScenarios)

View File

@@ -1,40 +1,41 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, watch, inject } from 'vue'
import { Panel } from 'soleprint-ui'
import NotificationCard from '../components/NotificationCard.vue'
import HandoverBrief from '../components/HandoverBrief.vue'
const flights = ref<any[]>([])
const selectedFlight = ref('')
const efhasStatus = ref<'idle' | 'processing' | 'live' | 'error'>('idle')
const fceStatus = ref<'idle' | 'processing' | 'live' | 'error'>('idle')
const handoverStatus = ref<'idle' | 'processing' | 'live' | 'error'>('idle')
const notification = ref<any>(null)
const handoverBrief = ref<any>(null)
const scenarioVersion = inject<any>('scenarioVersion')
watch(scenarioVersion, () => {
loadFlights()
notification.value = null
handoverBrief.value = null
fceStatus.value = 'idle'
handoverStatus.value = 'idle'
})
async function loadFlights() {
// Load flights from active scenario via a simple status check on known IDs
const res = await fetch('/scenarios/active')
const scenario = await res.json()
// Fetch flights by triggering the shared MCP server isn't exposed directly
// so we'll use a hardcoded list per scenario for now
// TODO: expose flight list via API
flights.value = [
{ id: 'UA432', label: 'UA432 ORD→SFO' },
{ id: 'UA881', label: 'UA881 ORD→LAX' },
{ id: 'UA233', label: 'UA233 ORD→DEN' },
{ id: 'UA094', label: 'UA094 ORD→EWR' },
{ id: 'UA517', label: 'UA517 ORD→IAH (CANCELLED)' },
{ id: 'UA1220', label: 'UA1220 ORD→IAD (on-time)' },
]
const res = await fetch('/scenarios/data/flights')
const data = await res.json()
flights.value = data.map((f: any) => ({
id: f.flight_id,
label: `${f.flight_id} ${f.origin}${f.destination}${f.status !== 'ON_TIME' ? ' (' + f.status + ')' : ''}`,
}))
selectedFlight.value = flights.value[0]?.id || ''
}
async function runEfhas() {
async function runFce() {
if (!selectedFlight.value) return
efhasStatus.value = 'processing'
fceStatus.value = 'processing'
notification.value = null
const res = await fetch('/agents/efhas', {
const res = await fetch('/agents/fce', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ flight_id: selectedFlight.value }),
@@ -48,10 +49,10 @@ async function runEfhas() {
if (data.status === 'completed') {
clearInterval(poll)
notification.value = data.result
efhasStatus.value = 'live'
fceStatus.value = 'live'
} else if (data.status === 'error') {
clearInterval(poll)
efhasStatus.value = 'error'
fceStatus.value = 'error'
}
}, 1000)
}
@@ -86,20 +87,20 @@ onMounted(loadFlights)
<template>
<div class="ops-page">
<Panel title="FCE — Behind Every Departure" :status="efhasStatus">
<Panel title="FCE — Behind Every Departure" :status="fceStatus">
<template #actions>
<select v-model="selectedFlight" class="flight-select">
<option v-for="f in flights" :key="f.id" :value="f.id">{{ f.label }}</option>
</select>
<button class="run-btn" @click="runEfhas" :disabled="efhasStatus === 'processing'">
{{ efhasStatus === 'processing' ? 'Running...' : 'Run FCE' }}
<button class="run-btn" @click="runFce" :disabled="fceStatus === 'processing'">
{{ fceStatus === 'processing' ? 'Running...' : 'Run FCE' }}
</button>
</template>
<div v-if="notification" class="result-area">
<NotificationCard :data="notification" />
</div>
<div v-else-if="efhasStatus === 'processing'" class="loading">
<div v-else-if="fceStatus === 'processing'" class="loading">
Running agent... gathering flight data, weather, crew notes...
</div>
<div v-else class="empty">