From 25244ebca5717c611bc97cacf94ceea68cd6fb17 Mon Sep 17 00:00:00 2001 From: SHIVANG <133517596+Shivang9983@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:19:24 +0530 Subject: [PATCH 1/2] feat(ai): add Gemini AI task description generator in TaskModal --- devboard/client/package-lock.json | 11 + devboard/client/package.json | 2 + .../client/src/components/Task/TaskModal.jsx | 82 +++- devboard/client/src/context/BoardContext.jsx | 51 ++- devboard/client/src/pages/Login.jsx | 61 ++- devboard/client/vite.config.js | 11 +- devboard/server/.env.example | 4 + devboard/server/index.js | 1 + devboard/server/package-lock.json | 394 ++++++++++++++++++ devboard/server/package.json | 1 + devboard/server/routes/ai.js | 31 ++ 11 files changed, 606 insertions(+), 43 deletions(-) create mode 100644 devboard/server/.env.example create mode 100644 devboard/server/routes/ai.js diff --git a/devboard/client/package-lock.json b/devboard/client/package-lock.json index 39a0327..3cbf7c2 100644 --- a/devboard/client/package-lock.json +++ b/devboard/client/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@hello-pangea/dnd": "^16.3.0", "axios": "^1.5.0", + "canvas-confetti": "^1.9.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.16.0", @@ -1153,6 +1154,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-confetti": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.4.tgz", + "integrity": "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==", + "license": "ISC", + "funding": { + "type": "donate", + "url": "https://www.paypal.me/kirilvatev" + } + }, "node_modules/character-entities": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", diff --git a/devboard/client/package.json b/devboard/client/package.json index 76ebef8..647d9fc 100644 --- a/devboard/client/package.json +++ b/devboard/client/package.json @@ -2,6 +2,7 @@ "name": "devboard-client", "version": "1.0.0", "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", @@ -10,6 +11,7 @@ "dependencies": { "@hello-pangea/dnd": "^16.3.0", "axios": "^1.5.0", + "canvas-confetti": "^1.9.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.16.0", diff --git a/devboard/client/src/components/Task/TaskModal.jsx b/devboard/client/src/components/Task/TaskModal.jsx index 8ac65ca..224a520 100644 --- a/devboard/client/src/components/Task/TaskModal.jsx +++ b/devboard/client/src/components/Task/TaskModal.jsx @@ -21,6 +21,45 @@ const TaskModal = ({ const [snippetLang, setSnippetLang] = useState("javascript"); const [loading, setLoading] = useState(false); + // AI Loading & Error States + const [isGenerating, setIsGenerating] = useState(false); + const [aiError, setAiError] = useState(""); + + const handleGenerateAI = async () => { + if (!form.title.trim()) { + setAiError("Please enter a task title first."); + return; + } + + setIsGenerating(true); + setAiError(""); + + try { + const res = await fetch("/api/ai/generate-description", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ title: form.title }), + }); + + const data = await res.json(); + + if (!res.ok) { + throw new Error(data.error || "Failed to generate description."); + } + + setForm((prev) => ({ + ...prev, + description: data.description, + })); + } catch (err) { + setAiError(err.message || "Error generating AI description."); + } finally { + setIsGenerating(false); + } + }; + const handleSave = async () => { if (!form.title.trim()) return; setLoading(true); @@ -39,6 +78,7 @@ const TaskModal = ({ }; await onSave(payload); setLoading(false); + onClose(); }; return ( @@ -61,17 +101,41 @@ const TaskModal = ({ type="text" placeholder="Task title *" value={form.title} - onChange={(e) => setForm({ ...form, title: e.target.value })} + onChange={(e) => { + setForm({ ...form, title: e.target.value }); + if (aiError) setAiError(""); + }} className="w-full bg-[var(--bg-primary)] border border-[var(--border-primary)] rounded-lg px-3 py-2 text-sm text-[#f0f0f0] placeholder-[#555] focus:outline-none focus:border-purple-500" /> -