// agent

Messy Itinerary Travel Planner

by ozzo · Jul 12, 2026 Public

Choose how to run this agent

Local runs on your GPU. For usable speed it needs a WebGPU-capable browser — Chrome or Edge on a machine with a graphics card, or an Apple Silicon Mac. Without a supported GPU, pick OpenAI or Anthropic above instead.
Try it now

Requires an API key and an AgentOp account.

0 downloads
0 forks
0.0 rating

Description

Drop your messy pile of booking PDFs and trip notes and get a clean day-by-day itinerary — plus warnings about gaps like a 14:00 landing vs a 16:00 check-in — all in your browser.

Source Code

agent.py
import json
import re

# How many document passages to retrieve per follow-up question.
RAG_TOP_K = [[[RAG_TOP_K|5]]]
# Cap on the pre-parsed schedule sheet handed to the model when building the
# itinerary (sized for the 8K-token default context of current local models).
MAX_FACTS_CHARS = [[[MAX_FACTS_CHARS|6000]]]

# One entry per ingested file: {"source": name, "facts": [schedule lines]}
_TRIP_FILES = []

# Lines that look schedule-relevant: times, dates, travel keywords…
_TIMEY_RE = re.compile(
    r"[0-9]{1,2}:[0-9]{2}"                                    # 14:05
    r"|\b[0-9]{1,2}\s?(?:am|pm)\b"                         # 2 pm
    r"|[0-9]{4}-[0-9]{2}-[0-9]{2}"                            # 2026-08-14
    r"|\b[0-9]{1,2}[/.][0-9]{1,2}[/.][0-9]{2,4}\b"          # 14/08/2026
    r"|\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\.?\s+[0-9]{1,2}\b"
    r"|\b[0-9]{1,2}(?:st|nd|rd|th)?\s+(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b"
    r"|\b(?:mon|tues?|wed(?:nes)?|thu(?:rs)?|fri|sat(?:ur)?|sun)day\b"
    r"|check[- ]?in|check[- ]?out|departure|departs|arrival|arrives|boarding"
    r"|confirmation|booking|reservation|pick[- ]?up",
    re.I,
)
# …plus flight numbers (case-sensitive so plain words never match).
_FLIGHT_RE = re.compile(r"\b[A-Z]{2}[0-9]{2,4}\b")


def _extract_pdf_text(pdf_bytes):
    """(JS-callable) Extract plain text from a PDF's raw bytes via pypdf."""
    import io
    from pypdf import PdfReader

    reader = PdfReader(io.BytesIO(pdf_bytes))
    return "\n\n".join((page.extract_text() or "") for page in reader.pages)


def _scan_facts(text):
    """Pull the schedule-relevant lines out of one file's text."""
    facts = []
    for raw in text.splitlines():
        line = " ".join(raw.split())
        if len(line) < 4:
            continue
        if _TIMEY_RE.search(line) or _FLIGHT_RE.search(line):
            facts.append(line[:200])
    return facts


async def _ingest_trip_file(source, text):
    """(JS-callable) Index one booking/note file and scan its schedule lines.

    Returns JSON {"chunks": int, "facts": int} for the file list UI.
    """
    chunks = await agentop_rag.add_document(source, text)
    facts = _scan_facts(text)
    _TRIP_FILES.append({"source": source, "facts": facts})
    return json.dumps({"chunks": chunks, "facts": len(facts)})


def _facts_sheet():
    lines = []
    for f in _TRIP_FILES:
        lines.append(f"FILE: {f['source']}")
        lines.extend(f"  - {fact}" for fact in (f["facts"] or ["(no dated lines found)"]))
    return "\n".join(lines)[:MAX_FACTS_CHARS]


async def _build_itinerary():
    """(JS-callable) Draft the day-by-day itinerary + gap check via wllama."""
    if not _TRIP_FILES:
        return "Drop your booking PDFs and notes first, then build the itinerary."
    prompt = (
        "Below are schedule lines extracted from one traveller's booking files "
        "(flights, hotels, notes). Reconstruct the trip as a clean itinerary.\n"
        "Format exactly:\n"
        "## Itinerary\n"
        "One '### <day, date>' section per day, in order; under each, one "
        "chronological bullet per event: time — what and where, with flight "
        "numbers or booking references when given.\n\n"
        "## Gaps & watch-outs\n"
        "Bullets for logistical problems you can see: arrival vs check-in "
        "mismatches (e.g. landing at 14:00 but check-in from 16:00), "
        "connections under 90 minutes, nights with no lodging, missing "
        "transport between cities. Write 'None spotted.' if it all lines up.\n\n"
        "Use ONLY the lines below. If a date or time is ambiguous, say so "
        "rather than guessing.\n\n"
        f"SCHEDULE LINES:\n{_facts_sheet()}"
    )
    # TEMPLATE_SYSTEM_PROMPT only exists as a JS global; the query bridge falls
    # back to it when custom_prompt is empty (same idiom as the base template).
    return await process_user_query_wllama(
        prompt, globals().get("TEMPLATE_SYSTEM_PROMPT", "")
    )


async def process_user_query(query):
    """Follow-up Q&A grounded in the uploaded trip documents (RAG).

    Overrides the default router so retrieval is deterministic (small local
    models are unreliable at deciding to call a search tool themselves).
    """
    if not _TRIP_FILES:
        return (
            "I don't have any trip files yet — drop your flight PDFs, hotel "
            "emails, and notes above and I'll piece the trip together."
        )
    hits = await agentop_rag.search(query, RAG_TOP_K)
    excerpts = "\n\n".join(
        f"[{i + 1}] (from {h['source']}) {h['text']}" for i, h in enumerate(hits)
    ) or "(nothing relevant found)"
    grounded_prompt = (
        "Answer the traveller's QUESTION using ONLY the TRIP DOCUMENT "
        "EXCERPTS below. Use exact dates, times, flight numbers, and booking "
        "references from the excerpts, and cite them like [1]. If the answer "
        "is not in the excerpts, say you could not find it in the trip files.\n\n"
        f"TRIP DOCUMENT EXCERPTS:\n{excerpts}\n\nQUESTION: {query}"
    )
    return await process_user_query_wllama(
        grounded_prompt, globals().get("TEMPLATE_SYSTEM_PROMPT", "")
    )

More by ozzo

WhatsApp Sales Copilot

Turn a raw WhatsApp chat export into a mini CRM — typed quotes, bookings, payments and boarding pas…

Private Quote & Material Estimator

Drop competing contractor quotes and compare them side by side — totals, inclusions, exclusions — t…

Semantic Search

Search your own notes or documents by meaning, not keywords — instant results with no LLM download.

Meeting Minutes

Turn a meeting recording into a summary, key decisions, and action items, then ask follow-up questi…

Voice Transcriber

Upload a voice note or audio file and get an instant transcript, then ask questions about it — all …

Document Q&A

Upload a PDF or text file and ask questions answered only from that document — fully in your browse…