/// /** * United Airlines Flight Tracker * * Tracks United Airlines flights from email notifications, showing upcoming flights, * check-in availability, delays, and confirmation details. * * Features: * - Embeds gmail-importer for United emails * - Extracts flight information using LLM from email markdown content * - Deduplicates flights across multiple emails (confirmation, check-in, boarding) * - Tracks upcoming and past flights * - Shows check-in availability, delays, gate changes * * Usage: * 1. Deploy a google-auth piece and complete OAuth * 2. Deploy this pattern * 3. Link: ct piece link google-auth/auth united-flight-tracker/overrideAuth */ import { computed, ifElse, JSONSchema, NAME, pattern, UI } from "commontools"; import type { Schema } from "commontools/schema"; import GmailExtractor from "../core/gmail-extractor.tsx"; import type { Auth } from "../core/gmail-extractor.tsx"; import ProcessingStatus from "../core/processing-status.tsx"; // Email type imported from GmailExtractor // ============================================================================= // TYPES // ============================================================================= type EmailType = | "booking_confirmation" | "check_in_available" | "check_in_confirmation" | "boarding_pass" | "flight_delay" | "flight_cancellation" | "gate_change" | "upgrade_offer" | "itinerary_update" | "receipt" | "mileageplus" | "other"; type FlightStatus = "scheduled" | "delayed" | "cancelled" | "completed"; interface FlightInfo { flightNumber?: string; // e.g., "UA 1234" confirmationNumber?: string; departureCity?: string; departureAirport?: string; // 3-letter code arrivalCity?: string; arrivalAirport?: string; // 3-letter code departureDate?: string; // ISO format YYYY-MM-DD departureTime?: string; // HH:MM format arrivalTime?: string; gate?: string; seat?: string; terminal?: string; status?: string; // on-time, delayed, cancelled delayMinutes?: number; newDepartureTime?: string; // Updated time if delayed } interface UnitedEmailAnalysis { emailType: EmailType; flights: FlightInfo[]; passengerName?: string; checkInAvailable?: boolean; checkInDeadline?: string; // ISO datetime summary: string; } /** A tracked flight with deduplicated data */ interface TrackedFlight { key: string; // Deduplication: confirmationNumber|flightNumber|departureDate confirmationNumber: string; flightNumber: string; departureCity: string; departureAirport: string; arrivalCity: string; arrivalAirport: string; departureDate: string; // YYYY-MM-DD departureTime: string; // HH:MM arrivalTime: string; seat?: string; gate?: string; terminal?: string; status: FlightStatus; delayMinutes?: number; newDepartureTime?: string; checkInAvailable: boolean; checkInDeadline?: string; isUpcoming: boolean; daysUntilFlight: number; passengerName?: string; emailIds: string[]; // Source email IDs } /** Flights grouped by confirmation number */ interface TrackedTrip { confirmationNumber: string; flights: TrackedFlight[]; passengerName?: string; hasUpcomingFlights: boolean; nextFlight?: TrackedFlight; } // ============================================================================= // CONSTANTS // ============================================================================= // United sends from various addresses const UNITED_GMAIL_QUERY = "from:united.com"; // 32 distinct colors for flight number badges (to reduce collisions) const FLIGHT_COLORS = [ // Blues (United brand-ish) "#3b82f6", "#2563eb", "#1d4ed8", "#1e40af", // Indigos & purples "#6366f1", "#4f46e5", "#8b5cf6", "#7c3aed", // Teals & cyans "#14b8a6", "#0d9488", "#06b6d4", "#0891b2", // Greens "#22c55e", "#16a34a", "#84cc16", "#65a30d", // Oranges & ambers "#f97316", "#ea580c", "#f59e0b", "#d97706", // Reds & pinks "#ef4444", "#dc2626", "#ec4899", "#db2777", // More blues & slates "#0ea5e9", "#0284c7", "#64748b", "#475569", // More purples "#a855f7", "#9333ea", "#d946ef", "#c026d3", ]; /** * Get a consistent color for a flight number. * Same flight number always gets the same color. */ function getFlightColor(flightNumber: string | undefined): string { if (!flightNumber || typeof flightNumber !== "string") { return FLIGHT_COLORS[0]; } let hash = 0; for (let i = 0; i < flightNumber.length; i++) { hash = (hash * 31 + flightNumber.charCodeAt(i)) % 32; } return FLIGHT_COLORS[hash]; } // Schema for LLM email analysis const EMAIL_ANALYSIS_SCHEMA = { type: "object", properties: { emailType: { type: "string", enum: [ "booking_confirmation", "check_in_available", "check_in_confirmation", "boarding_pass", "flight_delay", "flight_cancellation", "gate_change", "upgrade_offer", "itinerary_update", "receipt", "mileageplus", "other", ], description: "Type of United email", }, flights: { type: "array", items: { type: "object", properties: { flightNumber: { type: "string", description: "Flight number like 'UA 1234' or 'United 1234'", }, confirmationNumber: { type: "string", description: "6-character booking confirmation code", }, departureCity: { type: "string", description: "Departure city name", }, departureAirport: { type: "string", description: "3-letter departure airport code (e.g., SFO, ORD)", }, arrivalCity: { type: "string", description: "Arrival city name", }, arrivalAirport: { type: "string", description: "3-letter arrival airport code", }, departureDate: { type: "string", description: "Departure date in YYYY-MM-DD format", }, departureTime: { type: "string", description: "Departure time in HH:MM format (24-hour)", }, arrivalTime: { type: "string", description: "Arrival time in HH:MM format (24-hour)", }, gate: { type: "string", description: "Gate number if mentioned", }, seat: { type: "string", description: "Seat assignment if mentioned (e.g., '12A')", }, terminal: { type: "string", description: "Terminal if mentioned", }, status: { type: "string", description: "Flight status: on-time, delayed, cancelled", }, delayMinutes: { type: "number", description: "Delay in minutes if flight is delayed", }, newDepartureTime: { type: "string", description: "New departure time in HH:MM format if flight is delayed", }, }, }, description: "List of flights mentioned in the email", }, passengerName: { type: "string", description: "Passenger name if mentioned", }, checkInAvailable: { type: "boolean", description: "Whether check-in is currently available", }, checkInDeadline: { type: "string", description: "Check-in deadline in ISO datetime format if mentioned", }, summary: { type: "string", description: "Brief one-sentence summary of the email", }, }, required: ["emailType", "flights", "summary"], } as const satisfies JSONSchema; type EmailAnalysisResult = Schema; // Prompt template for LLM extraction const EXTRACTION_PROMPT_TEMPLATE = `Analyze this United Airlines email and extract flight information. EMAIL SUBJECT: {{email.subject}} EMAIL DATE: {{email.date}} EMAIL CONTENT: {{email.markdownContent}} Extract: 1. The type of email: - booking_confirmation: New booking or itinerary confirmation - check_in_available: Check-in is now open (24h before departure) - check_in_confirmation: Check-in completed successfully - boarding_pass: Mobile boarding pass - flight_delay: Flight has been delayed - flight_cancellation: Flight has been cancelled - gate_change: Gate has changed - upgrade_offer: Upgrade opportunity - itinerary_update: Pre-trip reminder or itinerary change - receipt: Wi-Fi, upgrade, or other purchase receipt - mileageplus: MileagePlus status or miles update - other: Unrelated to flights 2. All flights mentioned with: - Flight number (e.g., "UA 1234") - Confirmation number (6-character code) - Departure/arrival cities and airport codes - Departure date (YYYY-MM-DD) and time (HH:MM 24-hour) - Arrival time - Gate, terminal, seat if mentioned - Status (on-time, delayed, cancelled) - Delay in minutes and new time if delayed 3. Passenger name if mentioned 4. Check-in availability and deadline if mentioned 5. Brief summary of the email`; // ============================================================================= // HELPERS // ============================================================================= /** * Create a deduplication key for a flight. * Uses confirmation + flight number + departure date. */ function createFlightKey( confirmationNumber: string, flightNumber: string, departureDate: string, ): string { return `${confirmationNumber}|${flightNumber}|${departureDate}`; } /** * Calculate days until flight. * Returns negative number for past flights. */ function calculateDaysUntilFlight( departureDate: string | undefined, referenceDate: Date, ): number { if (!departureDate) return 999; const match = departureDate.match(/^(\d{4})-(\d{2})-(\d{2})/); if (!match) return 999; const [, year, month, day] = match; const departure = new Date( parseInt(year), parseInt(month) - 1, parseInt(day), ); if (isNaN(departure.getTime())) return 999; departure.setHours(0, 0, 0, 0); return Math.ceil( (departure.getTime() - referenceDate.getTime()) / (1000 * 60 * 60 * 24), ); } /** * Calculate hours until a flight using both date and time. * Returns actual hours (can be fractional) until departure. */ function calculateHoursUntilFlight( departureDate: string | undefined, departureTime: string | undefined, referenceDate: Date, ): number { if (!departureDate) return 999 * 24; const dateMatch = departureDate.match(/^(\d{4})-(\d{2})-(\d{2})/); if (!dateMatch) return 999 * 24; const [, year, month, day] = dateMatch; // Default to noon if no time specified let hours = 12; let minutes = 0; if (departureTime) { const timeMatch = departureTime.match(/^(\d{1,2}):(\d{2})/); if (timeMatch) { hours = parseInt(timeMatch[1]); minutes = parseInt(timeMatch[2]); } } const departure = new Date( parseInt(year), parseInt(month) - 1, parseInt(day), hours, minutes, ); if (isNaN(departure.getTime())) return 999 * 24; return (departure.getTime() - referenceDate.getTime()) / (1000 * 60 * 60); } /** * Format date for display. */ function formatDate(dateStr: string | undefined): string { if (!dateStr) return "N/A"; const [year, month, day] = dateStr.split("-").map(Number); const date = new Date(year, month - 1, day); return date.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", }); } /** * Format time for display. */ function formatTime(timeStr: string | undefined): string { if (!timeStr) return ""; // Convert 24h to 12h format const [hours, minutes] = timeStr.split(":").map(Number); if (isNaN(hours) || isNaN(minutes)) return timeStr; const ampm = hours >= 12 ? "PM" : "AM"; const hour12 = hours % 12 || 12; return `${hour12}:${minutes.toString().padStart(2, "0")} ${ampm}`; } /** * Parse flight status from email analysis. */ function parseFlightStatus(status: string | undefined): FlightStatus { if (!status) return "scheduled"; const lower = status.toLowerCase(); if (lower.includes("delay")) return "delayed"; if (lower.includes("cancel")) return "cancelled"; if (lower.includes("complete") || lower.includes("landed")) { return "completed"; } return "scheduled"; } // ============================================================================= // PATTERN // ============================================================================= interface Input { overrideAuth?: Auth; } /** United Airlines flight tracker. #unitedFlights */ interface Output { emailCount: number; flights: TrackedFlight[]; upcomingFlights: TrackedFlight[]; checkInAvailable: TrackedFlight[]; activeAlerts: TrackedFlight[]; pastFlights: TrackedFlight[]; trips: TrackedTrip[]; previewUI: unknown; } export default pattern(({ overrideAuth }) => { // Use GmailExtractor building block with LLM extraction const extractor = GmailExtractor({ gmailQuery: UNITED_GMAIL_QUERY, extraction: { promptTemplate: EXTRACTION_PROMPT_TEMPLATE, schema: EMAIL_ANALYSIS_SCHEMA, }, title: "United Flights", limit: 100, overrideAuth, }); // Get values from extractor const unitedEmailCount = extractor.emailCount; const { pendingCount, completedCount, rawAnalyses } = extractor; // Add emailSubject to each analysis item for debug view const emailAnalyses = computed(() => rawAnalyses?.map((item) => ({ ...item, emailSubject: item.email?.subject, // Alias analysis.result as result for backward compatibility in flight aggregation result: item.analysis?.result as EmailAnalysisResult | undefined, })) || [] ); // ========================================================================== // FLIGHT TRACKING - DEDUPLICATION AND MERGING // ========================================================================== const flights = computed(() => { const flightMap: Record = {}; // Create reference dates for calculations const now = new Date(); // Current time for hour-based calculations const today = new Date(now); today.setHours(0, 0, 0, 0); // Midnight for day-based calculations // Sort emails by date (newest first) so we get latest status const sortedAnalyses = [...(emailAnalyses || [])] .filter((a) => a?.result) .sort((a, b) => { const dateA = new Date(a.emailDate || 0).getTime(); const dateB = new Date(b.emailDate || 0).getTime(); if (dateB !== dateA) return dateB - dateA; return (a.emailId || "").localeCompare(b.emailId || ""); }); // Process each email analysis for (const analysisItem of sortedAnalyses) { const result = analysisItem.result as EmailAnalysisResult | undefined; if (!result || !result.flights) continue; const emailType = result.emailType; // Skip non-flight emails if ( emailType === "receipt" || emailType === "mileageplus" || emailType === "other" ) { continue; } // Process each flight in the email for (const flight of result.flights) { // Need confirmation, flight number, and date to track if ( !flight.confirmationNumber || !flight.flightNumber || !flight.departureDate ) { continue; } const key = createFlightKey( flight.confirmationNumber, flight.flightNumber, flight.departureDate, ); const daysUntilFlight = calculateDaysUntilFlight( flight.departureDate, today, ); const isUpcoming = daysUntilFlight >= 0; // Check if we should mark check-in available // Check-in opens 24 hours before departure let checkInAvailable = false; let checkInDeadline: string | undefined; if (emailType === "check_in_available" || result.checkInAvailable) { checkInAvailable = true; checkInDeadline = result.checkInDeadline; } else if (isUpcoming) { // Auto-detect check-in window using true 24-hour calculation // Use `now` (current time) not `today` (midnight) for accurate hours const hoursUntilFlight = calculateHoursUntilFlight( flight.departureDate, flight.departureTime, now, ); if (hoursUntilFlight >= 0 && hoursUntilFlight <= 24) { checkInAvailable = true; } } // Parse status let status = parseFlightStatus(flight.status); // Override with email type if more specific if (emailType === "flight_delay") { status = "delayed"; } else if (emailType === "flight_cancellation") { status = "cancelled"; } // Mark as completed if past if (!isUpcoming && status === "scheduled") { status = "completed"; } if (flightMap[key]) { // Merge with existing flight - update with newer information const existing = flightMap[key]; // Update seat, gate, terminal if we have newer info if (flight.seat) existing.seat = flight.seat; if (flight.gate) existing.gate = flight.gate; if (flight.terminal) existing.terminal = flight.terminal; // Update status to more severe (cancelled > delayed > scheduled) if ( status === "cancelled" || (status === "delayed" && existing.status !== "cancelled") ) { existing.status = status; } // Update delay info if (flight.delayMinutes) { existing.delayMinutes = flight.delayMinutes; } if (flight.newDepartureTime) { existing.newDepartureTime = flight.newDepartureTime; } // Update check-in info if (checkInAvailable) { existing.checkInAvailable = true; if (checkInDeadline) existing.checkInDeadline = checkInDeadline; } // Update passenger name if we have it if (result.passengerName && !existing.passengerName) { existing.passengerName = result.passengerName; } // Add email ID to sources if (!existing.emailIds.includes(analysisItem.emailId)) { existing.emailIds.push(analysisItem.emailId); } } else { // Create new tracked flight flightMap[key] = { key, confirmationNumber: flight.confirmationNumber, flightNumber: flight.flightNumber, departureCity: flight.departureCity || "", departureAirport: flight.departureAirport || "", arrivalCity: flight.arrivalCity || "", arrivalAirport: flight.arrivalAirport || "", departureDate: flight.departureDate, departureTime: flight.departureTime || "", arrivalTime: flight.arrivalTime || "", seat: flight.seat, gate: flight.gate, terminal: flight.terminal, status, delayMinutes: flight.delayMinutes, newDepartureTime: flight.newDepartureTime, checkInAvailable, checkInDeadline, isUpcoming, daysUntilFlight, passengerName: result.passengerName, emailIds: [analysisItem.emailId], }; } } } // Convert to array and sort by departure date const items = Object.values(flightMap); return items.sort((a, b) => a.daysUntilFlight - b.daysUntilFlight); }); // ========================================================================== // DERIVED STATE // ========================================================================== // Upcoming flights (future, not cancelled) const upcomingFlights = computed(() => flights .filter((f) => f.isUpcoming && f.status !== "cancelled") .sort((a, b) => a.daysUntilFlight - b.daysUntilFlight) ); // Flights ready for check-in const checkInAvailable = computed(() => upcomingFlights.filter((f) => f.checkInAvailable && f.status !== "cancelled" ) ); // Active alerts (delays and cancellations) const activeAlerts = computed(() => flights.filter( (f) => f.isUpcoming && (f.status === "delayed" || f.status === "cancelled"), ) ); // Past flights const pastFlights = computed(() => flights .filter((f) => !f.isUpcoming) .sort((a, b) => b.departureDate.localeCompare(a.departureDate)) ); // Group flights by trip (confirmation number) const trips = computed(() => { const tripMap: Record = {}; for (const flight of flights || []) { const conf = flight.confirmationNumber; if (!tripMap[conf]) { tripMap[conf] = { confirmationNumber: conf, flights: [], passengerName: flight.passengerName, hasUpcomingFlights: false, nextFlight: undefined, }; } tripMap[conf].flights.push(flight); if (flight.isUpcoming && flight.status !== "cancelled") { tripMap[conf].hasUpcomingFlights = true; if ( !tripMap[conf].nextFlight || flight.daysUntilFlight < tripMap[conf].nextFlight!.daysUntilFlight ) { tripMap[conf].nextFlight = flight; } } if (flight.passengerName && !tripMap[conf].passengerName) { tripMap[conf].passengerName = flight.passengerName; } } // Sort trips by next flight date return Object.values(tripMap).sort((a, b) => { if (a.nextFlight && b.nextFlight) { return a.nextFlight.daysUntilFlight - b.nextFlight.daysUntilFlight; } if (a.nextFlight) return -1; if (b.nextFlight) return 1; return 0; }); }); // Next upcoming flight for preview const nextFlight = computed(() => upcomingFlights[0]); // ========================================================================== // PREVIEW UI // ========================================================================== const previewUI = (
{/* Badge with count */}
checkInAvailable?.length > 0 ? "#fef3c7" : activeAlerts?.length > 0 ? "#fee2e2" : "#eff6ff" ), border: computed(() => checkInAvailable?.length > 0 ? "2px solid #f59e0b" : activeAlerts?.length > 0 ? "2px solid #ef4444" : "2px solid #3b82f6" ), color: computed(() => checkInAvailable?.length > 0 ? "#92400e" : activeAlerts?.length > 0 ? "#b91c1c" : "#1d4ed8" ), display: "flex", alignItems: "center", justifyContent: "center", fontWeight: "bold", fontSize: "16px", }} > {computed(() => upcomingFlights?.length || 0)}
United Flights
{/* Check-in status */} checkInAvailable?.length > 0 ? "inline" : "none" ), color: "#d97706", fontWeight: "600", }} > {computed(() => checkInAvailable?.length)} ready for check-in {/* Next flight info */} checkInAvailable?.length === 0 && nextFlight ? "inline" : "none" ), }} > {computed(() => nextFlight ? `${nextFlight.departureAirport || "???"} → ${ nextFlight.arrivalAirport || "???" } ${formatDate(nextFlight.departureDate)}` : "" )} {/* No upcoming flights */} !nextFlight && upcomingFlights?.length === 0 ? "inline" : "none" ), }} > No upcoming flights
{/* Loading/progress indicator */}
); // ========================================================================== // FULL UI // ========================================================================== return { [NAME]: "United Flight Tracker", emailCount: unitedEmailCount, flights, upcomingFlights, checkInAvailable, activeAlerts, pastFlights, trips, previewUI, [UI]: (
United Flight Tracker
{/* Auth UI */} {extractor.ui.authStatusUI} {/* Connection Status */} {extractor.ui.connectionStatusUI} {/* Analysis Status */} {extractor.ui.analysisProgressUI} {/* Summary Stats */}
{computed(() => upcomingFlights?.length || 0)}
Upcoming Flights
checkInAvailable?.length > 0 ? "block" : "none" ), }} >
{computed(() => checkInAvailable?.length || 0)}
Check-in Ready
activeAlerts?.length > 0 ? "block" : "none" ), }} >
{computed(() => activeAlerts?.length || 0)}
Alerts
{ /* ================================================================ CHECK-IN AVAILABLE SECTION (Yellow, top priority) ================================================================ */ }
checkInAvailable?.length > 0 ? "block" : "none" ), }} >
Check-In Available
{checkInAvailable.map((flight) => (
getFlightColor(flight.flightNumber) ), padding: "3px 10px", borderRadius: "4px", }} > {flight.flightNumber} {flight.confirmationNumber}
{flight.departureAirport || flight.departureCity} →{" "} {flight.arrivalAirport || flight.arrivalCity}
{formatDate(flight.departureDate)} at{" "} {formatTime(flight.departureTime)} {computed(() => flight.seat ? ` • Seat ${flight.seat}` : "" )}
Check In Now →
))}
{ /* ================================================================ ACTIVE ALERTS SECTION (Red, for delays/cancellations) ================================================================ */ }
activeAlerts?.length > 0 ? "block" : "none" ), }} >
⚠️ Flight Alerts
{activeAlerts.map((flight) => (
flight.status === "cancelled" ? "2px solid #dc2626" : "2px solid #f87171" ), }} >
getFlightColor(flight.flightNumber) ), padding: "3px 10px", borderRadius: "4px", }} > {flight.flightNumber} flight.status === "cancelled" ? "#fee2e2" : "#fef3c7" ), color: computed(() => flight.status === "cancelled" ? "#dc2626" : "#d97706" ), }} > {ifElse( flight.status === "cancelled", "CANCELLED", "DELAYED", )}
{flight.departureAirport || flight.departureCity} →{" "} {flight.arrivalAirport || flight.arrivalCity}
{formatDate(flight.departureDate)} flight.status === "delayed" && flight.delayMinutes ? "inline" : "none" ), }} > Delayed {computed(() => flight.delayMinutes || 0)} min flight.newDepartureTime ? "inline" : "none" ), }} > New time: {computed(() => flight.newDepartureTime ? formatTime(flight.newDepartureTime) : "" )}
))}
{ /* ================================================================ UPCOMING FLIGHTS SECTION (Blue cards) ================================================================ */ }
upcomingFlights?.length > 0 ? "block" : "none" ), }} >

