jobbler commited on
Commit
1d84519
·
1 Parent(s): 275e3b0

docs: Add local and HF deployment options and Firefox extension instructions

Browse files
README.md CHANGED
@@ -36,7 +36,13 @@ We chose **Gemma 2B** because it offers an incredible balance of deep semantic u
36
  * **Frontend:** Pure HTML/JS/CSS with a minimalist, distraction-free "academic paper" aesthetic.
37
  * **Database:** SQLite for storing A/B test results.
38
 
39
- ## How to Run Locally
 
 
 
 
 
 
40
 
41
  1. **Install Dependencies:**
42
  ```bash
@@ -48,11 +54,40 @@ We chose **Gemma 2B** because it offers an incredible balance of deep semantic u
48
  huggingface-cli login
49
  ```
50
  3. **Start the Server:**
 
51
  ```bash
52
- python main.py
53
  ```
54
  4. **Open the Web App:**
55
- Navigate to `http://localhost:7860/static/index.html` in your browser.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
  ## The User Study (Help Us Prove It Works!)
58
- When you deploy this project to Hugging Face Spaces, users can take the built-in **2-Minute Study**. The system randomly assigns users to read plain text vs. FlowRead text, times their reading speed, and tests their comprehension. The global statistics dashboard proves the real-world impact of Gemma-powered saliency!
 
36
  * **Frontend:** Pure HTML/JS/CSS with a minimalist, distraction-free "academic paper" aesthetic.
37
  * **Database:** SQLite for storing A/B test results.
38
 
39
+ ## How to Deploy and Run
40
+
41
+ FlowRead AI can be run locally on your machine or deployed directly to Hugging Face Spaces.
42
+
43
+ ### Option 1: Run Locally (Recommended for Development)
44
+
45
+ Running locally allows you to use your own GPU (like Apple Silicon MPS or Nvidia CUDA) for significantly faster processing.
46
 
47
  1. **Install Dependencies:**
48
  ```bash
 
54
  huggingface-cli login
55
  ```
56
  3. **Start the Server:**
57
+ You can start the FastAPI server using Uvicorn:
58
  ```bash
59
+ uvicorn main:app --reload --host 0.0.0.0 --port 8000
60
  ```
61
  4. **Open the Web App:**
62
+ Navigate to `http://localhost:8000` in your browser.
63
+
64
+ ### Option 2: Deploy to Hugging Face Spaces (Docker)
65
+
66
+ This repository is already configured with a `Dockerfile` and the correct YAML frontmatter to be deployed directly as a Hugging Face Docker Space.
67
+
68
+ 1. Create a new **Docker Space** on Hugging Face.
69
+ 2. Add your Hugging Face Token as a Repository Secret named `HF_TOKEN`. This is required to download the gated Gemma 2B model.
70
+ 3. Push this repository to your Space.
71
+ 4. The Space will automatically build the Docker image and start the FastAPI server. The SQLite database (`study.db`) is safely written to the `/tmp` or `/data` directory to avoid read-only filesystem errors.
72
+
73
+ ## FlowRead Firefox Extension
74
+
75
+ You can use FlowRead directly on any website using the included Firefox Extension! The extension communicates with your FlowRead backend (either Local or Hugging Face) to highlight text directly on the page.
76
+
77
+ ### How to Install the Extension:
78
+ 1. Locate the `flowread-extension.zip` file in this repository (or the `firefox-extension` folder).
79
+ 2. Open Firefox and navigate to `about:debugging`.
80
+ 3. Click **"This Firefox"** on the left sidebar.
81
+ 4. Click **"Load Temporary Add-on..."**.
82
+ 5. Select the `manifest.json` file inside the `firefox-extension` folder (or select the `.zip` file).
83
+
84
+ ### How to Configure and Use:
85
+ 1. **Set your Backend URL:** Click the FlowRead icon in your Firefox toolbar. In the settings popup, set the **Backend API URL**:
86
+ - If running locally: Enter `http://127.0.0.1:8000`
87
+ - If using Hugging Face: Enter your Space URL (e.g., `https://your-username-flowread.hf.space`)
88
+ 2. **Read:** Highlight any paragraph of text on any website.
89
+ 3. **FlowRead It:** Right-click the highlighted text and select **"FlowRead Highlight"**.
90
+ 4. The text on the page will dynamically transform using Gemma's attention vectors!
91
 
