From ab24f07d5c7d55eb05d8c8c7e8a5efe83c6044e8 Mon Sep 17 00:00:00 2001 From: dwrz Date: Mon, 29 Jun 2026 18:13:57 +0000 Subject: [PATCH] Add geolocation --- internal/service/static/icons.svg | 5 ++ internal/service/static/main.css | 10 +++ internal/service/static/main.js | 63 ++++++++++++++++++- .../templates/static/footer/compose.gohtml | 27 +++++--- 4 files changed, 94 insertions(+), 11 deletions(-) diff --git a/internal/service/static/icons.svg b/internal/service/static/icons.svg index 62ded67..9a71bfb 100644 --- a/internal/service/static/icons.svg +++ b/internal/service/static/icons.svg @@ -119,4 +119,9 @@ + + + + + diff --git a/internal/service/static/main.css b/internal/service/static/main.css index 0a1a37c..680d635 100644 --- a/internal/service/static/main.css +++ b/internal/service/static/main.css @@ -273,6 +273,10 @@ body { height: var(--action-icon-size); } +.compose__action-btn--location.active { + color: var(--color-green); +} + .compose__action-btn--record.recording { color: white; background: var(--color-recording); @@ -330,6 +334,12 @@ body { border-top: 1px dotted var(--color-border-light); } +.compose__actions-left { + display: flex; + align-items: center; + gap: 0.125rem; +} + .compose__actions-right { display: flex; align-items: center; diff --git a/internal/service/static/main.js b/internal/service/static/main.js index 3dc1a87..4f3d998 100644 --- a/internal/service/static/main.js +++ b/internal/service/static/main.js @@ -61,6 +61,7 @@ class Odidere { this.isResetPending = false; this.isScrollPending = false; this.isStopPending = false; + this.locationData = null; this.mediaRecorder = null; this.wakeLock = null; this.pendingDeleteId = null; @@ -95,6 +96,7 @@ class Odidere { this.$chat = document.getElementById('chat'); this.$chatLoading = document.getElementById('chat-loading'); this.$fileInput = document.getElementById('file-input'); + this.$location = document.getElementById('location'); this.$model = document.getElementById('model'); this.$ptt = document.getElementById('ptt'); this.$reset = document.getElementById('reset'); @@ -170,6 +172,7 @@ class Odidere { el.remove(); }); this.#clearAttachments(); + this.#clearLocation(); this.$textInput.value = ''; this.$textInput.style.height = 'auto'; this.totalTokens = 0; @@ -231,6 +234,11 @@ class Odidere { this.document.addEventListener('click', (e) => this.#handleScrollCancel(e)); this.document.addEventListener('click', (e) => this.#handleDeleteCancel(e)); + // Location toggle. + this.$location.addEventListener('click', () => + this.#handleLocationToggle(), + ); + // File attachment. this.$attach.addEventListener('click', () => this.$fileInput.click()); this.$fileInput.addEventListener('change', (e) => @@ -656,6 +664,38 @@ class Odidere { this.#submitText(); }; + /** + * #handleLocationToggle toggles GPS location inclusion. + * When enabled, requests the user's current position via the Geolocation API. + * If denied or unavailable, silently leaves the button off. + */ + #handleLocationToggle = () => { + if (this.locationData) { + // Turn off. + this.locationData = null; + this.$location.classList.remove( + 'compose__action-btn--location', + 'active', + ); + this.#updateSendButtonState(); + return; + } + + if (!navigator.geolocation) { + return; + } + + navigator.geolocation.getCurrentPosition( + (position) => { + this.locationData = position; + this.$location.classList.add('compose__action-btn--location', 'active'); + this.#updateSendButtonState(); + }, + // On error, leave the button off; user must retry manually. + () => {}, + ); + }; + /** * #handleTextareaInput adjusts textarea height to fit content. */ @@ -1469,7 +1509,8 @@ class Odidere { this.currentController = new AbortController(); try { - const hasNewInput = text || this.attachments.length > 0; + const hasNewInput = + text || this.attachments.length > 0 || this.locationData; // Build messages array from active path + current user message. const activePath = this.getActivePath(); @@ -1500,8 +1541,9 @@ class Odidere { payload.system_message = systemMessage; } - // Clear attachments after building payload (before async operations). + // Clear attachments and location after building payload (before async operations). this.#clearAttachments(); + this.#clearLocation(); // For text-only requests with new input, add to history and render // immediately. Skip when continuing conversation (no new user message). @@ -1789,6 +1831,12 @@ class Odidere { textParts.push(text); } + // Include GPS location if enabled (appended at end). + if (this.locationData) { + const locationText = JSON.stringify(this.locationData, null, 2); + textParts.push(locationText); + } + const combinedText = textParts.join('\n\n'); // If no images, return simple string content. @@ -1825,8 +1873,9 @@ class Odidere { #updateSendButtonState() { const hasText = this.$textInput.value.trim().length > 0; const hasAttachments = this.attachments.length > 0; + const hasLocation = this.locationData !== null; const hasHistory = this.messagesMap.size > 0; - const enabled = hasText || hasAttachments || hasHistory; + const enabled = hasText || hasAttachments || hasLocation || hasHistory; this.$send.disabled = !enabled; this.$send.setAttribute( 'aria-label', @@ -2705,6 +2754,14 @@ class Odidere { this.#updateSendButtonState(); } + /** + * #clearLocation clears location data and resets the button state. + */ + #clearLocation() { + this.locationData = null; + this.$location.classList.remove('compose__action-btn--location', 'active'); + } + /** * #clearAttachments removes all attachments. */ diff --git a/internal/service/templates/static/footer/compose.gohtml b/internal/service/templates/static/footer/compose.gohtml index 2e6a19e..363d1a6 100644 --- a/internal/service/templates/static/footer/compose.gohtml +++ b/internal/service/templates/static/footer/compose.gohtml @@ -9,14 +9,25 @@ >
- +
+ + +