Add UI to trips
This commit is contained in:
@@ -10,7 +10,7 @@ from app.internal.vocabulary_learner import vocabulary_induction_loop
|
|||||||
from app.internal.recorrelation_sweep import recorrelation_loop
|
from app.internal.recorrelation_sweep import recorrelation_loop
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.internal.auth import require_firebase_token, require_service_or_firebase_token
|
from app.internal.auth import require_firebase_token, require_service_or_firebase_token
|
||||||
from app.routers import nodes, systems, calls, upload, tokens, incidents, alerts, admin, trips
|
from app.routers import nodes, systems, calls, upload, tokens, incidents, alerts, admin, trips, places
|
||||||
from app.internal import firestore as fstore
|
from app.internal import firestore as fstore
|
||||||
|
|
||||||
|
|
||||||
@@ -69,6 +69,7 @@ app.include_router(tokens.router, dependencies=[Depends(require_service_or_fi
|
|||||||
app.include_router(incidents.router, dependencies=[Depends(require_service_or_firebase_token)])
|
app.include_router(incidents.router, dependencies=[Depends(require_service_or_firebase_token)])
|
||||||
app.include_router(alerts.router, dependencies=[Depends(require_service_or_firebase_token)])
|
app.include_router(alerts.router, dependencies=[Depends(require_service_or_firebase_token)])
|
||||||
app.include_router(trips.router, dependencies=[Depends(require_service_or_firebase_token)])
|
app.include_router(trips.router, dependencies=[Depends(require_service_or_firebase_token)])
|
||||||
|
app.include_router(places.router, dependencies=[Depends(require_service_or_firebase_token)])
|
||||||
app.include_router(upload.router) # auth is per-node, handled inline
|
app.include_router(upload.router) # auth is per-node, handled inline
|
||||||
app.include_router(admin.router) # auth is per-endpoint (read: firebase, write: admin)
|
app.include_router(admin.router) # auth is per-endpoint (read: firebase, write: admin)
|
||||||
|
|
||||||
|
|||||||
@@ -151,9 +151,11 @@ class TripCreate(BaseModel):
|
|||||||
class TripEventCreate(BaseModel):
|
class TripEventCreate(BaseModel):
|
||||||
title: str
|
title: str
|
||||||
date: str # YYYY-MM-DD, must fall within parent trip range
|
date: str # YYYY-MM-DD, must fall within parent trip range
|
||||||
time: Optional[str] = None # HH:MM (24h)
|
start_time: Optional[str] = None # HH:MM (24h)
|
||||||
|
end_time: Optional[str] = None # HH:MM (24h)
|
||||||
location: Optional[str] = None # inherits trip location if None
|
location: Optional[str] = None # inherits trip location if None
|
||||||
maps_link: Optional[str] = None
|
maps_link: Optional[str] = None
|
||||||
|
place_id: Optional[str] = None # Google Place ID
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import httpx
|
||||||
|
from fastapi import APIRouter, HTTPException, Query
|
||||||
|
from app.config import settings
|
||||||
|
from app.internal.logger import logger
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/places", tags=["places"])
|
||||||
|
|
||||||
|
PLACES_SEARCH_URL = "https://maps.googleapis.com/maps/api/place/textsearch/json"
|
||||||
|
DIRECTIONS_URL = "https://maps.googleapis.com/maps/api/directions/json"
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/search")
|
||||||
|
async def search_places(query: str = Query(...), near: str = Query("")):
|
||||||
|
if not settings.google_maps_api_key:
|
||||||
|
raise HTTPException(503, "Google Maps API not configured.")
|
||||||
|
|
||||||
|
full_query = f"{query} {near}".strip()
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=10) as client:
|
||||||
|
r = await client.get(
|
||||||
|
PLACES_SEARCH_URL,
|
||||||
|
params={"query": full_query, "key": settings.google_maps_api_key},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Places search failed: {e}")
|
||||||
|
raise HTTPException(502, "Places search failed.")
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"name": p.get("name"),
|
||||||
|
"address": p.get("formatted_address"),
|
||||||
|
"place_id": p.get("place_id"),
|
||||||
|
"lat": p.get("geometry", {}).get("location", {}).get("lat"),
|
||||||
|
"lng": p.get("geometry", {}).get("location", {}).get("lng"),
|
||||||
|
"maps_link": f"https://www.google.com/maps/place/?q=place_id:{p.get('place_id')}",
|
||||||
|
"rating": p.get("rating"),
|
||||||
|
}
|
||||||
|
for p in data.get("results", [])[:6]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/directions")
|
||||||
|
async def get_directions(
|
||||||
|
origin: str = Query(...),
|
||||||
|
destination: str = Query(...),
|
||||||
|
):
|
||||||
|
if not settings.google_maps_api_key:
|
||||||
|
raise HTTPException(503, "Google Maps API not configured.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=10) as client:
|
||||||
|
r = await client.get(
|
||||||
|
DIRECTIONS_URL,
|
||||||
|
params={
|
||||||
|
"origin": origin,
|
||||||
|
"destination": destination,
|
||||||
|
"mode": "driving",
|
||||||
|
"key": settings.google_maps_api_key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Directions failed: {e}")
|
||||||
|
raise HTTPException(502, "Directions request failed.")
|
||||||
|
|
||||||
|
routes = data.get("routes", [])
|
||||||
|
if not routes:
|
||||||
|
return {"duration_text": None, "duration_seconds": None, "distance_text": None}
|
||||||
|
|
||||||
|
leg = routes[0]["legs"][0]
|
||||||
|
return {
|
||||||
|
"duration_text": leg["duration"]["text"],
|
||||||
|
"duration_seconds": leg["duration"]["value"],
|
||||||
|
"distance_text": leg["distance"]["text"],
|
||||||
|
}
|
||||||
@@ -1,12 +1,148 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
import json
|
||||||
|
import httpx
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
from app.models import TripCreate, TripEventCreate, AttendeeAction
|
from app.models import TripCreate, TripEventCreate, AttendeeAction
|
||||||
from app.internal import firestore as fstore
|
from app.internal import firestore as fstore
|
||||||
|
from app.config import settings
|
||||||
|
from app.internal.logger import logger
|
||||||
|
|
||||||
router = APIRouter(prefix="/trips", tags=["trips"])
|
router = APIRouter(prefix="/trips", tags=["trips"])
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# AI assistant — tool definitions
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_TOOLS = [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "search_places",
|
||||||
|
"description": (
|
||||||
|
"Search Google Maps for places (restaurants, bars, attractions, hotels, venues). "
|
||||||
|
"Use this whenever the user asks about specific places or you need to find options."
|
||||||
|
),
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "What to search for, e.g. 'rooftop bars', 'Italian restaurants'",
|
||||||
|
},
|
||||||
|
"near": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Location to search near, e.g. 'downtown Nashville, TN'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["query", "near"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "propose_event",
|
||||||
|
"description": (
|
||||||
|
"Propose a specific event to add to the itinerary. "
|
||||||
|
"The user will see a card and can approve or dismiss it. "
|
||||||
|
"Call this once per proposed event — do not bundle multiple events into one call."
|
||||||
|
),
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {"type": "string"},
|
||||||
|
"date": {"type": "string", "description": "YYYY-MM-DD — must be within the trip date range"},
|
||||||
|
"start_time": {"type": "string", "description": "HH:MM (24h), e.g. '19:30'"},
|
||||||
|
"end_time": {"type": "string", "description": "HH:MM (24h), e.g. '22:00'"},
|
||||||
|
"location": {"type": "string", "description": "Full address or place name"},
|
||||||
|
"maps_link": {"type": "string", "description": "Google Maps URL"},
|
||||||
|
"notes": {"type": "string", "description": "Brief tips or reasoning"},
|
||||||
|
},
|
||||||
|
"required": ["title"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def _places_search(query: str, near: str) -> list[dict]:
|
||||||
|
if not settings.google_maps_api_key:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=8) as client:
|
||||||
|
r = await client.get(
|
||||||
|
"https://maps.googleapis.com/maps/api/place/textsearch/json",
|
||||||
|
params={"query": f"{query} {near}".strip(), "key": settings.google_maps_api_key},
|
||||||
|
)
|
||||||
|
data = r.json()
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"name": p.get("name"),
|
||||||
|
"address": p.get("formatted_address"),
|
||||||
|
"place_id": p.get("place_id"),
|
||||||
|
"maps_link": f"https://www.google.com/maps/place/?q=place_id:{p.get('place_id')}",
|
||||||
|
"rating": p.get("rating"),
|
||||||
|
}
|
||||||
|
for p in data.get("results", [])[:5]
|
||||||
|
]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Places search in assistant failed: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _build_system_prompt(trip: dict, events: list[dict]) -> str:
|
||||||
|
by_date: dict[str, list] = {}
|
||||||
|
for e in sorted(events, key=lambda x: (x.get("date", ""), x.get("start_time") or "")):
|
||||||
|
by_date.setdefault(e["date"], []).append(e)
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
for date, day_events in sorted(by_date.items()):
|
||||||
|
lines.append(f"\n {date}:")
|
||||||
|
for e in day_events:
|
||||||
|
t = ""
|
||||||
|
if e.get("start_time"):
|
||||||
|
t = f" {e['start_time']}"
|
||||||
|
if e.get("end_time"):
|
||||||
|
t += f"–{e['end_time']}"
|
||||||
|
loc = f" @ {e['location']}" if e.get("location") and not e.get("location_inherited") else ""
|
||||||
|
lines.append(f" • {e['title']}{t}{loc}")
|
||||||
|
if e.get("notes"):
|
||||||
|
lines.append(f" Notes: {e['notes']}")
|
||||||
|
|
||||||
|
itinerary = "".join(lines) if lines else "\n (no events yet)"
|
||||||
|
attendees = ", ".join(trip.get("attendees", {}).values()) or "not specified"
|
||||||
|
|
||||||
|
return f"""You are a trip planning assistant for the following trip.
|
||||||
|
|
||||||
|
Trip: {trip["name"]}
|
||||||
|
Destination: {trip["location"]}
|
||||||
|
Dates: {trip["start_date"]} to {trip["end_date"]}
|
||||||
|
Attendees: {attendees}
|
||||||
|
|
||||||
|
Current itinerary:{itinerary}
|
||||||
|
|
||||||
|
Guidelines:
|
||||||
|
- Be conversational and concise — don't over-explain.
|
||||||
|
- When the user mentions places, activities, or asks for suggestions, search for them with search_places before proposing.
|
||||||
|
- Use propose_event for each concrete suggestion — one call per event. The user will approve or skip each one.
|
||||||
|
- Be mindful of the existing schedule when assigning times. Avoid obvious conflicts.
|
||||||
|
- All proposed dates must fall between {trip["start_date"]} and {trip["end_date"]}.
|
||||||
|
- If the user says something like "everyone should be there by 6", factor that into your time proposals.
|
||||||
|
- If you don't know a specific address, search for the place first."""
|
||||||
|
|
||||||
|
|
||||||
|
class ChatMsg(BaseModel):
|
||||||
|
role: str
|
||||||
|
content: str
|
||||||
|
|
||||||
|
|
||||||
|
class ChatRequest(BaseModel):
|
||||||
|
message: str
|
||||||
|
history: list[ChatMsg] = []
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
async def list_trips():
|
async def list_trips():
|
||||||
@@ -102,10 +238,12 @@ async def create_event(trip_id: str, body: TripEventCreate):
|
|||||||
"trip_id": trip_id,
|
"trip_id": trip_id,
|
||||||
"title": body.title,
|
"title": body.title,
|
||||||
"date": body.date,
|
"date": body.date,
|
||||||
"time": body.time,
|
"start_time": body.start_time,
|
||||||
|
"end_time": body.end_time,
|
||||||
"location": body.location if body.location is not None else trip["location"],
|
"location": body.location if body.location is not None else trip["location"],
|
||||||
"location_inherited": body.location is None,
|
"location_inherited": body.location is None,
|
||||||
"maps_link": body.maps_link,
|
"maps_link": body.maps_link,
|
||||||
|
"place_id": body.place_id,
|
||||||
"notes": body.notes,
|
"notes": body.notes,
|
||||||
"attendees": {},
|
"attendees": {},
|
||||||
"created_at": now,
|
"created_at": now,
|
||||||
@@ -148,3 +286,79 @@ async def leave_event(trip_id: str, event_id: str, body: AttendeeAction):
|
|||||||
attendees.pop(body.discord_user_id, None)
|
attendees.pop(body.discord_user_id, None)
|
||||||
await fstore.doc_update("trip_events", event_id, {"attendees": attendees})
|
await fstore.doc_update("trip_events", event_id, {"attendees": attendees})
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# AI trip planning assistant
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@router.post("/{trip_id}/chat")
|
||||||
|
async def trip_chat(trip_id: str, body: ChatRequest):
|
||||||
|
if not settings.openai_api_key:
|
||||||
|
raise HTTPException(503, "OpenAI not configured.")
|
||||||
|
|
||||||
|
trip = await fstore.doc_get("trips", trip_id)
|
||||||
|
if not trip:
|
||||||
|
raise HTTPException(404, f"Trip '{trip_id}' not found.")
|
||||||
|
|
||||||
|
events = await fstore.collection_list("trip_events", trip_id=trip_id)
|
||||||
|
|
||||||
|
from openai import AsyncOpenAI
|
||||||
|
oai = AsyncOpenAI(api_key=settings.openai_api_key)
|
||||||
|
|
||||||
|
messages: list[dict] = [
|
||||||
|
{"role": "system", "content": _build_system_prompt(trip, events)},
|
||||||
|
*[{"role": m.role, "content": m.content} for m in body.history[-20:]],
|
||||||
|
{"role": "user", "content": body.message},
|
||||||
|
]
|
||||||
|
|
||||||
|
suggestions: list[dict] = []
|
||||||
|
reply = ""
|
||||||
|
|
||||||
|
for _ in range(6): # max tool-call iterations
|
||||||
|
response = await oai.chat.completions.create(
|
||||||
|
model="gpt-4o-mini",
|
||||||
|
messages=messages,
|
||||||
|
tools=_TOOLS,
|
||||||
|
tool_choice="auto",
|
||||||
|
max_tokens=1000,
|
||||||
|
)
|
||||||
|
msg = response.choices[0].message
|
||||||
|
|
||||||
|
if not msg.tool_calls:
|
||||||
|
reply = msg.content or ""
|
||||||
|
break
|
||||||
|
|
||||||
|
# Append assistant message with tool calls
|
||||||
|
messages.append({
|
||||||
|
"role": "assistant",
|
||||||
|
"content": msg.content,
|
||||||
|
"tool_calls": [tc.model_dump() for tc in msg.tool_calls],
|
||||||
|
})
|
||||||
|
|
||||||
|
for tc in msg.tool_calls:
|
||||||
|
args = json.loads(tc.function.arguments)
|
||||||
|
|
||||||
|
if tc.function.name == "search_places":
|
||||||
|
results = await _places_search(args.get("query", ""), args.get("near", ""))
|
||||||
|
messages.append({
|
||||||
|
"role": "tool",
|
||||||
|
"tool_call_id": tc.id,
|
||||||
|
"content": json.dumps(results),
|
||||||
|
})
|
||||||
|
|
||||||
|
elif tc.function.name == "propose_event":
|
||||||
|
suggestion = {k: args.get(k) for k in (
|
||||||
|
"title", "date", "start_time", "end_time", "location", "maps_link", "notes"
|
||||||
|
)}
|
||||||
|
suggestions.append(suggestion)
|
||||||
|
messages.append({
|
||||||
|
"role": "tool",
|
||||||
|
"tool_call_id": tc.id,
|
||||||
|
"content": json.dumps({"proposed": True, "title": args.get("title")}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if not reply:
|
||||||
|
reply = f"Here {'are' if len(suggestions) != 1 else 'is'} {len(suggestions) or 'my'} suggestion{'s' if len(suggestions) != 1 else ''} for your trip."
|
||||||
|
|
||||||
|
return {"reply": reply, "suggestions": suggestions}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -146,6 +146,21 @@ export const c2api = {
|
|||||||
request<import("@/lib/types").TripEvent>(`/trips/${tripId}/events`, { method: "POST", body: JSON.stringify(body) }),
|
request<import("@/lib/types").TripEvent>(`/trips/${tripId}/events`, { method: "POST", body: JSON.stringify(body) }),
|
||||||
deleteTripEvent: (tripId: string, eventId: string) =>
|
deleteTripEvent: (tripId: string, eventId: string) =>
|
||||||
request(`/trips/${tripId}/events/${eventId}`, { method: "DELETE" }),
|
request(`/trips/${tripId}/events/${eventId}`, { method: "DELETE" }),
|
||||||
|
tripChat: (tripId: string, message: string, history: { role: string; content: string }[]) =>
|
||||||
|
request<{ reply: string; suggestions: import("@/lib/types").TripEvent[] }>(
|
||||||
|
`/trips/${tripId}/chat`,
|
||||||
|
{ method: "POST", body: JSON.stringify({ message, history }) }
|
||||||
|
),
|
||||||
|
|
||||||
|
// Places
|
||||||
|
searchPlaces: (query: string, near: string) =>
|
||||||
|
request<import("@/lib/types").PlaceResult[]>(
|
||||||
|
`/places/search?${new URLSearchParams({ query, near }).toString()}`
|
||||||
|
),
|
||||||
|
getDirections: (origin: string, destination: string) =>
|
||||||
|
request<{ duration_text: string | null; duration_seconds: number | null; distance_text: string | null }>(
|
||||||
|
`/places/directions?${new URLSearchParams({ origin, destination }).toString()}`
|
||||||
|
),
|
||||||
|
|
||||||
// Per-system AI flag overrides
|
// Per-system AI flag overrides
|
||||||
setSystemAiFlags: (systemId: string, flags: { stt_enabled?: boolean | null; correlation_enabled?: boolean | null }) =>
|
setSystemAiFlags: (systemId: string, flags: { stt_enabled?: boolean | null; correlation_enabled?: boolean | null }) =>
|
||||||
|
|||||||
@@ -103,15 +103,27 @@ export interface TripEvent {
|
|||||||
trip_id: string;
|
trip_id: string;
|
||||||
title: string;
|
title: string;
|
||||||
date: string;
|
date: string;
|
||||||
time: string | null;
|
start_time: string | null;
|
||||||
|
end_time: string | null;
|
||||||
location: string;
|
location: string;
|
||||||
location_inherited: boolean;
|
location_inherited: boolean;
|
||||||
maps_link: string | null;
|
maps_link: string | null;
|
||||||
|
place_id: string | null;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
attendees: Record<string, string>;
|
attendees: Record<string, string>;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlaceResult {
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
place_id: string;
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
maps_link: string;
|
||||||
|
rating?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TripRecord {
|
export interface TripRecord {
|
||||||
trip_id: string;
|
trip_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -316,7 +316,8 @@ class TripCommands(commands.Cog):
|
|||||||
trip="The trip to add this event to.",
|
trip="The trip to add this event to.",
|
||||||
title="Event title",
|
title="Event title",
|
||||||
date="Date of the event (YYYY-MM-DD or MM/DD/YYYY)",
|
date="Date of the event (YYYY-MM-DD or MM/DD/YYYY)",
|
||||||
time="Time of the event (e.g. 14:00 or 2:00 PM) — optional",
|
start_time="Start time (e.g. 14:00 or 2:00 PM) — optional",
|
||||||
|
end_time="End time (e.g. 16:00 or 4:00 PM) — optional",
|
||||||
location="Location override (optional, inherits trip location if omitted)",
|
location="Location override (optional, inherits trip location if omitted)",
|
||||||
maps_link="Google Maps link for this event (optional)",
|
maps_link="Google Maps link for this event (optional)",
|
||||||
notes="Any additional notes (optional)",
|
notes="Any additional notes (optional)",
|
||||||
@@ -328,7 +329,8 @@ class TripCommands(commands.Cog):
|
|||||||
trip: str,
|
trip: str,
|
||||||
title: str,
|
title: str,
|
||||||
date: str,
|
date: str,
|
||||||
time: Optional[str] = None,
|
start_time: Optional[str] = None,
|
||||||
|
end_time: Optional[str] = None,
|
||||||
location: Optional[str] = None,
|
location: Optional[str] = None,
|
||||||
maps_link: Optional[str] = None,
|
maps_link: Optional[str] = None,
|
||||||
notes: Optional[str] = None,
|
notes: Optional[str] = None,
|
||||||
@@ -340,17 +342,21 @@ class TripCommands(commands.Cog):
|
|||||||
await interaction.followup.send("Invalid date format. Use YYYY-MM-DD.")
|
await interaction.followup.send("Invalid date format. Use YYYY-MM-DD.")
|
||||||
return
|
return
|
||||||
|
|
||||||
parsed_time = _parse_time(time) if time else None
|
parsed_start = _parse_time(start_time) if start_time else None
|
||||||
if time and parsed_time is None:
|
parsed_end = _parse_time(end_time) if end_time else None
|
||||||
await interaction.followup.send(
|
|
||||||
"Couldn't parse that time. Try `14:00` or `2:00 PM`."
|
if start_time and parsed_start is None:
|
||||||
)
|
await interaction.followup.send("Couldn't parse start time. Try `14:00` or `2:00 PM`.")
|
||||||
|
return
|
||||||
|
if end_time and parsed_end is None:
|
||||||
|
await interaction.followup.send("Couldn't parse end time. Try `16:00` or `4:00 PM`.")
|
||||||
return
|
return
|
||||||
|
|
||||||
event = await c2.create_trip_event(trip, {
|
event = await c2.create_trip_event(trip, {
|
||||||
"title": title,
|
"title": title,
|
||||||
"date": parsed_date.strftime("%Y-%m-%d"),
|
"date": parsed_date.strftime("%Y-%m-%d"),
|
||||||
"time": parsed_time,
|
"start_time": parsed_start,
|
||||||
|
"end_time": parsed_end,
|
||||||
"location": location,
|
"location": location,
|
||||||
"maps_link": maps_link,
|
"maps_link": maps_link,
|
||||||
"notes": notes,
|
"notes": notes,
|
||||||
@@ -362,7 +368,7 @@ class TripCommands(commands.Cog):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
time_display = f" at {_fmt_time(parsed_time)}" if parsed_time else ""
|
time_display = f" at {_fmt_time(parsed_start)}" if parsed_start else ""
|
||||||
await interaction.followup.send(
|
await interaction.followup.send(
|
||||||
f"Added **{title}**{time_display} on {_fmt_date(parsed_date.strftime('%Y-%m-%d'))}."
|
f"Added **{title}**{time_display} on {_fmt_date(parsed_date.strftime('%Y-%m-%d'))}."
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user