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
28 changes: 14 additions & 14 deletions .firebase/hosting.YnVpbGQ.cache
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
robots.txt,1752820012049,bfe106a3fb878dc83461c86818bf74fc1bdc7f28538ba613cd3e775516ce8b49
prodhub.svg,1754019485965,c2c8cf818dc05dc535464f2334c4c6049749c2a541c7b8d4ff246e334c89c729
manifest.json,1752820011412,ee04fb47e525c67d8424ffe9b4d8a8a24e434504478afca4e0ca602146836d4c
logo512.png,1752820011921,212b102aa09e51b3b3e06647e81f7801a61333e171f6582e8124379aabccb41d
logo192.png,1752820011825,79e2b749561016bc8af300ea19f48347ceed3cb1a54f48ae456172eca45e08f0
favicorn.svg,1754024730579,e80dddc27b301efdf9e44c2df1952423ea4b443d9ef1fe22f4bbc3797a0f7a73
favicon.ico,1752820010545,27edce7be5922cf0bef7d4136f69b5bfbdd5bf8c13c7b026f71187d41a00aa7d
index.html,1754028003818,57e6789a3d329598a44645286795982fa8b73e8015c81a7fdc29bbf1aa8689a8
asset-manifest.json,1754028003818,a08001f483f26f26d0956c310d6bf864cf2d16aff7f6e6b724572a794e98401e
static/js/main.704858e1.js.LICENSE.txt,1754028003834,016d7efb946774a48216c296cff20c2b87a782bac74ad8d5a1808a48879486df
static/css/main.6ba6e54a.css.map,1754028003834,a7381dc3fa64bc0f389db7aab40bc58e27d99abb5081ac9475c6e0333743e40e
static/css/main.6ba6e54a.css,1754028003834,e623b62e114b105d31402c3a35740ed4c6105f633ac219018b4f35214af3448f
static/js/main.704858e1.js,1754028003834,557517fda1c252521658c8a60cc03d4b8320e37752b174c5fb520b1da3e29c28
static/js/main.704858e1.js.map,1754028003834,defd1375fe3794f16bd6317f41672b1950d32b233ae79d51aeaa93863c1d816c
favicorn.svg,1755761307597,e80dddc27b301efdf9e44c2df1952423ea4b443d9ef1fe22f4bbc3797a0f7a73
asset-manifest.json,1769145784811,7003c3de58911ea083c6b4c996323b85197ee04ff75892bc1d4c0f641015a522
robots.txt,1755761307597,b2090cf9761ef60aa06e4fab97679bd43dfa5e5df073701ead5879d7c68f1ec5
manifest.json,1755761307597,4368aeaf848ae2e048765562c289452f33ad2a175c4b1951ea8bdf2ada0d5b10
static/js/main.ed1316f0.js.LICENSE.txt,1769145784829,016d7efb946774a48216c296cff20c2b87a782bac74ad8d5a1808a48879486df
index.html,1769145784811,8bc49c33b64813d87cf40385c7a8716995325053ca57701670095b2d5687e12b
static/css/main.4409151e.css.map,1769145784827,7dd7f69674e3cf47346aebcac1862d1851d6f1e9fb45159a938e2918f8ee8cfc
logo192.png,1755761307597,79e2b749561016bc8af300ea19f48347ceed3cb1a54f48ae456172eca45e08f0
static/css/main.4409151e.css,1769145784827,5d4ab4f8c7df3fafa8c4bcbf5364d12f6628b371e2cccd12c5d5e03252317ccc
favicon.ico,1755761307597,27edce7be5922cf0bef7d4136f69b5bfbdd5bf8c13c7b026f71187d41a00aa7d
logo512.png,1755761307597,212b102aa09e51b3b3e06647e81f7801a61333e171f6582e8124379aabccb41d
prodhub.svg,1755761307597,c9ccf834f48ca21eaeda0f5566073e9f6757b1e14aaf82591d5afd1486ac39ba
static/js/main.ed1316f0.js,1769145784829,9b44dbd94b84b0732dbff34210e2d2e9a9ab671ba9f29182eed7949187977881
static/js/main.ed1316f0.js.map,1769145784830,438f6987590f4ffea01fb79415dd8fe340f043f1ae9aa7cbc0c8ac4975d8779c
9 changes: 6 additions & 3 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"projects": {
"default": "my-productivity-hub-5a3ba"
}
}
"default": "my-productivity-hub-5a3ba",
"staging": "my-productivity-hub-5a3ba"
},
"targets": {},
"etags": {}
}
16 changes: 4 additions & 12 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { initializeApp } from 'firebase/app';
import { getAuth, onAuthStateChanged, GoogleAuthProvider, signInWithPopup, signOut, createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth';
import { getFirestore, collection, doc, addDoc, updateDoc, deleteDoc, onSnapshot, query, where, getDocs, writeBatch } from 'firebase/firestore';
import { Book, Calendar, CheckSquare, ChevronDown, ChevronRight, Clock, Edit2, Flame, Info, LogOut, Plus, Repeat, Save, Sparkles, Tag, Trash2, TrendingUp, X } from 'lucide-react';
import { callGeminiWithRetry } from './services/geminiService';

// --- Firebase Configuration ---
const firebaseConfig = {
Expand Down Expand Up @@ -478,10 +479,7 @@ function Dashboard({ projects, tasks, goals, onViewChange, syncedEvents }) {
const payload = { contents: [{ role: "user", parts: [{ text: prompt }] }] };

try {
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
const result = await response.json();
const result = await callGeminiWithRetry(payload, apiKey);

if (result.candidates && result.candidates[0].content.parts[0].text) {
setDailyPlan(result.candidates[0].content.parts[0].text);
Expand Down Expand Up @@ -677,10 +675,7 @@ function ProjectDetail({ project, allTasks, syncedEvents }) {

const payload = { contents: [{ role: "user", parts: [{ text: prompt }] }], generationConfig: { responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { tasks: { type: "ARRAY", items: { type: "OBJECT", properties: { title: { type: "STRING" }, priority: { type: "STRING", enum: ["High", "Medium", "Low"] } }, required: ["title", "priority"] } } }, required: ["tasks"] } } };
try {
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
if (!response.ok) throw new Error(`API request failed with status ${response.status}`);
const result = await response.json();
const result = await callGeminiWithRetry(payload, apiKey);
if (result.candidates && result.candidates[0].content.parts[0].text) {
const generated = JSON.parse(result.candidates[0].content.parts[0].text);
if (generated.tasks && generated.tasks.length > 0) {
Expand Down Expand Up @@ -1389,10 +1384,7 @@ function GoalPlannerModal({ isOpen, onClose, userId }) {
};

try {
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
const result = await response.json();
const result = await callGeminiWithRetry(payload, apiKey);

if (result.candidates && result.candidates[0].content.parts[0].text) {
const plan = JSON.parse(result.candidates[0].content.parts[0].text);
Expand Down
16 changes: 4 additions & 12 deletions src/App2.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { initializeApp } from 'firebase/app';
import { getAuth, onAuthStateChanged, GoogleAuthProvider, signInWithPopup, signOut, createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth';
import { getFirestore, collection, doc, addDoc, updateDoc, deleteDoc, onSnapshot, query, where, getDocs, writeBatch } from 'firebase/firestore';
import { Book, Calendar, CheckSquare, ChevronDown, ChevronRight, Clock, Edit2, Flame, Info, LogOut, Plus, Repeat, Save, Sparkles, Tag, Trash2, TrendingUp, X } from 'lucide-react';
import { callGeminiWithRetry } from './services/geminiService';

// --- Firebase Configuration ---
// It's recommended to store these in environment variables
Expand Down Expand Up @@ -468,10 +469,7 @@ function Dashboard({ projects, tasks, onViewChange, syncedEvents }) {
const payload = { contents: [{ role: "user", parts: [{ text: prompt }] }] };

try {
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
const result = await response.json();
const result = await callGeminiWithRetry(payload, apiKey);

if (result.candidates && result.candidates[0].content.parts[0].text) {
setDailyPlan(result.candidates[0].content.parts[0].text);
Expand Down Expand Up @@ -622,10 +620,7 @@ function ProjectDetail({ project, allTasks, syncedEvents }) {

const payload = { contents: [{ role: "user", parts: [{ text: prompt }] }], generationConfig: { responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { tasks: { type: "ARRAY", items: { type: "OBJECT", properties: { title: { type: "STRING" }, priority: { type: "STRING", enum: ["High", "Medium", "Low"] } }, required: ["title", "priority"] } } }, required: ["tasks"] } } };
try {
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
if (!response.ok) throw new Error(`API request failed with status ${response.status}`);
const result = await response.json();
const result = await callGeminiWithRetry(payload, apiKey);
if (result.candidates && result.candidates[0].content.parts[0].text) {
const generated = JSON.parse(result.candidates[0].content.parts[0].text);
if (generated.tasks && generated.tasks.length > 0) {
Expand Down Expand Up @@ -1334,10 +1329,7 @@ function GoalPlannerModal({ isOpen, onClose, userId }) {
};

try {
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
const result = await response.json();
const result = await callGeminiWithRetry(payload, apiKey);

if (result.candidates && result.candidates[0].content.parts[0].text) {
const plan = JSON.parse(result.candidates[0].content.parts[0].text);
Expand Down
6 changes: 2 additions & 4 deletions src/App_v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { initializeApp } from 'firebase/app';
import { getAuth, onAuthStateChanged, GoogleAuthProvider, signInWithPopup, signOut, createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth';
import { getFirestore, collection, doc, addDoc, updateDoc, deleteDoc, onSnapshot, query, where, getDocs, writeBatch } from 'firebase/firestore';
import { Book, Calendar, CheckSquare, Clock, Edit2, Flame, Info, LogOut, Plus, Repeat, Save, Sparkles, Tag, Trash2, TrendingUp, X } from 'lucide-react';
import { callGeminiWithRetry } from './services/geminiService';

// --- Firebase Configuration ---
const firebaseConfig = {
Expand Down Expand Up @@ -438,10 +439,7 @@ function ProjectDetail({ project, allTasks, syncedEvents }) {

const payload = { contents: [{ role: "user", parts: [{ text: prompt }] }], generationConfig: { responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { tasks: { type: "ARRAY", items: { type: "OBJECT", properties: { title: { type: "STRING" }, priority: { type: "STRING", enum: ["High", "Medium", "Low"] } }, required: ["title", "priority"] } } }, required: ["tasks"] } } };
try {
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
if (!response.ok) throw new Error(`API request failed with status ${response.status}`);
const result = await response.json();
const result = await callGeminiWithRetry(payload, apiKey);
if (result.candidates && result.candidates[0].content.parts[0].text) {
const generated = JSON.parse(result.candidates[0].content.parts[0].text);
if (generated.tasks && generated.tasks.length > 0) {
Expand Down
46 changes: 46 additions & 0 deletions src/services/geminiService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const GEMINI_MODEL = 'gemini-2.5-flash';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The Gemini model name gemini-2.5-flash appears to be incorrect. Google's publicly available models don't include a 2.5 version of flash. This will likely cause all API calls to fail with a 'model not found' error. Did you mean to use gemini-1.5-flash-latest?

Suggested change
const GEMINI_MODEL = 'gemini-2.5-flash';
const GEMINI_MODEL = 'gemini-1.5-flash-latest';


export const callGeminiWithRetry = async (payload, apiKey, maxRetries = 3) => {
let retries = 0;
while (retries < maxRetries) {
try {
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});

if (response.ok) {
return await response.json();
}

if (response.status === 429) {
retries++;
if (retries >= maxRetries) {
throw new Error("Rate limit exceeded. Please try again later.");
}
const waitTime = Math.pow(2, retries) * 1000 + Math.random() * 1000;
console.warn(`Gemini API 429: Retrying in ${Math.round(waitTime)}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
continue;
}

const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error?.message || `API request failed with status ${response.status}`);
} catch (error) {
if (error.message.includes("Rate limit exceeded") || (retries < maxRetries && error.name === 'TypeError')) {
// If it's a network error (TypeError) or we already handled 429, we might want to try again unless it's the last retry
if (error.message.includes("Rate limit exceeded")) throw error;

retries++;
const waitTime = Math.pow(2, retries) * 1000;
await new Promise(resolve => setTimeout(resolve, waitTime));
continue;
}
throw error;
}
Comment on lines +33 to +44
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error handling logic in this catch block is a bit complex and could be simplified for better readability. Additionally, the exponential backoff for retrying network errors (TypeError) is missing jitter. Jitter is important to prevent multiple clients from retrying simultaneously, and you've already correctly used it for handling 429 status codes. I've suggested a refactoring that simplifies the logic and adds jitter.

} catch (error) {
    // Only retry on network errors, and only if we have retries left.
    if (error.name !== 'TypeError' || retries >= maxRetries) {
        throw error;
    }

    retries++;
    // Added jitter to the wait time for network error retries.
    const waitTime = Math.pow(2, retries) * 1000 + Math.random() * 1000;
    console.warn(`Gemini API network error: Retrying in ${Math.round(waitTime)}ms...`);
    await new Promise(resolve => setTimeout(resolve, waitTime));
    continue;
}

}
};