Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
@tailwind utilities;


.map {
height: 60vh;
}

.loader-dots div {
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
Expand Down
6 changes: 4 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useState } from 'react'
import MapComponent from './components/Map.jsx'
import Chat from './components/Chat.jsx';

const App = () => {
let [places, setPlaces] = useState([]);

return (
<>
<div className="w-screen h-screen m-0 p-3 bg-slate-500">
<MapComponent locations={places}/>
<div className="w-full h-full m-0 p-3 bg-slate-50 flex">
<MapComponent locations={places} />
<Chat setPlaces={setPlaces} />
</div>
</>
);
Expand Down
51 changes: 51 additions & 0 deletions src/components/Chat.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Textarea } from "@headlessui/react";
import { useState } from "react";
import generateAi from '../utils/modelConfig.js';

const Chat = ({ setPlaces }) => {

const [prompt, setPrompt] = useState("");
const [messageSent, setMessageSent] = useState(false);

const handleChange = (e) => {
setPrompt(e.target.value);
setMessageSent(false);
};

const handleSubmit = async (e) => {
e.preventDefault();
console.log(prompt);
setPrompt("");
setMessageSent(true);
setPlaces(await generateAi(prompt))
}

const handleKeyPress = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit(e)
}
}

return (
<div className='w-1/5 flex flex-col justify-end'>
<section className="border border-slate-200 rounded-md mx-2">
<div className="p-2 my-5">
{/* {prompt} */}
</div>
<form onSubmit={handleSubmit} className="flex items-center">
<Textarea
className='w-4/5 h-20 p-2'
placeholder='Type a message...'
value={prompt}
onChange={handleChange}
onKeyDown={handleKeyPress}
/>
<button className='w-auto h-10 mx-auto' type='submit'><svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.20308 1.04312C1.00481 0.954998 0.772341 1.0048 0.627577 1.16641C0.482813 1.32802 0.458794 1.56455 0.568117 1.75196L3.92115 7.50002L0.568117 13.2481C0.458794 13.4355 0.482813 13.672 0.627577 13.8336C0.772341 13.9952 1.00481 14.045 1.20308 13.9569L14.7031 7.95693C14.8836 7.87668 15 7.69762 15 7.50002C15 7.30243 14.8836 7.12337 14.7031 7.04312L1.20308 1.04312ZM4.84553 7.10002L2.21234 2.586L13.2689 7.50002L2.21234 12.414L4.84552 7.90002H9C9.22092 7.90002 9.4 7.72094 9.4 7.50002C9.4 7.27911 9.22092 7.10002 9 7.10002H4.84553Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button>
</form>
</section>
</div>
);
};

export default Chat;
134 changes: 68 additions & 66 deletions src/components/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import { AdvancedMarker, APIProvider, Map, Pin, useMap } from '@vis.gl/react-goo
import { MarkerClusterer } from '@googlemaps/markerclusterer'
import { Circle } from './Circle.jsx'

// import {main} from '../utils/modelConfig.js'

const center = {
lat: -27.33056,
lng: -55.86667,
lat: -27.33056,
lng: -55.86667,
};

const MapComponent = ({locations=[]}) => {
return (
<div className="map">
<div className="h-full w-4/5 rounded-md">
<APIProvider
apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}
onLoad={() => console.log("Maps API has loaded.")}
Expand All @@ -37,74 +39,74 @@ const MapComponent = ({locations=[]}) => {
);
}

