jobbler commited on
Commit
2ecb595
·
1 Parent(s): 1d84519

feat: add FlowRead Entire Page functionality to Firefox extension

Browse files
firefox-extension/background.js CHANGED
@@ -9,14 +9,36 @@ browser.runtime.onInstalled.addListener(() => {
9
  title: "FlowRead Highlight",
10
  contexts: ["selection"]
11
  });
 
 
 
 
 
 
12
  });
13
 
14
  browser.contextMenus.onClicked.addListener((info, tab) => {
15
  if (info.menuItemId === "flowread-selection") {
16
- // We send a message to the content script asking it to grab the selection and replace it
17
  browser.tabs.sendMessage(tab.id, {
18
  action: "flowread_selection",
19
  text: info.selectionText
20
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
  });
 
9
  title: "FlowRead Highlight",
10
  contexts: ["selection"]
11
  });
12
+
13
+ browser.contextMenus.create({
14
+ id: "flowread-page",
15
+ title: "FlowRead Entire Page",
16
+ contexts: ["page"]
17
+ });
18
  });
19
 
20
  browser.contextMenus.onClicked.addListener((info, tab) => {
21
  if (info.menuItemId === "flowread-selection") {
22
+ // Send a message to the content script asking it to grab the selection and replace it
23
  browser.tabs.sendMessage(tab.id, {
24
  action: "flowread_selection",
25
  text: info.selectionText
26
  });
27
+ } else if (info.menuItemId === "flowread-page") {
28
+ browser.tabs.sendMessage(tab.id, {
29
+ action: "flowread_page"
30
+ });
31
+ }
32
+ });
33
+
34
+ browser.commands.onCommand.addListener((command) => {
35
+ if (command === "flowread-page") {
36
+ browser.tabs.query({active: true, currentWindow: true}, function(tabs) {
37
+ if (tabs[0]) {
38
+ browser.tabs.sendMessage(tabs[0].id, {
39
+ action: "flowread_page"
40
+ });
41
+ }
42
+ });
43
  }
44
  });
firefox-extension/content.js CHANGED
@@ -79,9 +79,118 @@ browser.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
79
  console.error(err);
80
  showToast("Error: Could not reach FlowRead API", 3000);
81
  }
 
 
82
  }
83
  });
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  // Grouping logic extracted from your frontend code
86
  function generateFlowReadHTML(currentTokens, threshold, useGradient) {
87
  let html = "";
 
79
  console.error(err);
80
  showToast("Error: Could not reach FlowRead API", 3000);
81
  }
82
+ } else if (request.action === "flowread_page") {
83
+ await processEntirePage();
84
  }
85
  });
86
 
87
+ async function processEntirePage() {
88
+ const settings = await browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'apiUrl']);
89
+ const threshold = settings.threshold !== undefined ? settings.threshold : 0.35;
90
+ const useGradient = settings.gradientMode || false;
91
+ const preprompt = settings.preprompt || "";
92
+ const apiUrl = settings.apiUrl || "http://127.0.0.1:8000";
93
+ const checkedLayers = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
94
+
95
+ // To prevent freezing the browser or overwhelming the API, process in batches
96
+ const walkerObj = document.createTreeWalker(
97
+ document.body,
98
+ NodeFilter.SHOW_TEXT,
99
+ {
100
+ acceptNode: function(node) {
101
+ const text = node.nodeValue.trim();
102
+ // Skip very short fragments
103
+ if (text.split(/\s+/).length < 5) {
104
+ return NodeFilter.FILTER_SKIP;
105
+ }
106
+
107
+ // Exclude specific parent tags
108
+ let p = node.parentNode;
109
+ const excludeTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'BUTTON', 'INPUT', 'TEXTAREA', 'CODE', 'PRE', 'NAV', 'HEADER', 'FOOTER', 'A', 'SELECT', 'OPTION', 'svg'];
110
+
111
+ while (p && p !== document.body) {
112
+ if (excludeTags.includes(p.tagName)) {
113
+ return NodeFilter.FILTER_REJECT;
114
+ }
115
+ if (p.classList && p.classList.contains('flowread-container')) {
116
+ return NodeFilter.FILTER_REJECT;
117
+ }
118
+ p = p.parentNode;
119
+ }
120
+
121
+ return NodeFilter.FILTER_ACCEPT;
122
+ }
123
+ }
124
+ );
125
+
126
+ const nodesToProcess = [];
127
+ let currentNode;
128
+ while (currentNode = walkerObj.nextNode()) {
129
+ // Exclude hidden elements dynamically
130
+ const element = currentNode.parentElement;
131
+ if (element) {
132
+ const style = window.getComputedStyle(element);
133
+ if (style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0') {
134
+ nodesToProcess.push(currentNode);
135
+ }
136
+ } else {
137
+ nodesToProcess.push(currentNode);
138
+ }
139
+ }
140
+
141
+ if (nodesToProcess.length === 0) {
142
+ showToast("No suitable text found on page.", 2000);
143
+ return;
144
+ }
145
+
146
+ // To prevent freezing the browser or overwhelming the API, process in batches
147
+ const batchSize = 3;
148
+ let processedCount = 0;
149
+
150
+ for (let i = 0; i < nodesToProcess.length; i += batchSize) {
151
+ const batch = nodesToProcess.slice(i, i + batchSize);
152
+ showToast(`FlowRead analyzing page (${processedCount}/${nodesToProcess.length} blocks)...`, 0);
153
+
154
+ await Promise.all(batch.map(async (node) => {
155
+ const text = node.nodeValue;
156
+ if (!text.trim()) return;
157
+
158
+ try {
159
+ const response = await fetch(`${apiUrl}/analyze`, {
160
+ method: 'POST',
161
+ headers: { 'Content-Type': 'application/json' },
162
+ body: JSON.stringify({
163
+ text: text,
164
+ preprompt: preprompt,
165
+ layers: checkedLayers
166
+ })
167
+ });
168
+
169
+ if (!response.ok) return;
170
+ const data = await response.json();
171
+ if (!data.words) return;
172
+
173
+ const htmlString = generateFlowReadHTML(data.words, threshold, useGradient);
174
+
175
+ const container = document.createElement('span');
176
+ container.className = 'flowread-container';
177
+ container.innerHTML = htmlString;
178
+
179
+ if (node.parentNode) {
180
+ node.parentNode.replaceChild(container, node);
181
+ }
182
+
183
+ } catch (err) {
184
+ console.error("Batch error on node:", err);
185
+ }
186
+ }));
187
+
188
+ processedCount += batch.length;
189
+ }
190
+
191
+ showToast(`Done! Analyzed ${processedCount} blocks.`, 2000);
192
+ }
193
+
194
  // Grouping logic extracted from your frontend code
