Skip to content
Open
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
Binary file removed .DS_Store
Binary file not shown.
32 changes: 12 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,27 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3

- name: Use Node.js 18.x
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18.x'
cache: 'npm'

- name: Cache node_modules
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Install dependencies
run: npm ci

- name: Lint (ESLint)
run: npm run lint

# We dont have any test yet
# - name: Run tests (Jest)
# What tests !
# - name: Run Tests
# run: npm test

- name: Build (Webpack)
run: npm run build

# This doesnt really work yet we need to figure this out
# - name: Package Firefox Extension
# run: npm run build:ext

- name: Generate docs (JSDoc)
run: npm run docs

# This doesnt work yet need to figure this out
# - name: Upload extension artifact
# uses: actions/upload-artifact@v3
# with:
# name: firefox-recap-extension
# path: web-ext-artifacts/*.zip

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -705,3 +705,4 @@ fabric.properties
# Exclude web-ext build artifacts
/web-ext-artifacts/
/*.zip
.DS_Store
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"dev": "webpack --mode development",
"build": "webpack --mode production",
"test": "jest --env=jsdom",
"build:ext": "npm run build && web-ext build --source-dir ./dist/"
"build:ext": "npm run build && web-ext build --overwrite-dest --source-dir ./dist/"
},
"repository": {
"type": "git",
Expand Down
43 changes: 23 additions & 20 deletions src/background/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,23 @@
*/

import { initDB } from './initdb.js';
import handlers from './handlers/index.js';
import handlers from './handlers/index.js'; // Keep the default import for window assignment

// Destructure the specific handlers needed for the message listener
const {
fetchAndStoreHistory,
getMostVisitedSites,
getVisitsPerHour,
getLabelCounts,
//getTimeSpentPerSite,
getCategoryTrends,
getCOCounts,
getDailyVisitCounts,
getRecencyFrequency,
getTransitionPatterns,
getUniqueWebsites
} = handlers;


/**
* Initialize the extension’s database on startup.
Expand Down Expand Up @@ -54,10 +70,11 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
return true;
}

if (action === "getTimeSpentPerSite") {
getTimeSpentPerSite(days, limit).then(sendResponse);
return true;
}
// Note: 'getTimeSpentPerSite' is not defined in handlers/index.js
// if (action === "getTimeSpentPerSite") {
// getTimeSpentPerSite(days, limit).then(sendResponse);
// return true;
// }

if (action === "getCategoryTrends") {
getCategoryTrends(days).then(sendResponse);
Expand Down Expand Up @@ -91,19 +108,5 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {

console.warn("[Background] No handler for action:", action);
sendResponse(null);
return true;
return true; // Keep true here for async sendResponse
});


/**
* Expose background handler functions on the global `window` object.
*
* This allows you to call e.g.
* ```
* getMostVisitedSites(7).then(console.log)
* ```
* directly from the console for debugging or ad‐hoc testing.
*
* @type {Object.<string, Function>}
*/
Object.assign(window, handlers);
2 changes: 1 addition & 1 deletion src/background/services/ml.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export async function classifyURLAndTitle(
console.log('ML classify:', textToClassify);
const result = await mlApi.runEngine({
args: [textToClassify],
options: { top_k: null },
options: { top_k: null }, // mutli-label classification we apply threshold later this might be better at 2
});

const mapped = result
Expand Down
12 changes: 9 additions & 3 deletions src/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Firefox Recap",
"version": "2.0.0",
"version": "1.0.0",
"description": "Categorize and analyze browsing history for productivity insights.",
"permissions": [
"history",
Expand All @@ -18,7 +18,7 @@
"scripts": [
"background.js"
],
"persistent": false
"persistent": true
},
"browser_action": {
"default_popup": "popup.html",
Expand All @@ -32,7 +32,13 @@
"128": "assets/icon128.png"
},
"web_accessible_resources": [
"recap.html"
"recap.html",
"assets/videos/*.mp4"
],
"browser_specific_settings": {
"gecko": {
"id": "firefoxrecap@gmail.com"
}
},
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'; connect-src https://big.oisd.nl blob:;"
}
66 changes: 36 additions & 30 deletions src/popup/SlideShow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FaArrowRight, FaArrowLeft } from 'react-icons/fa';
import promptsData from "./prompts.json";
import RadarCategoryChart from './RadarCategoryChart';
import TimeOfDayHistogram from './TimeOfDayHistogram';
import WavyText from './WavyText';
import CategoryTrendsLineChart from './CategoryTrendsLineChart';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer as LineContainer } from 'recharts';

