diff --git a/src/components/Map.vue b/src/components/Map.vue index 1bdd6864f..36d75c626 100644 --- a/src/components/Map.vue +++ b/src/components/Map.vue @@ -11,7 +11,7 @@ - + {{ naturalLanguageError }} -
+
- - - {{ $t('search.processing') }} - - - {{ $t('search.applyNaturalLanguageQuery') }} - - +
+ + + {{ $t('search.processing') }} + + + {{ $t('search.populateForm') }} + + + + + {{ $t('search.processing') }} + + + {{ $t('search.searchDirectly') }} + + +
+
+ +
+
+ {{ $t('search.spatialExtentOptions.naturalSearchArea') }}: + {{ $t('search.naturalSearchAreaDescription') }} +
+ +
@@ -192,6 +211,7 @@ function getDefaults() { naturalLanguageExplanation: '', naturalLanguageLoading: false, naturalLanguageError: null, + naturalLanguageAction: null, naturalSearchArea: null }; } @@ -417,6 +437,7 @@ export default { if (this.naturalLanguageQuery) { this.naturalLanguageLoading = true; + this.naturalLanguageAction = 'populate'; try { const SEMANTIC_SEARCH_API_URL = this.$store.state.semanticSearchApiUrl; const response = await fetch(`${SEMANTIC_SEARCH_API_URL}/items/search`, { @@ -506,6 +527,97 @@ export default { this.naturalLanguageError = error.message; } finally { this.naturalLanguageLoading = false; + this.naturalLanguageAction = null; + } + } + }, + async directNaturalLanguageSearch(event) { + // Prevent form submission + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + + if (this.naturalLanguageQuery) { + this.naturalLanguageLoading = true; + this.naturalLanguageAction = 'search'; + try { + const SEMANTIC_SEARCH_API_URL = this.$store.state.semanticSearchApiUrl; + const response = await fetch(`${SEMANTIC_SEARCH_API_URL}/items/search`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + return_search_params_only: true, + query: this.naturalLanguageQuery, + limit: this.query.limit || this.itemsPerPage + }) + }); + + if (!response.ok) { + throw new Error(`API request failed with status ${response.status}`); + } + + const responseData = await response.json(); + + // Create a search query from the response and emit it directly + const searchQuery = {}; + + if (responseData.results && responseData.results.search_params) { + const params = responseData.results.search_params; + + // Extract datetime + if (params.datetime) { + const datetimeString = params.datetime; + const dates = datetimeString.split('/'); + if (dates.length === 2) { + const startDate = dates[0] === '..' ? null : new Date(dates[0]); + const endDate = dates[1] === '..' ? null : new Date(dates[1]); + searchQuery.datetime = [startDate, endDate]; + } + } + + // Extract collections + if (params.collections && Array.isArray(params.collections)) { + searchQuery.collections = [...new Set(params.collections)]; + } + + // Extract limit + if (params.max_items) { + const maxItems = parseInt(params.max_items, 10); + if (!isNaN(maxItems) && maxItems > 0) { + searchQuery.limit = Math.min(maxItems, this.maxItems); + } + } + + // Extract spatial parameters + if (params.intersects) { + searchQuery.intersects = params.intersects; + // Also store the natural search area locally for UI display + this.naturalSearchArea = params.intersects; + this.spatialExtentType = 'naturalSearchArea'; + } + } + + // Store explanation for display + if (responseData.explanation) { + this.naturalLanguageExplanation = responseData.explanation; + } else if (responseData.results && responseData.results.explanation) { + this.naturalLanguageExplanation = responseData.results.explanation; + } + + // Emit the search query directly to trigger immediate search + this.$emit('input', searchQuery, false); + + this.naturalLanguageError = null; + + } catch (error) { + console.error('Error in direct semantic search:', error); + this.naturalLanguageError = error.message; + } finally { + this.naturalLanguageLoading = false; + this.naturalLanguageAction = null; } } }, @@ -693,6 +805,7 @@ export default { this.naturalLanguageExplanation = ''; this.naturalLanguageLoading = false; this.naturalLanguageError = null; + this.naturalLanguageAction = null; this.naturalSearchArea = null; this.query.intersects = null; this.$emit('input', {}, true); diff --git a/src/locales/en/texts.json b/src/locales/en/texts.json index 51129cb48..417fa046e 100644 --- a/src/locales/en/texts.json +++ b/src/locales/en/texts.json @@ -226,8 +226,10 @@ "enterSearchTerms": "Enter one or more search terms...", "enterNaturalLanguageQuery": "e.g., 'satellite images of California from 2023'", "naturalLanguageQuery": "Natural Language Search", - "naturalLanguageInfo": "Enter a natural language query to automatically populate the search form fields. Then click 'Submit' to execute the search with the populated criteria.", + "naturalLanguageInfo": "Enter a description of what you are searching for using natural language, such as 'satellite images of California from 2023'.", "applyNaturalLanguageQuery": "Populate", + "searchDirectly": "Search", + "populateForm": "Populate Form", "naturalLanguageSearchArea": "Natural Language Search Area", "naturalLanguageSearchAreaDescription": "This area represents the spatial extent used in your natural language search query.", "processing": "Processing...", @@ -271,6 +273,7 @@ "boundingBox": "Bounding Box", "naturalSearchArea": "Natural Search Area" }, + "naturalSearchAreaDescription": "This spatial area was automatically derived from your natural language query and will be used to filter search results.", "tabs": { "collections": "Search for Collections", "items": "Search for Items"