backpacking-assistant / src /tools /widgets /get_city_info_widget.html
khlevon's picture
feat: add opeai chatgpt app widgets support
1ddfc0b
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#root {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.hidden {
display: none !important;
}
/* Loader styles */
.loader-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 24px;
}
.loader {
width: 40px;
height: 40px;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.loader-text {
color: #6b7280;
font-size: 14px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* City card styles */
.city-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
color: white;
width: 100%;
max-width: 360px;
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3);
animation: fadeIn 0.4s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.city-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.city-icon {
width: 48px;
height: 48px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.city-title {
flex: 1;
}
.city-name {
font-size: 22px;
font-weight: 600;
margin-bottom: 2px;
}
.city-country {
font-size: 14px;
opacity: 0.85;
display: flex;
align-items: center;
gap: 6px;
}
.city-details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.detail-item {
background: rgba(255, 255, 255, 0.15);
border-radius: 10px;
padding: 12px;
backdrop-filter: blur(10px);
}
.detail-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
opacity: 0.8;
margin-bottom: 4px;
}
.detail-value {
font-size: 14px;
font-weight: 500;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
}
.city-id {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
.id-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
opacity: 0.7;
margin-bottom: 6px;
}
.id-value {
font-size: 12px;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
background: rgba(0, 0, 0, 0.2);
padding: 8px 12px;
border-radius: 6px;
word-break: break-all;
}
/* Error card styles */
.error-card {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
border-radius: 16px;
padding: 24px;
color: white;
width: 100%;
max-width: 360px;
box-shadow: 0 10px 40px rgba(239, 68, 68, 0.3);
animation: fadeIn 0.4s ease-out;
text-align: center;
}
.error-icon {
width: 56px;
height: 56px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
margin: 0 auto 16px;
}
.error-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
.error-message {
font-size: 14px;
opacity: 0.9;
line-height: 1.5;
}
</style>
<div id="root">
<div class="loader-container" id="loader">
<div class="loader"></div>
<span class="loader-text">Loading city info...</span>
</div>
<div class="city-card hidden" id="city-card">
<div class="city-header">
<div class="city-icon">🏙️</div>
<div class="city-title">
<div class="city-name" id="city-name"></div>
<div class="city-country">
<span>📍</span>
<span id="city-country"></span>
</div>
</div>
</div>
<div class="city-details">
<div class="detail-item">
<div class="detail-label">Latitude</div>
<div class="detail-value" id="city-latitude"></div>
</div>
<div class="detail-item">
<div class="detail-label">Longitude</div>
<div class="detail-value" id="city-longitude"></div>
</div>
</div>
<div class="city-id">
<div class="id-label">Flixbus UUID</div>
<div class="id-value" id="city-uuid"></div>
</div>
</div>
<div class="error-card hidden" id="error-card">
<div class="error-icon">⚠️</div>
<div class="error-title">City Not Found</div>
<div class="error-message" id="error-message"></div>
</div>
</div>
<script>
function render() {
const payload = window.openai?.toolOutput;
if (!payload) return;
const data = JSON.parse(payload.text);
document.getElementById('loader').classList.add('hidden');
if (data.error) {
document.getElementById('error-message').textContent = data.error;
document.getElementById('error-card').classList.remove('hidden');
return;
}
document.getElementById('city-name').textContent = data.name;
document.getElementById('city-country').textContent = data.country;
document.getElementById('city-latitude').textContent = data.latitude.toFixed(4) + '°';
document.getElementById('city-longitude').textContent = data.longitude.toFixed(4) + '°';
document.getElementById('city-uuid').textContent = data.id;
document.getElementById('city-card').classList.remove('hidden');
}
window.addEventListener("openai:set_globals", (event) => {
if (event.detail?.globals?.toolOutput) render();
}, { passive: true });
render();
</script>