From 8d1fc7a15c7828c909da5a21f37a4eec5b735b73 Mon Sep 17 00:00:00 2001 From: Kavuru Buvanesh <169604812+Kavurubuvanesh@users.noreply.github.com> Date: Sat, 27 Jun 2026 12:24:02 +0530 Subject: [PATCH 1/2] feat(dashboard): add Gemini-powered weekly standup generator widget --- src/app/api/ai/roast/route.ts | 48 ++++++-------------------- src/components/WeeklyStandupWidget.tsx | 38 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 37 deletions(-) create mode 100644 src/components/WeeklyStandupWidget.tsx diff --git a/src/app/api/ai/roast/route.ts b/src/app/api/ai/roast/route.ts index 2eb468a8c..8967e6d4e 100644 --- a/src/app/api/ai/roast/route.ts +++ b/src/app/api/ai/roast/route.ts @@ -1,53 +1,27 @@ import { NextResponse } from 'next/server'; import { GoogleGenerativeAI } from '@google/generative-ai'; -// Initialize the Google Generative AI SDK const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || ''); -export async function POST(req: Request) { +export async function POST(request: Request) { try { - const body = await req.json(); - const { mode, stats } = body; + const { commits, prsMerged, issuesClosed } = await request.json(); - if (!mode || !stats) { - return NextResponse.json( - { error: 'Mode (roast/hype) and user stats are required.' }, - { status: 400 } - ); - } - - const model = genAI.getGenerativeModel({ model: 'gemini-2.5-flash' }); - - let systemInstruction = ''; - if (mode === 'roast') { - systemInstruction = `You are a hilariously brutal, sarcastic senior developer reviewing a junior's code stats. Roast their coding habits, commit streaks, or languages used based on the provided stats. Keep it strictly safe for work (SFW), funny, and under 3 sentences. No cursing.`; - } else if (mode === 'hype') { - systemInstruction = `You are the ultimate enthusiastic developer hype-man. Look at the user's coding stats and hype them up! Make them feel like a 10x coding god. Keep it energetic, modern, and under 3 sentences.`; - } else { - return NextResponse.json({ error: 'Invalid mode.' }, { status: 400 }); + if (!process.env.GEMINI_API_KEY) { + return NextResponse.json({ error: 'Gemini API key not configured.' }, { status: 500 }); } + const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); const prompt = ` - ${systemInstruction} - - User Stats: - - Commits this week: ${stats.commits || 0} - - Top Languages: ${stats.languages?.join(', ') || 'None'} - - Merged PRs: ${stats.mergedPRs || 0} - - Failed Goals: ${stats.failedGoals || 0} - - Give me the ${mode}! + You are an expert developer assistant. Write a concise, professional weekly standup update based on: + Commits: ${commits}, PRs: ${prsMerged}, Issues: ${issuesClosed} + Rules: Under 4 sentences, professional tone, Slack-ready format, use emojis. DO NOT hallucinate details. `; const result = await model.generateContent(prompt); - const responseText = result.response.text(); + return NextResponse.json({ standup: result.response.text() }, { status: 200 }); - return NextResponse.json({ message: responseText.trim() }); } catch (error) { - console.error('Gemini API Error:', error); - return NextResponse.json( - { error: 'Failed to generate response. Please try again.' }, - { status: 500 } - ); + return NextResponse.json({ error: 'Failed to generate update.' }, { status: 500 }); } -} \ No newline at end of file +} diff --git a/src/components/WeeklyStandupWidget.tsx b/src/components/WeeklyStandupWidget.tsx new file mode 100644 index 000000000..8f45288f5 --- /dev/null +++ b/src/components/WeeklyStandupWidget.tsx @@ -0,0 +1,38 @@ +'use client'; +import { useState } from 'react'; + +export default function WeeklyStandupWidget({ commits, prsMerged, issuesClosed }: { commits: number, prsMerged: number, issuesClosed: number }) { + const [standup, setStandup] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const handleGenerate = async () => { + setIsLoading(true); + try { + const res = await fetch('/api/ai/standup', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ commits, prsMerged, issuesClosed }), + }); + const data = await res.json(); + if (res.ok) setStandup(data.standup); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+

Weekly Standup Generator 🤖

+ +
+ {standup && ( +
+

{standup}

+
+ )} +
+ ); +} From b45648385332f1f8f78e0c98d366e6f329d95688 Mon Sep 17 00:00:00 2001 From: Kavuru Buvanesh <169604812+Kavurubuvanesh@users.noreply.github.com> Date: Sat, 27 Jun 2026 12:28:59 +0530 Subject: [PATCH 2/2] feat(dashboard): add copy to clipboard and error handling to standup widget --- src/components/WeeklyStandupWidget.tsx | 43 +++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/components/WeeklyStandupWidget.tsx b/src/components/WeeklyStandupWidget.tsx index 8f45288f5..508333540 100644 --- a/src/components/WeeklyStandupWidget.tsx +++ b/src/components/WeeklyStandupWidget.tsx @@ -4,9 +4,14 @@ import { useState } from 'react'; export default function WeeklyStandupWidget({ commits, prsMerged, issuesClosed }: { commits: number, prsMerged: number, issuesClosed: number }) { const [standup, setStandup] = useState(''); const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [copied, setCopied] = useState(false); const handleGenerate = async () => { setIsLoading(true); + setError(null); + setCopied(false); + try { const res = await fetch('/api/ai/standup', { method: 'POST', @@ -14,23 +19,53 @@ export default function WeeklyStandupWidget({ commits, prsMerged, issuesClosed } body: JSON.stringify({ commits, prsMerged, issuesClosed }), }); const data = await res.json(); - if (res.ok) setStandup(data.standup); + + if (!res.ok) { + throw new Error(data.error || 'Failed to generate update'); + } + + setStandup(data.standup); + } catch (err: any) { + setError(err.message); } finally { setIsLoading(false); } }; + const handleCopy = () => { + navigator.clipboard.writeText(standup); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + return (

Weekly Standup Generator 🤖

-
+ + {error && ( +
+ {error} +
+ )} + {standup && ( -
-

{standup}

+
+

{standup}

+
)}