const PoiMarkers = ({locations=[]}) => {
const map = useMap()
const [markers, setMarkers] = useState({})
const clusterer = useRef(null)
const [circleCenter, setCircleCenter] = useState(null)
const handleClick = useCallback(ev => {
if (!map) return
if (!ev.latLng) return
map.panTo(ev.latLng)
setCircleCenter(ev.latLng)
})
// Initialize MarkerClusterer, if the map has changed
useEffect(() => {
if (!map) return
if (!clusterer.current) {
clusterer.current = new MarkerClusterer({ map })
}
}, [map])
const PoiMarkers = ({ locations = [] }) => {
const map = useMap()
const [markers, setMarkers] = useState({})
const clusterer = useRef(null)
const [circleCenter, setCircleCenter] = useState(null)
const handleClick = useCallback(ev => {
if (!map) return
if (!ev.latLng) return
map.panTo(ev.latLng)
setCircleCenter(ev.latLng)
})
// Initialize MarkerClusterer, if the map has changed
useEffect(() => {
if (!map) return
if (!clusterer.current) {
clusterer.current = new MarkerClusterer({ map })
}
}, [map])

// Update markers, if the markers array has changed
useEffect(() => {
clusterer.current?.clearMarkers()
clusterer.current?.addMarkers(Object.values(markers))
}, [markers])
// Update markers, if the markers array has changed
useEffect(() => {
clusterer.current?.clearMarkers()
clusterer.current?.addMarkers(Object.values(markers))
}, [markers])

const setMarkerRef = (marker, key) => {
if (marker && markers[key]) return
if (!marker && !markers[key]) return
const setMarkerRef = (marker, key) => {
if (marker && markers[key]) return
if (!marker && !markers[key]) return

setMarkers(prev => {
if (marker) {
return { ...prev, [key]: marker }
} else {
const newMarkers = { ...prev }
delete newMarkers[key]
return newMarkers
}
})
}
setMarkers(prev => {
if (marker) {
return { ...prev, [key]: marker }
} else {
const newMarkers = { ...prev }
delete newMarkers[key]
return newMarkers
}
})
}

return (
<>
<Circle
radius={800}
center={circleCenter}
strokeColor={"#0c4cb3"}
strokeOpacity={1}
strokeWeight={3}
fillColor={"#3b82f6"}
fillOpacity={0.3}
return (
<>
<Circle
radius={800}
center={circleCenter}
strokeColor={"#0c4cb3"}
strokeOpacity={1}
strokeWeight={3}
fillColor={"#3b82f6"}
fillOpacity={0.3}
/>
{locations.map(poi => (
<AdvancedMarker
title={poi.key}
key={poi.key}
position={poi.location}
clickable={true}
onClick={handleClick}
>
<Pin
background={'#FBBC04'}
glyphColor={'#1a70fd'}
borderColor={'#1a70fd'}
/>
{locations.map(poi => (
<AdvancedMarker
title={poi.key}
key={poi.key}
position={poi.location}
clickable={true}
onClick={handleClick}
>
<Pin
background={'#FBBC04'}
glyphColor={'#1a70fd'}
borderColor={'#1a70fd'}
/>
</AdvancedMarker>
))}
</>
)
</AdvancedMarker>
))}
</>
)
}

export default MapComponent;
20 changes: 20 additions & 0 deletions src/utils/modelConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import OpenAI from "openai";

const openai = new OpenAI({ apiKey: import.meta.env.VITE_OPENAI_API_KEY, dangerouslyAllowBrowser: true });

async function generateAi(prompt) {
const completion = await openai.chat.completions.create({
messages: [{ "role": "system", "content": "You are a helpful assistant from a tourist webpage in the world." },
{ "role": "user", "content": "please use this format always for your responses in a json format: [{'name':'Circuito Comercial','type':'Turístico','location':{'lat':-27.3593327,'lng':-55.8489188},'description':'Zona baja de Encarnación, conocida por ser un punto de encuentro para turismo de compras.','address':'Zona baja de Encarnación'}, another one] " },
{ "role": "assistant", "content": "Sure i can do that" },
{ "role": "user", "content": prompt }],
model: "gpt-4o-mini",
response_format: { type: "json_object" }
});
let respuesta = JSON.parse(completion.choices[0].message.content)
return (respuesta[Object.keys(respuesta)[0]]);
}



export default generateAi;