From 0c7d8b5b3569a75197024d1548ddfd1aab080095 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Fri, 13 Sep 2024 00:23:19 -0500 Subject: [PATCH 01/40] Add New Geo-copilot components. --- .../geo-copilot/geo-copilot-control.tsx | 38 +++ .../geo-copilot/geo-copilot-interaction.ts | 51 ++++ .../geo-copilot/geo-copilot-system-dialog.tsx | 134 +++++++++ .../geo-copilot/geo-copilot-user-dialog.tsx | 40 +++ .../components/geo-copilot/geo-copilot.tsx | 269 ++++++++++++++++++ 5 files changed, 532 insertions(+) create mode 100644 app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx create mode 100644 app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts create mode 100644 app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx create mode 100644 app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx create mode 100644 app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx new file mode 100644 index 000000000..dfc862e3f --- /dev/null +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx @@ -0,0 +1,38 @@ +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'; +import { sequentialColorMaps } from '../datasets/colorMaps'; + +interface GeoCoPilotControlProps { + showGeoCoPilot: () => void; + setMap: (any) => void; +} + +export function GeoCoPilotComponent({onClick}: { + onClick: (event: React.MouseEvent) => void; +}) { + return ( + + + + ); +} + +export function GeoCoPilotControl(props: GeoCoPilotControlProps) { + const {showGeoCoPilot, setMap} = props; + const disabled = false; + // Show conversation modal + const {main} = useMaps(); + setMap(main); + + useThemedControl(() => , { + position: 'top-right' + }); + return null; +} diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts new file mode 100644 index 000000000..cdcad14e7 --- /dev/null +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -0,0 +1,51 @@ +import axios, { AxiosRequestConfig } from 'axios'; +import { QueryClient } from '@tanstack/react-query'; +import { FeatureCollection, Polygon } from 'geojson'; +import { ConcurrencyManagerInstance } from '$components/exploration/concurrency'; +import { GeoCoPilotModalProps } from '$components/exploration/components/geo-copilot/geo-copilot-system-dialog'; +import { TimelineDataset, TimelineDatasetForUrl } from '$components/exploration/types.d.ts'; + +import { ExtendedError } from '$components/exploration//data-utils'; + +import { + fixAoiFcForStacSearch, + getFilterPayload +} from '$components/analysis/utils'; +import { json } from 'd3'; + +interface GeoCoPilotInteractionQuery { + question: string; + chat_history: any; + content: any; +} + +const GEOCOPILOT_ENDPOINT = 'https://veda-search-poc.azurewebsites.net/score'; + +/** + * 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 +){ + const queryResponse = await axios.post( + GEOCOPILOT_ENDPOINT, + { + 'question': question, + 'chat_history': chat_history + } + ).then((answer) => { + setSystemResponse(JSON.parse(answer.data.answer), content); + }); +}; + + diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx new file mode 100644 index 000000000..1747b8ce0 --- /dev/null +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -0,0 +1,134 @@ +import React, {useState} from 'react'; + +import { Button } from '@devseed-ui/button'; +import { + CollecticonHandThumbsUp, + CollecticonHandThumbsDown, + CollecticonLink, + CollecticonChevronUpTrailSmall, + CollecticonChevronDownTrailSmall, +} from '@devseed-ui/collecticons'; + +import styled from 'styled-components'; + +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: 1em; + background: #f6f7f8; + border-radius: 10px; + + pre { + text-wrap: pretty; + } +` + +export interface GeoCoPilotModalProps { + summary: string; + dataset_ids: any; + bbox: any; + dateRange: any; + date: Date; + action: string; + explanation: any; + query: string; +} + +export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dateRange, date, action, explanation, query}: { + summary: string; + dataset_ids: any; + bbox: any; + dateRange: any; + date: Date; + action: string; + explanation: any; + query: string; +}) { + const [showDetails, setShowDetails] = useState(false); + const updateShowDetails = () => { + setShowDetails(!showDetails); + } + + return ( + +
{summary}
+ {/*Content*/} + {explanation && +
+ + +
| + {/*Interaction*/} +
+ +
+ {/*Summary*/} + + + +
} + { + showDetails && + +
+            {JSON.stringify(explanation?.verification)}
+          
+
+ } +
+ ) +} + +export function GeoCoPilotSystemDialog(props: GeoCoPilotModalProps) { + const {summary, dataset_ids, bbox, dateRange, date, action, explanation, query} = props; + return +} diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx new file mode 100644 index 000000000..e1b306cf6 --- /dev/null +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx @@ -0,0 +1,40 @@ +import React from 'react'; + +import styled from 'styled-components'; + +const DialogContent = styled.div` + min-width: 25%; + max-width: 75%; + width: fit-content; + margin: 1em 1em 1em 0; + margin-left: auto; + background: #d5ecfb; + padding: 1em; + border-radius: 10px; + justify-content: flex-end; + display: flex; +`; + +interface GeoCoPilotModalProps { + explanations: any; + query: string; +} + +export function GeoCoPilotUserDialogComponent({explanations, query}: { + explanations: any; + query: string; +}) { + return ( + +
{query}
+
+ ) +} + +export function GeoCoPilotUserDialog(props: GeoCoPilotModalProps) { + const {query, explanations} = props; + return +} diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx new file mode 100644 index 000000000..33f058523 --- /dev/null +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -0,0 +1,269 @@ +import React, {useState, useRef, useEffect, CSSProperties} from 'react'; + +import { themeVal, glsp } from '@devseed-ui/theme-provider'; + +import { GeoCoPilotSystemDialog } from './geo-copilot-system-dialog'; +import { GeoCoPilotUserDialog } from './geo-copilot-user-dialog'; +import { askGeoCoPilot } from './geo-copilot-interaction'; + +import { reconcileDatasets } from '$components/exploration/data-utils-no-faux-module'; +import { TimelineDataset } from '$components/exploration/types.d.ts'; +import { datasetLayers} from '$components/exploration/data-utils'; +import useMaps from '$components/common/map/hooks/use-maps'; +import { getZoomFromBbox } from '$components/common/map/utils'; + +import bbox from '@turf/bbox'; +import centroid from '@turf/centroid'; +import { AllGeoJSON } from '@turf/helpers'; + + +import PulseLoader from "react-spinners/PulseLoader"; + +import { CollecticonChevronRightTrailSmall, CollecticonArrowLoop, CollecticonXmarkSmall } from '@devseed-ui/collecticons'; + +import { Button } from '@devseed-ui/button'; + +import { + FormInput +} from '@devseed-ui/form'; + +import styled from 'styled-components'; + +interface GeoCoPilotModalProps { + show: boolean; + close: () => void; + datasets: TimelineDataset[]; + setDatasets: (datasets: TimelineDataset[]) => void; + selectedDay: Date | null; + setSelectedDay: (d: Date | null) => void; + selectedCompareDay: Date | null; + setSelectedCompareDay: (d: Date | null) => void; + map: any; +} + +const GeoCoPilotWrapper = styled.div` + padding-bottom: ${glsp()}; + height: calc(100% - 10px); + width: 100%; + background: #f6f7f8; + position: relative; +` +const GeoCoPilotContent = styled.div` + width: 100%; + height: calc(100% - 80px); + overflow-y: auto; + flex-direction: column; + font-size: 12px; + display: flex; +` +const GeoCoPilotQueryWrapper = styled.div` + display: flex; + overflow: hidden; + width: 95%; + margin: auto; + background-color: ${themeVal('color.surface')}; + + > button { + margin-left: -35px; + } +` + +const GeoCoPilotQuery = styled(FormInput)` + width: 100%; + &:focus-within { + border-radius: ${themeVal('shape.rounded')}; + outline-width: 0.25rem; + outline-color: ${themeVal('color.primary-200a')}; + outline-style: solid; + } +` + +const GeoCoPilotTitleWrapper = styled.div` + background: white; + padding: 10px; + height: 50px; + box-shadow: 0 2px 4px #b1b1b1; + margin-bottom: 3px; + display: flex; +` + +const GeoCoPilotTitle = styled.strong` + color: #2276ad; + width: 210px; + margin: auto; +` + +const RestartSession = styled(Button)` + align-self: flex-end; + background: #2276ad; + margin: auto; + color: white; +` + +const CloseSession = styled(Button)` + align-self: flex-end; +` + +const override: CSSProperties = { + display: "block", + margin: "1em auto 1em 1em", + borderColor: "red", +}; + + +export function GeoCoPilotComponent({ + close, + show, + datasets, + setDatasets, + selectedDay, + setSelectedDay, + selectedCompareDay, + setSelectedCompareDay, + map +}: { + close: () => void; + show: boolean; + datasets: TimelineDataset[]; + setDatasets: (datasets: TimelineDataset[]) => void; + selectedDay: Date | null; + setSelectedDay: (d: Date | null) => void; + selectedCompareDay: Date | null; + setSelectedCompareDay: (d: Date | null) => void; + map: any; +}) { + const defaultSystemComment = { + summary: "Welcome to Geo Co-Pilot! I'm here to assist you with identifying datasets with location and date information. Whether you're analyzing time-sensitive trends or working with geospatial data, I've got you covered. Let me know how I can assist you today!", + dataset_ids: [], + bbox: {}, + dateRange: {}, + date: new Date(), + action: '', + explanation: null, + query: '', + contentType: 'system' + } + const [conversation, setConversation] = useState([defaultSystemComment]); + const [query, setQuery] = useState(''); + const phantomElementRef = useRef(null); + const [loading, setLoading] = useState(false); + + const scrollToBottom = () => { + const phantomElement = phantomElementRef.current; + phantomElement.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + scrollToBottom(); + }, [loading]); + // hook to setup chat UI + // hook to process and start load/process/analyze data + // change behavior of bounding box zoom + // add upvote/downvote/share link, approaches + // add reset conversation option + // add localstorage option for conversation + // add verification links to the user dialog + const addSystemResponse = (answer: any, content: any) => { + answer['contentType'] = 'system'; + const action = answer['action']; + + switch(action) { + case 'load': { + const newDatasets = reconcileDatasets(answer['dataset_ids'], datasetLayers, datasets); + setSelectedDay(new Date(answer['date_range']['start_date'])); + setDatasets(newDatasets); + const geojson = JSON.parse(JSON.stringify(answer.bbox).replace('coordinates:', 'coordinates')) + // const {simplifiedFeatures} = getAoiAppropriateFeatures(geojson); + // debugger; + const bounds = bbox(geojson); + const center = centroid(geojson as AllGeoJSON).geometry.coordinates; + map.flyTo({ + center, + zoom: getZoomFromBbox(bounds) + }); + break; + + } + case 'compare': { + const newDatasets = reconcileDatasets(answer['dataset_ids'], datasetLayers, datasets); + setSelectedCompareDay(new Date(answer['date_range']['end_date'])); + setDatasets(newDatasets); + break; + } + case 'analysis': + console.log('analysis'); + default: + console.log(action, answer); + } + content = [...content, answer] + setConversation(content); + setLoading(false); + //close loading + } + + const addNewResponse = () => { + const userContent = { + explanations: '', + query: query, + contentType: 'user' + } + const content = [...conversation, userContent]; + setConversation(content); + setQuery(''); + setLoading(true); + askGeoCoPilot({question: query, chat_history: [], content: content}, addSystemResponse); + }; + + const clearSession = () => { + setConversation([defaultSystemComment]); + } + + return ( + + + Geo Co-Pilot + + Restart Session + + + + + {conversation.map((convComponent, index) => { + if(convComponent.contentType == 'user') { + return + } + else if (convComponent.contentType == 'system') { + return + } + })} + +
+
+ + {setQuery(e.target.value)}} onKeyUp={(e) => e.code == 'Enter' ? addNewResponse() : ''}/> + + +
+ ) +} + +export function GeoCoPilot(props: GeoCoPilotModalProps) { + return ; +} From 3ea56cf56867c1165bc517a561f86e4ed5222067 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Fri, 13 Sep 2024 00:23:57 -0500 Subject: [PATCH 02/40] Add loaders. --- package.json | 1 + yarn.lock | 23 +++++++++-------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 3eb84da8b..08572206c 100644 --- a/package.json +++ b/package.json @@ -210,6 +210,7 @@ "react-resizable-panels": "^0.0.45", "react-router": "^6.0.0", "react-router-dom": "^6.0.0", + "react-spinners": "^0.14.1", "react-transition-group": "^4.4.2", "react-virtual": "^2.10.4", "recharts": "2.1.12", diff --git a/yarn.lock b/yarn.lock index 6980311f6..86e7c60eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5570,20 +5570,10 @@ camelize@^1.0.0: resolved "http://verdaccio.ds.io:4873/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= -caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001366: - version "1.0.30001441" - resolved "http://verdaccio.ds.io:4873/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz" - integrity sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg== - -caniuse-lite@^1.0.30001538: - version "1.0.30001640" - resolved "http://verdaccio.ds.io:4873/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz#32c467d4bf1f1a0faa63fc793c2ba81169e7652f" - integrity sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA== - -caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001629: - version "1.0.30001639" - resolved "http://verdaccio.ds.io:4873/caniuse-lite/-/caniuse-lite-1.0.30001639.tgz#972b3a6adeacdd8f46af5fc7f771e9639f6c1521" - integrity sha512-eFHflNTBIlFwP2AIKaYuBQN/apnUoKNhBdza8ZnW/h2di4LCZ4xFqYlxUxo+LQ76KFI1PGcC1QDxMbxTZpSCAg== +caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001366, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001629: + version "1.0.30001658" + resolved "http://verdaccio.ds.io:4873/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz" + integrity sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw== ccount@^1.0.0: version "1.1.0" @@ -12728,6 +12718,11 @@ react-smooth@^2.0.1: fast-equals "^2.0.0" react-transition-group "2.9.0" +react-spinners@^0.14.1: + version "0.14.1" + resolved "http://verdaccio.ds.io:4873/react-spinners/-/react-spinners-0.14.1.tgz#de7d7d6b3e6d4f29d9620c65495b502c7dd90812" + integrity sha512-2Izq+qgQ08HTofCVEdcAQCXFEYfqTDdfeDQJeo/HHQiQJD4imOicNLhkfN2eh1NYEWVOX4D9ok2lhuDB0z3Aag== + react-style-singleton@^2.2.1: version "2.2.1" resolved "http://verdaccio.ds.io:4873/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" From b2765be5d06c0c0a44cd01b108ad66df9c26c137 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Fri, 13 Sep 2024 00:24:44 -0500 Subject: [PATCH 03/40] Add geo-copilot components to existing maps. --- .../exploration/components/map/index.tsx | 18 +++- app/scripts/components/exploration/index.tsx | 89 +++++++++++++++++-- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/app/scripts/components/exploration/components/map/index.tsx b/app/scripts/components/exploration/components/map/index.tsx index c9dc13c34..0ebfce88d 100644 --- a/app/scripts/components/exploration/components/map/index.tsx +++ b/app/scripts/components/exploration/components/map/index.tsx @@ -10,8 +10,10 @@ import { import { Layer } from './layer'; import { AnalysisMessageControl } from './analysis-message-control'; import { ShowTourControl } from './tour-control'; +import { GeoCoPilotControl } from '$components/exploration/components/geo-copilot/geo-copilot-control'; import Map, { Compare, MapControls } from '$components/common/map'; + import { Basemap } from '$components/common/map/style-generators/basemap'; import GeocoderControl from '$components/common/map/controls/geocoder'; import { @@ -32,10 +34,23 @@ interface ExplorationMapProps { setDatasets: (datasets: TimelineDataset[]) => void; selectedDay: Date | null; selectedCompareDay: Date | null; + openGeoCoPilot: () => void; + closeGeoCoPilot: () => void; + showGeoCoPilot: boolean; + setMap: (any) => void; } export function ExplorationMap(props: ExplorationMapProps) { - const { datasets, setDatasets, selectedDay, selectedCompareDay } = props; + const { + datasets, + setDatasets, + selectedDay, + selectedCompareDay, + showGeoCoPilot, + closeGeoCoPilot, + openGeoCoPilot, + setMap + } = props; const [projection, setProjection] = useState(projectionDefault); @@ -161,6 +176,7 @@ export function ExplorationMap(props: ExplorationMapProps) { + {comparing && ( // Compare map layers diff --git a/app/scripts/components/exploration/index.tsx b/app/scripts/components/exploration/index.tsx index d50b55512..503522370 100644 --- a/app/scripts/components/exploration/index.tsx +++ b/app/scripts/components/exploration/index.tsx @@ -1,8 +1,11 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState, useRef } from 'react'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import styled from 'styled-components'; import { themeVal } from '@devseed-ui/theme-provider'; +import { Map as MapboxMap } from 'mapbox-gl'; +import { MapRef } from 'react-map-gl'; + import { useAtom, useSetAtom } from 'jotai'; import Timeline from './components/timeline/timeline'; import { ExplorationMap } from './components/map'; @@ -11,6 +14,9 @@ import { useAnalysisController } from './hooks/use-analysis-data-request'; import { TimelineDataset } from './types.d.ts'; import { selectedCompareDateAtom, selectedDateAtom } from './atoms/dates'; import { CLEAR_LOCATION, urlAtom } from '$utils/params-location-atom/url'; +import { GeoCoPilot } from './components/geo-copilot/geo-copilot'; + +import useMaps from '$components/common/map/hooks/use-maps'; const Container = styled.div` display: flex; @@ -72,6 +78,12 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { !datasets.length ); + const [showGeoCoPilot, setShowGeoCoPilot] = useState( + false + ); + + const [map, setMap] = useState(); + // @TECH-DEBT: panelHeight needs to be passed to work around Safari CSS const [panelHeight, setPanelHeight] = useState(0); @@ -89,6 +101,33 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { }; }, [resetAnalysisController, setUrl]); + const mapRef = useRef(null); + const geoCoPilotRef = useRef(null); + + const expandGeoCoPilotPanel = () => { + const mapPanel = mapRef.current; + const geoCoPilotPanel = geoCoPilotRef.current; + if (mapPanel || geoCoPilotPanel) { + // panel.expand(50); + // resize panel from 0 to 50 + mapPanel.resize(75); + geoCoPilotPanel.resize(25); + setShowGeoCoPilot(true); + } + }; + + const closeGeoCoPilotPanel = () => { + const mapPanel = mapRef.current; + const geoCoPilotPanel = geoCoPilotRef.current; + if (mapPanel || geoCoPilotPanel) { + // panel.expand(50); + // resize panel from 0 to 50 + mapPanel.resize(100); + geoCoPilotPanel.resize(0); + setShowGeoCoPilot(false); + } + }; + return ( @@ -99,12 +138,48 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { setPanelHeight(size); }} > - + + { + setPanelHeight(size); + }} + className='panel panel-map' + ref={mapRef} + > + + + + + + From fa579e713ea92258e789dbdd84af024a0f491c64 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Tue, 24 Sep 2024 14:18:31 -0500 Subject: [PATCH 04/40] Handle case when server responds with bad content. --- .../geo-copilot/geo-copilot-interaction.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index cdcad14e7..0a6f26ce4 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -45,7 +45,20 @@ export async function askGeoCoPilot( } ).then((answer) => { setSystemResponse(JSON.parse(answer.data.answer), content); + }).catch((e) => { + console.log(e); + setSystemResponse({ + "dataset_ids": [], + "summary": "An unidentified error occured. Please try again later.", + "date_range": {'start_date': '', 'end_date': ''}, + "bbox":{}, + "action": "error", + "explanation": + { + "validation": "", + "verification":[] + }, + "query": question + }, content); }); }; - - From 3ce1fed17ba1bdf7c556ebbcc4719750ac503cf3 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Tue, 24 Sep 2024 14:18:46 -0500 Subject: [PATCH 05/40] Add copy text functionality. --- .../components/geo-copilot/geo-copilot-system-dialog.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx index 1747b8ce0..e0a711a9c 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -72,6 +72,10 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat setShowDetails(!showDetails); } + const copyURL = () => { + navigator.clipboard.writeText(document.URL); + } + return (
{summary}
@@ -89,7 +93,7 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat
{/*Summary*/} From 0bb87278b535e465200655c4f3d60e8e50356934 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Tue, 24 Sep 2024 14:20:17 -0500 Subject: [PATCH 06/40] Update interaction with UI. --- .../components/geo-copilot/geo-copilot.tsx | 147 ++++++++++++++---- 1 file changed, 115 insertions(+), 32 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index 33f058523..2fa133b57 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -9,7 +9,13 @@ import { askGeoCoPilot } from './geo-copilot-interaction'; import { reconcileDatasets } from '$components/exploration/data-utils-no-faux-module'; import { TimelineDataset } from '$components/exploration/types.d.ts'; import { datasetLayers} from '$components/exploration/data-utils'; -import useMaps from '$components/common/map/hooks/use-maps'; +import { aoiDeleteAllAtom, selectedForEditingAtom } from '$components/common/map/controls/aoi/atoms'; +import { useAnalysisController } from '$components/exploration/hooks/use-analysis-data-request'; +import { makeFeatureCollection } from '$components/common/aoi/utils'; +import { STATIC_MODE } from '$components/common/map/controls/aoi/index'; +import useAois from '$components/common/map/controls/hooks/use-aois'; + +import { useAtom, useSetAtom } from 'jotai'; import { getZoomFromBbox } from '$components/common/map/utils'; import bbox from '@turf/bbox'; @@ -28,6 +34,11 @@ import { } from '@devseed-ui/form'; import styled from 'styled-components'; +import { TemporalExtent } from '../timeline/timeline-utils'; + +import { + selectedIntervalAtom +} from '$components/exploration/atoms/dates'; interface GeoCoPilotModalProps { show: boolean; @@ -39,6 +50,7 @@ interface GeoCoPilotModalProps { selectedCompareDay: Date | null; setSelectedCompareDay: (d: Date | null) => void; map: any; + setStartEndDates: (startEndDates: TemporalExtent) => void; } const GeoCoPilotWrapper = styled.div` @@ -70,6 +82,7 @@ const GeoCoPilotQueryWrapper = styled.div` const GeoCoPilotQuery = styled(FormInput)` width: 100%; + padding-right: 35px; &:focus-within { border-radius: ${themeVal('shape.rounded')}; outline-width: 0.25rem; @@ -120,7 +133,8 @@ export function GeoCoPilotComponent({ setSelectedDay, selectedCompareDay, setSelectedCompareDay, - map + map, + setStartEndDates }: { close: () => void; show: boolean; @@ -131,6 +145,7 @@ export function GeoCoPilotComponent({ selectedCompareDay: Date | null; setSelectedCompareDay: (d: Date | null) => void; map: any; + setStartEndDates: (startEndDates: TemporalExtent) => void; }) { const defaultSystemComment = { summary: "Welcome to Geo Co-Pilot! I'm here to assist you with identifying datasets with location and date information. Whether you're analyzing time-sensitive trends or working with geospatial data, I've got you covered. Let me know how I can assist you today!", @@ -147,54 +162,99 @@ export function GeoCoPilotComponent({ const [query, setQuery] = useState(''); const phantomElementRef = useRef(null); const [loading, setLoading] = useState(false); + const [history, setHistory] = useState([]); + + const [selectedInterval, setSelectedInterval] = useAtom(selectedIntervalAtom); + + const { onUpdate } = useAois(); + const { runAnalysis } = useAnalysisController(); const scrollToBottom = () => { const phantomElement = phantomElementRef.current; phantomElement.scrollIntoView({ behavior: "smooth" }); }; + const loadInMap = (answer: any) => { + const geojson = JSON.parse(JSON.stringify(answer.bbox).replace('coordinates:', 'coordinates')); + const bounds = bbox(geojson); + const center = centroid(geojson as AllGeoJSON).geometry.coordinates; + + map.flyTo({ + center, + zoom: getZoomFromBbox(bounds) + }); + return geojson; + }; + useEffect(() => { scrollToBottom(); }, [loading]); - // hook to setup chat UI // hook to process and start load/process/analyze data - // change behavior of bounding box zoom // add upvote/downvote/share link, approaches - // add reset conversation option // add localstorage option for conversation // add verification links to the user dialog const addSystemResponse = (answer: any, content: any) => { answer['contentType'] = 'system'; const action = answer['action']; + const startDate = new Date(answer['date_range']['start_date']); + const endDate = new Date(answer['date_range']['end_date']); + const newDatasetIds = answer['dataset_ids'].reduce((layerIds, collectionId) => { + const foundDataset = datasetLayers.find((dataset) => dataset.stacCol == collectionId); + if (!!foundDataset) { + layerIds.push(foundDataset.id) + } + return layerIds; + }, []); + const newDatasets = reconcileDatasets(newDatasetIds, datasetLayers, datasets); + const mbDraw = map?._drawControl; - switch(action) { - case 'load': { - const newDatasets = reconcileDatasets(answer['dataset_ids'], datasetLayers, datasets); - setSelectedDay(new Date(answer['date_range']['start_date'])); - setDatasets(newDatasets); - const geojson = JSON.parse(JSON.stringify(answer.bbox).replace('coordinates:', 'coordinates')) - // const {simplifiedFeatures} = getAoiAppropriateFeatures(geojson); - // debugger; - const bounds = bbox(geojson); - const center = centroid(geojson as AllGeoJSON).geometry.coordinates; - map.flyTo({ - center, - zoom: getZoomFromBbox(bounds) - }); - break; - - } - case 'compare': { - const newDatasets = reconcileDatasets(answer['dataset_ids'], datasetLayers, datasets); - setSelectedCompareDay(new Date(answer['date_range']['end_date'])); - setDatasets(newDatasets); - break; - } - case 'analysis': - console.log('analysis'); - default: - console.log(action, answer); + mbDraw.deleteAll(); + + setDatasets(newDatasets); + try { + switch(action) { + case 'load': { + loadInMap(answer); + setSelectedCompareDay(null); + setSelectedDay(endDate); + break; + } + case 'compare': { + loadInMap(answer); + setSelectedDay(startDate); + setSelectedCompareDay(endDate); + break; + } + case 'statistics': { + const geojson = loadInMap(answer); + const updatedGeojson = makeFeatureCollection( + geojson.features.map((f, i) => ({ id: `${new Date().getTime().toString().slice(-4)}${i}`, ...f })) + ) + if (!mbDraw) return; + + setStartEndDates([startDate, endDate]); + + setSelectedInterval({ + start: startDate, end: endDate + }); + + onUpdate(updatedGeojson); + + const pids = mbDraw.add(geojson); + + mbDraw.changeMode(STATIC_MODE, { + featureIds: pids + }); + runAnalysis(newDatasets); + break; + } + default: + console.log(action, answer); + } + } catch (error) { + console.log('Error processing', error); } + content = [...content, answer] setConversation(content); setLoading(false); @@ -207,10 +267,33 @@ export function GeoCoPilotComponent({ query: query, contentType: 'user' } + const length = conversation.length; + // merge user and system in one payload rather than multiple elements + let chatHistory = conversation.reduce((history, innerContent, index) => { + let identifier = innerContent.contentType; + let chatElement = {}; + if(identifier == 'user' && index != (length - 1)) { + chatElement = { inputs: {question: innerContent.query} }; + history.push(chatElement); + } + else { + const innerLength = history.length - 1; + if (!!innerContent.action) { + chatElement = { outputs: {answer: innerContent.summary} }; + } + else { + chatElement = { outputs: {answer: ''} }; + } + history[innerLength] = {...history[innerLength], ...chatElement}; + } + return history; + }, []); + const content = [...conversation, userContent]; setConversation(content); setQuery(''); setLoading(true); + askGeoCoPilot({question: query, chat_history: [], content: content}, addSystemResponse); }; From 4bf0fcb808cf019e0306f3bd598f9de6dbf81e03 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Tue, 24 Sep 2024 14:20:36 -0500 Subject: [PATCH 07/40] Handle cases of analysis/statistics. --- .../components/timeline/timeline.tsx | 22 +++++++++++++------ app/scripts/components/exploration/index.tsx | 7 ++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/app/scripts/components/exploration/components/timeline/timeline.tsx b/app/scripts/components/exploration/components/timeline/timeline.tsx index 3680c07af..630af5ebd 100644 --- a/app/scripts/components/exploration/components/timeline/timeline.tsx +++ b/app/scripts/components/exploration/components/timeline/timeline.tsx @@ -29,7 +29,7 @@ import styled from 'styled-components'; import { DatasetList } from '../datasets/dataset-list'; -import { applyTransform, getLabelFormat, getTemporalExtent, isEqualTransform, rescaleX } from './timeline-utils'; +import { applyTransform, getLabelFormat, getTemporalExtent, isEqualTransform, rescaleX, TemporalExtent } from './timeline-utils'; import { TimelineControls, getInitialScale, @@ -172,6 +172,7 @@ interface TimelineProps { setSelectedCompareDay: (d: Date | null) => void; onDatasetAddClick: () => void; panelHeight: number; + startEndDates: TemporalExtent; } const getIntervalFromDate = (selectedDay: Date, dataDomain: [Date, Date]) => { @@ -202,7 +203,8 @@ export default function Timeline(props: TimelineProps) { selectedCompareDay, setSelectedCompareDay, onDatasetAddClick, - panelHeight + panelHeight, + startEndDates } = props; // Refs for non react based interactions. @@ -626,11 +628,17 @@ export default function Timeline(props: TimelineProps) { const initialScale = useMemo(() => getInitialScale(width), [width]); const minMaxTemporalExtent = useMemo( - () => getTemporalExtent( - // Filter the datasets to only include those with status 'SUCCESS'. - datasets.filter((dataset): dataset is TimelineDatasetSuccess => dataset.status === DatasetStatus.SUCCESS) - ), - [datasets] + () => { + const temporalExtent = getTemporalExtent( + // Filter the datasets to only include those with status 'SUCCESS'. + datasets.filter((dataset): dataset is TimelineDatasetSuccess => dataset.status === DatasetStatus.SUCCESS) + ); + return startEndDates[0] ? + [((startEndDates[0] > temporalExtent[0]) ? startEndDates[0] : temporalExtent[0]), + ((startEndDates[1] > temporalExtent[1]) ? startEndDates[1] : temporalExtent[1])] : + temporalExtent + }, + [datasets, startEndDates] ); const lowestCommonTimeDensity = useMemo( diff --git a/app/scripts/components/exploration/index.tsx b/app/scripts/components/exploration/index.tsx index 503522370..61f5225d8 100644 --- a/app/scripts/components/exploration/index.tsx +++ b/app/scripts/components/exploration/index.tsx @@ -17,6 +17,7 @@ import { CLEAR_LOCATION, urlAtom } from '$utils/params-location-atom/url'; import { GeoCoPilot } from './components/geo-copilot/geo-copilot'; import useMaps from '$components/common/map/hooks/use-maps'; +import { TemporalExtent } from './components/timeline/timeline-utils'; const Container = styled.div` display: flex; @@ -78,6 +79,10 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { !datasets.length ); + const [startEndDates, setStartEndDates] = useState( + [undefined, undefined] + ); + const [showGeoCoPilot, setShowGeoCoPilot] = useState( false ); @@ -177,6 +182,7 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { selectedCompareDay={selectedCompareDay} setSelectedCompareDay={setSelectedCompareDay} map={map} + setStartEndDates={setStartEndDates} />
@@ -190,6 +196,7 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { selectedCompareDay={selectedCompareDay} setSelectedCompareDay={setSelectedCompareDay} onDatasetAddClick={openModal} + startEndDates={startEndDates} panelHeight={panelHeight} /> From b115defda9f190894ce934b5dd39b9d28d4d2921 Mon Sep 17 00:00:00 2001 From: Slesa Adhikari Date: Tue, 24 Sep 2024 16:21:54 -0500 Subject: [PATCH 08/40] Add relevant details to answer details with style --- .../geo-copilot/geo-copilot-interaction.ts | 15 +---- .../geo-copilot/geo-copilot-system-dialog.tsx | 60 +++++++++++++++---- .../components/geo-copilot/geo-copilot.tsx | 1 - 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index 0a6f26ce4..34838e602 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -1,17 +1,4 @@ -import axios, { AxiosRequestConfig } from 'axios'; -import { QueryClient } from '@tanstack/react-query'; -import { FeatureCollection, Polygon } from 'geojson'; -import { ConcurrencyManagerInstance } from '$components/exploration/concurrency'; -import { GeoCoPilotModalProps } from '$components/exploration/components/geo-copilot/geo-copilot-system-dialog'; -import { TimelineDataset, TimelineDatasetForUrl } from '$components/exploration/types.d.ts'; - -import { ExtendedError } from '$components/exploration//data-utils'; - -import { - fixAoiFcForStacSearch, - getFilterPayload -} from '$components/analysis/utils'; -import { json } from 'd3'; +import axios from 'axios'; interface GeoCoPilotInteractionQuery { question: string; diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx index e0a711a9c..1456bee54 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -7,8 +7,15 @@ import { CollecticonLink, CollecticonChevronUpTrailSmall, CollecticonChevronDownTrailSmall, + CollecticonCalendarRange, + CollecticonMarker, + CollecticonMap, + } from '@devseed-ui/collecticons'; +import centroid from '@turf/centroid'; +import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder'; + import styled from 'styled-components'; const DialogContent = styled.div` @@ -37,12 +44,26 @@ const ShowHideDetail = styled.div` const AnswerDetails = styled.div` font-size: 0.6rem; - padding: 1em; + padding: 2em; background: #f6f7f8; border-radius: 10px; +` + +const AnswerDetailsIcon = styled.div` + display: flex; + align-items: center; - pre { - text-wrap: pretty; + span { + margin-left: 4px; + } +` + +const AnswerDetailsItem = styled.div` + margin-bottom: 6px; + + p { + font-size: 0.7rem; + margin-left: 12px; } ` @@ -50,7 +71,7 @@ export interface GeoCoPilotModalProps { summary: string; dataset_ids: any; bbox: any; - dateRange: any; + date_range: any; date: Date; action: string; explanation: any; @@ -71,7 +92,11 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat const updateShowDetails = () => { setShowDetails(!showDetails); } - + console.log("bbox", bbox); + console.log("dateRange", dateRange); + console.log("date", date); + console.log("dataset_ids", dataset_ids); + const geojson = JSON.parse(JSON.stringify(bbox).replace('coordinates:', 'coordinates')); const copyURL = () => { navigator.clipboard.writeText(document.URL); } @@ -114,9 +139,24 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat { showDetails && -
-            {JSON.stringify(explanation?.verification)}
-          
+ + + Location + +

{centroid(geojson).geometry.coordinates.join(", ")}

+
+ + + Timeframe + +

{`${dateRange.start_date} > ${dateRange.end_date}`}

+
+ + + Map layers + +

{dataset_ids.join(", ")}

+
} @@ -124,12 +164,12 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat } export function GeoCoPilotSystemDialog(props: GeoCoPilotModalProps) { - const {summary, dataset_ids, bbox, dateRange, date, action, explanation, query} = props; + const {summary, dataset_ids, bbox, date_range, date, action, explanation, query} = props; return Date: Wed, 25 Sep 2024 09:35:40 -0500 Subject: [PATCH 09/40] Remove AOI after each interaction. --- .../exploration/components/geo-copilot/geo-copilot.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index 2fa133b57..3cda90ed8 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -162,9 +162,9 @@ export function GeoCoPilotComponent({ const [query, setQuery] = useState(''); const phantomElementRef = useRef(null); const [loading, setLoading] = useState(false); - const [history, setHistory] = useState([]); const [selectedInterval, setSelectedInterval] = useAtom(selectedIntervalAtom); + const aoiDeleteAll = useSetAtom(aoiDeleteAllAtom); const { onUpdate } = useAois(); const { runAnalysis } = useAnalysisController(); @@ -189,9 +189,7 @@ export function GeoCoPilotComponent({ useEffect(() => { scrollToBottom(); }, [loading]); - // hook to process and start load/process/analyze data - // add upvote/downvote/share link, approaches - // add localstorage option for conversation + // add upvote/downvote, approaches // add verification links to the user dialog const addSystemResponse = (answer: any, content: any) => { answer['contentType'] = 'system'; @@ -210,6 +208,7 @@ export function GeoCoPilotComponent({ mbDraw.deleteAll(); + aoiDeleteAll(); setDatasets(newDatasets); try { switch(action) { From e275f82be6453d746ed4f6a34a4921b0359d3c3d Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 25 Sep 2024 09:36:06 -0500 Subject: [PATCH 10/40] Remove console.log. --- .../components/geo-copilot/geo-copilot-interaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index 0a6f26ce4..ca849f2d6 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -46,7 +46,6 @@ export async function askGeoCoPilot( ).then((answer) => { setSystemResponse(JSON.parse(answer.data.answer), content); }).catch((e) => { - console.log(e); setSystemResponse({ "dataset_ids": [], "summary": "An unidentified error occured. Please try again later.", From c537bfd6633f9d09290149d93ea518144eb3662e Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 25 Sep 2024 15:38:01 -0500 Subject: [PATCH 11/40] Add extra lines for better readability. --- .../components/common/map/controls/aoi/custom-aoi-control.tsx | 1 + .../components/geo-copilot/geo-copilot-system-dialog.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/app/scripts/components/common/map/controls/aoi/custom-aoi-control.tsx b/app/scripts/components/common/map/controls/aoi/custom-aoi-control.tsx index c31ad4d54..1e9d80c21 100644 --- a/app/scripts/components/common/map/controls/aoi/custom-aoi-control.tsx +++ b/app/scripts/components/common/map/controls/aoi/custom-aoi-control.tsx @@ -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 && diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx index e0a711a9c..e11e86a8d 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -68,6 +68,7 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat query: string; }) { const [showDetails, setShowDetails] = useState(false); + const updateShowDetails = () => { setShowDetails(!showDetails); } From 695940d771c3c424d1b37255ee2a7216fdbeb7ab Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 25 Sep 2024 15:38:48 -0500 Subject: [PATCH 12/40] Reformat for better readability. --- .../components/geo-copilot/geo-copilot.tsx | 107 +++++++++++------- 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index 3cda90ed8..30031a846 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -1,44 +1,53 @@ import React, {useState, useRef, useEffect, CSSProperties} from 'react'; +import styled from 'styled-components'; import { themeVal, glsp } from '@devseed-ui/theme-provider'; -import { GeoCoPilotSystemDialog } from './geo-copilot-system-dialog'; -import { GeoCoPilotUserDialog } from './geo-copilot-user-dialog'; -import { askGeoCoPilot } from './geo-copilot-interaction'; - -import { reconcileDatasets } from '$components/exploration/data-utils-no-faux-module'; -import { TimelineDataset } from '$components/exploration/types.d.ts'; -import { datasetLayers} from '$components/exploration/data-utils'; -import { aoiDeleteAllAtom, selectedForEditingAtom } from '$components/common/map/controls/aoi/atoms'; -import { useAnalysisController } from '$components/exploration/hooks/use-analysis-data-request'; -import { makeFeatureCollection } from '$components/common/aoi/utils'; -import { STATIC_MODE } from '$components/common/map/controls/aoi/index'; -import useAois from '$components/common/map/controls/hooks/use-aois'; +import { Button } from '@devseed-ui/button'; +import { FormInput } from '@devseed-ui/form'; +import { + CollecticonChevronRightTrailSmall, + CollecticonArrowLoop, + CollecticonXmarkSmall +} from '@devseed-ui/collecticons'; import { useAtom, useSetAtom } from 'jotai'; -import { getZoomFromBbox } from '$components/common/map/utils'; + +import { + TimelineDataset, + TimeDensity, + TimelineDatasetSuccess, + DatasetStatus +} from '$components/exploration/types.d.ts'; import bbox from '@turf/bbox'; import centroid from '@turf/centroid'; import { AllGeoJSON } from '@turf/helpers'; +import { GeoCoPilotSystemDialog } from './geo-copilot-system-dialog'; +import { GeoCoPilotUserDialog } from './geo-copilot-user-dialog'; +import { askGeoCoPilot } from './geo-copilot-interaction'; -import PulseLoader from "react-spinners/PulseLoader"; - -import { CollecticonChevronRightTrailSmall, CollecticonArrowLoop, CollecticonXmarkSmall } from '@devseed-ui/collecticons'; - -import { Button } from '@devseed-ui/button'; +import { + getLowestCommonTimeDensity, + reconcileDatasets +} from '$components/exploration/data-utils-no-faux-module'; -import { - FormInput -} from '@devseed-ui/form'; +import { + aoiDeleteAllAtom, + aoisUpdateGeometryAtom +} from '$components/common/map/controls/aoi/atoms'; +import { selectedIntervalAtom } from '$components/exploration/atoms/dates'; -import styled from 'styled-components'; +import { datasetLayers} from '$components/exploration/data-utils'; +import { makeFeatureCollection } from '$components/common/aoi/utils'; +import { getZoomFromBbox } from '$components/common/map/utils'; import { TemporalExtent } from '../timeline/timeline-utils'; -import { - selectedIntervalAtom -} from '$components/exploration/atoms/dates'; +import { useAnalysisController } from '$components/exploration/hooks/use-analysis-data-request'; + +import { SIMPLE_SELECT } from '$components/common/map/controls/aoi/index'; +import useAois from '$components/common/map/controls/hooks/use-aois'; interface GeoCoPilotModalProps { show: boolean; @@ -51,6 +60,7 @@ interface GeoCoPilotModalProps { setSelectedCompareDay: (d: Date | null) => void; map: any; setStartEndDates: (startEndDates: TemporalExtent) => void; + setTimeDensity: (timeDensity: TimeDensity) => void; } const GeoCoPilotWrapper = styled.div` @@ -134,7 +144,8 @@ export function GeoCoPilotComponent({ selectedCompareDay, setSelectedCompareDay, map, - setStartEndDates + setStartEndDates, + setTimeDensity }: { close: () => void; show: boolean; @@ -146,6 +157,7 @@ export function GeoCoPilotComponent({ setSelectedCompareDay: (d: Date | null) => void; map: any; setStartEndDates: (startEndDates: TemporalExtent) => void; + setTimeDensity: (timeDensity: TimeDensity) => void; }) { const defaultSystemComment = { summary: "Welcome to Geo Co-Pilot! I'm here to assist you with identifying datasets with location and date information. Whether you're analyzing time-sensitive trends or working with geospatial data, I've got you covered. Let me know how I can assist you today!", @@ -160,14 +172,14 @@ export function GeoCoPilotComponent({ } const [conversation, setConversation] = useState([defaultSystemComment]); const [query, setQuery] = useState(''); - const phantomElementRef = useRef(null); + const phantomElementRef = useRef(null); const [loading, setLoading] = useState(false); const [selectedInterval, setSelectedInterval] = useAtom(selectedIntervalAtom); const aoiDeleteAll = useSetAtom(aoiDeleteAllAtom); const { onUpdate } = useAois(); - const { runAnalysis } = useAnalysisController(); + const { cancelAnalysis, runAnalysis } = useAnalysisController(); const scrollToBottom = () => { const phantomElement = phantomElementRef.current; @@ -186,13 +198,13 @@ export function GeoCoPilotComponent({ return geojson; }; + const aoisUpdateGeometry = useSetAtom(aoisUpdateGeometryAtom); + useEffect(() => { scrollToBottom(); }, [loading]); - // add upvote/downvote, approaches - // add verification links to the user dialog + const addSystemResponse = (answer: any, content: any) => { - answer['contentType'] = 'system'; const action = answer['action']; const startDate = new Date(answer['date_range']['start_date']); const endDate = new Date(answer['date_range']['end_date']); @@ -206,19 +218,26 @@ export function GeoCoPilotComponent({ const newDatasets = reconcileDatasets(newDatasetIds, datasetLayers, datasets); const mbDraw = map?._drawControl; - mbDraw.deleteAll(); + answer['contentType'] = 'system'; aoiDeleteAll(); setDatasets(newDatasets); try { switch(action) { case 'load': { + mbDraw.deleteAll(); + aoiDeleteAll(); + loadInMap(answer); setSelectedCompareDay(null); setSelectedDay(endDate); break; } case 'compare': { + mbDraw.deleteAll(); + aoiDeleteAll(); + cancelAnalysis(); + loadInMap(answer); setSelectedDay(startDate); setSelectedCompareDay(endDate); @@ -228,7 +247,12 @@ export function GeoCoPilotComponent({ const geojson = loadInMap(answer); const updatedGeojson = makeFeatureCollection( geojson.features.map((f, i) => ({ id: `${new Date().getTime().toString().slice(-4)}${i}`, ...f })) - ) + ); + + setSelectedCompareDay(null); + + aoisUpdateGeometry(updatedGeojson.features); + if (!mbDraw) return; setStartEndDates([startDate, endDate]); @@ -236,19 +260,26 @@ export function GeoCoPilotComponent({ setSelectedInterval({ start: startDate, end: endDate }); + + setTimeDensity( + getLowestCommonTimeDensity( + datasets.filter((dataset): + dataset is TimelineDatasetSuccess => dataset.status === DatasetStatus.SUCCESS) + ) + ); + + runAnalysis(newDatasetIds); onUpdate(updatedGeojson); - const pids = mbDraw.add(geojson); + const pids = mbDraw.add(updatedGeojson); - mbDraw.changeMode(STATIC_MODE, { + mbDraw.changeMode(SIMPLE_SELECT, { featureIds: pids }); - runAnalysis(newDatasets); + break; } - default: - console.log(action, answer); } } catch (error) { console.log('Error processing', error); From 6105284a0554fd130393d8bc1507e162d3e138f2 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 25 Sep 2024 15:39:25 -0500 Subject: [PATCH 13/40] Pass timeDensity for better analysis experience. --- .../exploration/components/timeline/timeline.tsx | 10 +++++++--- app/scripts/components/exploration/index.tsx | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/scripts/components/exploration/components/timeline/timeline.tsx b/app/scripts/components/exploration/components/timeline/timeline.tsx index 630af5ebd..dd5a15ca9 100644 --- a/app/scripts/components/exploration/components/timeline/timeline.tsx +++ b/app/scripts/components/exploration/components/timeline/timeline.tsx @@ -71,6 +71,8 @@ import { useAnalysisController } from '$components/exploration/hooks/use-analysi import useAois from '$components/common/map/controls/hooks/use-aois'; import Pluralize from '$utils/pluralize'; import { getLowestCommonTimeDensity } from '$components/exploration/data-utils-no-faux-module'; +import { TimeDensity } from '$context/layer-data'; +import { TimeDensity as TimeDensityType} from '$components/exploration/types.d.ts'; const TimelineWrapper = styled.div` position: relative; @@ -173,6 +175,7 @@ interface TimelineProps { onDatasetAddClick: () => void; panelHeight: number; startEndDates: TemporalExtent; + timeDensity: TimeDensityType | null; } const getIntervalFromDate = (selectedDay: Date, dataDomain: [Date, Date]) => { @@ -204,7 +207,8 @@ export default function Timeline(props: TimelineProps) { setSelectedCompareDay, onDatasetAddClick, panelHeight, - startEndDates + startEndDates, + timeDensity } = props; // Refs for non react based interactions. @@ -627,7 +631,7 @@ export default function Timeline(props: TimelineProps) { // Stub scale for when there is no layers const initialScale = useMemo(() => getInitialScale(width), [width]); - const minMaxTemporalExtent = useMemo( + const minMaxTemporalExtent = useMemo( () => { const temporalExtent = getTemporalExtent( // Filter the datasets to only include those with status 'SUCCESS'. @@ -714,7 +718,7 @@ export default function Timeline(props: TimelineProps) { width={width} onZoom={onControlsZoom} outOfViewHeads={outOfViewHeads} - timeDensity={lowestCommonTimeDensity} + timeDensity={timeDensity || lowestCommonTimeDensity} timelineLabelsFormat={timelineLabelFormat} /> diff --git a/app/scripts/components/exploration/index.tsx b/app/scripts/components/exploration/index.tsx index 61f5225d8..e5dfb1762 100644 --- a/app/scripts/components/exploration/index.tsx +++ b/app/scripts/components/exploration/index.tsx @@ -11,7 +11,7 @@ import Timeline from './components/timeline/timeline'; import { ExplorationMap } from './components/map'; import { DatasetSelectorModal } from './components/dataset-selector-modal'; import { useAnalysisController } from './hooks/use-analysis-data-request'; -import { TimelineDataset } from './types.d.ts'; +import { TimelineDataset, TimeDensity } from './types.d.ts'; import { selectedCompareDateAtom, selectedDateAtom } from './atoms/dates'; import { CLEAR_LOCATION, urlAtom } from '$utils/params-location-atom/url'; import { GeoCoPilot } from './components/geo-copilot/geo-copilot'; @@ -87,6 +87,8 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { false ); + const [timeDensity, setTimeDensity] = useState(null); + const [map, setMap] = useState(); // @TECH-DEBT: panelHeight needs to be passed to work around Safari CSS @@ -183,6 +185,7 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { setSelectedCompareDay={setSelectedCompareDay} map={map} setStartEndDates={setStartEndDates} + setTimeDensity={setTimeDensity} /> @@ -198,6 +201,7 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { onDatasetAddClick={openModal} startEndDates={startEndDates} panelHeight={panelHeight} + timeDensity={timeDensity} /> From 06aeebe974ba9118b1024dc3d7eb24788f1433c9 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 25 Sep 2024 15:41:53 -0500 Subject: [PATCH 14/40] Import pulse loader. --- .../exploration/components/geo-copilot/geo-copilot.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index 30031a846..7c46eef3b 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -11,6 +11,8 @@ import { CollecticonXmarkSmall } from '@devseed-ui/collecticons'; +import PulseLoader from "react-spinners/PulseLoader"; + import { useAtom, useSetAtom } from 'jotai'; import { From 16150a565b7760dc37862b72d28ac580c07ef8f4 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 25 Sep 2024 15:54:59 -0500 Subject: [PATCH 15/40] Use env variable. --- .../geo-copilot/geo-copilot-interaction.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index ca849f2d6..8fbee015d 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -1,17 +1,4 @@ import axios, { AxiosRequestConfig } from 'axios'; -import { QueryClient } from '@tanstack/react-query'; -import { FeatureCollection, Polygon } from 'geojson'; -import { ConcurrencyManagerInstance } from '$components/exploration/concurrency'; -import { GeoCoPilotModalProps } from '$components/exploration/components/geo-copilot/geo-copilot-system-dialog'; -import { TimelineDataset, TimelineDatasetForUrl } from '$components/exploration/types.d.ts'; - -import { ExtendedError } from '$components/exploration//data-utils'; - -import { - fixAoiFcForStacSearch, - getFilterPayload -} from '$components/analysis/utils'; -import { json } from 'd3'; interface GeoCoPilotInteractionQuery { question: string; @@ -19,7 +6,7 @@ interface GeoCoPilotInteractionQuery { content: any; } -const GEOCOPILOT_ENDPOINT = 'https://veda-search-poc.azurewebsites.net/score'; +const GEOCOPILOT_ENDPOINT = process.env.GEO_COPILOT_ENDPOINT; /** * Gets the asset urls for all datasets in the results of a STAC search given by From 27a1d0b918f416a8cfb60e91ace7d6e6a0ffb390 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 25 Sep 2024 15:56:07 -0500 Subject: [PATCH 16/40] Update .env. --- .env | 1 + 1 file changed, 1 insertion(+) diff --git a/.env b/.env index 65308c498..0f84c55fa 100644 --- a/.env +++ b/.env @@ -4,6 +4,7 @@ APP_CONTACT_EMAIL=email@example.org 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: From be2c199dec9d203e445fbb3595a5302d318de92a Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 25 Sep 2024 16:01:27 -0500 Subject: [PATCH 17/40] Remove quotes for testing. --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 0f84c55fa..8c239785a 100644 --- a/.env +++ b/.env @@ -4,7 +4,7 @@ APP_CONTACT_EMAIL=email@example.org 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'; +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: From b045e2067050db0c54776e0747a1ed984bd0b62a Mon Sep 17 00:00:00 2001 From: xhagrg Date: Thu, 26 Sep 2024 08:40:20 -0500 Subject: [PATCH 18/40] Remove unwanted ; --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 8c239785a..25f14439a 100644 --- a/.env +++ b/.env @@ -4,7 +4,7 @@ APP_CONTACT_EMAIL=email@example.org 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; +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: From d0f1345c3b8e11686a6587a8745f19985fd7dd74 Mon Sep 17 00:00:00 2001 From: Slesa Adhikari Date: Thu, 26 Sep 2024 16:16:42 -0500 Subject: [PATCH 19/40] Add reverse geocoding to get placename from bbox --- .../geo-copilot/geo-copilot-interaction.ts | 4 +++ .../geo-copilot/geo-copilot-system-dialog.tsx | 32 ++++++++++++++----- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index 8fbee015d..ad38eceef 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -48,3 +48,7 @@ export async function askGeoCoPilot( }, 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}`; diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx index 5425fa544..e6349cb00 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -1,4 +1,6 @@ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; +import axios from 'axios'; + import { Button } from '@devseed-ui/button'; import { @@ -14,9 +16,9 @@ import { } from '@devseed-ui/collecticons'; import centroid from '@turf/centroid'; -import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder'; import styled from 'styled-components'; +import { geolocationUrl } from './geo-copilot-interaction'; const DialogContent = styled.div` width: fit-content; @@ -89,15 +91,29 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat query: string; }) { 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)); + console.log(response.data.features[2].place_name) + setLocation(response.data.features[2].place_name); // assuming 'features' is the array in the API response + } catch (error) { + console.error("Reverse geocoding failed.", error); + } + }; + + if (!!Object.keys(bbox).length) { + const geojson = JSON.parse(JSON.stringify(bbox).replace('coordinates:', 'coordinates')); + const center = centroid(geojson).geometry.coordinates; + fetchGeolocation(center); + } + }, [bbox]) const updateShowDetails = () => { setShowDetails(!showDetails); } - console.log("bbox", bbox); - console.log("dateRange", dateRange); - console.log("date", date); - console.log("dataset_ids", dataset_ids); - const geojson = JSON.parse(JSON.stringify(bbox).replace('coordinates:', 'coordinates')); const copyURL = () => { navigator.clipboard.writeText(document.URL); } @@ -144,7 +160,7 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat Location -

{centroid(geojson).geometry.coordinates.join(", ")}

+

{location}

From 8afa736531e1b2976732e134e0e926c7e87d1221 Mon Sep 17 00:00:00 2001 From: Slesa Adhikari Date: Thu, 26 Sep 2024 16:47:48 -0500 Subject: [PATCH 20/40] WIP query highlighting --- .../geo-copilot/geo-copilot-user-dialog.tsx | 84 ++++++++++++++++++- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx index e1b306cf6..da38aceae 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx @@ -15,6 +15,30 @@ const DialogContent = styled.div` display: flex; `; +const Query = styled.span` + background-color: #d1e7ff; + padding: 2px 6px; + border-radius: 5px; + margin: 0 3px; + position: relative; + cursor: pointer; + + &:hover::after { + content: attr(data-tooltip); + position: absolute; + background-color: #333; + color: #fff; + padding: 5px; + border-radius: 5px; + top: 120%; + left: 0; + white-space: nowrap; + z-index: 100; + opacity: 0.9; + font-size: 12px; + } +`; + interface GeoCoPilotModalProps { explanations: any; query: string; @@ -24,10 +48,64 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { explanations: any; query: string; }) { + let explanationss = [ + { + "query_part": "precipitation", + "matchup": "lis-global-da-totalprecip" + }, + { + "query_part": "in 2022", + "matchup": "2022-01-01T00:00:00 to 2022-12-31T23:59:59" + }, + { + "query_part": "Atlanta", + "matchup": "[-85.06468963623047, 34.112266540527344, -85.06468963623047, 33.41950225830078, -83.71636199951172, 33.41950225830078, -83.71636199951172, 34.112266540527344, -85.06468963623047, 34.112266540527344]" + }, + { "query_part": "show", "matchup": "load" } + ] + + // Function to dynamically split the query and insert Query parts + const renderHighlightedQuery = (query: string, explanations: any) => { + let remainingQuery = query.toLowerCase(); + let elements = []; + + explanations.forEach(({ query_part, matchup }) => { + const index = remainingQuery.indexOf(query_part.toLowerCase()); + if (index !== -1) { + // Before query_part text + if (index > 0) { + elements.push(remainingQuery.slice(0, index)); + } + // Highlighted query_part with a tooltip + elements.push( + + {query_part} + + ); + // Update remaining query + remainingQuery = remainingQuery.slice(index + query_part.length); + } + }); + + // Add remaining text after the last match + if (remainingQuery) { + elements.push(remainingQuery); + } + + return elements; + }; + return ( - -
{query}
-
+ <> + { + (query.length) && + + { explanationss.length ? + renderHighlightedQuery(query, explanationss) : query + } + + } + ) } From 349707a1693e67451ef10ebbb1ec4a0c6a9ef2cf Mon Sep 17 00:00:00 2001 From: xhagrg Date: Fri, 27 Sep 2024 09:30:45 -0500 Subject: [PATCH 21/40] Remove unwanted import. --- .../exploration/components/geo-copilot/geo-copilot-control.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx index dfc862e3f..cb6eaa178 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx @@ -3,7 +3,6 @@ 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'; -import { sequentialColorMaps } from '../datasets/colorMaps'; interface GeoCoPilotControlProps { showGeoCoPilot: () => void; From cace7a5a344ae91ef5b736bbb1b0399e5e2ade78 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Fri, 27 Sep 2024 09:31:35 -0500 Subject: [PATCH 22/40] Remove unwanted import and handle cases when data is not present. --- .../geo-copilot/geo-copilot-interaction.ts | 37 ++++++++++++------- .../components/timeline/timeline.tsx | 4 +- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index 8fbee015d..7e4ec7ffa 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -8,6 +8,20 @@ interface GeoCoPilotInteractionQuery { const GEOCOPILOT_ENDPOINT = process.env.GEO_COPILOT_ENDPOINT; +const ERROR_RESPONSE = { + "dataset_ids": [], + "summary": "An unidentified error occured. Please try again later.", + "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. @@ -24,7 +38,14 @@ export async function askGeoCoPilot( }: GeoCoPilotInteractionQuery, setSystemResponse: (answer: any, content: any) => void ){ - const queryResponse = await axios.post( + ERROR_RESPONSE['query'] = question + + if (!GEOCOPILOT_ENDPOINT) { + setSystemResponse(ERROR_RESPONSE, content); + return; + } + + await axios.post( GEOCOPILOT_ENDPOINT, { 'question': question, @@ -33,18 +54,6 @@ export async function askGeoCoPilot( ).then((answer) => { setSystemResponse(JSON.parse(answer.data.answer), content); }).catch((e) => { - setSystemResponse({ - "dataset_ids": [], - "summary": "An unidentified error occured. Please try again later.", - "date_range": {'start_date': '', 'end_date': ''}, - "bbox":{}, - "action": "error", - "explanation": - { - "validation": "", - "verification":[] - }, - "query": question - }, content); + setSystemResponse(ERROR_RESPONSE, content); }); }; diff --git a/app/scripts/components/exploration/components/timeline/timeline.tsx b/app/scripts/components/exploration/components/timeline/timeline.tsx index dd5a15ca9..709fb5714 100644 --- a/app/scripts/components/exploration/components/timeline/timeline.tsx +++ b/app/scripts/components/exploration/components/timeline/timeline.tsx @@ -71,7 +71,6 @@ import { useAnalysisController } from '$components/exploration/hooks/use-analysi import useAois from '$components/common/map/controls/hooks/use-aois'; import Pluralize from '$utils/pluralize'; import { getLowestCommonTimeDensity } from '$components/exploration/data-utils-no-faux-module'; -import { TimeDensity } from '$context/layer-data'; import { TimeDensity as TimeDensityType} from '$components/exploration/types.d.ts'; const TimelineWrapper = styled.div` @@ -637,6 +636,9 @@ export default function Timeline(props: TimelineProps) { // Filter the datasets to only include those with status 'SUCCESS'. datasets.filter((dataset): dataset is TimelineDatasetSuccess => dataset.status === DatasetStatus.SUCCESS) ); + if (!temporalExtent[0] || !temporalExtent[1] || !startEndDates[0] || !startEndDates[1]) + return [undefined, undefined]; + return startEndDates[0] ? [((startEndDates[0] > temporalExtent[0]) ? startEndDates[0] : temporalExtent[0]), ((startEndDates[1] > temporalExtent[1]) ? startEndDates[1] : temporalExtent[1])] : From 0942a38e918e6743e3181bdabc88d5842515f86f Mon Sep 17 00:00:00 2001 From: xhagrg Date: Fri, 27 Sep 2024 09:32:33 -0500 Subject: [PATCH 23/40] Fix issues with analysis date range. --- .../components/geo-copilot/geo-copilot.tsx | 64 +++++++++++++------ 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index 7c46eef3b..e6fc1648c 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -13,7 +13,7 @@ import { import PulseLoader from "react-spinners/PulseLoader"; -import { useAtom, useSetAtom } from 'jotai'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { TimelineDataset, @@ -51,6 +51,11 @@ import { useAnalysisController } from '$components/exploration/hooks/use-analysi import { SIMPLE_SELECT } from '$components/common/map/controls/aoi/index'; import useAois from '$components/common/map/controls/hooks/use-aois'; +import { RIGHT_AXIS_SPACE, HEADER_COLUMN_WIDTH } from '$components/exploration/constants'; +import { timelineWidthAtom } from '$components/exploration/atoms/timeline'; +import { useScales } from '$components/exploration/hooks/scales-hooks'; +import { useOnTOIZoom } from '$components/exploration/hooks/use-toi-zoom'; + interface GeoCoPilotModalProps { show: boolean; close: () => void; @@ -185,6 +190,7 @@ export function GeoCoPilotComponent({ const scrollToBottom = () => { const phantomElement = phantomElementRef.current; + if (!phantomElement) return; phantomElement.scrollIntoView({ behavior: "smooth" }); }; @@ -201,11 +207,31 @@ export function GeoCoPilotComponent({ }; const aoisUpdateGeometry = useSetAtom(aoisUpdateGeometryAtom); - + + const timelineWidth = useAtomValue(timelineWidthAtom); + const { main } = useScales(); + const { onTOIZoom } = useOnTOIZoom(); + + const interval = useAtomValue(selectedIntervalAtom); + useEffect(() => { scrollToBottom(); }, [loading]); + useEffect(() => { + // Fit TOI only after datasets are available + // way to do this is by using useeffect for datasets and aoi atom then checking for missing values. + if(!main || !timelineWidth || datasets.length == 0 || !interval?.end) + return; + + const widthToFit = (timelineWidth - RIGHT_AXIS_SPACE - HEADER_COLUMN_WIDTH) * 0.9; + const startPoint = 0; + const new_k = widthToFit/(main(interval.end) - main(interval.start)); + const new_x = startPoint - new_k * main(interval.start); + + onTOIZoom(new_x, new_k); + }, [datasets, interval]); + const addSystemResponse = (answer: any, content: any) => { const action = answer['action']; const startDate = new Date(answer['date_range']['start_date']); @@ -247,32 +273,24 @@ export function GeoCoPilotComponent({ } case 'statistics': { const geojson = loadInMap(answer); + const updatedGeojson = makeFeatureCollection( - geojson.features.map((f, i) => ({ id: `${new Date().getTime().toString().slice(-4)}${i}`, ...f })) + geojson.features.map((f, i) => ({ + id: `${new Date().getTime().toString().slice(-4)}${i}`, ...f + })) ); setSelectedCompareDay(null); - aoisUpdateGeometry(updatedGeojson.features); - - if (!mbDraw) return; - - setStartEndDates([startDate, endDate]); - setSelectedInterval({ start: startDate, end: endDate }); - setTimeDensity( - getLowestCommonTimeDensity( - datasets.filter((dataset): - dataset is TimelineDatasetSuccess => dataset.status === DatasetStatus.SUCCESS) - ) - ); - - runAnalysis(newDatasetIds); - onUpdate(updatedGeojson); + + aoisUpdateGeometry(updatedGeojson.features); + + setStartEndDates([startDate, endDate]); const pids = mbDraw.add(updatedGeojson); @@ -280,6 +298,14 @@ export function GeoCoPilotComponent({ featureIds: pids }); + runAnalysis(newDatasetIds); + + setTimeDensity( + getLowestCommonTimeDensity( + datasets.filter((dataset): + dataset is TimelineDatasetSuccess => dataset.status === DatasetStatus.SUCCESS) + ) + ); break; } } @@ -347,11 +373,13 @@ export function GeoCoPilotComponent({ if(convComponent.contentType == 'user') { return } else if (convComponent.contentType == 'system') { return } })} From 879d9cece359ee2227e6499f3046fefd5f205428 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Fri, 27 Sep 2024 09:32:47 -0500 Subject: [PATCH 24/40] Lazy fix for lint errors. --- app/scripts/components/exploration/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/components/exploration/index.tsx b/app/scripts/components/exploration/index.tsx index e5dfb1762..e796e5c0c 100644 --- a/app/scripts/components/exploration/index.tsx +++ b/app/scripts/components/exploration/index.tsx @@ -108,8 +108,8 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { }; }, [resetAnalysisController, setUrl]); - const mapRef = useRef(null); - const geoCoPilotRef = useRef(null); + const mapRef = useRef(null); + const geoCoPilotRef = useRef(null); const expandGeoCoPilotPanel = () => { const mapPanel = mapRef.current; From 4fd1d2cc766136ef8c1516717b7f6783619d7bd5 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Fri, 27 Sep 2024 10:32:34 -0500 Subject: [PATCH 25/40] Update message for error handling. --- .../components/geo-copilot/geo-copilot-interaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index 4113da85e..f1d244c72 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -10,7 +10,7 @@ const GEOCOPILOT_ENDPOINT = process.env.GEO_COPILOT_ENDPOINT; const ERROR_RESPONSE = { "dataset_ids": [], - "summary": "An unidentified error occured. Please try again later.", + "summary": "An unexpected error occurred with this request. Please ask another question.", "date_range": {'start_date': '', 'end_date': ''}, "bbox":{}, "action": "error", From 4cd4e9a8a4de6000649ac6515d7e189810d8022d Mon Sep 17 00:00:00 2001 From: xhagrg Date: Fri, 27 Sep 2024 11:19:08 -0500 Subject: [PATCH 26/40] Hide explanations for now. --- .../components/geo-copilot/geo-copilot-user-dialog.tsx | 4 +--- .../exploration/components/geo-copilot/geo-copilot.tsx | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx index da38aceae..7617020bc 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx @@ -100,9 +100,7 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { { (query.length) && - { explanationss.length ? - renderHighlightedQuery(query, explanationss) : query - } + { query } } diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index e6fc1648c..6495ac6d7 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -221,7 +221,7 @@ export function GeoCoPilotComponent({ useEffect(() => { // Fit TOI only after datasets are available // way to do this is by using useeffect for datasets and aoi atom then checking for missing values. - if(!main || !timelineWidth || datasets.length == 0 || !interval?.end) + if(!main || !timelineWidth || datasets?.length == 0 || !interval?.end) return; const widthToFit = (timelineWidth - RIGHT_AXIS_SPACE - HEADER_COLUMN_WIDTH) * 0.9; From d328aa5dd8d4967f92f180b172cfaf2c939715e8 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Mon, 30 Sep 2024 14:21:58 -0500 Subject: [PATCH 27/40] Use prod url (for now). --- .env | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.env b/.env index a05f1e7a7..606964a1b 100644 --- a/.env +++ b/.env @@ -2,8 +2,8 @@ APP_TITLE=VEDA UI APP_DESCRIPTION=User interface of module VEDA APP_CONTACT_EMAIL=email@example.org -API_RASTER_ENDPOINT='https://staging.openveda.cloud/api/raster' -API_STAC_ENDPOINT='https://staging.openveda.cloud/api/stac' +API_RASTER_ENDPOINT='https://openveda.cloud/api/raster' +API_STAC_ENDPOINT='https://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. @@ -15,4 +15,3 @@ GOOGLE_FORM = 'https://docs.google.com/forms/d/e/1FAIpQLSfGcd3FDsM3kQIOVKjzdPn4f FEATURE_NEW_EXPLORATION = 'TRUE' SHOW_CONFIGURABLE_COLOR_MAP = 'FALSE' - From 066fff78aafc9eeb992e27591cafdc04600d0962 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Mon, 30 Sep 2024 14:22:48 -0500 Subject: [PATCH 28/40] Update explanation after answer is retrieved. --- .../geo-copilot/geo-copilot-interaction.ts | 8 ++- .../geo-copilot/geo-copilot-user-dialog.tsx | 62 +++++++++++-------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index f1d244c72..2bfa6af5a 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -41,10 +41,10 @@ export async function askGeoCoPilot( ERROR_RESPONSE['query'] = question if (!GEOCOPILOT_ENDPOINT) { - setSystemResponse(ERROR_RESPONSE, content); + setSystemResponse(ERROR_RESPONSE, content); return; } - + await axios.post( GEOCOPILOT_ENDPOINT, { @@ -52,6 +52,8 @@ export async function askGeoCoPilot( '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((e) => { setSystemResponse(ERROR_RESPONSE, content); @@ -60,5 +62,5 @@ export async function askGeoCoPilot( // Returns the full geolocation url based on centroid (lat, lon) and mapboxaccesstoken -export const geolocationUrl = (centroid, mapboxAccessToken) => +export const geolocationUrl = (centroid, mapboxAccessToken) => `https://api.mapbox.com/geocoding/v5/mapbox.places/${centroid[0]},${centroid[1]}.json?access_token=${mapboxAccessToken}`; diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx index 7617020bc..5075ea45e 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx @@ -12,16 +12,38 @@ const DialogContent = styled.div` padding: 1em; border-radius: 10px; justify-content: flex-end; - display: flex; + display: inline-block; + `; const Query = styled.span` - background-color: #d1e7ff; - padding: 2px 6px; - border-radius: 5px; - margin: 0 3px; position: relative; cursor: pointer; + border-bottom: 1px dashed; + + &[data-explanation-index='0'] { + border-bottom-color: blue; + } + + &[data-explanation-index='1'] { + border-bottom-color: red; + } + + &[data-explanation-index='2'] { + border-bottom-color: black; + } + + &[data-explanation-index='3'] { + border-bottom-color: orange; + } + + &[data-explanation-index='4'] { + border-bottom-color: yellow; + } + + &[data-explanation-index='5'] { + border-bottom-color: pink; + } &:hover::after { content: attr(data-tooltip); @@ -30,8 +52,8 @@ const Query = styled.span` color: #fff; padding: 5px; border-radius: 5px; - top: 120%; - left: 0; + bottom: 100%; + right: 0; white-space: nowrap; z-index: 100; opacity: 0.9; @@ -48,28 +70,13 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { explanations: any; query: string; }) { - let explanationss = [ - { - "query_part": "precipitation", - "matchup": "lis-global-da-totalprecip" - }, - { - "query_part": "in 2022", - "matchup": "2022-01-01T00:00:00 to 2022-12-31T23:59:59" - }, - { - "query_part": "Atlanta", - "matchup": "[-85.06468963623047, 34.112266540527344, -85.06468963623047, 33.41950225830078, -83.71636199951172, 33.41950225830078, -83.71636199951172, 34.112266540527344, -85.06468963623047, 34.112266540527344]" - }, - { "query_part": "show", "matchup": "load" } - ] - + // Function to dynamically split the query and insert Query parts const renderHighlightedQuery = (query: string, explanations: any) => { let remainingQuery = query.toLowerCase(); - let elements = []; + let elements: (string | any)[] = []; - explanations.forEach(({ query_part, matchup }) => { + explanations.forEach(({ query_part, matchup }, internalIndex) => { const index = remainingQuery.indexOf(query_part.toLowerCase()); if (index !== -1) { // Before query_part text @@ -78,7 +85,7 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { } // Highlighted query_part with a tooltip elements.push( - + {query_part} ); @@ -100,7 +107,8 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { { (query.length) && - { query } + { explanations.length ? + renderHighlightedQuery(query, explanations) : query } } From d95651a9dddc82839f228222ca7a65a0b62d5489 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Mon, 30 Sep 2024 14:23:04 -0500 Subject: [PATCH 29/40] Update to use full vertical space. --- app/scripts/components/exploration/index.tsx | 73 ++++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/app/scripts/components/exploration/index.tsx b/app/scripts/components/exploration/index.tsx index e796e5c0c..4dfb19442 100644 --- a/app/scripts/components/exploration/index.tsx +++ b/app/scripts/components/exploration/index.tsx @@ -16,7 +16,6 @@ import { selectedCompareDateAtom, selectedDateAtom } from './atoms/dates'; import { CLEAR_LOCATION, urlAtom } from '$utils/params-location-atom/url'; import { GeoCoPilot } from './components/geo-copilot/geo-copilot'; -import useMaps from '$components/common/map/hooks/use-maps'; import { TemporalExtent } from './components/timeline/timeline-utils'; const Container = styled.div` @@ -38,6 +37,10 @@ const Container = styled.div` box-shadow: 0 -1px 0 0 ${themeVal('color.base-100')}; } + .panel-geo-copilot { + height: 90vh; + } + .resize-handle { flex: 0; position: relative; @@ -108,13 +111,13 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { }; }, [resetAnalysisController, setUrl]); - const mapRef = useRef(null); + const mapPanelRef = useRef(null); const geoCoPilotRef = useRef(null); const expandGeoCoPilotPanel = () => { - const mapPanel = mapRef.current; + const mapPanel = mapPanelRef.current; const geoCoPilotPanel = geoCoPilotRef.current; - if (mapPanel || geoCoPilotPanel) { + if (mapPanel && geoCoPilotPanel) { // panel.expand(50); // resize panel from 0 to 50 mapPanel.resize(75); @@ -124,11 +127,9 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { }; const closeGeoCoPilotPanel = () => { - const mapPanel = mapRef.current; + const mapPanel = mapPanelRef.current; const geoCoPilotPanel = geoCoPilotRef.current; - if (mapPanel || geoCoPilotPanel) { - // panel.expand(50); - // resize panel from 0 to 50 + if (mapPanel && geoCoPilotPanel) { mapPanel.resize(100); geoCoPilotPanel.resize(0); setShowGeoCoPilot(false); @@ -137,24 +138,22 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { return ( - + { setPanelHeight(size); }} + ref={mapPanelRef} > - - + { setPanelHeight(size); }} className='panel panel-map' - ref={mapRef} > - - + + - - - + From 5b38fd8b761af251332b40b7966cf9f248b099c3 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Mon, 30 Sep 2024 14:23:34 -0500 Subject: [PATCH 30/40] Fix linting errors. --- .../geo-copilot/geo-copilot-system-dialog.tsx | 30 +++--- .../components/geo-copilot/geo-copilot.tsx | 93 +++++++++---------- .../exploration/components/map/index.tsx | 14 +-- .../components/timeline/timeline.tsx | 8 +- 4 files changed, 68 insertions(+), 77 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx index e6349cb00..f983abe0a 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -3,11 +3,11 @@ import axios from 'axios'; import { Button } from '@devseed-ui/button'; -import { - CollecticonHandThumbsUp, - CollecticonHandThumbsDown, - CollecticonLink, - CollecticonChevronUpTrailSmall, +import { + CollecticonHandThumbsUp, + CollecticonHandThumbsDown, + CollecticonLink, + CollecticonChevronUpTrailSmall, CollecticonChevronDownTrailSmall, CollecticonCalendarRange, CollecticonMarker, @@ -103,8 +103,8 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat console.error("Reverse geocoding failed.", error); } }; - - if (!!Object.keys(bbox).length) { + + if (Object.keys(bbox).length > 0) { const geojson = JSON.parse(JSON.stringify(bbox).replace('coordinates:', 'coordinates')); const center = centroid(geojson).geometry.coordinates; fetchGeolocation(center); @@ -120,7 +120,7 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat return ( -
{summary}
+
{summary}
{/*Content*/} {explanation &&
@@ -130,22 +130,22 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat -
| + | {/*Interaction*/}
{/*Summary*/} - + ) } diff --git a/app/scripts/components/exploration/components/map/index.tsx b/app/scripts/components/exploration/components/map/index.tsx index 0ebfce88d..df41fc138 100644 --- a/app/scripts/components/exploration/components/map/index.tsx +++ b/app/scripts/components/exploration/components/map/index.tsx @@ -41,13 +41,13 @@ interface ExplorationMapProps { } export function ExplorationMap(props: ExplorationMapProps) { - const { - datasets, - setDatasets, - selectedDay, + const { + datasets, + setDatasets, + selectedDay, selectedCompareDay, - showGeoCoPilot, - closeGeoCoPilot, + showGeoCoPilot, + closeGeoCoPilot, openGeoCoPilot, setMap } = props; @@ -176,7 +176,7 @@ export function ExplorationMap(props: ExplorationMapProps) { - + {comparing && ( // Compare map layers diff --git a/app/scripts/components/exploration/components/timeline/timeline.tsx b/app/scripts/components/exploration/components/timeline/timeline.tsx index 709fb5714..e1bf9e237 100644 --- a/app/scripts/components/exploration/components/timeline/timeline.tsx +++ b/app/scripts/components/exploration/components/timeline/timeline.tsx @@ -638,11 +638,11 @@ export default function Timeline(props: TimelineProps) { ); if (!temporalExtent[0] || !temporalExtent[1] || !startEndDates[0] || !startEndDates[1]) return [undefined, undefined]; - - return startEndDates[0] ? - [((startEndDates[0] > temporalExtent[0]) ? startEndDates[0] : temporalExtent[0]), + + return startEndDates[0] ? + [((startEndDates[0] > temporalExtent[0]) ? startEndDates[0] : temporalExtent[0]), ((startEndDates[1] > temporalExtent[1]) ? startEndDates[1] : temporalExtent[1])] : - temporalExtent + temporalExtent; }, [datasets, startEndDates] ); From 82f6522180e617d05c54eafe23301803a6756c01 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Mon, 30 Sep 2024 14:35:50 -0500 Subject: [PATCH 31/40] Update tooltip style. --- .../components/geo-copilot/geo-copilot-user-dialog.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx index 5075ea45e..d671daaed 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx @@ -13,7 +13,6 @@ const DialogContent = styled.div` border-radius: 10px; justify-content: flex-end; display: inline-block; - `; const Query = styled.span` @@ -58,6 +57,8 @@ const Query = styled.span` z-index: 100; opacity: 0.9; font-size: 12px; + width: 390px; + text-wrap: balance; } `; From a40ec8f622cea824e663e606ef1afb87e63c9a8c Mon Sep 17 00:00:00 2001 From: xhagrg Date: Tue, 1 Oct 2024 09:23:07 -0500 Subject: [PATCH 32/40] Revert back to staging. --- .env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env b/.env index 606964a1b..a8e639c46 100644 --- a/.env +++ b/.env @@ -2,8 +2,8 @@ APP_TITLE=VEDA UI APP_DESCRIPTION=User interface of module VEDA APP_CONTACT_EMAIL=email@example.org -API_RASTER_ENDPOINT='https://openveda.cloud/api/raster' -API_STAC_ENDPOINT='https://openveda.cloud/api/stac' +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. From 75a396fd3a95fc2e17f17e482a3529d94fbba01e Mon Sep 17 00:00:00 2001 From: xhagrg Date: Tue, 1 Oct 2024 14:04:52 -0500 Subject: [PATCH 33/40] Use unique dataset ids. --- .../components/geo-copilot/geo-copilot.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index 3c701292f..070c376f1 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -227,13 +227,18 @@ export function GeoCoPilotComponent({ const action = answer['action']; const startDate = new Date(answer['date_range']['start_date']); const endDate = new Date(answer['date_range']['end_date']); - const newDatasetIds = answer['dataset_ids'].reduce((layerIds, collectionId) => { + let newDatasetIds = answer['dataset_ids'].reduce((layerIds, collectionId) => { const foundDataset = datasetLayers.find((dataset) => dataset.stacCol == collectionId); if (foundDataset) { layerIds.push(foundDataset.id); } return layerIds; }, []); + + newDatasetIds = newDatasetIds.filter((datasetId, index, internalArray) => + internalArray.indexOf(datasetId) === index + ); + const newDatasets = reconcileDatasets(newDatasetIds, datasetLayers, datasets); const mbDraw = map?._drawControl; @@ -258,8 +263,8 @@ export function GeoCoPilotComponent({ cancelAnalysis(); loadInMap(answer); - setSelectedDay(startDate); - setSelectedCompareDay(endDate); + setSelectedDay(endDate); + setSelectedCompareDay(startDate); break; } case 'statistics': { @@ -381,7 +386,6 @@ export function GeoCoPilotComponent({ size={5} aria-label='Processing...' data-testid='loader' - ref={phantomElementRef} />
From 9170fb130acea5ff14bc56399e338620c7eb6518 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Tue, 1 Oct 2024 14:05:01 -0500 Subject: [PATCH 34/40] Do not use query params for now. --- .../exploration/hooks/use-stac-metadata-datasets.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/scripts/components/exploration/hooks/use-stac-metadata-datasets.ts b/app/scripts/components/exploration/hooks/use-stac-metadata-datasets.ts index 9ce184919..92119f46b 100644 --- a/app/scripts/components/exploration/hooks/use-stac-metadata-datasets.ts +++ b/app/scripts/components/exploration/hooks/use-stac-metadata-datasets.ts @@ -47,10 +47,11 @@ function reconcileQueryDataWithDataset( if (queryData.status === DatasetStatus.SUCCESS) { const domain = resolveLayerTemporalExtent(base.data.id, queryData.data); - const renderParams = resolveRenderParams( - base.data.sourceParams, - queryData.data.renders - ); + // Commenting this out because of issues with vector datasets. + // const renderParams = resolveRenderParams( + // base.data.sourceParams, + // queryData.data.renders + // ); base = { ...base, @@ -58,7 +59,7 @@ function reconcileQueryDataWithDataset( ...base.data, ...queryData.data, domain, - sourceParams: renderParams + // sourceParams: renderParams } }; } From 5e3742a3dd55bcf4222aac9d17219504cf64d6aa Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 2 Oct 2024 14:52:15 -0500 Subject: [PATCH 35/40] Remove console.log. --- .../components/geo-copilot/geo-copilot-system-dialog.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx index f983abe0a..ef1570a02 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -97,7 +97,6 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat const fetchGeolocation = async (center) => { try { const response = await axios.get(geolocationUrl(center, process.env.MAPBOX_TOKEN)); - console.log(response.data.features[2].place_name) setLocation(response.data.features[2].place_name); // assuming 'features' is the array in the API response } catch (error) { console.error("Reverse geocoding failed.", error); From 97e57a08ee92b4fa9e8ccde532798f7a04ec5e09 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 2 Oct 2024 14:52:38 -0500 Subject: [PATCH 36/40] Use solid lines to represent matches. --- .../geo-copilot/geo-copilot-user-dialog.tsx | 101 ++++++++++-------- 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx index d671daaed..406a2e91b 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx @@ -2,6 +2,8 @@ import React from 'react'; import styled from 'styled-components'; +import JsxParser from 'react-jsx-parser' + const DialogContent = styled.div` min-width: 25%; max-width: 75%; @@ -13,12 +15,32 @@ const DialogContent = styled.div` border-radius: 10px; justify-content: flex-end; display: inline-block; + position: relative; + + &.active::after { + content: attr(data-tooltip); + position: absolute; + background-color: #333; + color: #fff; + padding: 5px; + border-radius: 5px; + bottom: 100%; + right: 0; + white-space: nowrap; + z-index: 100; + opacity: 0.9; + font-size: 12px; + max-width: 150px; + text-wrap: balance; + } `; const Query = styled.span` position: relative; cursor: pointer; - border-bottom: 1px dashed; + display: inline-block; + padding-bottom: 1px; + border-bottom: 1px solid; &[data-explanation-index='0'] { border-bottom-color: blue; @@ -43,23 +65,6 @@ const Query = styled.span` &[data-explanation-index='5'] { border-bottom-color: pink; } - - &:hover::after { - content: attr(data-tooltip); - position: absolute; - background-color: #333; - color: #fff; - padding: 5px; - border-radius: 5px; - bottom: 100%; - right: 0; - white-space: nowrap; - z-index: 100; - opacity: 0.9; - font-size: 12px; - width: 390px; - text-wrap: balance; - } `; interface GeoCoPilotModalProps { @@ -71,45 +76,55 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { explanations: any; query: string; }) { - // Function to dynamically split the query and insert Query parts const renderHighlightedQuery = (query: string, explanations: any) => { - let remainingQuery = query.toLowerCase(); - let elements: (string | any)[] = []; + let elementsToRender: string[] = query.toLowerCase().split(' '); explanations.forEach(({ query_part, matchup }, internalIndex) => { - const index = remainingQuery.indexOf(query_part.toLowerCase()); - if (index !== -1) { - // Before query_part text - if (index > 0) { - elements.push(remainingQuery.slice(0, index)); - } - // Highlighted query_part with a tooltip - elements.push( - - {query_part} - - ); - // Update remaining query - remainingQuery = remainingQuery.slice(index + query_part.length); - } + const index = query.indexOf(query_part.toLowerCase()); + if (index < 0) return; + let splits = query_part.split(' '); + let lastWord = splits.at(-1) || ''; + let firstWord = splits[0] || ''; + let firstWordIndex = elementsToRender.indexOf(firstWord.toLowerCase()); + if (firstWordIndex < 0) return; + elementsToRender.splice(firstWordIndex, 0, ``); + let lastWordIndex = elementsToRender.indexOf(lastWord.toLowerCase()); + elementsToRender.splice(lastWordIndex + 1, 0, '') }); + return elementsToRender.join(' '); + }; - // Add remaining text after the last match - if (remainingQuery) { - elements.push(remainingQuery); + const handleEnter = (e) => { + if (e.target) { + const toolTipValue = e.target.attributes['data-tooltip']?.value; + if (toolTipValue) { + e.currentTarget.setAttribute('data-tooltip', toolTipValue); + e.currentTarget.classList.add('active'); + } } + } - return elements; - }; + const handleExit = (e) => { + if (e.currentTarget) { + e.currentTarget.setAttribute('data-tooltip', ''); + e.currentTarget.classList.remove('active'); + } + } return ( <> { (query.length) && - + { explanations.length ? - renderHighlightedQuery(query, explanations) : query } + : + query + } } From 8a7c96cb17fc6edfbb0d782d4d0cba3f23a665b3 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Wed, 2 Oct 2024 14:52:56 -0500 Subject: [PATCH 37/40] Add react-jsx-parser for dynamic rendering. --- package.json | 1 + yarn.lock | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f347cce4..34f5eb5ff 100644 --- a/package.json +++ b/package.json @@ -203,6 +203,7 @@ "react-gtm-module": "^2.0.11", "react-helmet": "^6.1.0", "react-indiana-drag-scroll": "^2.2.0", + "react-jsx-parser": "^2.1.0", "react-lazyload": "^3.2.0", "react-map-gl": "^7.1.5", "react-nl2br": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index 86e7c60eb..45897a35d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4522,7 +4522,14 @@ dependencies: "@types/react" "^17" -"@types/react@*", "@types/react@18.0.32", "@types/react@^17": +"@types/react-dom@^18.3.0": + version "18.3.0" + resolved "http://verdaccio.ds.io:4873/@types%2freact-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" + integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@18.0.32", "@types/react@^17", "@types/react@^18.3.3": version "18.0.32" resolved "http://verdaccio.ds.io:4873/@types%2freact/-/react-18.0.32.tgz#5e88b2af6833251d54ec7fe86d393224499f41d5" integrity sha512-gYGXdtPQ9Cj0w2Fwqg5/ak6BcK3Z15YgjSqtyDizWUfx7mQ8drs0NBUzRRsAdoFVTO8kJ8L2TL8Skm7OFPnLUw== @@ -4755,6 +4762,11 @@ acorn@^8.0.0, acorn@^8.5.0, acorn@^8.7.1: resolved "http://verdaccio.ds.io:4873/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +acorn@^8.12.1: + version "8.12.1" + resolved "http://verdaccio.ds.io:4873/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + adler-32@~1.3.0: version "1.3.1" resolved "http://verdaccio.ds.io:4873/adler-32/-/adler-32-1.3.1.tgz#1dbf0b36dda0012189a32b3679061932df1821e2" @@ -12618,6 +12630,17 @@ react-json-tree@^0.18.0: "@types/lodash" "^4.14.191" react-base16-styling "^0.9.1" +react-jsx-parser@^2.1.0: + version "2.1.0" + resolved "http://verdaccio.ds.io:4873/react-jsx-parser/-/react-jsx-parser-2.1.0.tgz#605960947100625f70afc73f4ab915d92130f157" + integrity sha512-cGp8ceqA6j7OylsqeK2kuCGRitHpWzximsxsQyqkQO+1fwyG82Mb1l2nx9ImrfdCO6GT2kgt7C6cX9vJ46B7ow== + dependencies: + acorn "^8.12.1" + acorn-jsx "^5.3.2" + optionalDependencies: + "@types/react" "^18.3.3" + "@types/react-dom" "^18.3.0" + react-lazyload@^3.2.0: version "3.2.0" resolved "http://verdaccio.ds.io:4873/react-lazyload/-/react-lazyload-3.2.0.tgz#497bd06a6dbd7015e3376e1137a67dc47d2dd021" From f430052aa649cb3e55d20a54366ade6cb16565d0 Mon Sep 17 00:00:00 2001 From: xhagrg Date: Mon, 7 Oct 2024 10:41:24 -0500 Subject: [PATCH 38/40] Fix linting issues. --- .../geo-copilot/geo-copilot-system-dialog.tsx | 25 ++++----- .../geo-copilot/geo-copilot-user-dialog.tsx | 29 +++++++---- .../components/geo-copilot/geo-copilot.tsx | 51 ++++++++++--------- app/scripts/components/exploration/index.tsx | 5 +- 4 files changed, 60 insertions(+), 50 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx index ef1570a02..8eb1e2a37 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -34,22 +34,22 @@ const DialogContent = styled.div` 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; @@ -58,7 +58,7 @@ const AnswerDetailsIcon = styled.div` span { margin-left: 4px; } -` +`; const AnswerDetailsItem = styled.div` margin-bottom: 6px; @@ -67,7 +67,7 @@ const AnswerDetailsItem = styled.div` font-size: 0.7rem; margin-left: 12px; } -` +`; export interface GeoCoPilotModalProps { summary: string; @@ -78,7 +78,7 @@ export interface GeoCoPilotModalProps { action: string; explanation: any; query: string; -} +}; export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dateRange, date, action, explanation, query}: { summary: string; @@ -108,14 +108,15 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat const center = centroid(geojson).geometry.coordinates; fetchGeolocation(center); } - }, [bbox]) + }, [bbox]); const updateShowDetails = () => { setShowDetails(!showDetails); - } + }; + const copyURL = () => { navigator.clipboard.writeText(document.URL); - } + }; return ( @@ -176,7 +177,7 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat } - ) + ); } export function GeoCoPilotSystemDialog(props: GeoCoPilotModalProps) { @@ -190,5 +191,5 @@ export function GeoCoPilotSystemDialog(props: GeoCoPilotModalProps) { action={action} explanation={explanation} query={query} - /> + />; } diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx index 406a2e91b..03f9b2896 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx @@ -83,18 +83,27 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { explanations.forEach(({ query_part, matchup }, internalIndex) => { const index = query.indexOf(query_part.toLowerCase()); if (index < 0) return; - let splits = query_part.split(' '); - let lastWord = splits.at(-1) || ''; - let firstWord = splits[0] || ''; - let firstWordIndex = elementsToRender.indexOf(firstWord.toLowerCase()); + const splits = query_part.split(' '); + const lastWord = splits.at(-1) || ''; + const firstWord = splits[0] || ''; + const firstWordIndex = elementsToRender.indexOf(firstWord.toLowerCase()); if (firstWordIndex < 0) return; - elementsToRender.splice(firstWordIndex, 0, ``); + elementsToRender.splice( + firstWordIndex, + 0, + `` + ); let lastWordIndex = elementsToRender.indexOf(lastWord.toLowerCase()); elementsToRender.splice(lastWordIndex + 1, 0, '') }); return elementsToRender.join(' '); }; + const resetToolTip = (e) => { + e.currentTarget.setAttribute('data-tooltip', ''); + e.currentTarget.classList.remove('active'); + }; + const handleEnter = (e) => { if (e.target) { const toolTipValue = e.target.attributes['data-tooltip']?.value; @@ -102,15 +111,15 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { e.currentTarget.setAttribute('data-tooltip', toolTipValue); e.currentTarget.classList.add('active'); } + else resetToolTip(e); } - } + }; const handleExit = (e) => { if (e.currentTarget) { - e.currentTarget.setAttribute('data-tooltip', ''); - e.currentTarget.classList.remove('active'); + resetToolTip(e); } - } + }; return ( <> @@ -136,5 +145,5 @@ export function GeoCoPilotUserDialog(props: GeoCoPilotModalProps) { return + />; } diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index 070c376f1..a4adc2b89 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -19,15 +19,15 @@ import bbox from '@turf/bbox'; import centroid from '@turf/centroid'; import { AllGeoJSON } from '@turf/helpers'; -import { GeoCoPilotSystemDialog } from './geo-copilot-system-dialog'; -import { GeoCoPilotUserDialog } from './geo-copilot-user-dialog'; -import { askGeoCoPilot } from './geo-copilot-interaction'; - import { datasetLayers} from '$components/exploration/data-utils'; import { makeFeatureCollection } from '$components/common/aoi/utils'; import { getZoomFromBbox } from '$components/common/map/utils'; import { TemporalExtent } from '../timeline/timeline-utils'; +import { GeoCoPilotSystemDialog } from './geo-copilot-system-dialog'; +import { GeoCoPilotUserDialog } from './geo-copilot-user-dialog'; +import { askGeoCoPilot } from './geo-copilot-interaction'; + import { TimelineDataset, TimeDensity, @@ -65,7 +65,7 @@ interface GeoCoPilotModalProps { map: any; setStartEndDates: (startEndDates: TemporalExtent) => void; setTimeDensity: (timeDensity: TimeDensity) => void; -} +}; const GeoCoPilotWrapper = styled.div` padding-bottom: ${glsp()}; @@ -73,7 +73,8 @@ const GeoCoPilotWrapper = styled.div` width: 100%; background: #f6f7f8; position: relative; -` +`; + const GeoCoPilotContent = styled.div` width: 100%; height: calc(100% - 80px); @@ -81,7 +82,8 @@ const GeoCoPilotContent = styled.div` flex-direction: column; font-size: 12px; display: flex; -` +`; + const GeoCoPilotQueryWrapper = styled.div` display: flex; overflow: hidden; @@ -92,7 +94,7 @@ const GeoCoPilotQueryWrapper = styled.div` > button { margin-left: -35px; } -` +`; const GeoCoPilotQuery = styled(FormInput)` width: 100%; @@ -103,7 +105,7 @@ const GeoCoPilotQuery = styled(FormInput)` outline-color: ${themeVal('color.primary-200a')}; outline-style: solid; } -` +`; const GeoCoPilotTitleWrapper = styled.div` background: white; @@ -112,24 +114,24 @@ const GeoCoPilotTitleWrapper = styled.div` box-shadow: 0 2px 4px #b1b1b1; margin-bottom: 3px; display: flex; -` +`; const GeoCoPilotTitle = styled.strong` color: #2276ad; width: 210px; margin: auto; -` +`; const RestartSession = styled(Button)` align-self: flex-end; background: #2276ad; margin: auto; color: white; -` +`; const CloseSession = styled(Button)` align-self: flex-end; -` +`; const override: CSSProperties = { display: "block", @@ -167,13 +169,14 @@ export function GeoCoPilotComponent({ explanation: null, query: '', contentType: 'system' - } + }; + const [conversation, setConversation] = useState([defaultSystemComment]); const [query, setQuery] = useState(''); const phantomElementRef = useRef(null); const [loading, setLoading] = useState(false); - const [selectedInterval, setSelectedInterval] = useAtom(selectedIntervalAtom); + const [_, setSelectedInterval] = useAtom(selectedIntervalAtom); const aoiDeleteAll = useSetAtom(aoiDeleteAllAtom); const { onUpdate } = useAois(); @@ -307,24 +310,24 @@ export function GeoCoPilotComponent({ } } catch (error) { console.log('Error processing', error); - } + }; - content = [...content, answer] + content = [...content, answer]; setConversation(content); setLoading(false); //close loading - } + }; const addNewResponse = () => { const userContent = { explanations: '', query: query, contentType: 'user' - } + }; const length = conversation.length; // merge user and system in one payload rather than multiple elements let chatHistory = conversation.reduce((history, innerContent, index) => { - let identifier = innerContent.contentType; + const identifier = innerContent.contentType; let chatElement = {}; if(identifier == 'user' && index != (length - 1)) { chatElement = { inputs: {question: innerContent.query} }; @@ -353,7 +356,7 @@ export function GeoCoPilotComponent({ const clearSession = () => { setConversation([defaultSystemComment]); - } + }; return ( @@ -370,13 +373,13 @@ export function GeoCoPilotComponent({ return + />; } else if (convComponent.contentType == 'system') { return + />; } })} - ) + ); } export function GeoCoPilot(props: GeoCoPilotModalProps) { diff --git a/app/scripts/components/exploration/index.tsx b/app/scripts/components/exploration/index.tsx index 4dfb19442..a01f6df95 100644 --- a/app/scripts/components/exploration/index.tsx +++ b/app/scripts/components/exploration/index.tsx @@ -13,10 +13,10 @@ import { DatasetSelectorModal } from './components/dataset-selector-modal'; import { useAnalysisController } from './hooks/use-analysis-data-request'; import { TimelineDataset, TimeDensity } from './types.d.ts'; import { selectedCompareDateAtom, selectedDateAtom } from './atoms/dates'; -import { CLEAR_LOCATION, urlAtom } from '$utils/params-location-atom/url'; import { GeoCoPilot } from './components/geo-copilot/geo-copilot'; import { TemporalExtent } from './components/timeline/timeline-utils'; +import { CLEAR_LOCATION, urlAtom } from '$utils/params-location-atom/url'; const Container = styled.div` display: flex; @@ -190,13 +190,10 @@ function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { ref={geoCoPilotRef} > Date: Mon, 7 Oct 2024 11:56:24 -0500 Subject: [PATCH 39/40] Fix linting errors. --- .../geo-copilot/geo-copilot-control.tsx | 3 +- .../geo-copilot/geo-copilot-interaction.ts | 8 +-- .../geo-copilot/geo-copilot-system-dialog.tsx | 41 +++++------- .../geo-copilot/geo-copilot-user-dialog.tsx | 25 ++++---- .../components/geo-copilot/geo-copilot.tsx | 63 ++++++++++++------- 5 files changed, 76 insertions(+), 64 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx index cb6eaa178..3828ee446 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-control.tsx @@ -25,12 +25,11 @@ export function GeoCoPilotComponent({onClick}: { export function GeoCoPilotControl(props: GeoCoPilotControlProps) { const {showGeoCoPilot, setMap} = props; - const disabled = false; // Show conversation modal const {main} = useMaps(); setMap(main); - useThemedControl(() => , { + useThemedControl(() => , { position: 'top-right' }); return null; diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index 2bfa6af5a..2b36a8420 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -20,7 +20,7 @@ const ERROR_RESPONSE = { "verification":[] }, "query": '' -} +}; /** * Gets the asset urls for all datasets in the results of a STAC search given by @@ -38,7 +38,7 @@ export async function askGeoCoPilot( }: GeoCoPilotInteractionQuery, setSystemResponse: (answer: any, content: any) => void ){ - ERROR_RESPONSE['query'] = question + ERROR_RESPONSE['query'] = question; if (!GEOCOPILOT_ENDPOINT) { setSystemResponse(ERROR_RESPONSE, content); @@ -55,9 +55,9 @@ export async function askGeoCoPilot( const extractedAnswer = JSON.parse(answer.data.answer); content[content.length - 1].explanations = extractedAnswer.explanation.verification; setSystemResponse(JSON.parse(answer.data.answer), content); - }).catch((e) => { + }).catch(() => { setSystemResponse(ERROR_RESPONSE, content); - }); + }) }; diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx index 8eb1e2a37..6a2c5848d 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -74,21 +74,15 @@ export interface GeoCoPilotModalProps { dataset_ids: any; bbox: any; date_range: any; - date: Date; - action: string; explanation: any; - query: string; -}; +} -export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dateRange, date, action, explanation, query}: { +export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dateRange, explanation}: { summary: string; dataset_ids: any; bbox: any; dateRange: any; - date: Date; - action: string; explanation: any; - query: string; }) { const [showDetails, setShowDetails] = useState(false); const [location, setLocation] = useState(""); @@ -124,37 +118,37 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat {/*Content*/} {explanation &&
- -
| {/*Interaction*/}
-
{/*Summary*/} -
} - { - showDetails && + {showDetails && @@ -181,15 +175,14 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat } export function GeoCoPilotSystemDialog(props: GeoCoPilotModalProps) { - const {summary, dataset_ids, bbox, date_range, date, action, explanation, query} = props; - return ; + /> + ); } diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx index 03f9b2896..9a1403691 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-user-dialog.tsx @@ -2,7 +2,7 @@ import React from 'react'; import styled from 'styled-components'; -import JsxParser from 'react-jsx-parser' +import JsxParser from 'react-jsx-parser'; const DialogContent = styled.div` min-width: 25%; @@ -78,7 +78,7 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { }) { // Function to dynamically split the query and insert Query parts const renderHighlightedQuery = (query: string, explanations: any) => { - let elementsToRender: string[] = query.toLowerCase().split(' '); + const elementsToRender: string[] = query.toLowerCase().split(' '); explanations.forEach(({ query_part, matchup }, internalIndex) => { const index = query.indexOf(query_part.toLowerCase()); @@ -93,8 +93,8 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { 0, `` ); - let lastWordIndex = elementsToRender.indexOf(lastWord.toLowerCase()); - elementsToRender.splice(lastWordIndex + 1, 0, '') + const lastWordIndex = elementsToRender.indexOf(lastWord.toLowerCase()); + elementsToRender.splice(lastWordIndex + 1, 0, '
'); }); return elementsToRender.join(' '); }; @@ -126,24 +126,25 @@ export function GeoCoPilotUserDialogComponent({explanations, query}: { { (query.length) && - { explanations.length ? + {explanations.length ? : - query - } + query} } - ) + ); } export function GeoCoPilotUserDialog(props: GeoCoPilotModalProps) { const {query, explanations} = props; - return ; + return ( + + ); } diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index a4adc2b89..55bfdcb3f 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -19,15 +19,16 @@ import bbox from '@turf/bbox'; import centroid from '@turf/centroid'; import { AllGeoJSON } from '@turf/helpers'; -import { datasetLayers} from '$components/exploration/data-utils'; -import { makeFeatureCollection } from '$components/common/aoi/utils'; -import { getZoomFromBbox } from '$components/common/map/utils'; import { TemporalExtent } from '../timeline/timeline-utils'; import { GeoCoPilotSystemDialog } from './geo-copilot-system-dialog'; import { GeoCoPilotUserDialog } from './geo-copilot-user-dialog'; import { askGeoCoPilot } from './geo-copilot-interaction'; +import { datasetLayers} from '$components/exploration/data-utils'; +import { makeFeatureCollection } from '$components/common/aoi/utils'; +import { getZoomFromBbox } from '$components/common/map/utils'; + import { TimelineDataset, TimeDensity, @@ -65,7 +66,7 @@ interface GeoCoPilotModalProps { map: any; setStartEndDates: (startEndDates: TemporalExtent) => void; setTimeDensity: (timeDensity: TimeDensity) => void; -}; +} const GeoCoPilotWrapper = styled.div` padding-bottom: ${glsp()}; @@ -176,7 +177,7 @@ export function GeoCoPilotComponent({ const phantomElementRef = useRef(null); const [loading, setLoading] = useState(false); - const [_, setSelectedInterval] = useAtom(selectedIntervalAtom); + const [selectedInterval, setSelectedInterval] = useAtom(selectedIntervalAtom); const aoiDeleteAll = useSetAtom(aoiDeleteAllAtom); const { onUpdate } = useAois(); @@ -310,7 +311,7 @@ export function GeoCoPilotComponent({ } } catch (error) { console.log('Error processing', error); - }; + } content = [...content, answer]; setConversation(content); @@ -326,7 +327,7 @@ export function GeoCoPilotComponent({ }; const length = conversation.length; // merge user and system in one payload rather than multiple elements - let chatHistory = conversation.reduce((history, innerContent, index) => { + const chatHistory = conversation.reduce((history, innerContent, index) => { const identifier = innerContent.contentType; let chatElement = {}; if(identifier == 'user' && index != (length - 1)) { @@ -335,7 +336,7 @@ export function GeoCoPilotComponent({ } else { const innerLength = history.length - 1; - if (!!innerContent.action) { + if (innerContent.action) { chatElement = { outputs: {answer: innerContent.summary} }; } else { @@ -362,24 +363,30 @@ export function GeoCoPilotComponent({ Geo Co-Pilot - - Restart Session + + Restart Session {conversation.map((convComponent, index) => { if(convComponent.contentType == 'user') { - return ; + return ( + + ); } else if (convComponent.contentType == 'system') { - return ; + return ( + + ); } })} -
+
+
- {setQuery(e.target.value)}} onKeyUp={(e) => e.code == 'Enter' ? addNewResponse() : ''}/> + { + setQuery(e.target.value) + } + } + onKeyUp={(e) => + e.code == 'Enter' ? addNewResponse() : '' + } + />
From ca6183e9ca516d30994a1e638e9f16152eeb125f Mon Sep 17 00:00:00 2001 From: xhagrg Date: Mon, 7 Oct 2024 12:02:27 -0500 Subject: [PATCH 40/40] Fix additional linting issues. --- .../geo-copilot/geo-copilot-interaction.ts | 4 +- .../geo-copilot/geo-copilot-system-dialog.tsx | 53 +++++++++---------- .../components/geo-copilot/geo-copilot.tsx | 12 ++--- 3 files changed, 31 insertions(+), 38 deletions(-) diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts index 2b36a8420..72c1cc74d 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-interaction.ts @@ -57,8 +57,8 @@ export async function askGeoCoPilot( setSystemResponse(JSON.parse(answer.data.answer), content); }).catch(() => { setSystemResponse(ERROR_RESPONSE, content); - }) -}; + }); +} // Returns the full geolocation url based on centroid (lat, lon) and mapboxaccesstoken diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx index 6a2c5848d..be0779e50 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot-system-dialog.tsx @@ -116,26 +116,27 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat
{summary}
{/*Content*/} - {explanation && -
- - -
| - {/*Interaction*/} -
- -
- {/*Summary*/} - - + + | + {/*Interaction*/} +
+ +
+ {/*Summary*/} + + - -
} + } + +
+
} {showDetails && @@ -168,8 +168,7 @@ export function GeoCoPilotSystemDialogComponent({summary, dataset_ids, bbox, dat

{dataset_ids.join(", ")}

- - } + } ); } diff --git a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx index 55bfdcb3f..1f807f093 100644 --- a/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx +++ b/app/scripts/components/exploration/components/geo-copilot/geo-copilot.tsx @@ -397,21 +397,15 @@ export function GeoCoPilotComponent({ aria-label='Processing...' data-testid='loader' /> -
-
+
{ - setQuery(e.target.value) - } - } - onKeyUp={(e) => - e.code == 'Enter' ? addNewResponse() : '' - } + onChange={(e) => setQuery(e.target.value)} + onKeyUp={(e) => e.code == 'Enter' ? addNewResponse() : ''} />