diff --git a/frontend/src/lib/components/AITravelChat.svelte b/frontend/src/lib/components/AITravelChat.svelte
index f532262b..4a44cd26 100644
--- a/frontend/src/lib/components/AITravelChat.svelte
+++ b/frontend/src/lib/components/AITravelChat.svelte
@@ -24,6 +24,18 @@
rating?: number;
latitude?: number | string;
longitude?: number | string;
+ link?: string | null;
+ preferred_link?: string | null;
+ url?: string | null;
+ website?: string | null;
+ map_link?: string | null;
+ links?: {
+ link?: string | null;
+ preferred?: string | null;
+ official?: string | null;
+ website?: string | null;
+ map?: string | null;
+ } | null;
};
type Conversation = {
@@ -661,6 +673,43 @@
return null;
}
+ function normalizeHttpUrl(value: unknown): string | null {
+ if (typeof value !== 'string') {
+ return null;
+ }
+
+ const candidate = value.trim();
+ if (!candidate) {
+ return null;
+ }
+
+ try {
+ const parsed = new URL(candidate);
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
+ return null;
+ }
+
+ return parsed.toString();
+ } catch {
+ return null;
+ }
+ }
+
+ function getPreferredPlaceLink(place: PlaceResult): string | null {
+ return (
+ normalizeHttpUrl(place.link) ||
+ normalizeHttpUrl(place.preferred_link) ||
+ normalizeHttpUrl(place.url) ||
+ normalizeHttpUrl(place.website) ||
+ normalizeHttpUrl(place.map_link) ||
+ normalizeHttpUrl(place.links?.link) ||
+ normalizeHttpUrl(place.links?.preferred) ||
+ normalizeHttpUrl(place.links?.official) ||
+ normalizeHttpUrl(place.links?.website) ||
+ normalizeHttpUrl(place.links?.map)
+ );
+ }
+
function hasPlaceCoordinates(place: PlaceResult): boolean {
return parseCoordinate(place.latitude) !== null && parseCoordinate(place.longitude) !== null;
}
@@ -737,6 +786,7 @@
location: place.address || place.name,
latitude,
longitude,
+ link: getPreferredPlaceLink(place),
collections: [collectionId],
is_public: false
})
@@ -1146,24 +1196,37 @@
{#if msg.role === 'assistant' && msg.tool_results}
{#each deduplicateContextTools(uniqueToolResultsByCallId(msg.tool_results)) as result}
- {#if hasPlaceResults(result)}
-
- {#each getPlaceResults(result) as place}
-
-
{place.name}
- {#if place.address}
-
{place.address}
- {/if}
- {#if place.rating}
-
- ⭐
- {place.rating}
-
- {/if}
- {#if collectionId}
- {@const isDuplicate = mergedLocationNames.has(
- normalizeLocationName(place.name)
- )}
+ {#if hasPlaceResults(result)}
+
+ {#each getPlaceResults(result) as place}
+ {@const placeLink = getPreferredPlaceLink(place)}
+
+
{place.name}
+ {#if place.address}
+
{place.address}
+ {/if}
+ {#if place.rating}
+
+ ⭐
+ {place.rating}
+
+ {/if}
+ {#if placeLink}
+
+ {/if}
+ {#if collectionId}
+ {@const isDuplicate = mergedLocationNames.has(
+ normalizeLocationName(place.name)
+ )}
- {/if}
-
- {/each}
-
+ {/if}
+
+ {/each}
+
{:else if hasWebSearchResults(result)}
{#each getWebSearchResults(result) as item}
diff --git a/frontend/src/lib/components/collections/ItinerarySuggestionModal.svelte b/frontend/src/lib/components/collections/ItinerarySuggestionModal.svelte
index 7b078d4d..06ea3908 100644
--- a/frontend/src/lib/components/collections/ItinerarySuggestionModal.svelte
+++ b/frontend/src/lib/components/collections/ItinerarySuggestionModal.svelte
@@ -20,6 +20,7 @@
price_level?: string | null;
latitude?: number | string | null;
longitude?: number | string | null;
+ link?: string | null;
};
const dispatch = createEventDispatcher();
@@ -166,6 +167,40 @@
return null;
}
+ function normalizeHttpUrl(value: unknown): string | null {
+ if (typeof value !== 'string') return null;
+ const candidate = value.trim();
+ if (!candidate) return null;
+
+ try {
+ const parsed = new URL(candidate);
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
+ return null;
+ }
+ return parsed.toString();
+ } catch {
+ return null;
+ }
+ }
+
+ function getPreferredSuggestionLink(item: Record): string | null {
+ const links = asRecord(item.links);
+ return (
+ normalizeHttpUrl(item.link) ||
+ normalizeHttpUrl(item.preferred_link) ||
+ normalizeHttpUrl(item.url) ||
+ normalizeHttpUrl(item.website) ||
+ normalizeHttpUrl(item.map_link) ||
+ (links
+ ? normalizeHttpUrl(links.link) ||
+ normalizeHttpUrl(links.preferred) ||
+ normalizeHttpUrl(links.official) ||
+ normalizeHttpUrl(links.website) ||
+ normalizeHttpUrl(links.map)
+ : null)
+ );
+ }
+
function normalizeSuggestionItem(value: unknown): SuggestionItem | null {
const item = asRecord(value);
if (!item) return null;
@@ -191,6 +226,7 @@
const rating = normalizeRating(item.rating ?? item.score);
const latitude = normalizeCoordinate(item.latitude ?? item.lat);
const longitude = normalizeCoordinate(item.longitude ?? item.lon ?? item.lng);
+ const link = getPreferredSuggestionLink(item);
const finalName = name || location;
if (!finalName) return null;
@@ -204,7 +240,8 @@
rating,
price_level: priceLevel || null,
latitude,
- longitude
+ longitude,
+ link
};
}
@@ -225,6 +262,7 @@
const rating = normalizeRating(suggestion.rating);
const latitude = normalizeCoordinate(suggestion.latitude);
const longitude = normalizeCoordinate(suggestion.longitude);
+ const link = normalizeHttpUrl(suggestion.link);
return {
name,
@@ -233,6 +271,7 @@
rating,
latitude,
longitude,
+ link,
collections: [collection.id],
is_public: Boolean(collection?.is_public)
};
@@ -508,7 +547,17 @@
{/if}
-