92
  ## The User Study (Help Us Prove It Works!)
93
+ The web application includes a built-in **3-Minute Study**. The system randomly assigns users to read plain text, FlowRead (bolding), or FlowRead (gradient) text. It times their reading speed, tests their comprehension, and asks for their preference. The global statistics dashboard proves the real-world impact of Gemma-powered saliency!
firefox-extension/background.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // For cross-browser compatibility (Firefox uses browser, Chrome uses chrome)
2
+ if (typeof browser === "undefined") {
3
+ var browser = chrome;
4
+ }
5
+
6
+ browser.runtime.onInstalled.addListener(() => {
7
+ browser.contextMenus.create({
8
+ id: "flowread-selection",
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
+ });
firefox-extension/content.css ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .flowread-token {
2
+ transition: font-weight 0.2s;
3
+ font-family: inherit;
4
+ color: inherit;
5
+ }
6
+
7
+ .flowread-highlighted {
8
+ font-weight: 800; /* Extra bold */
9
+ color: inherit;
10
+ }
11
+
12
+ .flowread-semi-highlighted {
13
+ font-weight: 600;
14
+ color: inherit;
15
+ }
16
+
17
+ /* A small toast/loading indicator */
18
+ #flowread-toast {
19
+ position: fixed;
20
+ bottom: 20px;
21
+ right: 20px;
22
+ background: #292524;
23
+ color: #fdfbf7;
24
+ padding: 12px 24px;
25
+ border-radius: 8px;
26
+ font-family: system-ui, sans-serif;
27
+ font-weight: bold;
28
+ z-index: 999999;
29
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
30
+ opacity: 0;
31
+ transition: opacity 0.3s;
32
+ pointer-events: none;
33
+ }
34
+
35
+ #flowread-toast.show {
36
+ opacity: 1;
37
+ }
firefox-extension/content.js ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ if (typeof browser === "undefined") {
2
+ var browser = chrome;
3
+ }
4
+
5
+ // Ensure the toast element exists
6
+ function ensureToast() {
7
+ let toast = document.getElementById('flowread-toast');
8
+ if (!toast) {
9
+ toast = document.createElement('div');
10
+ toast.id = 'flowread-toast';
11
+ document.body.appendChild(toast);
12
+ }
13
+ return toast;
14
+ }
15
+
16
+ function showToast(message, duration = 3000) {
17
+ const toast = ensureToast();
18
+ toast.textContent = message;
19
+ toast.classList.add('show');
20
+ if (duration > 0) {
21
+ setTimeout(() => {
22
+ toast.classList.remove('show');
23
+ }, duration);
24
+ }
25
+ }
26
+
27
+ browser.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
28
+ if (request.action === "flowread_selection") {
29
+ const selectedText = window.getSelection().toString();
30
+ if (!selectedText.trim()) return;
31
+
32
+ showToast("Analyzing text with FlowRead AI...", 0);
33
+
34
+ // Save the selection range so we can replace it later
35
+ const selection = window.getSelection();
36
+ if (selection.rangeCount === 0) return;
37
+ const range = selection.getRangeAt(0);
38
+
39
+ // Get user settings
40
+ const settings = await browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'apiUrl']);
41
+ const threshold = settings.threshold !== undefined ? settings.threshold : 0.35;
42
+ const useGradient = settings.gradientMode || false;
43
+ const preprompt = settings.preprompt || "";
44
+ const apiUrl = settings.apiUrl || "http://127.0.0.1:8000";
45
+ // Default to middle layers just like the playground
46
+ const checkedLayers = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
47
+
48
+ try {
49
+ // Connect to the configured API Space
50
+ const response = await fetch(`${apiUrl}/analyze`, {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify({
54
+ text: selectedText,
55
+ preprompt: preprompt,
56
+ layers: checkedLayers
57
+ })
58
+ });
59
+
60
+ if (!response.ok) throw new Error('API error');
61
+
62
+ const data = await response.json();
63
+ const currentTokens = data.words || [];
64
+
65
+ // Reconstruct the HTML
66
+ const htmlString = generateFlowReadHTML(currentTokens, threshold, useGradient);
67
+
68
+ // Replace text
69
+ range.deleteContents();
70
+
71
+ const container = document.createElement('span');
72
+ container.className = 'flowread-container';
73
+ container.innerHTML = htmlString;
74
+ range.insertNode(container);
75
+
76
+ showToast("Done!", 1500);
77
+
78
+ } catch (err) {
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 = "";
88
+
89
+ let words = [];
90
+ let currentWordTokens = [];
91
+ let currentWordMaxScore = 0;
92
+
93
+ currentTokens.forEach((item, index) => {
94
+ if (index === 0 && (item.token.includes('<bos>') || item.word.includes('<bos>'))) return;
95
+
96
+ const isWhitespace = item.token.trim() === '';
97
+ const prevIsWhitespace = currentWordTokens.length > 0 && currentWordTokens[currentWordTokens.length - 1].token.trim() === '';
98
+
99
+ if (item.token.startsWith(' ') || (currentWordTokens.length > 0 && isWhitespace !== prevIsWhitespace)) {
100
+ if (currentWordTokens.length > 0) {
101
+ words.push({ tokens: currentWordTokens, maxScore: currentWordMaxScore });
102
+ }
103
+ currentWordTokens = [item];
104
+ currentWordMaxScore = item.score;
105
+ } else {
106
+ currentWordTokens.push(item);
107
+ currentWordMaxScore = Math.max(currentWordMaxScore, item.score);
108
+ }
109
+ });
110
+ if (currentWordTokens.length > 0) {
111
+ words.push({ tokens: currentWordTokens, maxScore: currentWordMaxScore });
112
+ }
113
+
114
+ // Render words
115
+ words.forEach(wordObj => {
116
+ const isWordHighlighted = wordObj.maxScore >= threshold;
117
+
118
+ wordObj.tokens.forEach(item => {
119
+ let styleAttr = '';
120
+ let classNames = 'flowread-token';
121
+
122
+ if (useGradient) {
123
+ const opacity = 0.4 + (item.score * 0.6);
124
+ const weight = 400 + Math.round(item.score * 400);
125
+ styleAttr = `opacity: ${opacity}; font-weight: ${weight}; color: inherit;`;
126
+ } else {
127
+ if (isWordHighlighted) {
128
+ classNames += ' flowread-highlighted';
129
+ }
130
+ }
131
+
132
+ const safeText = String(item.token || "").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>");
133
+
134
+ html += `<span class="${classNames}" ${styleAttr ? `style="${styleAttr}"` : ''}>${safeText}</span>`;
135
+ });
136
+ });
137
+
138
+ return html;
139
+ }
firefox-extension/icon.png ADDED
firefox-extension/manifest.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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",
8
+ "storage",
9
+ "activeTab",
10
+ "scripting"
11
+ ],
12
+ "host_permissions": [
13
+ "https://jobbler-flowread.hf.space/*",
14
+ "<all_urls>"
15
+ ],
16
+ "background": {
17
+ "scripts": ["background.js"]
18
+ },
19
+ "action": {
20
+ "default_popup": "popup.html",
21
+ "default_title": "FlowRead AI Settings"
22
+ },
23
+ "content_scripts": [
24
+ {
25
+ "matches": ["<all_urls>"],
26
+ "js": ["content.js"],
27
+ "css": ["content.css"]
28
+ }
29
+ ]
30
+ }
firefox-extension/popup.html ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>FlowRead Settings</title>
6
+ <style>
7
+ body {
8
+ font-family: system-ui, sans-serif;
9
+ width: 300px;
10
+ padding: 15px;
11
+ color: #292524;
12
+ background: #fdfbf7;
13
+ margin: 0;
14
+ }
15
+ h2 {
16
+ font-size: 1.25rem;
17
+ margin-top: 0;
18
+ margin-bottom: 15px;
19
+ color: #1c1917;
20
+ }
21
+ label {
22
+ display: block;
23
+ margin-bottom: 5px;
24
+ font-weight: 600;
25
+ color: #44403c;
26
+ font-size: 0.9rem;
27
+ }
28
+ input[type="range"] {
29
+ width: 100%;
30
+ margin-bottom: 15px;
31
+ }
32
+ input[type="text"] {
33
+ width: 100%;
34
+ padding: 8px;
35
+ box-sizing: border-box;
36
+ margin-bottom: 15px;
37
+ border: 1px solid #d6d3d1;
38
+ border-radius: 4px;
39
+ font-family: inherit;
40
+ }
41
+ .checkbox-group {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 8px;
45
+ margin-bottom: 15px;
46
+ }
47
+ .checkbox-group input {
48
+ margin: 0;
49
+ width: 16px;
50
+ height: 16px;
51
+ cursor: pointer;
52
+ }
53
+ .checkbox-group label {
54
+ margin: 0;
55
+ cursor: pointer;
56
+ }
57
+ p.help {
58
+ font-size: 0.8rem;
59
+ color: #78716c;
60
+ margin-top: -10px;
61
+ margin-bottom: 15px;
62
+ }
63
+ button {
64
+ width: 100%;
65
+ padding: 10px;
66
+ background: #292524;
67
+ color: #fff;
68
+ border: none;
69
+ border-radius: 4px;
70
+ font-weight: bold;
71
+ cursor: pointer;
72
+ }
73
+ button:hover {
74
+ background: #44403c;
75
+ }
76
+ </style>
77
+ </head>
78
+ <body>
79
+ <h2>FlowRead Settings</h2>
80
+
81
+ <label for="threshold">Threshold: <span id="threshold-val">0.35</span></label>
82
+ <input type="range" id="threshold" min="0" max="1" step="0.01" value="0.35">
83
+
84
+ <div class="checkbox-group">
85
+ <input type="checkbox" id="gradient-mode">
86
+ <label for="gradient-mode">Gradient Mode</label>
87
+ </div>
88
+ <p class="help">Maps attention scores to visual contrast dynamically.</p>
89
+
90
+ <label for="preprompt">Intent-Driven Reading</label>
91
+ <input type="text" id="preprompt" placeholder="e.g., Focus on numbers and dates">
92
+ <p class="help">Instruct the AI what to focus on before generating saliency.</p>
93
+
94
+ <label for="api-url">Backend API URL</label>
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>
102
+ </html>
firefox-extension/popup.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const thresholdInput = document.getElementById('threshold');
3
+ const thresholdVal = document.getElementById('threshold-val');
4
+ const gradientModeInput = document.getElementById('gradient-mode');
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) => {
11
+ if (res.threshold !== undefined) {
12
+ thresholdInput.value = res.threshold;
13
+ thresholdVal.textContent = parseFloat(res.threshold).toFixed(2);
14
+ }
15
+ if (res.gradientMode !== undefined) {
16
+ gradientModeInput.checked = res.gradientMode;
17
+ }
18
+ if (res.preprompt !== undefined) {
19
+ prepromptInput.value = res.preprompt;
20
+ }
21
+ apiUrlInput.value = res.apiUrl || "http://127.0.0.1:8000";
22
+ });
23
+
24
+ // Update threshold label
25
+ thresholdInput.addEventListener('input', (e) => {
26
+ thresholdVal.textContent = parseFloat(e.target.value).toFixed(2);
27
+ });
28
+
29
+ // Save settings
30
+ saveBtn.addEventListener('click', () => {
31
+ const settings = {
32
+ threshold: parseFloat(thresholdInput.value),
33
+ gradientMode: gradientModeInput.checked,
34
+ preprompt: prepromptInput.value.trim(),
35
+ apiUrl: apiUrlInput.value.trim().replace(/\/$/, '') // Remove trailing slash
36
+ };
37
+
38
+ browser.storage.local.set(settings).then(() => {
39
+ saveBtn.textContent = 'Saved!';
40
+ setTimeout(() => saveBtn.textContent = 'Save Settings', 1500);
41
+ });
42
+ });
43
+ });
flowread-extension.zip ADDED
Binary file (5.45 kB). View file