Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import * as usage$0 from "../../../../focusd-so/focusd/internal/usage/models.js"

function configure() {
Object.freeze(Object.assign($Create.Events, {
"protection:status": $$createType0,
"usage:update": $$createType1,
"daily-summary:ready": $$createType0,
"protection:status": $$createType1,
"usage:update": $$createType2,
}));
}

// Private type creation functions
const $$createType0 = usage$0.ProtectionPause.createFrom;
const $$createType1 = usage$0.ApplicationUsage.createFrom;
const $$createType0 = usage$0.LLMDailySummary.createFrom;
const $$createType1 = usage$0.ProtectionPause.createFrom;
const $$createType2 = usage$0.ApplicationUsage.createFrom;

configure();
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ declare module "@wailsio/runtime" {
namespace Events {
interface CustomEvents {
"authctx:updated": any;
"daily-summary:ready": usage$0.LLMDailySummary;
"protection:status": usage$0.ProtectionPause;
"usage:update": usage$0.ApplicationUsage;
}
Expand Down
56 changes: 42 additions & 14 deletions frontend/src/components/insights/ai-insight-card.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { useState, useMemo } from "react";
import { IconSparkles, IconChevronDown, IconChevronUp, IconBulb, IconTrophy } from "@tabler/icons-react";
import { IconSparkles, IconChevronDown, IconChevronUp, IconBulb, IconTrophy, IconEye, IconArrowsShuffle, IconTarget } from "@tabler/icons-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
// DailyUsageSummary defined locally - insights service was removed from backend
interface DailyUsageSummary {
headline: string;
summary: string;
suggestion: string;
day_vibe: string;
wins: string;
}
import type { DailyUsageSummary } from "@/stores/usage-store";

interface LLMInsightCardProps {
dailyUsageSummary: DailyUsageSummary;
Expand All @@ -34,8 +27,9 @@ export function LLMInsightCard({ dailyUsageSummary, isYesterday = false }: LLMIn
}
}, [dailyUsageSummary?.wins]);

const headline = dailyUsageSummary?.headline || "Daily LLM Insight";
const mainSummary = dailyUsageSummary?.summary || "";
const headline = dailyUsageSummary?.headline || "Daily Insight";
const narrative = dailyUsageSummary?.narrative || "";
const keyPattern = dailyUsageSummary?.key_pattern || "";
const suggestion = dailyUsageSummary?.suggestion || "";
const dayVibe = dailyUsageSummary?.day_vibe || "";

Expand Down Expand Up @@ -69,21 +63,55 @@ export function LLMInsightCard({ dailyUsageSummary, isYesterday = false }: LLMIn
</div>
</CardHeader>
<CardContent className="space-y-3">
{/* Main Summary */}
<div className="space-y-2">
<p className="text-sm text-muted-foreground leading-relaxed">
{mainSummary}
{narrative}
</p>
</div>

{/* Stat badges */}
<div className="flex gap-3 flex-wrap">
{dailyUsageSummary.context_switch_count > 0 && (
<span className="flex items-center gap-1 text-[10px] px-2 py-1 rounded-md bg-muted/50 text-muted-foreground">
<IconArrowsShuffle className="w-3 h-3" />
{dailyUsageSummary.context_switch_count} context switches
</span>
)}
{dailyUsageSummary.deep_work_minutes > 0 && (
<span className="flex items-center gap-1 text-[10px] px-2 py-1 rounded-md bg-emerald-500/10 text-emerald-400">
<IconTarget className="w-3 h-3" />
{dailyUsageSummary.deep_work_minutes}m deep work
</span>
)}
{dailyUsageSummary.longest_focus_minutes > 0 && (
<span className="flex items-center gap-1 text-[10px] px-2 py-1 rounded-md bg-blue-500/10 text-blue-400">
<IconTarget className="w-3 h-3" />
{dailyUsageSummary.longest_focus_minutes}m longest focus
</span>
)}
</div>

<CollapsibleContent className="space-y-4">
<div className="border-t border-violet-500/20 pt-3 space-y-4">
{/* Key Pattern */}
{keyPattern && (
<div className="space-y-1">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground flex items-center gap-1">
<IconEye className="w-3 h-3 text-violet-400" />
Key Pattern
</p>
<p className="text-sm text-violet-300/90">
{keyPattern}
</p>
</div>
)}

{/* Wins */}
{wins.length > 0 && (
<div className="space-y-2">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground flex items-center gap-1">
<IconTrophy className="w-3 h-3 text-amber-400" />
Today's Wins
Wins
</p>
<ul className="space-y-1">
{wins.map((win, i) => (
Expand Down
55 changes: 17 additions & 38 deletions frontend/src/components/insights/bento-dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
import {
formatMinutes,
formatDate,
getDataForDate,
} from "@/lib/mock-data";
import { useUsageStore, isToday } from "@/stores/usage-store";
import type { UsagePerHourBreakdown } from "@/stores/usage-store";
Expand Down Expand Up @@ -176,9 +175,6 @@ export function BentoDashboard() {
(item): item is UsagePerHourBreakdown => item !== null
);

// Mock data for new cards (until backend supports these)
const mockDayData = getDataForDate(selectedDate);

const canGoNext = !isToday(selectedDate);

// Show loading overlay if data is loading
Expand All @@ -199,7 +195,7 @@ export function BentoDashboard() {
}

return (
<div className="p-6 space-y-4 overflow-y-auto h-full">
<div className="p-6 space-y-6 overflow-y-auto h-full">
{/* Date Picker Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
Expand Down Expand Up @@ -328,7 +324,7 @@ export function BentoDashboard() {
</Card>
</div>

{/* Row 2: Full-width Hourly Breakdown with small blocked badge */}
{/* Row 2: Full-width Hourly Breakdown */}
<Card className="border-border/50">
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
Expand All @@ -341,12 +337,18 @@ export function BentoDashboard() {
<span className="w-2 h-2 bg-emerald-500 rounded-full" />
Productive
</span>

<span className="flex items-center gap-1">
<span className="w-2 h-2 bg-rose-500 rounded-full" />
Distractive
</span>
</div>
<Link
to="/screen-time/screentime"
className="flex items-center gap-1 text-xs text-violet-400 hover:text-violet-300 transition-colors"
>
<IconHistory className="w-3.5 h-3.5" />
History
</Link>
</div>
</div>
</CardHeader>
Expand All @@ -355,40 +357,17 @@ export function BentoDashboard() {
</CardContent>
</Card>

{/* Row 3: Top Blocked + More Insights placeholder */}
<div className="grid grid-cols-5 gap-4">
<div className="col-span-3">
<TopBlockedCard blockedAttempts={mockDayData.blockedAttempts} />
</div>
<div className="col-span-2">
<Link
to="/screen-time/screentime"
className="block h-full group transition-all"
>
<div className="bg-gradient-to-br from-violet-500/10 to-purple-600/5 border border-violet-500/20 rounded-xl p-4 flex flex-col justify-center h-full group-hover:bg-violet-500/20 group-hover:border-violet-500/40 transition-all">
<div className="flex items-center justify-between mb-1">
<p className="text-[10px] font-bold uppercase tracking-widest text-violet-400">Deep Dive</p>
<IconHistory className="w-4 h-4 text-violet-400 opacity-50 group-hover:opacity-100 transition-opacity" />
</div>
<h3 className="text-lg font-semibold group-hover:text-violet-200 transition-colors">Detailed History</h3>
<p className="text-xs text-muted-foreground mt-1">View your full activity feed and usage aggregations.</p>
</div>
</Link>
</div>
{/* Row 3: Time Lost To + Blocked Today */}
<div className="grid grid-cols-2 gap-4">
<TopDistractionsCard distractions={overview?.TopDistractions ?? []} />
<TopBlockedCard blockedAttempts={overview?.TopBlocked ?? []} />
</div>

{/* Row 4: Top Distractions + Categories/Projects */}
<div className="grid grid-cols-5 gap-4">
<div className="col-span-2">
<TopDistractionsCard distractions={mockDayData.topDistractions} />
</div>
<div className="col-span-3">
<CategoriesCard projects={mockDayData.projects} />
</div>
{/* Row 4: Projects + Communication */}
<div className="grid grid-cols-2 gap-4">
<CategoriesCard projects={overview?.ProjectBreakdown ?? []} />
<CommunicationCard channels={overview?.CommunicationBreakdown ?? []} />
</div>

{/* Row 5: Communication Channels */}
<CommunicationCard channels={mockDayData.communicationChannels} />
</div>
);
}
32 changes: 18 additions & 14 deletions frontend/src/components/insights/categories-card.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { IconFolder } from "@tabler/icons-react";
import { IconFolder, IconArrowRight } from "@tabler/icons-react";
import { Link } from "@tanstack/react-router";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { formatMinutes, type ProjectStats } from "@/lib/mock-data";
import { formatMinutes } from "@/lib/mock-data";
import type { ProjectBreakdown } from "@/../bindings/github.com/focusd-so/focusd/internal/usage/models";

interface CategoriesCardProps {
projects: ProjectStats[];
projects: ProjectBreakdown[];
}

// Color palette for projects
const projectColors = [
"bg-emerald-500",
"bg-blue-500",
Expand All @@ -17,11 +18,10 @@ const projectColors = [
];

export function CategoriesCard({ projects }: CategoriesCardProps) {
const totalMinutes = projects.reduce((sum, p) => sum + p.totalMinutes, 0);
const maxMinutes = Math.max(...projects.map((p) => p.totalMinutes), 1);
const totalMinutes = projects.reduce((sum, p) => sum + p.minutes, 0);
const maxMinutes = Math.max(...projects.map((p) => p.minutes), 1);

// Show top 5 projects
const topProjects = projects.slice(0, 5);
const topProjects = projects.slice(0, 3);

return (
<Card className="border-border/50">
Expand All @@ -31,22 +31,26 @@ export function CategoriesCard({ projects }: CategoriesCardProps) {
<IconFolder className="w-4 h-4 text-muted-foreground" />
Projects
</CardTitle>
<span className="text-xs text-muted-foreground">
<Link
to="/screen-time/screentime"
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
{formatMinutes(totalMinutes)} total
</span>
<IconArrowRight className="w-3 h-3" />
</Link>
</div>
</CardHeader>
<CardContent className="space-y-3">
<CardContent className="space-y-2">
{topProjects.length === 0 ? (
<p className="text-xs text-muted-foreground text-center py-4">
No project activity
</p>
) : (
topProjects.map((project, index) => {
const widthPct = (project.totalMinutes / maxMinutes) * 100;
const widthPct = (project.minutes / maxMinutes) * 100;
const colorClass = projectColors[index % projectColors.length];
return (
<div key={project.id} className="space-y-1.5">
<div key={index} className="space-y-1.5">
<div className="flex items-center justify-between text-xs">
<span className="flex items-center gap-2">
<span
Expand All @@ -57,7 +61,7 @@ export function CategoriesCard({ projects }: CategoriesCardProps) {
</span>
</span>
<span className="text-muted-foreground">
{formatMinutes(project.totalMinutes)}
{formatMinutes(project.minutes)}
</span>
</div>
<div className="h-2 bg-muted/30 rounded-full overflow-hidden">
Expand Down
Loading