Add weather caching, useless-tip redirect, clock widget, and IAM Bedrock auth

- Cache the latest fetched tomorrow weather and expose it via /api/system/weather/latest
- Add a header Clock widget showing date/time plus the cached weather summary
- Redirect useless-talk mentions from the weather bot to the useless-tips webhook
- Switch Bedrock auth from bearer token to standard IAM access key/secret

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 15:15:11 +09:00
co-authored by Claude Sonnet 4.6
parent 4a08ba29fa
commit 4e837d1419
8 changed files with 160 additions and 4 deletions
+87
View File
@@ -0,0 +1,87 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
interface LatestWeather {
weather: string
pop: number | null
judgement: string
reason: string
fetched_at: string
}
const now = ref(new Date())
const weather = ref<LatestWeather | null>(null)
let clockIntervalId: number | null = null
let weatherIntervalId: number | null = null
const formatDate = (date: Date) => {
const weekdays = ['日', '月', '火', '水', '木', '金', '土']
return `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')} (${weekdays[date.getDay()]})`
}
const formatTime = (date: Date) => {
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
}
const formatFetchedAt = (iso: string) => {
const d = new Date(iso)
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}時点`
}
const fetchWeather = async () => {
try {
const response = await fetch('/api/system/weather/latest')
const data = await response.json()
weather.value = data
} catch (error) {
console.error('Failed to fetch latest weather:', error)
}
}
onMounted(() => {
clockIntervalId = window.setInterval(() => {
now.value = new Date()
}, 1000)
fetchWeather()
weatherIntervalId = window.setInterval(fetchWeather, 60000)
})
onUnmounted(() => {
if (clockIntervalId) clearInterval(clockIntervalId)
if (weatherIntervalId) clearInterval(weatherIntervalId)
})
</script>
<template>
<div class="clock">
<span class="date">{{ formatDate(now) }}</span>
<span class="time">{{ formatTime(now) }}</span>
<span v-if="weather" class="weather" :title="`${formatFetchedAt(weather.fetched_at)}取得`">
🌤️ 明日: {{ weather.weather }}
<template v-if="weather.pop !== null">(降水{{ weather.pop }}%)</template>
・👕{{ weather.judgement }}
</span>
</div>
</template>
<style scoped>
.clock {
display: flex;
align-items: baseline;
gap: 8px;
font-family: monospace;
font-size: 13px;
color: #aaa;
}
.time {
color: #eee;
font-weight: 600;
}
.weather {
color: #8bc8ff;
font-size: 12px;
border-left: 1px solid #444;
padding-left: 8px;
}
</style>