195
  function generateFlowReadHTML(currentTokens, threshold, useGradient) {
196
  let html = "";
firefox-extension/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
  "manifest_version": 3,
3
  "name": "FlowRead AI",
4
- "version": "1.0",
5
  "description": "Accelerate reading comprehension using LLM attention vectors.",
6
  "permissions": [
7
  "contextMenus",
@@ -20,6 +20,15 @@
20
  "default_popup": "popup.html",
21
  "default_title": "FlowRead AI Settings"
22
  },
 
 
 
 
 
 
 
 
 
23
  "content_scripts": [
24
  {
25
  "matches": ["<all_urls>"],
 
1
  {
2
  "manifest_version": 3,
3
  "name": "FlowRead AI",
4
+ "version": "1.1",
5
  "description": "Accelerate reading comprehension using LLM attention vectors.",
6
  "permissions": [
7
  "contextMenus",
 
20
  "default_popup": "popup.html",
21
  "default_title": "FlowRead AI Settings"
22
  },
23
+ "commands": {
24
+ "flowread-page": {
25
+ "suggested_key": {
26
+ "default": "Ctrl+Shift+F",
27
+ "mac": "Command+Shift+F"
28
+ },
29
+ "description": "FlowRead the entire page"
30
+ }
31
+ },
32
  "content_scripts": [
33
  {
34
  "matches": ["<all_urls>"],
firefox-extension/popup.html CHANGED
@@ -95,7 +95,8 @@
95
  <input type="text" id="api-url" placeholder="http://127.0.0.1:8000">
96
  <p class="help">URL of your FlowRead backend (Local or Hugging Face).</p>
97
 
98
- <button id="save-btn">Save Settings</button>
 
99
 
100
  <script src="popup.js"></script>
101
  </body>
 
95
  <input type="text" id="api-url" placeholder="http://127.0.0.1:8000">
96
  <p class="help">URL of your FlowRead backend (Local or Hugging Face).</p>
97
 
98
+ <button id="save-btn" style="margin-bottom: 10px;">Save Settings</button>
99
+ <button id="page-btn" style="background: #4f46e5;">FlowRead Entire Page</button>
100
 
101
  <script src="popup.js"></script>
102
  </body>
firefox-extension/popup.js CHANGED
@@ -5,6 +5,7 @@ document.addEventListener('DOMContentLoaded', () => {
5
  const prepromptInput = document.getElementById('preprompt');
6
  const apiUrlInput = document.getElementById('api-url');
7
  const saveBtn = document.getElementById('save-btn');
 
8
 
9
  // Load existing settings
10
  browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'apiUrl'], (res) => {
@@ -40,4 +41,13 @@ document.addEventListener('DOMContentLoaded', () => {
40
  setTimeout(() => saveBtn.textContent = 'Save Settings', 1500);
41
  });
42
  });
 
 
 
 
 
 
 
 
 
43
  });
 
5
  const prepromptInput = document.getElementById('preprompt');
6
  const apiUrlInput = document.getElementById('api-url');
7
  const saveBtn = document.getElementById('save-btn');
8
+ const pageBtn = document.getElementById('page-btn');
9
 
10
  // Load existing settings
11
  browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'apiUrl'], (res) => {
 
41
  setTimeout(() => saveBtn.textContent = 'Save Settings', 1500);
42
  });
43
  });
44
+
45
+ pageBtn.addEventListener('click', () => {
46
+ browser.tabs.query({active: true, currentWindow: true}, (tabs) => {
47
+ if (tabs[0]) {
48
+ browser.tabs.sendMessage(tabs[0].id, { action: "flowread_page" });
49
+ window.close(); // Close popup
50
+ }
51
+ });
52
+ });
53
  });
flowread-extension.zip CHANGED
Binary files a/flowread-extension.zip and b/flowread-extension.zip differ