Expand All @@ -17,11 +18,13 @@ const safeCallBackground = async (action, payload = {}) => {
}
};


const SlideShow = ({ setView, timeRange }) => {
const [slides, setSlides] = useState([]);
const [index, setIndex] = useState(0);
const [loading, setLoading] = useState(true);
const [progress, setProgress] = useState(0);
const [notEnoughData, setNotEnoughData] = useState(false);
const videoRef = useRef(null);

const backgroundVideos = [
Expand Down Expand Up @@ -53,7 +56,7 @@ const SlideShow = ({ setView, timeRange }) => {
return array;
};

useEffect(() => {
useEffect(() => {
const loadSlides = async () => {
setLoading(true);
const daysMap = { day: 1, week: 7, month: 30 };
Expand Down Expand Up @@ -81,6 +84,16 @@ const SlideShow = ({ setView, timeRange }) => {
});

const totalUnique = await safeCallBackground("getUniqueWebsites", { days });

// see if theres any data, if not skip slides
if (!totalUnique || totalUnique === 0) {
console.log("[SlideShow] Not enough data (totalUnique=0).");
setNotEnoughData(true);
setLoading(false);
setProgress(100);
return;
}

slides.push({
id: 'totalWebsites',
video: videos[2],
Expand Down Expand Up @@ -179,12 +192,12 @@ const SlideShow = ({ setView, timeRange }) => {
video: videos[7],
prompt: pickPrompt("recapOutro", { x: timeRangeMap[timeRange] })
});

setSlides(slides);
setNotEnoughData(false);
setLoading(false);
setProgress(100);
};

loadSlides();
}, [timeRange]);

Expand All @@ -207,42 +220,37 @@ const SlideShow = ({ setView, timeRange }) => {
}, [index]);

useEffect(() => {
if (loading || notEnoughData) return;

const timer = setTimeout(() => {
setIndex(prev => (prev < slides.length - 1 ? prev + 1 : prev));
}, 5000);

return () => clearTimeout(timer);
}, [index, slides.length]);
}, [index, slides.length, loading, notEnoughData]);

// 🚀 LOADING SCREEN while slides are being fetched
// LOADING SCREEN
if (loading || progress < 100) {
return (
<div style={{
height: '100vh',
background: 'black',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
color: 'white'
}}>
<h1 style={{ marginBottom: '20px' }}>Preparing your recap...</h1>
<div style={{
width: '80%',
height: '8px',
backgroundColor: '#555',
borderRadius: '5px',
overflow: 'hidden'
}}>
<div style={{
width: `${progress}%`,
height: '100%',
backgroundColor: '#00C853',
transition: 'width 0.5s ease-in-out'
}}></div>
<div className="loading-screen">
<div className="center-container">
<WavyText text="Preparing your recap..." />
<div className="progress-bar">
<div className="progress-bar-fill" style={{ width: `${progress}%` }}></div>
</div>
</div>
</div>
);
}

if (notEnoughData) {
return (
<div style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', padding: '2rem', boxSizing: 'border-box' }}>
<h1 style={{ color: '#fff', textAlign: 'center', marginTop: '35vh' }}>Not enough browsing history yet. Your recap will be ready once you’ve explored a bit more!</h1>
</div>
);
}


// 🚀 SLIDESHOW UI after loading
return (
Expand All @@ -251,8 +259,6 @@ const SlideShow = ({ setView, timeRange }) => {
{slides[index]?.video && <source src={slides[index].video} type="video/mp4" />}
</video>

<button onClick={() => setView('home')} style={{ position: 'absolute', top: '10px', right: '10px', fontSize: '40px', border: 'none', background: 'transparent', color: '#fff', cursor: 'pointer' }}>×</button>

<div style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', padding: '2rem', boxSizing: 'border-box' }}>
{slides[index]?.chart ? (
<>
Expand Down
33 changes: 33 additions & 0 deletions src/popup/WavyText.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import './popup.css';


const WavyText = ({ text }) => {
return (
<div className="wavy-text">
{text.split(/(\s+)/).map((segment, index) => {
if (segment.trim() === '') {
// Render spaces without animation
return <span key={index}>{segment}</span>;
} else {
// Render each character in the word with animation
return (
<span key={index}>
{segment.split('').map((char, charIndex) => (
<span
key={charIndex}
className="wavy-char"
style={{ '--i': charIndex }}
>
{char}
</span>
))}
</span>
);
}
})}
</div>
);
};

export default WavyText;
Loading