-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #147 from vintasoftware/feat/json-tour-guide
JSON tour guide
- Loading branch information
Showing
13 changed files
with
295 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import "@mantine/core/styles.css"; | ||
import { | ||
Container, | ||
TextInput, | ||
Button, | ||
LoadingOverlay, | ||
Group, | ||
} from "@mantine/core"; | ||
import { useEffect, useState } from "react"; | ||
import { notifications } from "@mantine/notifications"; | ||
import { Link } from "react-router-dom"; | ||
|
||
export function TourGuide() { | ||
const [showLoginNotification, setShowLoginNotification] = | ||
useState<boolean>(false); | ||
const [latitude, setLatitude] = useState(""); | ||
const [longitude, setLongitude] = useState(""); | ||
const [attractions, setAttractions] = useState([]); | ||
const [loading, setLoading] = useState(false); | ||
|
||
useEffect(() => { | ||
navigator.geolocation.getCurrentPosition( | ||
(position: any) => { | ||
setLatitude(position.coords.latitude); | ||
setLongitude(position.coords.longitude); | ||
}, | ||
(error) => console.log(error) | ||
); | ||
}, []); | ||
|
||
async function findAttractions() { | ||
if (!latitude || !longitude) { | ||
return; | ||
} | ||
|
||
setLoading(true); | ||
const response = await fetch(`/tour-guide/?coordinate=${latitude},${longitude}`); | ||
const data = await response.json(); | ||
if (data.error) { | ||
setShowLoginNotification(true); | ||
} else { | ||
setAttractions(data.nearby_attractions); | ||
} | ||
setLoading(false) | ||
} | ||
|
||
useEffect(() => { | ||
if (!showLoginNotification) return; | ||
|
||
notifications.show({ | ||
title: "Login Required", | ||
message: ( | ||
<> | ||
You must be logged in to engage with the examples. Please{" "} | ||
<Link to="/admin/" target="_blank"> | ||
log in | ||
</Link>{" "} | ||
to continue. | ||
</> | ||
), | ||
color: "red", | ||
autoClose: 5000, | ||
withCloseButton: true, | ||
}); | ||
}, [showLoginNotification]); | ||
|
||
return ( | ||
<Container> | ||
<LoadingOverlay visible={loading} /> | ||
<Group justify="left" align="flex-end"> | ||
<TextInput | ||
required | ||
label="Latitude" | ||
value={latitude} | ||
onChange={(e) => setLatitude(e.target.value)} | ||
/> | ||
<TextInput | ||
required | ||
label="Longitude" | ||
value={longitude} | ||
onChange={(e) => setLongitude(e.target.value)} | ||
/> | ||
<Button onClick={findAttractions}>Guide Me!</Button> | ||
</Group> | ||
{loading ? <h3>Loading</h3> : null} | ||
<div> | ||
{attractions.map((item, i) => ( | ||
<div key={i}> | ||
<h2> | ||
{item.attraction_url ? ( | ||
<a href={item.attraction_url} target="_blank"> | ||
{item.attraction_name} | ||
</a> | ||
) : ( | ||
item.attraction_name | ||
)} | ||
</h2> | ||
<span>{item.attraction_description}</span> | ||
<div> | ||
<a | ||
href={`https://www.google.com/maps?q=${item.attraction_name}`} | ||
target="_blank" | ||
> | ||
Open in Google Maps | ||
</a> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
</Container> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export { ThreadsNav } from "./ThreadsNav/ThreadsNav"; | ||
export { Chat } from "./Chat/Chat"; | ||
export { TourGuide } from "./TourGuide/TourGuide"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ | |
"movies", | ||
"rag", | ||
"issue_tracker", | ||
"tour_guide", | ||
] | ||
|
||
MIDDLEWARE = [ | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import json | ||
|
||
from django.utils import timezone | ||
|
||
from django_ai_assistant import AIAssistant, method_tool | ||
from tour_guide.integrations import fetch_points_of_interest | ||
|
||
|
||
def _tour_guide_example_json(): | ||
return json.dumps( | ||
{ | ||
"nearby_attractions": [ | ||
{ | ||
"attraction_name": f"<attraction-{i}-name-here>", | ||
"attraction_description": f"<attraction-{i}-description-here>", | ||
"attraction_url": f"<attraction-{i}-imdb-page-url-here>", | ||
} | ||
for i in range(1, 6) | ||
] | ||
}, | ||
indent=2, | ||
).translate( # Necessary due to ChatPromptTemplate | ||
str.maketrans( | ||
{ | ||
"{": "{{", | ||
"}": "}}", | ||
} | ||
) | ||
) | ||
|
||
|
||
class TourGuideAIAssistant(AIAssistant): | ||
id = "tour_guide_assistant" # noqa: A003 | ||
name = "Tour Guide Assistant" | ||
instructions = ( | ||
"You are a tour guide assistant that offers information about nearby attractions. " | ||
"The application will capture the user coordinates, and should provide a list of nearby attractions. " | ||
"Use the available tools to suggest nearby attractions to the user. " | ||
"You don't need to include all the found items, only include attractions that are relevant for a tourist. " | ||
"Select the top 10 best attractions for a tourist, if there are less then 10 relevant items only return these. " | ||
"Order items by the most relevant to the least relevant. " | ||
"If there are no relevant attractions nearby, just keep the list empty. " | ||
"Your response will be integrated with a frontend web application therefore it's critical that " | ||
"it only contains a valid JSON. DON'T include '```json' in your response. " | ||
"The JSON should be formatted according to the following structure: \n" | ||
f"\n\n{_tour_guide_example_json()}\n\n\n" | ||
"In the 'attraction_name' field provide the name of the attraction in english. " | ||
"In the 'attraction_description' field generate an overview about the attraction with the most important information, " | ||
"curiosities and interesting facts. " | ||
"Only include a value for the 'attraction_url' field if you find a real value in the provided data otherwise keep it empty. " | ||
) | ||
model = "gpt-4o" | ||
|
||
def get_instructions(self): | ||
# Warning: this will use the server's timezone | ||
# See: https://docs.djangoproject.com/en/5.0/topics/i18n/timezones/#default-time-zone-and-current-time-zone | ||
# In a real application, you should use the user's timezone | ||
current_date_str = timezone.now().date().isoformat() | ||
|
||
return f"Today is: {current_date_str}. {self.instructions}" | ||
|
||
@method_tool | ||
def get_nearby_attractions_from_api(self, latitude: float, longitude: float) -> dict: | ||
"""Find nearby attractions based on user's current location.""" | ||
return fetch_points_of_interest( | ||
latitude=latitude, | ||
longitude=longitude, | ||
tags=["tourism", "leisure", "place", "building"], | ||
radius=500, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class TourGuideConfig(AppConfig): | ||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "tour_guide" |
Oops, something went wrong.