diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 215e126..d53055e 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -353,6 +353,7 @@ export interface BriefingMessage { role: 'user' | 'assistant' | 'system'; content: string; created_at: string; + metadata?: Record | null; } const DEFAULT_BRIEFING_CONFIG: BriefingConfig = { diff --git a/frontend/src/views/BriefingView.vue b/frontend/src/views/BriefingView.vue index 4a5fa64..e8dee64 100644 --- a/frontend/src/views/BriefingView.vue +++ b/frontend/src/views/BriefingView.vue @@ -2,6 +2,7 @@ import { ref, computed, onMounted, watch } from 'vue' import { useChatStore } from '@/stores/chat' import ChatMessage from '@/components/ChatMessage.vue' +import WeatherCard from '@/components/WeatherCard.vue' import BriefingSetupWizard from '@/components/BriefingSetupWizard.vue' import { getBriefingConfig, @@ -9,11 +10,28 @@ import { getBriefingToday, getBriefingConvMessages, triggerBriefingSlot, + postRssReaction, + deleteRssReaction, type BriefingConversation, type BriefingMessage, } from '@/api/client' import type { Message } from '@/types/chat' +interface MessageMetadata { + weather?: { + location: string + fetched_at: string + current_temp: number + condition: string + today_high: number | null + today_low: number | null + yesterday_high: number | null + yesterday_low: number | null + forecast: { day: string; condition: string; high: number; low: number }[] + } | null + rss_item_ids?: number[] +} + const chatStore = useChatStore() // Setup wizard @@ -114,6 +132,27 @@ function onKeydown(e: KeyboardEvent) { } } +// RSS reactions: map of rss_item_id -> 'up' | 'down' | null +const reactions = ref>({}) + +async function handleReaction(itemId: number, reaction: 'up' | 'down') { + const current = reactions.value[itemId] + reactions.value[itemId] = current === reaction ? null : reaction + try { + if (current === reaction) { + await deleteRssReaction(itemId) + } else { + await postRssReaction(itemId, reaction) + } + } catch { + reactions.value[itemId] = current ?? null + } +} + +function msgMetadata(msg: BriefingMessage): MessageMetadata | null { + return (msg.metadata as MessageMetadata | null | undefined) ?? null +} + // Manual trigger const triggering = ref(false) async function triggerNow() { @@ -192,12 +231,42 @@ onMounted(async () => {

Click "Refresh" to generate a briefing now, or wait for the scheduled slot.

- + { } .btn-send:disabled { opacity: 0.4; cursor: not-allowed; } .btn-send:hover:not(:disabled) { opacity: 0.9; } + +.rss-reactions { + display: flex; + flex-direction: column; + gap: 0.35rem; + padding: 0.5rem 0.75rem; + margin-bottom: 0.25rem; +} + +.rss-reaction-row { + display: flex; + align-items: center; + gap: 0.4rem; +} + +.reaction-label { + font-size: 0.78rem; + color: var(--color-text-muted); + min-width: 4rem; +} + +.reaction-btn { + background: none; + border: 1px solid var(--color-border); + border-radius: 6px; + padding: 0.15rem 0.4rem; + cursor: pointer; + font-size: 0.85rem; + line-height: 1.4; + opacity: 0.55; + transition: opacity 0.15s, border-color 0.15s; +} + +.reaction-btn:hover { + opacity: 1; + border-color: var(--color-primary); +} + +.reaction-btn.active { + opacity: 1; + border-color: var(--color-primary); + background: color-mix(in srgb, var(--color-primary) 12%, transparent); +}