"use client"; import { Check, ChevronDown, Database, Pencil, Plus, Save, ShieldAlert, TrendingUp, Trash2, X } from "lucide-react"; import { useState } from "react"; import type { Memory, MemoryPreferenceDefinition, MemoryValue, PreferenceSignal } from "@/lib/types"; const MEMORY_LABELS: Record = { minimum_arrival_buffer_minutes: "Arrival buffer", max_hotel_nightly_cad: "Hotel ceiling", seat_preference: "Seat preference", home_airport: "Home airport", trip_budget_cad: "Trip budget", favorite_teams: "Favorite teams", accessibility_needs: "Accessibility", dietary_needs: "Dietary needs", preferred_currency: "Currency", preferred_host_cities: "Preferred host cities", preferred_countries: "Preferred countries", budget_style: "Budget style", max_hotel_to_stadium_minutes: "Hotel-to-stadium limit", max_walking_minutes: "Walking limit", max_layover_minutes: "Layover limit", preferred_package_style: "Package style", max_vacation_rental_nightly_cad: "Vacation-rental ceiling", max_vacation_rental_to_stadium_minutes: "Rental-to-stadium limit", minimum_rental_bedrooms: "Minimum rental bedrooms", preferred_activity_categories: "Activity interests", max_activity_price_cad: "Activity price ceiling", preferred_vehicle_class: "Preferred vehicle class", preferred_cruise_style: "Cruise style", preferred_traveler_profile: "Traveler profile", }; function titleCase(value: string): string { return value .replaceAll("_", " ") .replace(/\b\w/g, (character) => character.toUpperCase()); } function displayValue(memory: Memory): string { if (memory.value.label) return memory.value.label; if (Array.isArray(memory.value.value)) return memory.value.value.join(", "); return `${memory.value.value}${memory.value.unit ? ` ${memory.value.unit}` : ""}`; } function correctedLabel(key: string, value: MemoryValue["value"]): string | null { if (key === "minimum_arrival_buffer_minutes" && typeof value === "number") { return `${value} minutes before kickoff`; } if (key === "max_hotel_nightly_cad" && typeof value === "number") { return `Hotel ceiling C$${value}/night`; } if (key === "seat_preference" && typeof value === "string") { return `${value.charAt(0).toUpperCase()}${value.slice(1)} seat`; } if (key === "max_vacation_rental_nightly_cad" && typeof value === "number") { return `Rental ceiling C$${value}/night`; } if (key === "max_vacation_rental_to_stadium_minutes" && typeof value === "number") { return `${value} minutes to stadium`; } if (key === "minimum_rental_bedrooms" && typeof value === "number") { return `${value} bedroom${value === 1 ? "" : "s"} minimum`; } if (key === "max_activity_price_cad" && typeof value === "number") { return `Activity ceiling C$${value}`; } if (key === "preferred_activity_categories" && Array.isArray(value)) { return value.map((item) => titleCase(String(item))).join(", "); } if ( ["preferred_vehicle_class", "preferred_cruise_style", "preferred_traveler_profile"].includes(key) && typeof value === "string" ) { return titleCase(value); } return null; } function sourceLabel(source: string): string { const labels: Record = { explicit_user: "Explicit user statement", user_action: "Explicit saved choice", behavior_inferred: "Repeated verified trips", demo_seed: "Demo profile seed", imported: "Imported profile", }; return labels[source] || titleCase(source); } function scopeLabel(scope: string): string { if (scope === "fan_memory") return "Fan profile"; if (scope === "trip_memory") return "This trip only"; return titleCase(scope); } function shortDate(value: string): string { const date = new Date(value); if (Number.isNaN(date.getTime())) return "Unknown"; return new Intl.DateTimeFormat("en-CA", { day: "numeric", month: "short", year: "numeric", timeZone: "UTC", }).format(date); } export function MemoryPanel({ memories, signals, enabled, busy, onToggle, onDelete, onDismissSignal, onUpdate, catalog, onCreate, }: { memories: Memory[]; signals: PreferenceSignal[]; enabled: boolean; busy: boolean; onToggle: (enabled: boolean) => Promise; onDelete: (memoryId: string) => Promise; onDismissSignal: (evidenceId: string) => Promise; onUpdate: (memory: Memory, value: MemoryValue, excerpt: string) => Promise; catalog: MemoryPreferenceDefinition[]; onCreate: (key: string, value: MemoryValue["value"]) => Promise; }) { const [editing, setEditing] = useState(null); const [draft, setDraft] = useState(""); const [adding, setAdding] = useState(false); const [newKey, setNewKey] = useState(""); const [newDraft, setNewDraft] = useState(""); const [newSelections, setNewSelections] = useState([]); const selectedDefinition = catalog.find((item) => item.key === newKey) || catalog[0]; function beginEdit(memory: Memory) { setEditing(memory.id); setDraft(String(memory.value.value)); } async function save(memory: Memory) { let value: MemoryValue["value"]; if (typeof memory.value.value === "number") { const parsed = Number(draft); if (!Number.isFinite(parsed)) return; value = parsed; } else if (Array.isArray(memory.value.value)) { value = draft.split(",").map((item) => item.trim()).filter(Boolean); if (!value.length) return; } else { value = draft.trim(); if (!value) return; } const saved = await onUpdate( memory, { ...memory.value, value, label: correctedLabel(memory.key, value) }, `I corrected ${MEMORY_LABELS[memory.key] || memory.key} to ${draft.trim()}.`, ); if (saved) setEditing(null); } function selectDefinition(key: string) { const definition = catalog.find((item) => item.key === key); setNewKey(key); setNewSelections([]); setNewDraft(definition?.value_kind === "select" ? definition.options[0] || "" : ""); } function beginAdd() { const definition = catalog[0]; if (!definition) return; setAdding(true); selectDefinition(definition.key); } async function saveNewPreference() { if (!selectedDefinition) return; let value: MemoryValue["value"]; if (selectedDefinition.value_kind === "integer") { const parsed = Number(newDraft); if (!Number.isInteger(parsed)) return; if (selectedDefinition.minimum != null && parsed < selectedDefinition.minimum) return; if (selectedDefinition.maximum != null && parsed > selectedDefinition.maximum) return; value = parsed; } else if (selectedDefinition.value_kind === "list") { value = newDraft.split(",").map((item) => item.trim()).filter(Boolean); if (!value.length) return; } else if (selectedDefinition.value_kind === "multi_select") { value = newSelections; if (!value.length) return; } else { value = newDraft.trim(); if (!value) return; } if (await onCreate(selectedDefinition.key, value)) { setAdding(false); setNewDraft(""); setNewSelections([]); } } return (
Persistent profile

Fan memory

{adding && selectedDefinition ? (

{selectedDefinition.description} {selectedDefinition.sensitivity === "private" ? " Kept private and excluded from public shares." : ""}

) : null}
{!memories.length && !signals.length ? (

{busy ? "Loading confirmed preferences…" : "No confirmed preferences yet."}

) : null} {memories.map((memory) => (
{MEMORY_LABELS[memory.key] || memory.key.replaceAll("_", " ")} {editing === memory.id ? ( setDraft(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter") void save(memory); if (event.key === "Escape") setEditing(null); }} autoFocus /> ) : ( <> {displayValue(memory)}
Inspect provenance
Source{sourceLabel(memory.source_kind)}
Confidence{Math.round(memory.confidence * 100)}%
Scope{scopeLabel(memory.scope)}
Versionv{memory.version}
Updated{shortDate(memory.updated_at)}
Expiry {memory.expires_at ? shortDate(memory.expires_at) : "No automatic expiry"}

Grounded evidence {memory.source_excerpt}

{memory.sensitivity === "private" ? ( ) : null}
)}
{editing === memory.id ? ( <> ) : ( <> )}
))} {signals.map((signal) => (
{signal.status === "conflicted" ? : }
{MEMORY_LABELS[signal.key] || signal.key.replaceAll("_", " ")} · learning evidence {signal.value.label || String(signal.value.value)} {signal.status === "confirmed" ? `Learned from ${signal.distinct_trip_count} verified trips` : signal.status === "conflicted" ? "Not used because an explicit preference wins" : `${signal.distinct_trip_count} of 2 verified trips · not used yet`}
))}
); }