Upcoming Flights

{upcomingFlights.map((flight) => (
getFlightColor(flight.flightNumber) ), padding: "3px 10px", borderRadius: "4px", }} > {flight.flightNumber} flight.status === "delayed" ? "#fef3c7" : flight.status === "cancelled" ? "#fee2e2" : "#d1fae5" ), color: computed(() => flight.status === "delayed" ? "#d97706" : flight.status === "cancelled" ? "#dc2626" : "#059669" ), display: computed(() => flight.status !== "scheduled" ? "inline" : "none" ), }} > {(flight.status || "scheduled").toUpperCase()}
{flight.confirmationNumber}
{/* Route */}
{flight.departureAirport || "???"} {flight.arrivalAirport || "???"} flight.departureCity && flight.arrivalCity ? "inline" : "none" ), }} > ({computed(() => `${flight.departureCity || ""} to ${ flight.arrivalCity || "" }` )})
{/* Date and Time */}
{formatDate(flight.departureDate)} flight.daysUntilFlight <= 1 ? "#fef3c7" : "#e5e7eb" ), borderRadius: "4px", fontSize: "12px", }} > {ifElse( flight.daysUntilFlight === 0, "Today", ifElse( flight.daysUntilFlight === 1, "Tomorrow", `in ${flight.daysUntilFlight} days`, ), )}
{formatTime(flight.departureTime)} flight.arrivalTime ? "inline" : "none" ), }} > {" "} → {computed(() => formatTime(flight.arrivalTime))}
{/* Details Row */}
(flight.seat ? "inline" : "none"), ), }} > Seat:{" "} {computed(() => flight.seat || "")} (flight.gate ? "inline" : "none"), ), }} > Gate:{" "} {computed(() => flight.gate || "")} (flight.terminal ? "inline" : "none"), ), }} > Terminal:{" "} {computed(() => flight.terminal || "")}
))}
{ /* ================================================================ PAST FLIGHTS SECTION (Collapsible, gray) ================================================================ */ }
pastFlights?.length > 0 ? "block" : "none" ), }} >
Past Flights ({computed(() => pastFlights?.length || 0)}) {pastFlights.map((flight) => (
getFlightColor(flight.flightNumber) ), padding: "2px 8px", borderRadius: "4px", }} > {flight.flightNumber} {flight.departureAirport} → {flight.arrivalAirport}
{formatDate(flight.departureDate)}
))}
{ /* ================================================================ DEBUG VIEW (Collapsible) ================================================================ */ }
unitedEmailCount > 0 ? "block" : "none" ), }} >
Debug View ({unitedEmailCount} emails)

LLM Analysis Results:

{emailAnalyses.map((item) => { const debugResult = item.result as | EmailAnalysisResult | undefined; return (
item.pending ? "1px solid #fbbf24" : item.error ? "1px solid #ef4444" : "1px solid #10b981" ), fontSize: "12px", }} >
{item.email?.subject}
Date: {item.emailDate}
{/* Pending */}
Analyzing...
{/* Error */}
Error: {computed( () => (item.error ? String(item.error) : ""), )}
{/* Result */}
!item.pending && !item.error && debugResult ? "block" : "none" ), }} >
Type:{" "} {computed(() => debugResult?.emailType || "N/A" )}
Summary:{" "} {computed(() => debugResult?.summary || "N/A")}
Flights: {computed(() => JSON.stringify( debugResult?.flights || [], null, 2, ) )}
debugResult?.checkInAvailable !== undefined ? "block" : "none" ), }} > Check-in Available:{" "} {computed(() => debugResult?.checkInAvailable ? "Yes" : "No" )}
{/* Raw email content */}
Show raw email content
                              {item.email.markdownContent}
                              
); })}
{/* United Website Link */}
), }; });