init commit

This commit is contained in:
2026-04-12 07:19:48 -03:00
commit 9dbf89da02
111 changed files with 14925 additions and 0 deletions

View File

@@ -0,0 +1,175 @@
<script setup lang="ts">
import { ref, onMounted } 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 handoverStatus = ref<'idle' | 'processing' | 'live' | 'error'>('idle')
const notification = ref<any>(null)
const handoverBrief = ref<any>(null)
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)' },
]
selectedFlight.value = flights.value[0]?.id || ''
}
async function runEfhas() {
if (!selectedFlight.value) return
efhasStatus.value = 'processing'
notification.value = null
const res = await fetch('/agents/efhas', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ flight_id: selectedFlight.value }),
})
const { run_id } = await res.json()
// Poll for result
const poll = setInterval(async () => {
const r = await fetch(`/agents/runs/${run_id}`)
const data = await r.json()
if (data.status === 'completed') {
clearInterval(poll)
notification.value = data.result
efhasStatus.value = 'live'
} else if (data.status === 'error') {
clearInterval(poll)
efhasStatus.value = 'error'
}
}, 1000)
}
async function runHandover() {
handoverStatus.value = 'processing'
handoverBrief.value = null
const res = await fetch('/agents/handover', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
})
const { run_id } = await res.json()
const poll = setInterval(async () => {
const r = await fetch(`/agents/runs/${run_id}`)
const data = await r.json()
if (data.status === 'completed') {
clearInterval(poll)
handoverBrief.value = data.result
handoverStatus.value = 'live'
} else if (data.status === 'error') {
clearInterval(poll)
handoverStatus.value = 'error'
}
}, 1000)
}
onMounted(loadFlights)
</script>
<template>
<div class="ops-page">
<Panel title="FCE — Behind Every Departure" :status="efhasStatus">
<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>
</template>
<div v-if="notification" class="result-area">
<NotificationCard :data="notification" />
</div>
<div v-else-if="efhasStatus === 'processing'" class="loading">
Running agent... gathering flight data, weather, crew notes...
</div>
<div v-else class="empty">
Select a flight and click Run FCE to generate a notification.
</div>
</Panel>
<Panel title="Shift Handover Brief" :status="handoverStatus">
<template #actions>
<button class="run-btn" @click="runHandover" :disabled="handoverStatus === 'processing'">
{{ handoverStatus === 'processing' ? 'Running...' : 'Run Handover' }}
</button>
</template>
<div v-if="handoverBrief" class="result-area">
<HandoverBrief :data="handoverBrief" />
</div>
<div v-else-if="handoverStatus === 'processing'" class="loading">
Running agent... scanning all hubs for active issues...
</div>
<div v-else class="empty">
Click Run Handover to generate a shift handover brief.
</div>
</Panel>
</div>
</template>
<style scoped>
.ops-page {
display: flex;
flex-direction: column;
gap: 24px;
}
.flight-select {
background: var(--surface-2);
color: var(--text-primary);
border: var(--panel-border);
padding: 4px 8px;
font-family: var(--font-mono);
font-size: 12px;
}
.run-btn {
background: var(--accent);
color: white;
border: none;
padding: 4px 16px;
font-family: var(--font-mono);
font-size: 12px;
cursor: pointer;
transition: background 0.15s;
}
.run-btn:hover { background: var(--accent-dim); }
.run-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.result-area {
padding: 16px;
}
.loading, .empty {
padding: 32px;
text-align: center;
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 13px;
}
.loading {
color: var(--accent);
}
</style>