Add geolocation
This commit is contained in:
@@ -119,4 +119,9 @@
|
|||||||
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
|
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<circle cx="12" cy="12" r="3"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="location" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M18 8c0 3.613-3.869 7.429-5.393 8.795a1 1 0 0 1-1.214 0C9.87 15.429 6 11.613 6 8a6 6 0 0 1 12 0"/>
|
||||||
|
<circle cx="12" cy="8" r="2"/>
|
||||||
|
<path d="M8.714 14h-3.71a1 1 0 0 0-.948.683l-2.004 6A1 1 0 0 0 3 22h18a1 1 0 0 0 .948-1.316l-2-6a1 1 0 0 0-.949-.684h-3.712"/>
|
||||||
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.4 KiB |
@@ -273,6 +273,10 @@ body {
|
|||||||
height: var(--action-icon-size);
|
height: var(--action-icon-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compose__action-btn--location.active {
|
||||||
|
color: var(--color-green);
|
||||||
|
}
|
||||||
|
|
||||||
.compose__action-btn--record.recording {
|
.compose__action-btn--record.recording {
|
||||||
color: white;
|
color: white;
|
||||||
background: var(--color-recording);
|
background: var(--color-recording);
|
||||||
@@ -330,6 +334,12 @@ body {
|
|||||||
border-top: 1px dotted var(--color-border-light);
|
border-top: 1px dotted var(--color-border-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compose__actions-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
.compose__actions-right {
|
.compose__actions-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class Odidere {
|
|||||||
this.isResetPending = false;
|
this.isResetPending = false;
|
||||||
this.isScrollPending = false;
|
this.isScrollPending = false;
|
||||||
this.isStopPending = false;
|
this.isStopPending = false;
|
||||||
|
this.locationData = null;
|
||||||
this.mediaRecorder = null;
|
this.mediaRecorder = null;
|
||||||
this.wakeLock = null;
|
this.wakeLock = null;
|
||||||
this.pendingDeleteId = null;
|
this.pendingDeleteId = null;
|
||||||
@@ -95,6 +96,7 @@ class Odidere {
|
|||||||
this.$chat = document.getElementById('chat');
|
this.$chat = document.getElementById('chat');
|
||||||
this.$chatLoading = document.getElementById('chat-loading');
|
this.$chatLoading = document.getElementById('chat-loading');
|
||||||
this.$fileInput = document.getElementById('file-input');
|
this.$fileInput = document.getElementById('file-input');
|
||||||
|
this.$location = document.getElementById('location');
|
||||||
this.$model = document.getElementById('model');
|
this.$model = document.getElementById('model');
|
||||||
this.$ptt = document.getElementById('ptt');
|
this.$ptt = document.getElementById('ptt');
|
||||||
this.$reset = document.getElementById('reset');
|
this.$reset = document.getElementById('reset');
|
||||||
@@ -170,6 +172,7 @@ class Odidere {
|
|||||||
el.remove();
|
el.remove();
|
||||||
});
|
});
|
||||||
this.#clearAttachments();
|
this.#clearAttachments();
|
||||||
|
this.#clearLocation();
|
||||||
this.$textInput.value = '';
|
this.$textInput.value = '';
|
||||||
this.$textInput.style.height = 'auto';
|
this.$textInput.style.height = 'auto';
|
||||||
this.totalTokens = 0;
|
this.totalTokens = 0;
|
||||||
@@ -231,6 +234,11 @@ class Odidere {
|
|||||||
this.document.addEventListener('click', (e) => this.#handleScrollCancel(e));
|
this.document.addEventListener('click', (e) => this.#handleScrollCancel(e));
|
||||||
this.document.addEventListener('click', (e) => this.#handleDeleteCancel(e));
|
this.document.addEventListener('click', (e) => this.#handleDeleteCancel(e));
|
||||||
|
|
||||||
|
// Location toggle.
|
||||||
|
this.$location.addEventListener('click', () =>
|
||||||
|
this.#handleLocationToggle(),
|
||||||
|
);
|
||||||
|
|
||||||
// File attachment.
|
// File attachment.
|
||||||
this.$attach.addEventListener('click', () => this.$fileInput.click());
|
this.$attach.addEventListener('click', () => this.$fileInput.click());
|
||||||
this.$fileInput.addEventListener('change', (e) =>
|
this.$fileInput.addEventListener('change', (e) =>
|
||||||
@@ -656,6 +664,38 @@ class Odidere {
|
|||||||
this.#submitText();
|
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.
|
* #handleTextareaInput adjusts textarea height to fit content.
|
||||||
*/
|
*/
|
||||||
@@ -1469,7 +1509,8 @@ class Odidere {
|
|||||||
this.currentController = new AbortController();
|
this.currentController = new AbortController();
|
||||||
|
|
||||||
try {
|
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.
|
// Build messages array from active path + current user message.
|
||||||
const activePath = this.getActivePath();
|
const activePath = this.getActivePath();
|
||||||
@@ -1500,8 +1541,9 @@ class Odidere {
|
|||||||
payload.system_message = systemMessage;
|
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.#clearAttachments();
|
||||||
|
this.#clearLocation();
|
||||||
|
|
||||||
// For text-only requests with new input, add to history and render
|
// For text-only requests with new input, add to history and render
|
||||||
// immediately. Skip when continuing conversation (no new user message).
|
// immediately. Skip when continuing conversation (no new user message).
|
||||||
@@ -1789,6 +1831,12 @@ class Odidere {
|
|||||||
textParts.push(text);
|
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');
|
const combinedText = textParts.join('\n\n');
|
||||||
|
|
||||||
// If no images, return simple string content.
|
// If no images, return simple string content.
|
||||||
@@ -1825,8 +1873,9 @@ class Odidere {
|
|||||||
#updateSendButtonState() {
|
#updateSendButtonState() {
|
||||||
const hasText = this.$textInput.value.trim().length > 0;
|
const hasText = this.$textInput.value.trim().length > 0;
|
||||||
const hasAttachments = this.attachments.length > 0;
|
const hasAttachments = this.attachments.length > 0;
|
||||||
|
const hasLocation = this.locationData !== null;
|
||||||
const hasHistory = this.messagesMap.size > 0;
|
const hasHistory = this.messagesMap.size > 0;
|
||||||
const enabled = hasText || hasAttachments || hasHistory;
|
const enabled = hasText || hasAttachments || hasLocation || hasHistory;
|
||||||
this.$send.disabled = !enabled;
|
this.$send.disabled = !enabled;
|
||||||
this.$send.setAttribute(
|
this.$send.setAttribute(
|
||||||
'aria-label',
|
'aria-label',
|
||||||
@@ -2705,6 +2754,14 @@ class Odidere {
|
|||||||
this.#updateSendButtonState();
|
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.
|
* #clearAttachments removes all attachments.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
<div class="compose__attachments" id="attachments"></div>
|
<div class="compose__attachments" id="attachments"></div>
|
||||||
<div class="compose__actions">
|
<div class="compose__actions">
|
||||||
|
<div class="compose__actions-left">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="compose__action-btn"
|
class="compose__action-btn"
|
||||||
@@ -17,6 +18,16 @@
|
|||||||
>
|
>
|
||||||
<svg class="icon"><use href="/static/icons.svg#attach"></use></svg>
|
<svg class="icon"><use href="/static/icons.svg#attach"></use></svg>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="compose__action-btn"
|
||||||
|
id="location"
|
||||||
|
aria-label="Include location"
|
||||||
|
title="Include GPS location"
|
||||||
|
>
|
||||||
|
<svg class="icon"><use href="/static/icons.svg#location"></use></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<input type="file" id="file-input" multiple hidden />
|
<input type="file" id="file-input" multiple hidden />
|
||||||
<div class="compose__actions-right">
|
<div class="compose__actions-right">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user