docs: Add local and HF deployment options and Firefox extension instructions
Browse files- README.md +39 -4
- firefox-extension/background.js +22 -0
- firefox-extension/content.css +37 -0
- firefox-extension/content.js +139 -0
- firefox-extension/icon.png +0 -0
- firefox-extension/manifest.json +30 -0
- firefox-extension/popup.html +102 -0
- firefox-extension/popup.js +43 -0
- flowread-extension.zip +0 -0
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 53 |
```
|
| 54 |
4. **Open the Web App:**
|
| 55 |
-
Navigate to `http://localhost:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
## The User Study (Help Us Prove It Works!)
|
| 58 |
-
|
|
|
|
| 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, "<").replace(/>/g, ">").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
|
|
|