fix: suppress duplicate assistant itinerary adds
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
export let startDate: string | undefined = undefined;
|
||||
export let endDate: string | undefined = undefined;
|
||||
export let destination: string | undefined = undefined;
|
||||
export let collectionLocations: Location[] = [];
|
||||
|
||||
let conversations: Conversation[] = [];
|
||||
let activeConversation: Conversation | null = null;
|
||||
@@ -662,7 +663,39 @@
|
||||
return parseCoordinate(place.latitude) !== null && parseCoordinate(place.longitude) !== null;
|
||||
}
|
||||
|
||||
function normalizeLocationName(value: unknown): string {
|
||||
if (typeof value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
let sessionDuplicateCollectionId: string | undefined = collectionId;
|
||||
let sessionAddedLocationNames = new Set<string>();
|
||||
|
||||
$: if (sessionDuplicateCollectionId !== collectionId) {
|
||||
sessionDuplicateCollectionId = collectionId;
|
||||
sessionAddedLocationNames = new Set<string>();
|
||||
}
|
||||
|
||||
$: existingLocationNames = new Set(
|
||||
(collectionLocations || [])
|
||||
.map((location) => normalizeLocationName(location?.name))
|
||||
.filter((name) => name.length > 0)
|
||||
);
|
||||
|
||||
$: mergedLocationNames = new Set([...existingLocationNames, ...sessionAddedLocationNames]);
|
||||
|
||||
function placeAlreadyInCollection(place: PlaceResult): boolean {
|
||||
return mergedLocationNames.has(normalizeLocationName(place.name));
|
||||
}
|
||||
|
||||
function openDateSelector(place: PlaceResult) {
|
||||
if (placeAlreadyInCollection(place)) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedPlaceToAdd = place;
|
||||
selectedDate = startDate || '';
|
||||
showDateSelector = true;
|
||||
@@ -679,6 +712,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (placeAlreadyInCollection(place)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const latitude = parseCoordinate(place.latitude);
|
||||
const longitude = parseCoordinate(place.longitude);
|
||||
if (latitude === null || longitude === null) {
|
||||
@@ -729,6 +766,10 @@
|
||||
}
|
||||
|
||||
const itineraryItem = await itineraryResponse.json();
|
||||
const normalizedPlaceName = normalizeLocationName(place.name);
|
||||
if (normalizedPlaceName) {
|
||||
sessionAddedLocationNames = new Set([...sessionAddedLocationNames, normalizedPlaceName]);
|
||||
}
|
||||
|
||||
dispatch('itemAdded', { location, itineraryItem, date });
|
||||
addToast('success', $t('added_successfully'));
|
||||
@@ -797,12 +838,92 @@
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'move_itinerary_item') {
|
||||
const item = asRecord(payload?.itinerary_item);
|
||||
const date = typeof item?.date === 'string' ? item.date : 'the selected day';
|
||||
return {
|
||||
icon: '↕️',
|
||||
text: `Moved itinerary item to ${date}.`
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'remove_itinerary_item') {
|
||||
return {
|
||||
icon: '🗑️',
|
||||
text: 'Removed item from itinerary.'
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'update_location_details') {
|
||||
const location = asRecord(payload?.location);
|
||||
const locationName = typeof location?.name === 'string' ? location.name : 'location';
|
||||
return {
|
||||
icon: '📍',
|
||||
text: `Updated details for ${locationName}.`
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'add_lodging') {
|
||||
const lodging = asRecord(payload?.lodging);
|
||||
const lodgingName = typeof lodging?.name === 'string' ? lodging.name : 'lodging';
|
||||
return {
|
||||
icon: '🏨',
|
||||
text: `Added lodging: ${lodgingName}.`
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'update_lodging') {
|
||||
const lodging = asRecord(payload?.lodging);
|
||||
const lodgingName = typeof lodging?.name === 'string' ? lodging.name : 'lodging';
|
||||
return {
|
||||
icon: '🏨',
|
||||
text: `Updated lodging: ${lodgingName}.`
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'remove_lodging') {
|
||||
return {
|
||||
icon: '🧹',
|
||||
text: 'Removed lodging from trip.'
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'add_transportation') {
|
||||
const transportation = asRecord(payload?.transportation);
|
||||
const name =
|
||||
typeof transportation?.name === 'string' ? transportation.name : 'transportation item';
|
||||
return {
|
||||
icon: '🚌',
|
||||
text: `Added transportation: ${name}.`
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'update_transportation') {
|
||||
const transportation = asRecord(payload?.transportation);
|
||||
const name =
|
||||
typeof transportation?.name === 'string' ? transportation.name : 'transportation item';
|
||||
return {
|
||||
icon: '🚌',
|
||||
text: `Updated transportation: ${name}.`
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'remove_transportation') {
|
||||
return {
|
||||
icon: '🧹',
|
||||
text: 'Removed transportation from trip.'
|
||||
};
|
||||
}
|
||||
|
||||
if (result.name === 'get_weather') {
|
||||
const entries = Array.isArray(payload?.results) ? payload.results : [];
|
||||
const availableCount = entries.filter((entry) => asRecord(entry)?.available === true).length;
|
||||
const estimatedCount = entries.filter(
|
||||
(entry) => asRecord(entry)?.is_estimate === true
|
||||
).length;
|
||||
return {
|
||||
icon: '🌤️',
|
||||
text: `Checked weather for ${entries.length} date${entries.length === 1 ? '' : 's'} (${availableCount} available).`
|
||||
text: `Checked weather for ${entries.length} date${entries.length === 1 ? '' : 's'} (${availableCount} available, ${estimatedCount} estimated).`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -820,10 +941,12 @@
|
||||
class:shadow-xl={!embedded}
|
||||
class:border={embedded}
|
||||
class:border-base-300={embedded}
|
||||
class:h-full={panelMode}
|
||||
class:min-h-0={panelMode}
|
||||
>
|
||||
<div class="card-body p-0">
|
||||
<div class="card-body p-0" class:h-full={panelMode} class:min-h-0={panelMode}>
|
||||
<div
|
||||
class="flex"
|
||||
class="flex min-h-0 overflow-hidden"
|
||||
class:h-[calc(100vh-64px)]={!embedded}
|
||||
class:h-full={panelMode}
|
||||
class:h-[65vh]={embedded && !panelMode}
|
||||
@@ -881,7 +1004,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-col min-w-0 {panelMode && sidebarOpen ? 'hidden' : ''}">
|
||||
<div class="flex-1 flex flex-col min-w-0 min-h-0 {panelMode && sidebarOpen ? 'hidden' : ''}">
|
||||
<div class="p-3 border-b border-base-300 flex items-center gap-3">
|
||||
<button
|
||||
class="btn btn-sm btn-ghost {panelMode ? '' : 'lg:hidden'}"
|
||||
@@ -983,7 +1106,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex-1 overflow-y-auto p-4 space-y-4" bind:this={messagesContainer}>
|
||||
<div class="flex-1 min-h-0 overflow-y-auto p-4 space-y-4" bind:this={messagesContainer}>
|
||||
{#if messages.length === 0 && !activeConversation}
|
||||
<div class="flex flex-col items-center justify-center h-full text-center px-4">
|
||||
<div class="{panelMode ? 'text-4xl' : 'text-6xl'} opacity-40 mb-3">🌍</div>
|
||||
@@ -1022,12 +1145,19 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if collectionId}
|
||||
{@const isDuplicate = mergedLocationNames.has(
|
||||
normalizeLocationName(place.name)
|
||||
)}
|
||||
<button
|
||||
class="btn btn-xs btn-primary btn-outline mt-2"
|
||||
on:click={() => openDateSelector(place)}
|
||||
disabled={!hasPlaceCoordinates(place)}
|
||||
disabled={!hasPlaceCoordinates(place) || isDuplicate}
|
||||
>
|
||||
{$t('add_to_itinerary')}
|
||||
{#if isDuplicate}
|
||||
{$t('adventures.itinerary_link_modal.already_added')}
|
||||
{:else}
|
||||
{$t('add_to_itinerary')}
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1648,7 +1648,9 @@
|
||||
{#if canModifyCollection}
|
||||
<div class="drawer-side z-40">
|
||||
<label for="collection-chat-drawer" class="drawer-overlay"></label>
|
||||
<div class="bg-base-100 h-full w-full sm:w-96 border-l border-base-300 flex flex-col">
|
||||
<div
|
||||
class="bg-base-100 h-full w-full sm:w-96 border-l border-base-300 flex flex-col min-h-0 overflow-hidden"
|
||||
>
|
||||
<div class="flex items-center justify-between p-3 border-b border-base-300">
|
||||
<h3 class="font-semibold text-sm">{$t('chat.travel_assistant')}</h3>
|
||||
<button
|
||||
@@ -1658,12 +1660,13 @@
|
||||
<X class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<div class="flex-1 min-h-0 overflow-hidden flex flex-col">
|
||||
<AITravelChat
|
||||
embedded={true}
|
||||
panelMode={true}
|
||||
collectionId={collection.id}
|
||||
collectionName={collection.name}
|
||||
collectionLocations={collection.locations || []}
|
||||
startDate={collection.start_date || undefined}
|
||||
endDate={collection.end_date || undefined}
|
||||
destination={collectionDestination}
|
||||
|
||||
Reference in New Issue
Block a user