Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Geo-Copilot to VEDA UI Exploration Page #1173

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
0c7d8b5
Add New Geo-copilot components.
xhagrg Sep 13, 2024
3ea56cf
Add loaders.
xhagrg Sep 13, 2024
b2765be
Add geo-copilot components to existing maps.
xhagrg Sep 13, 2024
fa579e7
Handle case when server responds with bad content.
xhagrg Sep 24, 2024
3ce1fed
Add copy text functionality.
xhagrg Sep 24, 2024
0bb8727
Update interaction with UI.
xhagrg Sep 24, 2024
4bf0fcb
Handle cases of analysis/statistics.
xhagrg Sep 24, 2024
b115def
Add relevant details to answer details with style
slesaad Sep 24, 2024
3ac7ed0
Remove AOI after each interaction.
xhagrg Sep 25, 2024
e275f82
Remove console.log.
xhagrg Sep 25, 2024
91b5a1e
Merge branch 'main' into feature-geocopilot
xhagrg Sep 25, 2024
c537bfd
Add extra lines for better readability.
xhagrg Sep 25, 2024
695940d
Reformat for better readability.
xhagrg Sep 25, 2024
6105284
Pass timeDensity for better analysis experience.
xhagrg Sep 25, 2024
06aeebe
Import pulse loader.
xhagrg Sep 25, 2024
16150a5
Use env variable.
xhagrg Sep 25, 2024
27a1d0b
Update .env.
xhagrg Sep 25, 2024
be2c199
Remove quotes for testing.
xhagrg Sep 25, 2024
b045e20
Remove unwanted ;
xhagrg Sep 26, 2024
c2b6d90
Merge branch 'main' of github.com:nasa-impact/veda-ui into feature-ge…
xhagrg Sep 26, 2024
77a98c3
Merge feature-geocopilot
slesaad Sep 26, 2024
d0f1345
Add reverse geocoding to get placename from bbox
slesaad Sep 26, 2024
8afa736
WIP query highlighting
slesaad Sep 26, 2024
349707a
Remove unwanted import.
xhagrg Sep 27, 2024
cace7a5
Remove unwanted import and handle cases when data is not present.
xhagrg Sep 27, 2024
0942a38
Fix issues with analysis date range.
xhagrg Sep 27, 2024
879d9ce
Lazy fix for lint errors.
xhagrg Sep 27, 2024
4f3c306
Merge conflict resolve with base feature-geocopilot.
xhagrg Sep 27, 2024
a623843
Merge conflict resolve with base feature-geocopilot.
xhagrg Sep 27, 2024
4fd1d2c
Update message for error handling.
xhagrg Sep 27, 2024
4cd4e9a
Hide explanations for now.
xhagrg Sep 27, 2024
d328aa5
Use prod url (for now).
xhagrg Sep 30, 2024
066fff7
Update explanation after answer is retrieved.
xhagrg Sep 30, 2024
d95651a
Update to use full vertical space.
xhagrg Sep 30, 2024
5b38fd8
Fix linting errors.
xhagrg Sep 30, 2024
82f6522
Update tooltip style.
xhagrg Sep 30, 2024
a40ec8f
Revert back to staging.
xhagrg Oct 1, 2024
75a396f
Use unique dataset ids.
xhagrg Oct 1, 2024
9170fb1
Do not use query params for now.
xhagrg Oct 1, 2024
5e3742a
Remove console.log.
xhagrg Oct 2, 2024
97e57a0
Use solid lines to represent matches.
xhagrg Oct 2, 2024
8a7c96c
Add react-jsx-parser for dynamic rendering.
xhagrg Oct 2, 2024
27f1de8
Merge branch 'main' into feature-geocopilot
xhagrg Oct 2, 2024
fa5f9e5
Conflict resolve with main.
xhagrg Oct 2, 2024
f430052
Fix linting issues.
xhagrg Oct 7, 2024
767c1a2
Fix linting errors.
xhagrg Oct 7, 2024
ca6183e
Fix additional linting issues.
xhagrg Oct 7, 2024
6e4e2ca
Details styling (#1185)
xhagrg Oct 7, 2024
8c759c1
Merge branch 'main' into feature-geocopilot
xhagrg Oct 7, 2024
955418b
Merge conflict resolve main.
xhagrg Oct 7, 2024
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
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ [email protected]

API_RASTER_ENDPOINT='https://staging.openveda.cloud/api/raster'
API_STAC_ENDPOINT='https://staging.openveda.cloud/api/stac'
GEO_COPILOT_ENDPOINT=https://veda-search-poc.azurewebsites.net/score

# If the app is being served in from a subfolder, the domain url must be set.
# For example, if the app is served from /mysite:
Expand All @@ -13,4 +14,3 @@ API_STAC_ENDPOINT='https://staging.openveda.cloud/api/stac'
GOOGLE_FORM = 'https://docs.google.com/forms/d/e/1FAIpQLSfGcd3FDsM3kQIOVKjzdPn4f88hX8RZ4Qef7qBsTtDqxjTSkg/viewform?embedded=true'

SHOW_CONFIGURABLE_COLOR_MAP = 'TRUE'

2 changes: 1 addition & 1 deletion .stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
"app/scripts/styles/continuum/utils.ts",
"**/*.d.ts"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ function CustomAoI({
// selected, the trash method doesn't do anything. So, in this case, we
// trigger the delete for the whole feature.
const selectedFeatures = mbDraw.getSelected()?.features;

if (
mbDraw.getMode() === DIRECT_SELECT &&
selectedFeatures.length &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { CollecticonCode } from '@devseed-ui/collecticons';
import useMaps from '$components/common/map/hooks/use-maps';
import { SelectorButton } from '$components/common/map/style/button';
import useThemedControl from '$components/common/map/controls/hooks/use-themed-control';

interface GeoCoPilotControlProps {
showGeoCoPilot: () => void;
setMap: (any) => void;
}

export function GeoCoPilotComponent({onClick}: {
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
}) {
return (
<SelectorButton
tipContent='Chat with Geo-Copilot'
tipProps={{ placement: 'left' }}
onClick={onClick}
>
<CollecticonCode />
</SelectorButton>
);
}

export function GeoCoPilotControl(props: GeoCoPilotControlProps) {
const {showGeoCoPilot, setMap} = props;
// Show conversation modal
const {main} = useMaps();
setMap(main);

useThemedControl(() => <GeoCoPilotComponent onClick={showGeoCoPilot} />, {
position: 'top-right'
});
return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import axios from 'axios';

interface GeoCoPilotInteractionQuery {
question: string;
chat_history: any;
content: any;
}

const GEOCOPILOT_ENDPOINT = process.env.GEO_COPILOT_ENDPOINT;

const ERROR_RESPONSE = {
"dataset_ids": [],
"summary": "An unexpected error occurred with this request. Please ask another question.",
"date_range": {'start_date': '', 'end_date': ''},
"bbox":{},
"action": "error",
"explanation":
{
"validation": "",
"verification":[]
},
"query": ''
};

/**
* Gets the asset urls for all datasets in the results of a STAC search given by
* the input parameters.
*
* @param params Dataset search request parameters
* @param opts Options for the request (see Axios)
* @returns Promise with the asset urls
*/
export async function askGeoCoPilot(
{
question,
chat_history,
content
}: GeoCoPilotInteractionQuery,
setSystemResponse: (answer: any, content: any) => void
){
ERROR_RESPONSE['query'] = question;

if (!GEOCOPILOT_ENDPOINT) {
setSystemResponse(ERROR_RESPONSE, content);
return;
}

await axios.post(
GEOCOPILOT_ENDPOINT,
{
'question': question,
'chat_history': chat_history
}
).then((answer) => {
const extractedAnswer = JSON.parse(answer.data.answer);
content[content.length - 1].explanations = extractedAnswer.explanation.verification;
setSystemResponse(JSON.parse(answer.data.answer), content);
}).catch(() => {
setSystemResponse(ERROR_RESPONSE, content);
});
}


// Returns the full geolocation url based on centroid (lat, lon) and mapboxaccesstoken
export const geolocationUrl = (centroid, mapboxAccessToken) =>
`https://api.mapbox.com/geocoding/v5/mapbox.places/${centroid[0]},${centroid[1]}.json?access_token=${mapboxAccessToken}`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import React, {useEffect, useState} from 'react';
import axios from 'axios';


import { Button } from '@devseed-ui/button';
import {
CollecticonHandThumbsUp,
CollecticonHandThumbsDown,
CollecticonLink,
CollecticonChevronUpTrailSmall,
CollecticonChevronDownTrailSmall,
CollecticonCalendarRange,
CollecticonMarker,
CollecticonMap,

} from '@devseed-ui/collecticons';

import centroid from '@turf/centroid';

import styled from 'styled-components';
import { geolocationUrl } from './geo-copilot-interaction';

const DialogContent = styled.div`
width: fit-content;
max-width: 75%;
min-width: 25%;
background: white;
padding: 1em;
margin: 1em 0 1em 1em;
margin-right: auto;
border-radius: 10px;
`;

const DialogInteraction = styled.div`
font-size: 0.6rem;
display: flex;
`;

const ButtonContent = styled.span`
font-size: 0.6rem;
`;

const ShowHideDetail = styled.div`
margin-left: auto;
`;

const AnswerDetails = styled.div`
font-size: 0.6rem;
padding: 2em;
background: #f6f7f8;
border-radius: 10px;
`;

const AnswerDetailsIcon = styled.div`
display: flex;
align-items: center;

span {
margin-left: 4px;
}
`;

const AnswerDetailsItem = styled.div`
margin-bottom: 6px;

p {
font-size: 0.7rem;
margin-left: 12px;
}
`;

export interface GeoCoPilotModalProps {
summary: string;
dataset_ids: any;
bbox: any;
date_range: any;
explanation: any;
}

export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dateRange, explanation}: {
summary: string;
dataset_ids: any;
bbox: any;
dateRange: any;
explanation: any;
}) {
const [showDetails, setShowDetails] = useState(false);
const [location, setLocation] = useState("");

useEffect(() => {
const fetchGeolocation = async (center) => {
try {
const response = await axios.get(geolocationUrl(center, process.env.MAPBOX_TOKEN));
setLocation(response.data.features[2].place_name); // assuming 'features' is the array in the API response
} catch (error) {
console.error("Reverse geocoding failed.", error);

Check failure on line 96 in app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
}
};

if (Object.keys(bbox).length > 0) {
const geojson = JSON.parse(JSON.stringify(bbox).replace('coordinates:', 'coordinates'));
const center = centroid(geojson).geometry.coordinates;
fetchGeolocation(center);
}
}, [bbox]);

const updateShowDetails = () => {
setShowDetails(!showDetails);
};

const copyURL = () => {
navigator.clipboard.writeText(document.URL);
};

return (
<DialogContent>
<div>{summary}</div>
{/*Content*/}
{explanation &&
<DialogInteraction>
<div>
<Button size='small'>
<CollecticonHandThumbsUp size={10} />
</Button>
<Button size='small'>
<CollecticonHandThumbsDown size={10} />
</Button>
</div> |
{/*Interaction*/}
<div>
<Button size='small'>
<CollecticonLink size={10} />
<ButtonContent onClick={copyURL}>Copy Map Link</ButtonContent>
</Button>
</div>
{/*Summary*/}
<ShowHideDetail onClick={updateShowDetails}>
<Button size='small'>
{showDetails ?
<>
<ButtonContent>Hide Details</ButtonContent>
<CollecticonChevronUpTrailSmall size={10} />
</> :
<>
<ButtonContent>Show Details</ButtonContent>
<CollecticonChevronDownTrailSmall size={10} />
</>}
</Button>
</ShowHideDetail>
</DialogInteraction>}
{showDetails &&
<AnswerDetails>
<AnswerDetailsItem>
<AnswerDetailsIcon>
<CollecticonMarker size={10} /><span>Location</span>
</AnswerDetailsIcon>
<p>{location}</p>
</AnswerDetailsItem>
<AnswerDetailsItem>
<AnswerDetailsIcon>
<CollecticonCalendarRange size={10} /><span>Timeframe</span>
</AnswerDetailsIcon>
<p>{`${dateRange.start_date} > ${dateRange.end_date}`}</p>
</AnswerDetailsItem>
<AnswerDetailsItem>
<AnswerDetailsIcon>
<CollecticonMap size={10} /><span>Map layers</span>
</AnswerDetailsIcon>
<p>{dataset_ids.join(", ")}</p>
</AnswerDetailsItem>
</AnswerDetails>}
</DialogContent>
);
}

export function GeoCoPilotSystemDialog(props: GeoCoPilotModalProps) {
const {summary, dataset_ids, bbox, date_range, explanation} = props;
return (
<GeoCoPilotSystemDialogComponent
summary={summary}
dataset_ids={dataset_ids}
bbox={bbox}
dateRange={date_range}
explanation={explanation}
/>
);
}
Loading
Loading