feat: daily briefing frontend — view, setup wizard, settings tab, nav
- BriefingView: soft chat UI with conversation history dropdown, SSE streaming via chat store, manual refresh trigger, today/past conv toggle - BriefingSetupWizard: 4-step first-time setup overlay (welcome → locations → office days → RSS feeds) - SettingsView: Briefing tab with enable toggle, location geocoding, office day picker, slot toggles, RSS feed management, notifications - AppHeader + router: /briefing route and nav link (desktop + mobile) - client.ts: briefing types and API functions (config, feeds, convs, geocode, trigger) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -300,6 +300,122 @@ export function apiSSEStream(
|
||||
return { close: () => controller.abort(), done };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Briefing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface BriefingLocation {
|
||||
label: string;
|
||||
address: string;
|
||||
lat?: number;
|
||||
lon?: number;
|
||||
}
|
||||
|
||||
export interface BriefingSlots {
|
||||
compilation: boolean;
|
||||
morning: boolean;
|
||||
midday: boolean;
|
||||
afternoon: boolean;
|
||||
}
|
||||
|
||||
export interface BriefingConfig {
|
||||
enabled: boolean;
|
||||
locations: {
|
||||
home?: BriefingLocation;
|
||||
work?: BriefingLocation;
|
||||
};
|
||||
use_caldav_event_locations: boolean;
|
||||
work_days: number[];
|
||||
slots: BriefingSlots;
|
||||
notifications: boolean;
|
||||
}
|
||||
|
||||
export interface BriefingFeed {
|
||||
id: number;
|
||||
title: string;
|
||||
url: string;
|
||||
category: string | null;
|
||||
last_fetched_at: string | null;
|
||||
}
|
||||
|
||||
export interface BriefingConversation {
|
||||
id: number;
|
||||
title: string;
|
||||
briefing_date: string | null;
|
||||
message_count: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface BriefingMessage {
|
||||
id: number;
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
const DEFAULT_BRIEFING_CONFIG: BriefingConfig = {
|
||||
enabled: false,
|
||||
locations: {},
|
||||
use_caldav_event_locations: false,
|
||||
work_days: [1, 2, 3, 4, 5],
|
||||
slots: { compilation: true, morning: true, midday: false, afternoon: false },
|
||||
notifications: true,
|
||||
};
|
||||
|
||||
export async function getBriefingConfig(): Promise<BriefingConfig> {
|
||||
try {
|
||||
const data = await apiGet<BriefingConfig>('/api/briefing/config');
|
||||
return { ...DEFAULT_BRIEFING_CONFIG, ...data };
|
||||
} catch {
|
||||
return { ...DEFAULT_BRIEFING_CONFIG };
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveBriefingConfig(config: BriefingConfig): Promise<void> {
|
||||
await apiPut('/api/briefing/config', config);
|
||||
}
|
||||
|
||||
export async function getBriefingFeeds(): Promise<BriefingFeed[]> {
|
||||
const data = await apiGet<BriefingFeed[]>('/api/briefing/feeds');
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function createBriefingFeed(url: string): Promise<BriefingFeed> {
|
||||
const data = await apiPost<{ id: number; url: string; title: string; category: string | null }>('/api/briefing/feeds', { url });
|
||||
return { ...data, last_fetched_at: null };
|
||||
}
|
||||
|
||||
export async function deleteBriefingFeed(id: number): Promise<void> {
|
||||
await apiDelete(`/api/briefing/feeds/${id}`);
|
||||
}
|
||||
|
||||
export async function getBriefingConversations(): Promise<BriefingConversation[]> {
|
||||
const data = await apiGet<{ conversations: BriefingConversation[] }>('/api/briefing/conversations');
|
||||
return data.conversations;
|
||||
}
|
||||
|
||||
export async function getBriefingToday(): Promise<{ id: number; title: string; messages: BriefingMessage[] }> {
|
||||
return apiGet('/api/briefing/conversations/today');
|
||||
}
|
||||
|
||||
export async function getBriefingConvMessages(id: number): Promise<BriefingMessage[]> {
|
||||
const data = await apiGet<{ messages: BriefingMessage[] }>(`/api/briefing/conversations/${id}/messages`);
|
||||
return data.messages;
|
||||
}
|
||||
|
||||
export async function triggerBriefingSlot(slot: string): Promise<void> {
|
||||
await apiPost('/api/briefing/trigger', { slot });
|
||||
}
|
||||
|
||||
export async function geocodeAddress(address: string): Promise<{ lat: number; lon: number; display_name: string } | null> {
|
||||
try {
|
||||
const r = await apiPost<{ lat: number; lon: number; label: string }>('/api/briefing/weather/geocode', { query: address });
|
||||
return { lat: r.lat, lon: r.lon, display_name: r.label };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiStreamPost(
|
||||
path: string,
|
||||
body: unknown,
|
||||
|
||||
Reference in New Issue
Block a user