jobbler commited on
Commit
bd01d05
·
1 Parent(s): 0b3a97c

Fix tokenizer extra_special_tokens crash and stabilize transformers

Browse files
README.md CHANGED
@@ -11,13 +11,13 @@ pinned: false
11
 
12
  **Accelerate reading comprehension using LLM attention vectors.**
13
 
14
- FlowRead AI is an accessibility and educational tool that dynamically bolds the most semantically important words in a text. It uses the raw, internal attention vectors of the **Google Gemma 2B** model to understand what words actually matter, creating a visually guided reading path that reduces cognitive load and improves reading comprehension.
15
 
16
  ## The Problem
17
  In the digital age, information overload is a massive barrier. General readers experience reduced reading speeds and poor retention when trying to skim long articles or academic papers, and individuals with ADHD, dyslexia, or cognitive fatigue face even more significant challenges when processing dense blocks of text. Existing solutions (like "Bionic Reading") simply bold the first half of *every* word, which is arbitrary and ignores the actual meaning of the sentence.
18
 
19
  ## The Solution
20
- FlowRead AI solves this by extracting the mathematical "saliency" of words. By averaging the incoming attention scores across different layers of Gemma 2B, we determine exactly which nouns, verbs, and adjectives anchor the semantic meaning of the sentence.
21
 
22
  For the general reader, this translates to significantly faster reading speeds and highly efficient skimming, as the eye is naturally drawn to the most information-dense words. For readers with attention deficits or cognitive fatigue, it creates a visually guided reading path that reduces cognitive load and improves reading comprehension. Everyone benefits from a more focused reading experience.
23
 
@@ -28,11 +28,11 @@ For the general reader, this translates to significantly faster reading speeds a
28
  * **Built-in A/B User Study:** A complete embedded research tool with an SQLite backend to gather real-world empirical data on how FlowRead impacts average reading speed and comprehension accuracy.
29
 
30
  ## Why Gemma?
31
- We chose **Gemma 2B** because it offers an incredible balance of deep semantic understanding and computational efficiency. At just 2 billion parameters, its attention heads are remarkably accurate at capturing contextual importance, punching well above its weight class. Because it has open weights, we can directly extract the `outputs.attentions` matrices—something impossible with closed-source, API-based models. Its compact size means the entire application can run locally on consumer hardware (like Macs with MPS) or on free cloud tiers (like Hugging Face Spaces), democratizing access to this tool.
32
 
33
  ## Technical Implementation
34
  * **Backend:** FastAPI (Python) serving a PyTorch/Hugging Face pipeline.
35
- * **Model:** `google/gemma-2b` running in `bfloat16` precision.
36
  * **Frontend:** Pure HTML/JS/CSS with a minimalist, distraction-free "academic paper" aesthetic.
37
  * **Database:** SQLite for storing A/B test results.
38
 
@@ -49,7 +49,7 @@ Running locally allows you to use your own GPU (like Apple Silicon MPS or Nvidia
49
  pip install -r requirements.txt
50
  ```
51
  2. **Authenticate with Hugging Face:**
52
- Gemma 2B is a gated model. You must accept the terms on Hugging Face and log in:
53
  ```bash
54
  huggingface-cli login
55
  ```
@@ -66,7 +66,7 @@ Running locally allows you to use your own GPU (like Apple Silicon MPS or Nvidia
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
 
 
11
 
12
  **Accelerate reading comprehension using LLM attention vectors.**
13
 
14
+ FlowRead AI is an accessibility and educational tool that dynamically bolds the most semantically important words in a text. It uses the raw, internal attention vectors of the **Gemma 4 (E2B and 26B)** models to understand what words actually matter, creating a visually guided reading path that reduces cognitive load and improves reading comprehension.
15
 
16
  ## The Problem
17
  In the digital age, information overload is a massive barrier. General readers experience reduced reading speeds and poor retention when trying to skim long articles or academic papers, and individuals with ADHD, dyslexia, or cognitive fatigue face even more significant challenges when processing dense blocks of text. Existing solutions (like "Bionic Reading") simply bold the first half of *every* word, which is arbitrary and ignores the actual meaning of the sentence.
18
 
19
  ## The Solution
20
+ FlowRead AI solves this by extracting the mathematical "saliency" of words. By averaging the incoming attention scores across different layers of Gemma 4, we determine exactly which nouns, verbs, and adjectives anchor the semantic meaning of the sentence.
21
 
22
  For the general reader, this translates to significantly faster reading speeds and highly efficient skimming, as the eye is naturally drawn to the most information-dense words. For readers with attention deficits or cognitive fatigue, it creates a visually guided reading path that reduces cognitive load and improves reading comprehension. Everyone benefits from a more focused reading experience.
23
 
 
28
  * **Built-in A/B User Study:** A complete embedded research tool with an SQLite backend to gather real-world empirical data on how FlowRead impacts average reading speed and comprehension accuracy.
29
 
30
  ## Why Gemma?
31
+ We chose the **Gemma 4** family because they offer an incredible balance of deep semantic understanding and computational efficiency. Their attention heads are remarkably accurate at capturing contextual importance, punching well above their weight class. Because it has open weights, we can directly extract the `outputs.attentions` matrices—something impossible with closed-source, API-based models. With options like the highly efficient E2B model or the quantized 4-bit 26B model, the application can run locally on consumer hardware (like Macs with MPS) or on free cloud tiers (like Hugging Face Spaces), democratizing access to this tool.
32
 
33
  ## Technical Implementation
34
  * **Backend:** FastAPI (Python) serving a PyTorch/Hugging Face pipeline.
35
+ * **Model:** `unsloth/gemma-4-E2B` (and 26B variants) running in `bfloat16` or 4-bit precision.
36
  * **Frontend:** Pure HTML/JS/CSS with a minimalist, distraction-free "academic paper" aesthetic.
37
  * **Database:** SQLite for storing A/B test results.
38
 
 
49
  pip install -r requirements.txt
50
  ```
51
  2. **Authenticate with Hugging Face:**
52
+ Gemma models are generally gated. You must accept the terms on Hugging Face and log in:
53
  ```bash
54
  huggingface-cli login
55
  ```
 
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 models.
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
 
firefox-extension/README.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FlowRead Firefox Extension
2
+
3
+ The FlowRead Firefox extension allows you to dynamically bold the most semantically important words on any webpage, directly in your browser. It does this by communicating with your local (or cloud-hosted) FlowRead API server powered by Gemma 4 models.
4
+
5
+ ## 1. Start the Backend API Server
6
+
7
+ The extension needs a running backend server to perform the mathematical saliency extraction. The backend uses the Hugging Face `transformers` library, served via FastAPI.
8
+
9
+ **Step 1: Install Dependencies**
10
+ Ensure you are in the root directory of the project and install the requirements:
11
+ ```bash
12
+ pip install -r requirements.txt
13
+ ```
14
+
15
+ **Step 2: Authenticate with Hugging Face**
16
+ The Gemma 4 models are gated on Hugging Face. You must log in first:
17
+ ```bash
18
+ huggingface-cli login
19
+ ```
20
+
21
+ **Step 3: Start the Server**
22
+ Run the backend using Uvicorn:
23
+ ```bash
24
+ uvicorn main:app --reload --host 0.0.0.0 --port 8000
25
+ ```
26
+ *Note: This will download and load `unsloth/gemma-4-E2B` (or the configured 26B variant) and start listening for requests on port 8000. It does not use `llama.cpp` or `llama-server`; it uses PyTorch and the Hugging Face ecosystem natively.*
27
+
28
+ ## 2. Install the Firefox Extension
29
+
30
+ 1. Open Firefox and navigate to `about:debugging`.
31
+ 2. Click **"This Firefox"** on the left sidebar.
32
+ 3. Click the **"Load Temporary Add-on..."** button.
33
+ 4. In the file dialog, navigate to the `firefox-extension` folder in this repository and select the `manifest.json` file. (Alternatively, you can select the `flowread-extension.zip` file if you prefer to use the packaged version).
34
+ 5. The extension will appear in your list of Temporary Extensions and its icon will appear in your toolbar.
35
+
36
+ ## 3. Configure the Extension
37
+
38
+ 1. Click the **FlowRead icon** in your Firefox toolbar.
39
+ 2. In the settings popup, locate the **Backend API URL** field.
40
+ 3. Ensure it matches where your backend server is running:
41
+ - If running locally: `http://127.0.0.1:8000`
42
+ - If using Hugging Face Spaces or another remote server: `https://your-domain.com` (do not include a trailing slash)
43
+ 4. You can also tweak other settings like the Saliency Threshold and Gradient Mode. Click **Save Settings**.
44
+
45
+ ## 4. How to Use It
46
+
47
+ 1. Navigate to any article, blog post, or text-heavy website.
48
+ 2. **Method 1 (Selection):** Highlight a paragraph of text, right-click it, and select **"FlowRead Highlight"** from the context menu.
49
+ 3. **Method 2 (Entire Page):** Right-click anywhere on the page and select **"FlowRead Entire Page"** (or use the "FlowRead Page" button in the extension popup).
50
+ 4. The extension will send the text to your local API, compute the attention values, and instantly transform the text on your screen!
firefox-extension/content.js CHANGED
@@ -37,15 +37,14 @@ browser.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
37
  const range = selection.getRangeAt(0);
38
 
39
  // Get user settings
40
- const settings = await browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'saliencyMode', 'modelVersion', 'apiUrl']);
41
  const threshold = settings.threshold !== undefined ? settings.threshold : 0.35;
42
  const useGradient = settings.gradientMode || false;
43
  const preprompt = settings.preprompt || "";
44
  const saliencyMode = settings.saliencyMode || "local";
 
45
  const modelVersion = settings.modelVersion || "2b";
46
  const apiUrl = settings.apiUrl || "http://127.0.0.1:8000";
47
- // Default to middle layers just like the playground
48
- const checkedLayers = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
49
 
50
  try {
51
  // Start polling status
@@ -74,12 +73,12 @@ browser.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
74
  const response = await fetch(`${apiUrl}/analyze/${modelVersion}`, {
75
  method: 'POST',
76
  headers: { 'Content-Type': 'application/json' },
77
- body: JSON.stringify({
78
- text: selectedText,
79
- preprompt: preprompt,
80
- saliency_mode: saliencyMode,
81
- layers: checkedLayers
82
- })
83
  });
84
  isFetching = false;
85
 
@@ -99,6 +98,7 @@ browser.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
99
  container.dataset.tokens = JSON.stringify(currentTokens);
100
  container.dataset.preprompt = preprompt;
101
  container.dataset.saliencyMode = saliencyMode;
 
102
  container.dataset.modelVersion = modelVersion;
103
  container.dataset.originalText = selectedText;
104
  container.innerHTML = htmlString;
@@ -118,14 +118,14 @@ browser.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
118
  });
119
 
120
  async function processEntirePage() {
121
- const settings = await browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'saliencyMode', 'modelVersion', 'apiUrl']);
122
  const threshold = settings.threshold !== undefined ? settings.threshold : 0.35;
123
  const useGradient = settings.gradientMode || false;
124
  const preprompt = settings.preprompt || "";
125
  const saliencyMode = settings.saliencyMode || "local";
 
126
  const modelVersion = settings.modelVersion || "2b";
127
  const apiUrl = settings.apiUrl || "http://127.0.0.1:8000";
128
- const checkedLayers = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
129
 
130
  // To prevent freezing the browser or overwhelming the API, process in batches
131
  const walkerObj = document.createTreeWalker(
@@ -243,6 +243,7 @@ async function processEntirePage() {
243
  container.dataset.tokens = JSON.stringify(data.words);
244
  container.dataset.preprompt = preprompt;
245
  container.dataset.saliencyMode = saliencyMode;
 
246
  container.dataset.modelVersion = modelVersion;
247
  container.dataset.originalText = text;
248
  container.innerHTML = htmlString;
@@ -268,9 +269,9 @@ async function updateExisting(newSettings) {
268
  const useGradient = newSettings.gradientMode || false;
269
  const preprompt = newSettings.preprompt || "";
270
  const saliencyMode = newSettings.saliencyMode || "local";
 
271
  const modelVersion = newSettings.modelVersion || "2b";
272
  const apiUrl = newSettings.apiUrl || "http://127.0.0.1:8000";
273
- const checkedLayers = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
274
 
275
  const containers = document.querySelectorAll('.flowread-container');
276
  if (containers.length === 0) return;
@@ -282,11 +283,12 @@ async function updateExisting(newSettings) {
282
  for (const container of containers) {
283
  const oldPreprompt = container.dataset.preprompt || "";
284
  const oldMode = container.dataset.saliencyMode || "local";
 
285
  const oldModelVersion = container.dataset.modelVersion || "2b";
286
  const text = container.dataset.originalText;
287
  if (!text) continue;
288
 
289
- if (oldPreprompt !== preprompt || oldMode !== saliencyMode || oldModelVersion !== modelVersion) {
290
  if (reFetchCount === 0) {
291
  showToast("Updating FlowRead elements with new settings...", 0);
292
  isFetchingStatus = true;
@@ -315,7 +317,7 @@ async function updateExisting(newSettings) {
315
  text: text,
316
  preprompt: preprompt,
317
  saliency_mode: saliencyMode,
318
- layers: checkedLayers
319
  })
320
  });
321
 
@@ -326,6 +328,7 @@ async function updateExisting(newSettings) {
326
  container.dataset.tokens = JSON.stringify(data.words);
327
  container.dataset.preprompt = preprompt;
328
  container.dataset.saliencyMode = saliencyMode;
 
329
  container.dataset.modelVersion = modelVersion;
330
  const htmlString = generateFlowReadHTML(data.words, threshold, useGradient);
331
  container.innerHTML = htmlString;
 
37
  const range = selection.getRangeAt(0);
38
 
39
  // Get user settings
40
+ const settings = await browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'saliencyMode', 'layerPreset', 'modelVersion', 'apiUrl']);
41
  const threshold = settings.threshold !== undefined ? settings.threshold : 0.35;
42
  const useGradient = settings.gradientMode || false;
43
  const preprompt = settings.preprompt || "";
44
  const saliencyMode = settings.saliencyMode || "local";
45
+ const layerPreset = settings.layerPreset || "middle";
46
  const modelVersion = settings.modelVersion || "2b";
47
  const apiUrl = settings.apiUrl || "http://127.0.0.1:8000";
 
 
48
 
49
  try {
50
  // Start polling status
 
73
  const response = await fetch(`${apiUrl}/analyze/${modelVersion}`, {
74
  method: 'POST',
75
  headers: { 'Content-Type': 'application/json' },
76
+ body: JSON.stringify({
77
+ text: text,
78
+ preprompt: preprompt,
79
+ saliency_mode: saliencyMode,
80
+ layer_preset: layerPreset
81
+ })
82
  });
83
  isFetching = false;
84
 
 
98
  container.dataset.tokens = JSON.stringify(currentTokens);
99
  container.dataset.preprompt = preprompt;
100
  container.dataset.saliencyMode = saliencyMode;
101
+ container.dataset.layerPreset = layerPreset;
102
  container.dataset.modelVersion = modelVersion;
103
  container.dataset.originalText = selectedText;
104
  container.innerHTML = htmlString;
 
118
  });
119
 
120
  async function processEntirePage() {
121
+ const settings = await browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'saliencyMode', 'layerPreset', 'modelVersion', 'apiUrl']);
122
  const threshold = settings.threshold !== undefined ? settings.threshold : 0.35;
123
  const useGradient = settings.gradientMode || false;
124
  const preprompt = settings.preprompt || "";
125
  const saliencyMode = settings.saliencyMode || "local";
126
+ const layerPreset = settings.layerPreset || "middle";
127
  const modelVersion = settings.modelVersion || "2b";
128
  const apiUrl = settings.apiUrl || "http://127.0.0.1:8000";
 
129
 
130
  // To prevent freezing the browser or overwhelming the API, process in batches
131
  const walkerObj = document.createTreeWalker(
 
243
  container.dataset.tokens = JSON.stringify(data.words);
244
  container.dataset.preprompt = preprompt;
245
  container.dataset.saliencyMode = saliencyMode;
246
+ container.dataset.layerPreset = layerPreset;
247
  container.dataset.modelVersion = modelVersion;
248
  container.dataset.originalText = text;
249
  container.innerHTML = htmlString;
 
269
  const useGradient = newSettings.gradientMode || false;
270
  const preprompt = newSettings.preprompt || "";
271
  const saliencyMode = newSettings.saliencyMode || "local";
272
+ const layerPreset = newSettings.layerPreset || "middle";
273
  const modelVersion = newSettings.modelVersion || "2b";
274
  const apiUrl = newSettings.apiUrl || "http://127.0.0.1:8000";
 
275
 
276
  const containers = document.querySelectorAll('.flowread-container');
277
  if (containers.length === 0) return;
 
283
  for (const container of containers) {
284
  const oldPreprompt = container.dataset.preprompt || "";
285
  const oldMode = container.dataset.saliencyMode || "local";
286
+ const oldPreset = container.dataset.layerPreset || "middle";
287
  const oldModelVersion = container.dataset.modelVersion || "2b";
288
  const text = container.dataset.originalText;
289
  if (!text) continue;
290
 
291
+ if (oldPreprompt !== preprompt || oldMode !== saliencyMode || oldPreset !== layerPreset || oldModelVersion !== modelVersion) {
292
  if (reFetchCount === 0) {
293
  showToast("Updating FlowRead elements with new settings...", 0);
294
  isFetchingStatus = true;
 
317
  text: text,
318
  preprompt: preprompt,
319
  saliency_mode: saliencyMode,
320
+ layer_preset: layerPreset
321
  })
322
  });
323
 
 
328
  container.dataset.tokens = JSON.stringify(data.words);
329
  container.dataset.preprompt = preprompt;
330
  container.dataset.saliencyMode = saliencyMode;
331
+ container.dataset.layerPreset = layerPreset;
332
  container.dataset.modelVersion = modelVersion;
333
  const htmlString = generateFlowReadHTML(data.words, threshold, useGradient);
334
  container.innerHTML = htmlString;
firefox-extension/popup.html CHANGED
@@ -98,6 +98,15 @@
98
  </select>
99
  <p class="help" style="margin-top: 0;">Global mode allows comparing importance across different paragraphs.</p>
100
 
 
 
 
 
 
 
 
 
 
101
  <label for="model-version">Model Version</label>
102
  <select id="model-version" style="width: 100%; margin-bottom: 15px; padding: 8px; border: 1px solid #d6d3d1; border-radius: 4px; font-family: inherit;">
103
  <option value="2b">Gemma 4 2B (Fast / Default)</option>
 
98
  </select>
99
  <p class="help" style="margin-top: 0;">Global mode allows comparing importance across different paragraphs.</p>
100
 
101
+ <label for="layer-preset">Network Layers</label>
102
+ <select id="layer-preset" style="width: 100%; margin-bottom: 15px; padding: 8px; border: 1px solid #d6d3d1; border-radius: 4px; font-family: inherit;">
103
+ <option value="middle">Middle Layers (Semantic focus)</option>
104
+ <option value="first">First Few Layers (Lexical/Syntax)</option>
105
+ <option value="last">Last Few Layers (Output formatting)</option>
106
+ <option value="all">All Layers (Averaged)</option>
107
+ </select>
108
+ <p class="help" style="margin-top: 0;">Select which network layers extract attention scores.</p>
109
+
110
  <label for="model-version">Model Version</label>
111
  <select id="model-version" style="width: 100%; margin-bottom: 15px; padding: 8px; border: 1px solid #d6d3d1; border-radius: 4px; font-family: inherit;">
112
  <option value="2b">Gemma 4 2B (Fast / Default)</option>
firefox-extension/popup.js CHANGED
@@ -4,13 +4,14 @@ document.addEventListener('DOMContentLoaded', () => {
4
  const gradientModeInput = document.getElementById('gradient-mode');
5
  const prepromptInput = document.getElementById('preprompt');
6
  const saliencyModeInput = document.getElementById('saliency-mode');
 
7
  const modelVersionInput = document.getElementById('model-version');
8
  const apiUrlInput = document.getElementById('api-url');
9
  const saveBtn = document.getElementById('save-btn');
10
  const pageBtn = document.getElementById('page-btn');
11
 
12
  // Load existing settings
13
- browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'saliencyMode', 'modelVersion', 'apiUrl'], (res) => {
14
  if (res.threshold !== undefined) {
15
  thresholdInput.value = res.threshold;
16
  thresholdVal.textContent = parseFloat(res.threshold).toFixed(2);
@@ -24,6 +25,9 @@ document.addEventListener('DOMContentLoaded', () => {
24
  if (res.saliencyMode !== undefined) {
25
  saliencyModeInput.value = res.saliencyMode;
26
  }
 
 
 
27
  if (res.modelVersion !== undefined) {
28
  modelVersionInput.value = res.modelVersion;
29
  }
@@ -42,6 +46,7 @@ document.addEventListener('DOMContentLoaded', () => {
42
  gradientMode: gradientModeInput.checked,
43
  preprompt: prepromptInput.value.trim(),
44
  saliencyMode: saliencyModeInput.value,
 
45
  modelVersion: modelVersionInput.value,
46
  apiUrl: apiUrlInput.value.trim().replace(/\/$/, '') // Remove trailing slash
47
  };
 
4
  const gradientModeInput = document.getElementById('gradient-mode');
5
  const prepromptInput = document.getElementById('preprompt');
6
  const saliencyModeInput = document.getElementById('saliency-mode');
7
+ const layerPresetInput = document.getElementById('layer-preset');
8
  const modelVersionInput = document.getElementById('model-version');
9
  const apiUrlInput = document.getElementById('api-url');
10
  const saveBtn = document.getElementById('save-btn');
11
  const pageBtn = document.getElementById('page-btn');
12
 
13
  // Load existing settings
14
+ browser.storage.local.get(['threshold', 'gradientMode', 'preprompt', 'saliencyMode', 'layerPreset', 'modelVersion', 'apiUrl'], (res) => {
15
  if (res.threshold !== undefined) {
16
  thresholdInput.value = res.threshold;
17
  thresholdVal.textContent = parseFloat(res.threshold).toFixed(2);
 
25
  if (res.saliencyMode !== undefined) {
26
  saliencyModeInput.value = res.saliencyMode;
27
  }
28
+ if (res.layerPreset !== undefined) {
29
+ layerPresetInput.value = res.layerPreset;
30
+ }
31
  if (res.modelVersion !== undefined) {
32
  modelVersionInput.value = res.modelVersion;
33
  }
 
46
  gradientMode: gradientModeInput.checked,
47
  preprompt: prepromptInput.value.trim(),
48
  saliencyMode: saliencyModeInput.value,
49
+ layerPreset: layerPresetInput.value,
50
  modelVersion: modelVersionInput.value,
51
  apiUrl: apiUrlInput.value.trim().replace(/\/$/, '') // Remove trailing slash
52
  };
main.py CHANGED
@@ -22,20 +22,23 @@ app.add_middleware(
22
 
23
  app.mount("/static", StaticFiles(directory="static"), name="static")
24
 
 
25
  @app.get("/")
26
  def read_root():
27
  return RedirectResponse(url="/static/index.html")
28
 
 
29
  # --- SQLite Database Setup ---
30
  # Hugging Face Spaces make the /app directory read-only by default.
31
  # We must write the database to /data (if persistent storage is enabled) or /tmp.
32
  DB_DIR = "/data" if os.path.exists("/data") else "/tmp"
33
  DB_FILE = os.path.join(DB_DIR, "study.db")
34
 
 
35
  def init_db():
36
  conn = sqlite3.connect(DB_FILE)
37
  c = conn.cursor()
38
- c.execute('''
39
  CREATE TABLE IF NOT EXISTS study_results (
40
  id INTEGER PRIMARY KEY AUTOINCREMENT,
41
  user_id TEXT,
@@ -46,19 +49,20 @@ def init_db():
46
  total_questions INTEGER,
47
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
48
  )
49
- ''')
50
-
51
- c.execute('''
52
  CREATE TABLE IF NOT EXISTS study_preferences (
53
  id INTEGER PRIMARY KEY AUTOINCREMENT,
54
  user_id TEXT,
55
  preference TEXT, -- "plain", "flowread", or "gradient"
56
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
57
  )
58
- ''')
59
  conn.commit()
60
  conn.close()
61
 
 
62
  init_db()
63
 
64
  # --- Study Content ---
@@ -70,13 +74,8 @@ STUDY_TEXTS = [
70
  "questions": [
71
  {
72
  "question": "Approximately how many neurons are in the human brain?",
73
- "options": [
74
- "86 million",
75
- "86 billion",
76
- "100 trillion",
77
- "50 billion"
78
- ],
79
- "correct": 1
80
  },
81
  {
82
  "question": "What is the term for the brain's ability to reorganize itself?",
@@ -84,13 +83,13 @@ STUDY_TEXTS = [
84
  "Synaptic generation",
85
  "Neurogenesis",
86
  "Neuroplasticity",
87
- "Cognitive adaptation"
88
  ],
89
- "correct": 2
90
- }
91
  ],
92
- "flowread_html": "<span class=\"token highlighted\">The</span><span class=\"token highlighted\"> human</span><span class=\"token highlighted\"> brain</span><span class=\"token highlighted\"> is</span><span class=\"token highlighted\"> a</span><span class=\"token highlighted\"> marvel</span><span class=\"token\"> of</span><span class=\"token highlighted\"> biological</span><span class=\"token highlighted\"> engineering</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> containing</span><span class=\"token\"> approximately</span><span class=\"token\"> </span><span class=\"token\">8</span><span class=\"token\">6</span><span class=\"token highlighted\"> billion</span><span class=\"token highlighted\"> neurons</span><span class=\"token highlighted\"> interconnected</span><span class=\"token highlighted\"> by</span><span class=\"token\"> tri</span><span class=\"token\">lli</span><span class=\"token\">ons</span><span class=\"token\"> of</span><span class=\"token highlighted\"> synapses</span><span class=\"token highlighted\">.</span><span class=\"token highlighted\"> These</span><span class=\"token highlighted\"> neural</span><span class=\"token highlighted\"> networks</span><span class=\"token\"> are</span><span class=\"token\"> responsible</span><span class=\"token highlighted\"> for</span><span class=\"token\"> everything</span><span class=\"token highlighted\"> from</span><span class=\"token highlighted\"> basic</span><span class=\"token highlighted\"> autonomic</span><span class=\"token highlighted\"> functions</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> like</span><span class=\"token\"> breathing</span><span class=\"token\"> and</span><span class=\"token\"> heart</span><span class=\"token\"> rate</span><span class=\"token\">,</span><span class=\"token highlighted\"> to</span><span class=\"token\"> complex</span><span class=\"token highlighted\"> cognitive</span><span class=\"token\"> processes</span><span class=\"token\"> such</span><span class=\"token\"> as</span><span class=\"token highlighted\"> memory</span><span class=\"token highlighted\">,</span><span class=\"token\"> emotion</span><span class=\"token\">,</span><span class=\"token\"> and</span><span class=\"token highlighted\"> problem</span><span class=\"token highlighted\">-</span><span class=\"token highlighted\">solving</span><span class=\"token highlighted\">.</span><span class=\"token highlighted\"> Neurop</span><span class=\"token highlighted\">lastic</span><span class=\"token highlighted\">ity</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> the</span><span class=\"token highlighted\"> brain</span><span class=\"token highlighted\">'</span><span class=\"token highlighted\">s</span><span class=\"token\"> ability</span><span class=\"token\"> to</span><span class=\"token highlighted\"> reorgan</span><span class=\"token highlighted\">ize</span><span class=\"token\"> itself</span><span class=\"token\"> by</span><span class=\"token\"> forming</span><span class=\"token\"> new</span><span class=\"token\"> neural</span><span class=\"token\"> connections</span><span class=\"token\"> throughout</span><span class=\"token highlighted\"> life</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> allows</span><span class=\"token highlighted\"> humans</span><span class=\"token\"> to</span><span class=\"token highlighted\"> learn</span><span class=\"token\"> new</span><span class=\"token\"> skills</span><span class=\"token\">,</span><span class=\"token\"> recover</span><span class=\"token\"> from</span><span class=\"token\"> injuries</span><span class=\"token\">,</span><span class=\"token\"> and</span><span class=\"token\"> adapt</span><span class=\"token\"> to</span><span class=\"token\"> changing</span><span class=\"token highlighted\"> environments</span><span class=\"token highlighted\">.</span><span class=\"token\"> This</span><span class=\"token\"> extraordinary</span><span class=\"token highlighted\"> adaptability</span><span class=\"token\"> is</span><span class=\"token\"> what</span><span class=\"token\"> makes</span><span class=\"token\"> our</span><span class=\"token\"> species</span><span class=\"token\"> so</span><span class=\"token\"> resilient</span><span class=\"token\"> and</span><span class=\"token\"> capable</span><span class=\"token\"> of</span><span class=\"token\"> continuous</span><span class=\"token\"> intellectual</span><span class=\"token\"> growth</span><span class=\"token\">.</span>",
93
- "flowread_gradient_html": "<span class=\"token\" style=\"opacity: 0.53; font-weight: 489;\">The</span><span class=\"token\" style=\"opacity: 0.56; font-weight: 506;\"> human</span><span class=\"token\" style=\"opacity: 1.00; font-weight: 800;\"> brain</span><span class=\"token\" style=\"opacity: 0.54; font-weight: 490;\"> is</span><span class=\"token\" style=\"opacity: 0.51; font-weight: 472;\"> a</span><span class=\"token\" style=\"opacity: 0.53; font-weight: 489;\"> marvel</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 452;\"> of</span><span class=\"token\" style=\"opacity: 0.53; font-weight: 484;\"> biological</span><span class=\"token\" style=\"opacity: 0.55; font-weight: 498;\"> engineering</span><span class=\"token\" style=\"opacity: 0.55; font-weight: 498;\">,</span><span class=\"token\" style=\"opacity: 0.56; font-weight: 506;\"> containing</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 455;\"> approximately</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 455;\"> </span><span class=\"token\" style=\"opacity: 0.43; font-weight: 421;\">8</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 421;\">6</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 469;\"> billion</span><span class=\"token\" style=\"opacity: 0.71; font-weight: 608;\"> neurons</span><span class=\"token\" style=\"opacity: 0.55; font-weight: 500;\"> interconnected</span><span class=\"token\" style=\"opacity: 0.53; font-weight: 484;\"> by</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 419;\"> tri</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 419;\">lli</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 419;\">ons</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 413;\"> of</span><span class=\"token\" style=\"opacity: 0.72; font-weight: 613;\"> synapses</span><span class=\"token\" style=\"opacity: 0.72; font-weight: 613;\">.</span><span class=\"token\" style=\"opacity: 0.51; font-weight: 472;\"> These</span><span class=\"token\" style=\"opacity: 0.57; font-weight: 516;\"> neural</span><span class=\"token\" style=\"opacity: 0.57; font-weight: 515;\"> networks</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 455;\"> are</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 452;\"> responsible</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 482;\"> for</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 464;\"> everything</span><span class=\"token\" style=\"opacity: 0.59; font-weight: 529;\"> from</span><span class=\"token\" style=\"opacity: 0.51; font-weight: 474;\"> basic</span><span class=\"token\" style=\"opacity: 0.56; font-weight: 504;\"> autonomic</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 482;\"> functions</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 482;\">,</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 481;\"> like</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 466;\"> breathing</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 444;\"> and</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 424;\"> heart</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 435;\"> rate</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 435;\">,</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 479;\"> to</span><span class=\"token\" style=\"opacity: 0.49; font-weight: 462;\"> complex</span><span class=\"token\" style=\"opacity: 0.53; font-weight: 487;\"> cognitive</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 451;\"> processes</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 432;\"> such</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 451;\"> as</span><span class=\"token\" style=\"opacity: 0.51; font-weight: 474;\"> memory</span><span class=\"token\" style=\"opacity: 0.51; font-weight: 474;\">,</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 450;\"> emotion</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 450;\">,</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 428;\"> and</span><span class=\"token\" style=\"opacity: 0.64; font-weight: 559;\"> problem</span><span class=\"token\" style=\"opacity: 0.64; font-weight: 559;\">-</span><span class=\"token\" style=\"opacity: 0.64; font-weight: 559;\">solving</span><span class=\"token\" style=\"opacity: 0.64; font-weight: 559;\">.</span><span class=\"token\" style=\"opacity: 0.68; font-weight: 587;\"> Neurop</span><span class=\"token\" style=\"opacity: 0.68; font-weight: 587;\">lastic</span><span class=\"token\" style=\"opacity: 0.68; font-weight: 587;\">ity</span><span class=\"token\" style=\"opacity: 0.68; font-weight: 587;\">,</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 469;\"> the</span><span class=\"token\" style=\"opacity: 0.59; font-weight: 527;\"> brain</span><span class=\"token\" style=\"opacity: 0.59; font-weight: 527;\">'</span><span class=\"token\" style=\"opacity: 0.59; font-weight: 527;\">s</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 434;\"> ability</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 464;\"> to</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 469;\"> reorgan</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 469;\">ize</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 444;\"> itself</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 454;\"> by</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 434;\"> forming</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 431;\"> new</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 426;\"> neural</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 433;\"> connections</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 426;\"> throughout</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 480;\"> life</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 480;\">,</span><span class=\"token\" style=\"opacity: 0.53; font-weight: 485;\"> allows</span><span class=\"token\" style=\"opacity: 0.54; font-weight: 493;\"> humans</span><span class=\"token\" style=\"opacity: 0.49; font-weight: 463;\"> to</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 482;\"> learn</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 433;\"> new</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 454;\"> skills</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 454;\">,</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 449;\"> recover</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> from</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 438;\"> injuries</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 438;\">,</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 433;\"> and</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 436;\"> adapt</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 421;\"> to</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 414;\"> changing</span><span class=\"token\" style=\"opacity: 0.53; font-weight: 485;\"> environments</span><span class=\"token\" style=\"opacity: 0.53; font-weight: 485;\">.</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> This</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 442;\"> extraordinary</span><span class=\"token\" style=\"opacity: 0.51; font-weight: 472;\"> adaptability</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 428;\"> is</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 435;\"> what</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 443;\"> makes</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 428;\"> our</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 468;\"> species</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 430;\"> so</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 443;\"> resilient</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 427;\"> and</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 412;\"> capable</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 422;\"> of</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 420;\"> continuous</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 413;\"> intellectual</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 403;\"> growth</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 403;\">.</span>"
94
  },
95
  {
96
  "id": 2,
@@ -99,13 +98,8 @@ STUDY_TEXTS = [
99
  "questions": [
100
  {
101
  "question": "Where did the Industrial Revolution begin?",
102
- "options": [
103
- "United States",
104
- "France",
105
- "Germany",
106
- "Britain"
107
- ],
108
- "correct": 3
109
  },
110
  {
111
  "question": "Which invention dramatically increased factory efficiency?",
@@ -113,13 +107,13 @@ STUDY_TEXTS = [
113
  "The cotton gin",
114
  "The telegraph",
115
  "The steam engine",
116
- "The assembly line"
117
  ],
118
- "correct": 2
119
- }
120
  ],
121
- "flowread_html": "<span class=\"token highlighted\">The</span><span class=\"token highlighted\"> Industrial</span><span class=\"token highlighted\"> Revolution</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> which</span><span class=\"token highlighted\"> began</span><span class=\"token\"> in</span><span class=\"token highlighted\"> Britain</span><span class=\"token\"> in</span><span class=\"token\"> the</span><span class=\"token\"> late</span><span class=\"token\"> </span><span class=\"token highlighted\">1</span><span class=\"token highlighted\">8</span><span class=\"token highlighted\">th</span><span class=\"token highlighted\"> century</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> marked</span><span class=\"token\"> a</span><span class=\"token highlighted\"> profound</span><span class=\"token\"> turning</span><span class=\"token highlighted\"> point</span><span class=\"token\"> in</span><span class=\"token highlighted\"> human</span><span class=\"token highlighted\"> history</span><span class=\"token highlighted\">.</span><span class=\"token highlighted\"> It</span><span class=\"token highlighted\"> initiated</span><span class=\"token\"> the</span><span class=\"token\"> transition</span><span class=\"token highlighted\"> from</span><span class=\"token highlighted\"> agrarian</span><span class=\"token highlighted\">,</span><span class=\"token\"> handic</span><span class=\"token\">raft</span><span class=\"token highlighted\"> economies</span><span class=\"token highlighted\"> to</span><span class=\"token highlighted\"> industry</span><span class=\"token\"> and</span><span class=\"token highlighted\"> machine</span><span class=\"token highlighted\"> manufacturing</span><span class=\"token highlighted\">.</span><span class=\"token highlighted\"> The</span><span class=\"token highlighted\"> invention</span><span class=\"token highlighted\"> of</span><span class=\"token\"> the</span><span class=\"token highlighted\"> steam</span><span class=\"token highlighted\"> engine</span><span class=\"token highlighted\">,</span><span class=\"token\"> pioneered</span><span class=\"token highlighted\"> by</span><span class=\"token\"> figures</span><span class=\"token\"> like</span><span class=\"token\"> James</span><span class=\"token\"> Watt</span><span class=\"token\">,</span><span class=\"token highlighted\"> dramatically</span><span class=\"token highlighted\"> increased</span><span class=\"token\"> the</span><span class=\"token\"> efficiency</span><span class=\"token\"> of</span><span class=\"token highlighted\"> factories</span><span class=\"token\"> and</span><span class=\"token highlighted\"> transportation</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> revolution</span><span class=\"token highlighted\">izing</span><span class=\"token\"> the</span><span class=\"token\"> textile</span><span class=\"token\"> industry</span><span class=\"token\"> and</span><span class=\"token\"> leading</span><span class=\"token\"> to</span><span class=\"token\"> the</span><span class=\"token\"> expansion</span><span class=\"token\"> of</span><span class=\"token highlighted\"> railways</span><span class=\"token highlighted\">.</span><span class=\"token\"> This</span><span class=\"token highlighted\"> era</span><span class=\"token highlighted\"> brought</span><span class=\"token\"> about</span><span class=\"token\"> unprecedented</span><span class=\"token highlighted\"> economic</span><span class=\"token highlighted\"> growth</span><span class=\"token\"> and</span><span class=\"token highlighted\"> urbanization</span><span class=\"token highlighted\">,</span><span class=\"token\"> fundamentally</span><span class=\"token highlighted\"> altering</span><span class=\"token\"> social</span><span class=\"token\"> structures</span><span class=\"token\"> and</span><span class=\"token\"> paving</span><span class=\"token\"> the</span><span class=\"token\"> way</span><span class=\"token\"> for</span><span class=\"token\"> the</span><span class=\"token\"> modern</span><span class=\"token\"> capitalist</span><span class=\"token\"> system</span><span class=\"token\">,</span><span class=\"token\"> despite</span><span class=\"token\"> also</span><span class=\"token\"> causing</span><span class=\"token\"> significant</span><span class=\"token\"> social</span><span class=\"token\"> inequalities</span><span class=\"token\"> and</span><span class=\"token\"> poor</span><span class=\"token\"> working</span><span class=\"token\"> conditions</span><span class=\"token\"> initially</span><span class=\"token\">.</span>",
122
- "flowread_gradient_html": "<span class=\"token\" style=\"opacity: 0.49; font-weight: 462;\">The</span><span class=\"token\" style=\"opacity: 0.59; font-weight: 524;\"> Industrial</span><span class=\"token\" style=\"opacity: 1.00; font-weight: 800;\"> Revolution</span><span class=\"token\" style=\"opacity: 1.00; font-weight: 800;\">,</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 456;\"> which</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 467;\"> began</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 430;\"> in</span><span class=\"token\" style=\"opacity: 0.58; font-weight: 519;\"> Britain</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> in</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 410;\"> the</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 418;\"> late</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 415;\"> </span><span class=\"token\" style=\"opacity: 0.49; font-weight: 457;\">1</span><span class=\"token\" style=\"opacity: 0.49; font-weight: 457;\">8</span><span class=\"token\" style=\"opacity: 0.49; font-weight: 457;\">th</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 467;\"> century</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 467;\">,</span><span class=\"token\" style=\"opacity: 0.49; font-weight: 460;\"> marked</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 422;\"> a</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 439;\"> profound</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 418;\"> turning</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 440;\"> point</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 423;\"> in</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 447;\"> human</span><span class=\"token\" style=\"opacity: 0.57; font-weight: 511;\"> history</span><span class=\"token\" style=\"opacity: 0.57; font-weight: 511;\">.</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 456;\"> It</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 450;\"> initiated</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 431;\"> the</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 426;\"> transition</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 444;\"> from</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 469;\"> agrarian</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 469;\">,</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 433;\"> handic</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 433;\">raft</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 451;\"> economies</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 452;\"> to</span><span class=\"token\" style=\"opacity: 0.53; font-weight: 489;\"> industry</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 432;\"> and</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 444;\"> machine</span><span class=\"token\" style=\"opacity: 0.55; font-weight: 498;\"> manufacturing</span><span class=\"token\" style=\"opacity: 0.55; font-weight: 498;\">.</span><span class=\"token\" style=\"opacity: 0.49; font-weight: 461;\"> The</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 442;\"> invention</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 440;\"> of</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 417;\"> the</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 465;\"> steam</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 483;\"> engine</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 483;\">,</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 434;\"> pioneered</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 436;\"> by</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 421;\"> figures</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 426;\"> like</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 414;\"> James</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 428;\"> Watt</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 428;\">,</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 437;\"> dramatically</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 445;\"> increased</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 421;\"> the</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 432;\"> efficiency</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 421;\"> of</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 467;\"> factories</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 432;\"> and</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 442;\"> transportation</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 442;\">,</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\"> revolution</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\">izing</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 415;\"> the</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 436;\"> textile</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 428;\"> industry</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 433;\"> and</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 416;\"> leading</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 425;\"> to</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 409;\"> the</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 413;\"> expansion</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 407;\"> of</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 468;\"> railways</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 468;\">.</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 423;\"> This</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 454;\"> era</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 447;\"> brought</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 418;\"> about</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 434;\"> unprecedented</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 438;\"> economic</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 437;\"> growth</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 431;\"> and</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 449;\"> urbanization</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 449;\">,</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 424;\"> fundamentally</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 441;\"> altering</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 432;\"> social</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 427;\"> structures</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 432;\"> and</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 419;\"> paving</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 407;\"> the</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 410;\"> way</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 423;\"> for</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 409;\"> the</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 422;\"> modern</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> capitalist</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 415;\"> system</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 415;\">,</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 432;\"> despite</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 417;\"> also</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 431;\"> causing</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 419;\"> significant</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 416;\"> social</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> inequalities</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 415;\"> and</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 414;\"> poor</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 407;\"> working</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 406;\"> conditions</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 403;\"> initially</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 403;\">.</span>"
123
  },
124
  {
125
  "id": 3,
@@ -128,13 +122,8 @@ STUDY_TEXTS = [
128
  "questions": [
129
  {
130
  "question": "What spectrum does the James Webb Space Telescope primarily operate in?",
131
- "options": [
132
- "Ultraviolet",
133
- "X-ray",
134
- "Infrared",
135
- "Visible light"
136
- ],
137
- "correct": 2
138
  },
139
  {
140
  "question": "Where does the telescope orbit to stay cold?",
@@ -142,19 +131,22 @@ STUDY_TEXTS = [
142
  "Low Earth Orbit",
143
  "The Moon's orbit",
144
  "The first Lagrange point",
145
- "The second Lagrange point"
146
  ],
147
- "correct": 3
148
- }
149
  ],
150
- "flowread_html": "<span class=\"token highlighted\">The</span><span class=\"token\"> James</span><span class=\"token highlighted\"> Webb</span><span class=\"token highlighted\"> Space</span><span class=\"token highlighted\"> Telescope</span><span class=\"token highlighted\"> is</span><span class=\"token highlighted\"> the</span><span class=\"token highlighted\"> largest</span><span class=\"token\"> and</span><span class=\"token\"> most</span><span class=\"token highlighted\"> powerful</span><span class=\"token highlighted\"> space</span><span class=\"token highlighted\"> telescope</span><span class=\"token\"> ever</span><span class=\"token highlighted\"> built</span><span class=\"token highlighted\">.</span><span class=\"token highlighted\"> Launched</span><span class=\"token\"> in</span><span class=\"token\"> </span><span class=\"token highlighted\">2</span><span class=\"token highlighted\">0</span><span class=\"token highlighted\">2</span><span class=\"token highlighted\">1</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> it</span><span class=\"token highlighted\"> operates</span><span class=\"token\"> primarily</span><span class=\"token\"> in</span><span class=\"token\"> the</span><span class=\"token highlighted\"> infrared</span><span class=\"token highlighted\"> spectrum</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> allowing</span><span class=\"token\"> it</span><span class=\"token\"> to</span><span class=\"token\"> peer</span><span class=\"token\"> through</span><span class=\"token\"> dense</span><span class=\"token highlighted\"> cosmic</span><span class=\"token\"> dust</span><span class=\"token\"> and</span><span class=\"token highlighted\"> observe</span><span class=\"token\"> the</span><span class=\"token highlighted\"> universe</span><span class=\"token highlighted\">'</span><span class=\"token highlighted\">s</span><span class=\"token\"> most</span><span class=\"token\"> distant</span><span class=\"token\">,</span><span class=\"token\"> early</span><span class=\"token highlighted\"> galaxies</span><span class=\"token highlighted\">.</span><span class=\"token highlighted\"> Unlike</span><span class=\"token\"> its</span><span class=\"token\"> predecessor</span><span class=\"token\">,</span><span class=\"token highlighted\"> Hubble</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> JW</span><span class=\"token highlighted\">ST</span><span class=\"token highlighted\"> orbits</span><span class=\"token\"> the</span><span class=\"token highlighted\"> Sun</span><span class=\"token highlighted\"> at</span><span class=\"token\"> the</span><span class=\"token\"> second</span><span class=\"token highlighted\"> Lagrange</span><span class=\"token highlighted\"> point</span><span class=\"token highlighted\">,</span><span class=\"token highlighted\"> keeping</span><span class=\"token\"> it</span><span class=\"token\"> constantly</span><span class=\"token highlighted\"> shielded</span><span class=\"token highlighted\"> from</span><span class=\"token\"> the</span><span class=\"token highlighted\"> Sun</span><span class=\"token highlighted\">'</span><span class=\"token highlighted\">s</span><span class=\"token\"> heat</span><span class=\"token\"> and</span><span class=\"token\"> light</span><span class=\"token\"> by</span><span class=\"token\"> its</span><span class=\"token\"> massive</span><span class=\"token highlighted\"> sun</span><span class=\"token highlighted\">shield</span><span class=\"token highlighted\">.</span><span class=\"token\"> This</span><span class=\"token\"> incredibly</span><span class=\"token highlighted\"> cold</span><span class=\"token\"> environment</span><span class=\"token\"> is</span><span class=\"token\"> necessary</span><span class=\"token\"> to</span><span class=\"token highlighted\"> prevent</span><span class=\"token\"> the</span><span class=\"token highlighted\"> telescope</span><span class=\"token highlighted\">'</span><span class=\"token highlighted\">s</span><span class=\"token\"> own</span><span class=\"token\"> infrared</span><span class=\"token\"> emissions</span><span class=\"token\"> from</span><span class=\"token\"> interfering</span><span class=\"token\"> with</span><span class=\"token\"> its</span><span class=\"token\"> highly</span><span class=\"token\"> sensitive</span><span class=\"token\"> observations</span><span class=\"token\"> of</span><span class=\"token\"> ex</span><span class=\"token\">oplan</span><span class=\"token\">et</span><span class=\"token\"> atmospheres</span><span class=\"token\"> and</span><span class=\"token\"> star</span><span class=\"token\"> formation</span><span class=\"token\">.</span>",
151
- "flowread_gradient_html": "<span class=\"token\" style=\"opacity: 0.49; font-weight: 458;\">The</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 435;\"> James</span><span class=\"token\" style=\"opacity: 1.00; font-weight: 800;\"> Webb</span><span class=\"token\" style=\"opacity: 0.59; font-weight: 523;\"> Space</span><span class=\"token\" style=\"opacity: 0.75; font-weight: 632;\"> Telescope</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 478;\"> is</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 447;\"> the</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 463;\"> largest</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 423;\"> and</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 412;\"> most</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 443;\"> powerful</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 441;\"> space</span><span class=\"token\" style=\"opacity: 0.54; font-weight: 491;\"> telescope</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 422;\"> ever</span><span class=\"token\" style=\"opacity: 0.56; font-weight: 503;\"> built</span><span class=\"token\" style=\"opacity: 0.56; font-weight: 503;\">.</span><span class=\"token\" style=\"opacity: 0.51; font-weight: 475;\"> Launched</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 421;\"> in</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 418;\"> </span><span class=\"token\" style=\"opacity: 0.50; font-weight: 463;\">2</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 463;\">0</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 463;\">2</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 463;\">1</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 463;\">,</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 452;\"> it</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 449;\"> operates</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 433;\"> primarily</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> in</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 418;\"> the</span><span class=\"token\" style=\"opacity: 0.57; font-weight: 510;\"> infrared</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\"> spectrum</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\">,</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 453;\"> allowing</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 420;\"> it</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 423;\"> to</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 437;\"> peer</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 437;\"> through</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 430;\"> dense</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 441;\"> cosmic</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 438;\"> dust</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 431;\"> and</span><span class=\"token\" style=\"opacity: 0.49; font-weight: 458;\"> observe</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 439;\"> the</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 451;\"> universe</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 451;\">'</span><span class=\"token\" style=\"opacity: 0.48; font-weight: 451;\">s</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 414;\"> most</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 436;\"> distant</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 436;\">,</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 414;\"> early</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 478;\"> galaxies</span><span class=\"token\" style=\"opacity: 0.52; font-weight: 478;\">.</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 445;\"> Unlike</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 419;\"> its</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 437;\"> predecessor</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 437;\">,</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 467;\"> Hubble</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 467;\">,</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 468;\"> JW</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 468;\">ST</span><span class=\"token\" style=\"opacity: 0.55; font-weight: 496;\"> orbits</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 430;\"> the</span><span class=\"token\" style=\"opacity: 0.53; font-weight: 489;\"> Sun</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 441;\"> at</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 417;\"> the</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 415;\"> second</span><span class=\"token\" style=\"opacity: 0.49; font-weight: 456;\"> Lagrange</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\"> point</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\">,</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 444;\"> keeping</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 431;\"> it</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 428;\"> constantly</span><span class=\"token\" style=\"opacity: 0.49; font-weight: 457;\"> shielded</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 445;\"> from</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 419;\"> the</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\"> Sun</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\">'</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\">s</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 438;\"> heat</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 423;\"> and</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 420;\"> light</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> by</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 421;\"> its</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 418;\"> massive</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 465;\"> sun</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 465;\">shield</span><span class=\"token\" style=\"opacity: 0.50; font-weight: 465;\">.</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 422;\"> This</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 415;\"> incredibly</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 440;\"> cold</span><span class=\"token\" style=\"opacity: 0.46; font-weight: 439;\"> environment</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 414;\"> is</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 422;\"> necessary</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 431;\"> to</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 446;\"> prevent</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 420;\"> the</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 445;\"> telescope</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 445;\">'</span><span class=\"token\" style=\"opacity: 0.47; font-weight: 445;\">s</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> own</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> infrared</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 436;\"> emissions</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 414;\"> from</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 417;\"> interfering</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 419;\"> with</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 416;\"> its</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 407;\"> highly</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 417;\"> sensitive</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 421;\"> observations</span><span class=\"token\" style=\"opacity: 0.44; font-weight: 429;\"> of</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 430;\"> ex</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 430;\">oplan</span><span class=\"token\" style=\"opacity: 0.45; font-weight: 430;\">et</span><span class=\"token\" style=\"opacity: 0.43; font-weight: 418;\"> atmospheres</span><span class=\"token\" style=\"opacity: 0.42; font-weight: 413;\"> and</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 408;\"> star</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 403;\"> formation</span><span class=\"token\" style=\"opacity: 0.41; font-weight: 403;\">.</span>"
152
- }
153
- ]# --- Study API Endpoints ---
 
 
154
  @app.get("/api/study/texts")
155
  def get_study_texts():
156
  return {"texts": STUDY_TEXTS}
157
 
 
158
  class StudySubmission(BaseModel):
159
  user_id: str
160
  text_id: int
@@ -163,79 +155,98 @@ class StudySubmission(BaseModel):
163
  score: int
164
  total_questions: int
165
 
 
166
  class StudyPreference(BaseModel):
167
  user_id: str
168
  preference: str
169
 
 
170
  @app.post("/api/study/preference")
171
  def submit_study_preference(submission: StudyPreference):
172
  conn = sqlite3.connect(DB_FILE)
173
  c = conn.cursor()
174
  c.execute(
175
  "INSERT INTO study_preferences (user_id, preference) VALUES (?, ?)",
176
- (submission.user_id, submission.preference)
177
  )
178
  conn.commit()
179
  conn.close()
180
  return {"status": "success"}
181
 
 
182
  @app.post("/api/study/submit")
183
  def submit_study_result(submission: StudySubmission):
184
  conn = sqlite3.connect(DB_FILE)
185
  c = conn.cursor()
186
  c.execute(
187
  "INSERT INTO study_results (user_id, text_id, condition, reading_time_ms, score, total_questions) VALUES (?, ?, ?, ?, ?, ?)",
188
- (submission.user_id, submission.text_id, submission.condition, submission.reading_time_ms, submission.score, submission.total_questions)
 
 
 
 
 
 
 
189
  )
190
  conn.commit()
191
  conn.close()
192
  return {"status": "success"}
193
 
 
194
  @app.get("/api/study/stats")
195
  def get_study_stats():
196
  conn = sqlite3.connect(DB_FILE)
197
  c = conn.cursor()
198
-
199
  # Calculate stats for plain
200
- c.execute("SELECT AVG(reading_time_ms), AVG(CAST(score AS FLOAT) / total_questions) * 100, COUNT(*) FROM study_results WHERE condition = 'plain'")
 
 
201
  plain_stats = c.fetchone()
202
-
203
  # Calculate stats for flowread
204
- c.execute("SELECT AVG(reading_time_ms), AVG(CAST(score AS FLOAT) / total_questions) * 100, COUNT(*) FROM study_results WHERE condition = 'flowread'")
 
 
205
  flowread_stats = c.fetchone()
206
-
207
  # Calculate stats for gradient
208
- c.execute("SELECT AVG(reading_time_ms), AVG(CAST(score AS FLOAT) / total_questions) * 100, COUNT(*) FROM study_results WHERE condition = 'gradient'")
 
 
209
  gradient_stats = c.fetchone()
210
-
211
  # Calculate preferences
212
  c.execute("SELECT preference, COUNT(*) FROM study_preferences GROUP BY preference")
213
  preferences = dict(c.fetchall())
214
-
215
  conn.close()
216
-
217
  return {
218
  "plain": {
219
  "avg_reading_time_ms": plain_stats[0] or 0,
220
  "avg_accuracy_percent": plain_stats[1] or 0,
221
- "sample_size": plain_stats[2]
222
  },
223
  "flowread": {
224
  "avg_reading_time_ms": flowread_stats[0] or 0,
225
  "avg_accuracy_percent": flowread_stats[1] or 0,
226
- "sample_size": flowread_stats[2]
227
  },
228
  "gradient": {
229
  "avg_reading_time_ms": gradient_stats[0] or 0,
230
  "avg_accuracy_percent": gradient_stats[1] or 0,
231
- "sample_size": gradient_stats[2]
232
  },
233
- "preferences": preferences
234
  }
235
 
 
236
  import sys
237
  import re
238
 
 
239
  class StderrProgressInterceptor:
240
  def __init__(self, original):
241
  self.original = original
@@ -244,68 +255,83 @@ class StderrProgressInterceptor:
244
 
245
  def write(self, s):
246
  self.original.write(s)
247
- match = re.search(r'(\d+)%\|', s)
248
  if match and self.active_model:
249
  pct = match.group(1)
250
  self.current_progress = f"{pct}%"
251
  # Update the global status explicitly so the API returns it immediately
252
  model_status[self.active_model] = f"downloading: {self.current_progress}"
253
-
254
  def flush(self):
255
  self.original.flush()
256
 
 
257
  stderr_interceptor = StderrProgressInterceptor(sys.stderr)
258
  sys.stderr = stderr_interceptor
259
 
260
  # --- Saliency API (Existing) ---
261
  models = {}
262
  tokenizers = {}
263
- model_status = {
264
- "2b": "unloaded",
265
- "27b-4a": "unloaded"
266
- }
267
-
268
- device = torch.device("cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu")
 
 
 
269
  hf_token = os.environ.get("HF_TOKEN")
270
 
 
271
  def load_model(model_name: str):
272
  if model_name in models:
273
  return models[model_name], tokenizers[model_name]
274
-
275
  print(f"Loading {model_name} on {device}...")
276
  model_status[model_name] = "downloading: 0%"
277
  stderr_interceptor.active_model = model_name
278
  try:
279
  if model_name == "27b-4a":
280
  # Use Gemma 4 26B A4B in 4-bit (requires CUDA)
281
- hf_model_id = "unsloth/gemma-4-26B-A4B"
282
- tokenizer = AutoTokenizer.from_pretrained(hf_model_id, token=hf_token)
283
-
 
 
 
 
 
 
 
 
284
  # BitsAndBytes for 4-bit quantization
285
  from transformers import BitsAndBytesConfig
 
286
  bnb_config = BitsAndBytesConfig(
287
- load_in_4bit=True,
288
- bnb_4bit_compute_dtype=torch.bfloat16
289
  )
290
-
291
  model = AutoModelForCausalLM.from_pretrained(
292
  hf_model_id,
293
  quantization_config=bnb_config,
294
  device_map="auto",
295
  attn_implementation="eager",
296
- token=hf_token
297
  )
298
  else:
299
  # Default to 2b (Gemma 4 E2B)
300
  hf_model_id = "unsloth/gemma-4-E2B"
301
- tokenizer = AutoTokenizer.from_pretrained(hf_model_id, token=hf_token)
 
 
302
  model = AutoModelForCausalLM.from_pretrained(
303
  hf_model_id,
304
  torch_dtype=torch.bfloat16,
305
  attn_implementation="eager",
306
- token=hf_token
307
  ).to(device)
308
-
309
  print(f"Model {model_name} loaded successfully.")
310
  models[model_name] = model
311
  tokenizers[model_name] = tokenizer
@@ -318,31 +344,37 @@ def load_model(model_name: str):
318
  stderr_interceptor.active_model = None
319
  raise e
320
 
 
321
  # Pre-load default 2b
322
  try:
323
  load_model("2b")
324
  except:
325
  print("Could not preload 2b model.")
326
 
 
327
  @app.get("/status")
328
  def get_model_status():
329
  return model_status
330
 
 
331
  class TextRequest(BaseModel):
332
  text: str
333
- layers: Optional[List[int]] = None # List of layer indices to average
334
- preprompt: str = "" # Optional task-driven intent
335
- saliency_mode: str = "local" # "local" or "global"
 
 
336
 
337
  @app.post("/analyze")
338
  def analyze_text_legacy(request: TextRequest):
339
  return analyze_text_model("2b", request)
340
 
 
341
  @app.post("/analyze/{model_name}")
342
  def analyze_text_model(model_name: str, request: TextRequest):
343
  text = request.text
344
  preprompt = request.preprompt.strip()
345
-
346
  if not text.strip():
347
  return {"tokens": [], "scores": []}
348
 
@@ -350,51 +382,66 @@ def analyze_text_model(model_name: str, request: TextRequest):
350
  model, tokenizer = load_model(model_name)
351
  except Exception as e:
352
  from fastapi import HTTPException
 
353
  raise HTTPException(status_code=500, detail=str(e))
354
 
355
  # Combine preprompt and text if preprompt exists
356
  full_text = f"{preprompt}\n\n{text}" if preprompt else text
357
 
358
  inputs = tokenizer(full_text, return_tensors="pt").to(model.device)
359
-
360
  # Calculate how many tokens belong to the preprompt so we can strip them later
361
- num_preprompt_tokens = 1 # default is 1 for <bos>
362
  if preprompt:
363
  p_toks = tokenizer(f"{preprompt}\n\n")["input_ids"]
364
  num_preprompt_tokens = len(p_toks)
 
 
 
 
 
 
365
 
366
  with torch.no_grad():
367
  # Ensure we ask the model to output attentions explicitly
368
  outputs = model(**inputs, output_attentions=True)
369
-
370
  # Check if attentions are actually returned
371
  if not outputs.attentions:
372
  print("Warning: Model did not return attentions.")
373
  return {"words": []}
374
-
375
  num_layers = len(outputs.attentions)
376
-
377
  selected_layers = request.layers
378
  if not selected_layers:
379
- start_layer = num_layers // 4
380
- end_layer = num_layers - (num_layers // 4)
381
- selected_layers = list(range(start_layer, end_layer))
382
-
 
 
 
 
 
 
 
 
383
  selected_layers = [l for l in selected_layers if 0 <= l < num_layers]
384
  if not selected_layers:
385
  selected_layers = [num_layers - 1]
386
-
387
  stacked_attentions = torch.stack([outputs.attentions[l] for l in selected_layers])
388
- avg_attention = stacked_attentions.mean(dim=(0, 2))[0]
389
-
390
  # Calculate importance: sum of attention each token *receives* from the sequence
391
  importance = avg_attention.sum(dim=0).cpu().float().numpy()
392
-
393
  import numpy as np
394
 
395
  if len(importance) > num_preprompt_tokens:
396
  text_importance = importance[num_preprompt_tokens:]
397
-
398
  if request.saliency_mode == "global":
399
  # Global Mode: absolute importance across different texts
400
  # We apply a soft root penalty so very high values don't entirely blow out the scale,
@@ -405,42 +452,55 @@ def analyze_text_model(model_name: str, request: TextRequest):
405
  # Local Mode: relative importance within this specific block of text
406
  min_score = text_importance.min()
407
  max_score = text_importance.max()
408
-
409
  if max_score > min_score:
410
  normalized_scores = (importance - min_score) / (max_score - min_score)
411
  else:
412
  normalized_scores = importance - min_score
413
-
414
  # Keep <bos> at max score
415
  normalized_scores[0] = 1.0
416
  normalized_scores = normalized_scores.clip(0, 1)
417
  else:
418
  normalized_scores = [1.0] * len(importance)
419
-
420
  input_ids = inputs["input_ids"][0].tolist()
421
  tokens = tokenizer.convert_ids_to_tokens(input_ids)
422
-
 
 
 
423
  result = []
424
  for i, t in enumerate(tokens):
425
  # Decode properly
426
  word = tokenizer.decode([input_ids[i]])
 
 
 
 
 
 
 
 
427
  # Special check for Gemma, decoding often removes spaces incorrectly or leaves tokens empty
428
  # Let's clean the raw token just in case
429
- raw_clean = t.replace('\u2581', ' ')
430
-
431
  # We will pass both decoded word and raw cleaned token to frontend to help render
432
- result.append({
433
- "token": raw_clean,
434
- "word": word,
435
- "score": float(normalized_scores[i])
436
- })
437
-
438
- # Return only the <bos> token plus the actual text tokens
439
- if num_preprompt_tokens > 1 and len(result) > num_preprompt_tokens:
440
- result = [result[0]] + result[num_preprompt_tokens:]
 
441
 
442
  return {"words": result}
443
 
 
444
  if __name__ == "__main__":
445
  port = int(os.environ.get("PORT", 7860))
446
- uvicorn.run("main:app", host="0.0.0.0", port=port, reload=True)
 
22
 
23
  app.mount("/static", StaticFiles(directory="static"), name="static")
24
 
25
+
26
  @app.get("/")
27
  def read_root():
28
  return RedirectResponse(url="/static/index.html")
29
 
30
+
31
  # --- SQLite Database Setup ---
32
  # Hugging Face Spaces make the /app directory read-only by default.
33
  # We must write the database to /data (if persistent storage is enabled) or /tmp.
34
  DB_DIR = "/data" if os.path.exists("/data") else "/tmp"
35
  DB_FILE = os.path.join(DB_DIR, "study.db")
36
 
37
+
38
  def init_db():
39
  conn = sqlite3.connect(DB_FILE)
40
  c = conn.cursor()
41
+ c.execute("""
42
  CREATE TABLE IF NOT EXISTS study_results (
43
  id INTEGER PRIMARY KEY AUTOINCREMENT,
44
  user_id TEXT,
 
49
  total_questions INTEGER,
50
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
51
  )
52
+ """)
53
+
54
+ c.execute("""
55
  CREATE TABLE IF NOT EXISTS study_preferences (
56
  id INTEGER PRIMARY KEY AUTOINCREMENT,
57
  user_id TEXT,
58
  preference TEXT, -- "plain", "flowread", or "gradient"
59
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
60
  )
61
+ """)
62
  conn.commit()
63
  conn.close()
64
 
65
+
66
  init_db()
67
 
68
  # --- Study Content ---
 
74
  "questions": [
75
  {
76
  "question": "Approximately how many neurons are in the human brain?",
77
+ "options": ["86 million", "86 billion", "100 trillion", "50 billion"],
78
+ "correct": 1,
 
 
 
 
 
79
  },
80
  {
81
  "question": "What is the term for the brain's ability to reorganize itself?",
 
83
  "Synaptic generation",
84
  "Neurogenesis",
85
  "Neuroplasticity",
86
+ "Cognitive adaptation",
87
  ],
88
+ "correct": 2,
89
+ },
90
  ],
91
+ "flowread_html": '<span class="token highlighted">The</span><span class="token highlighted"> human</span><span class="token highlighted"> brain</span><span class="token highlighted"> is</span><span class="token highlighted"> a</span><span class="token highlighted"> marvel</span><span class="token"> of</span><span class="token highlighted"> biological</span><span class="token highlighted"> engineering</span><span class="token highlighted">,</span><span class="token highlighted"> containing</span><span class="token"> approximately</span><span class="token"> </span><span class="token">8</span><span class="token">6</span><span class="token highlighted"> billion</span><span class="token highlighted"> neurons</span><span class="token highlighted"> interconnected</span><span class="token highlighted"> by</span><span class="token"> tri</span><span class="token">lli</span><span class="token">ons</span><span class="token"> of</span><span class="token highlighted"> synapses</span><span class="token highlighted">.</span><span class="token highlighted"> These</span><span class="token highlighted"> neural</span><span class="token highlighted"> networks</span><span class="token"> are</span><span class="token"> responsible</span><span class="token highlighted"> for</span><span class="token"> everything</span><span class="token highlighted"> from</span><span class="token highlighted"> basic</span><span class="token highlighted"> autonomic</span><span class="token highlighted"> functions</span><span class="token highlighted">,</span><span class="token highlighted"> like</span><span class="token"> breathing</span><span class="token"> and</span><span class="token"> heart</span><span class="token"> rate</span><span class="token">,</span><span class="token highlighted"> to</span><span class="token"> complex</span><span class="token highlighted"> cognitive</span><span class="token"> processes</span><span class="token"> such</span><span class="token"> as</span><span class="token highlighted"> memory</span><span class="token highlighted">,</span><span class="token"> emotion</span><span class="token">,</span><span class="token"> and</span><span class="token highlighted"> problem</span><span class="token highlighted">-</span><span class="token highlighted">solving</span><span class="token highlighted">.</span><span class="token highlighted"> Neurop</span><span class="token highlighted">lastic</span><span class="token highlighted">ity</span><span class="token highlighted">,</span><span class="token highlighted"> the</span><span class="token highlighted"> brain</span><span class="token highlighted">\'</span><span class="token highlighted">s</span><span class="token"> ability</span><span class="token"> to</span><span class="token highlighted"> reorgan</span><span class="token highlighted">ize</span><span class="token"> itself</span><span class="token"> by</span><span class="token"> forming</span><span class="token"> new</span><span class="token"> neural</span><span class="token"> connections</span><span class="token"> throughout</span><span class="token highlighted"> life</span><span class="token highlighted">,</span><span class="token highlighted"> allows</span><span class="token highlighted"> humans</span><span class="token"> to</span><span class="token highlighted"> learn</span><span class="token"> new</span><span class="token"> skills</span><span class="token">,</span><span class="token"> recover</span><span class="token"> from</span><span class="token"> injuries</span><span class="token">,</span><span class="token"> and</span><span class="token"> adapt</span><span class="token"> to</span><span class="token"> changing</span><span class="token highlighted"> environments</span><span class="token highlighted">.</span><span class="token"> This</span><span class="token"> extraordinary</span><span class="token highlighted"> adaptability</span><span class="token"> is</span><span class="token"> what</span><span class="token"> makes</span><span class="token"> our</span><span class="token"> species</span><span class="token"> so</span><span class="token"> resilient</span><span class="token"> and</span><span class="token"> capable</span><span class="token"> of</span><span class="token"> continuous</span><span class="token"> intellectual</span><span class="token"> growth</span><span class="token">.</span>',
92
+ "flowread_gradient_html": '<span class="token" style="opacity: 0.53; font-weight: 489;">The</span><span class="token" style="opacity: 0.56; font-weight: 506;"> human</span><span class="token" style="opacity: 1.00; font-weight: 800;"> brain</span><span class="token" style="opacity: 0.54; font-weight: 490;"> is</span><span class="token" style="opacity: 0.51; font-weight: 472;"> a</span><span class="token" style="opacity: 0.53; font-weight: 489;"> marvel</span><span class="token" style="opacity: 0.48; font-weight: 452;"> of</span><span class="token" style="opacity: 0.53; font-weight: 484;"> biological</span><span class="token" style="opacity: 0.55; font-weight: 498;"> engineering</span><span class="token" style="opacity: 0.55; font-weight: 498;">,</span><span class="token" style="opacity: 0.56; font-weight: 506;"> containing</span><span class="token" style="opacity: 0.48; font-weight: 455;"> approximately</span><span class="token" style="opacity: 0.48; font-weight: 455;"> </span><span class="token" style="opacity: 0.43; font-weight: 421;">8</span><span class="token" style="opacity: 0.43; font-weight: 421;">6</span><span class="token" style="opacity: 0.50; font-weight: 469;"> billion</span><span class="token" style="opacity: 0.71; font-weight: 608;"> neurons</span><span class="token" style="opacity: 0.55; font-weight: 500;"> interconnected</span><span class="token" style="opacity: 0.53; font-weight: 484;"> by</span><span class="token" style="opacity: 0.43; font-weight: 419;"> tri</span><span class="token" style="opacity: 0.43; font-weight: 419;">lli</span><span class="token" style="opacity: 0.43; font-weight: 419;">ons</span><span class="token" style="opacity: 0.42; font-weight: 413;"> of</span><span class="token" style="opacity: 0.72; font-weight: 613;"> synapses</span><span class="token" style="opacity: 0.72; font-weight: 613;">.</span><span class="token" style="opacity: 0.51; font-weight: 472;"> These</span><span class="token" style="opacity: 0.57; font-weight: 516;"> neural</span><span class="token" style="opacity: 0.57; font-weight: 515;"> networks</span><span class="token" style="opacity: 0.48; font-weight: 455;"> are</span><span class="token" style="opacity: 0.48; font-weight: 452;"> responsible</span><span class="token" style="opacity: 0.52; font-weight: 482;"> for</span><span class="token" style="opacity: 0.50; font-weight: 464;"> everything</span><span class="token" style="opacity: 0.59; font-weight: 529;"> from</span><span class="token" style="opacity: 0.51; font-weight: 474;"> basic</span><span class="token" style="opacity: 0.56; font-weight: 504;"> autonomic</span><span class="token" style="opacity: 0.52; font-weight: 482;"> functions</span><span class="token" style="opacity: 0.52; font-weight: 482;">,</span><span class="token" style="opacity: 0.52; font-weight: 481;"> like</span><span class="token" style="opacity: 0.50; font-weight: 466;"> breathing</span><span class="token" style="opacity: 0.47; font-weight: 444;"> and</span><span class="token" style="opacity: 0.44; font-weight: 424;"> heart</span><span class="token" style="opacity: 0.45; font-weight: 435;"> rate</span><span class="token" style="opacity: 0.45; font-weight: 435;">,</span><span class="token" style="opacity: 0.52; font-weight: 479;"> to</span><span class="token" style="opacity: 0.49; font-weight: 462;"> complex</span><span class="token" style="opacity: 0.53; font-weight: 487;"> cognitive</span><span class="token" style="opacity: 0.48; font-weight: 451;"> processes</span><span class="token" style="opacity: 0.45; font-weight: 432;"> such</span><span class="token" style="opacity: 0.48; font-weight: 451;"> as</span><span class="token" style="opacity: 0.51; font-weight: 474;"> memory</span><span class="token" style="opacity: 0.51; font-weight: 474;">,</span><span class="token" style="opacity: 0.48; font-weight: 450;"> emotion</span><span class="token" style="opacity: 0.48; font-weight: 450;">,</span><span class="token" style="opacity: 0.44; font-weight: 428;"> and</span><span class="token" style="opacity: 0.64; font-weight: 559;"> problem</span><span class="token" style="opacity: 0.64; font-weight: 559;">-</span><span class="token" style="opacity: 0.64; font-weight: 559;">solving</span><span class="token" style="opacity: 0.64; font-weight: 559;">.</span><span class="token" style="opacity: 0.68; font-weight: 587;"> Neurop</span><span class="token" style="opacity: 0.68; font-weight: 587;">lastic</span><span class="token" style="opacity: 0.68; font-weight: 587;">ity</span><span class="token" style="opacity: 0.68; font-weight: 587;">,</span><span class="token" style="opacity: 0.50; font-weight: 469;"> the</span><span class="token" style="opacity: 0.59; font-weight: 527;"> brain</span><span class="token" style="opacity: 0.59; font-weight: 527;">\'</span><span class="token" style="opacity: 0.59; font-weight: 527;">s</span><span class="token" style="opacity: 0.45; font-weight: 434;"> ability</span><span class="token" style="opacity: 0.50; font-weight: 464;"> to</span><span class="token" style="opacity: 0.50; font-weight: 469;"> reorgan</span><span class="token" style="opacity: 0.50; font-weight: 469;">ize</span><span class="token" style="opacity: 0.47; font-weight: 444;"> itself</span><span class="token" style="opacity: 0.48; font-weight: 454;"> by</span><span class="token" style="opacity: 0.45; font-weight: 434;"> forming</span><span class="token" style="opacity: 0.45; font-weight: 431;"> new</span><span class="token" style="opacity: 0.44; font-weight: 426;"> neural</span><span class="token" style="opacity: 0.45; font-weight: 433;"> connections</span><span class="token" style="opacity: 0.44; font-weight: 426;"> throughout</span><span class="token" style="opacity: 0.52; font-weight: 480;"> life</span><span class="token" style="opacity: 0.52; font-weight: 480;">,</span><span class="token" style="opacity: 0.53; font-weight: 485;"> allows</span><span class="token" style="opacity: 0.54; font-weight: 493;"> humans</span><span class="token" style="opacity: 0.49; font-weight: 463;"> to</span><span class="token" style="opacity: 0.52; font-weight: 482;"> learn</span><span class="token" style="opacity: 0.45; font-weight: 433;"> new</span><span class="token" style="opacity: 0.48; font-weight: 454;"> skills</span><span class="token" style="opacity: 0.48; font-weight: 454;">,</span><span class="token" style="opacity: 0.47; font-weight: 449;"> recover</span><span class="token" style="opacity: 0.44; font-weight: 429;"> from</span><span class="token" style="opacity: 0.46; font-weight: 438;"> injuries</span><span class="token" style="opacity: 0.46; font-weight: 438;">,</span><span class="token" style="opacity: 0.45; font-weight: 433;"> and</span><span class="token" style="opacity: 0.45; font-weight: 436;"> adapt</span><span class="token" style="opacity: 0.43; font-weight: 421;"> to</span><span class="token" style="opacity: 0.42; font-weight: 414;"> changing</span><span class="token" style="opacity: 0.53; font-weight: 485;"> environments</span><span class="token" style="opacity: 0.53; font-weight: 485;">.</span><span class="token" style="opacity: 0.44; font-weight: 429;"> This</span><span class="token" style="opacity: 0.46; font-weight: 442;"> extraordinary</span><span class="token" style="opacity: 0.51; font-weight: 472;"> adaptability</span><span class="token" style="opacity: 0.44; font-weight: 428;"> is</span><span class="token" style="opacity: 0.45; font-weight: 435;"> what</span><span class="token" style="opacity: 0.46; font-weight: 443;"> makes</span><span class="token" style="opacity: 0.44; font-weight: 428;"> our</span><span class="token" style="opacity: 0.50; font-weight: 468;"> species</span><span class="token" style="opacity: 0.45; font-weight: 430;"> so</span><span class="token" style="opacity: 0.47; font-weight: 443;"> resilient</span><span class="token" style="opacity: 0.44; font-weight: 427;"> and</span><span class="token" style="opacity: 0.42; font-weight: 412;"> capable</span><span class="token" style="opacity: 0.43; font-weight: 422;"> of</span><span class="token" style="opacity: 0.43; font-weight: 420;"> continuous</span><span class="token" style="opacity: 0.42; font-weight: 413;"> intellectual</span><span class="token" style="opacity: 0.41; font-weight: 403;"> growth</span><span class="token" style="opacity: 0.41; font-weight: 403;">.</span>',
93
  },
94
  {
95
  "id": 2,
 
98
  "questions": [
99
  {
100
  "question": "Where did the Industrial Revolution begin?",
101
+ "options": ["United States", "France", "Germany", "Britain"],
102
+ "correct": 3,
 
 
 
 
 
103
  },
104
  {
105
  "question": "Which invention dramatically increased factory efficiency?",
 
107
  "The cotton gin",
108
  "The telegraph",
109
  "The steam engine",
110
+ "The assembly line",
111
  ],
112
+ "correct": 2,
113
+ },
114
  ],
115
+ "flowread_html": '<span class="token highlighted">The</span><span class="token highlighted"> Industrial</span><span class="token highlighted"> Revolution</span><span class="token highlighted">,</span><span class="token highlighted"> which</span><span class="token highlighted"> began</span><span class="token"> in</span><span class="token highlighted"> Britain</span><span class="token"> in</span><span class="token"> the</span><span class="token"> late</span><span class="token"> </span><span class="token highlighted">1</span><span class="token highlighted">8</span><span class="token highlighted">th</span><span class="token highlighted"> century</span><span class="token highlighted">,</span><span class="token highlighted"> marked</span><span class="token"> a</span><span class="token highlighted"> profound</span><span class="token"> turning</span><span class="token highlighted"> point</span><span class="token"> in</span><span class="token highlighted"> human</span><span class="token highlighted"> history</span><span class="token highlighted">.</span><span class="token highlighted"> It</span><span class="token highlighted"> initiated</span><span class="token"> the</span><span class="token"> transition</span><span class="token highlighted"> from</span><span class="token highlighted"> agrarian</span><span class="token highlighted">,</span><span class="token"> handic</span><span class="token">raft</span><span class="token highlighted"> economies</span><span class="token highlighted"> to</span><span class="token highlighted"> industry</span><span class="token"> and</span><span class="token highlighted"> machine</span><span class="token highlighted"> manufacturing</span><span class="token highlighted">.</span><span class="token highlighted"> The</span><span class="token highlighted"> invention</span><span class="token highlighted"> of</span><span class="token"> the</span><span class="token highlighted"> steam</span><span class="token highlighted"> engine</span><span class="token highlighted">,</span><span class="token"> pioneered</span><span class="token highlighted"> by</span><span class="token"> figures</span><span class="token"> like</span><span class="token"> James</span><span class="token"> Watt</span><span class="token">,</span><span class="token highlighted"> dramatically</span><span class="token highlighted"> increased</span><span class="token"> the</span><span class="token"> efficiency</span><span class="token"> of</span><span class="token highlighted"> factories</span><span class="token"> and</span><span class="token highlighted"> transportation</span><span class="token highlighted">,</span><span class="token highlighted"> revolution</span><span class="token highlighted">izing</span><span class="token"> the</span><span class="token"> textile</span><span class="token"> industry</span><span class="token"> and</span><span class="token"> leading</span><span class="token"> to</span><span class="token"> the</span><span class="token"> expansion</span><span class="token"> of</span><span class="token highlighted"> railways</span><span class="token highlighted">.</span><span class="token"> This</span><span class="token highlighted"> era</span><span class="token highlighted"> brought</span><span class="token"> about</span><span class="token"> unprecedented</span><span class="token highlighted"> economic</span><span class="token highlighted"> growth</span><span class="token"> and</span><span class="token highlighted"> urbanization</span><span class="token highlighted">,</span><span class="token"> fundamentally</span><span class="token highlighted"> altering</span><span class="token"> social</span><span class="token"> structures</span><span class="token"> and</span><span class="token"> paving</span><span class="token"> the</span><span class="token"> way</span><span class="token"> for</span><span class="token"> the</span><span class="token"> modern</span><span class="token"> capitalist</span><span class="token"> system</span><span class="token">,</span><span class="token"> despite</span><span class="token"> also</span><span class="token"> causing</span><span class="token"> significant</span><span class="token"> social</span><span class="token"> inequalities</span><span class="token"> and</span><span class="token"> poor</span><span class="token"> working</span><span class="token"> conditions</span><span class="token"> initially</span><span class="token">.</span>',
116
+ "flowread_gradient_html": '<span class="token" style="opacity: 0.49; font-weight: 462;">The</span><span class="token" style="opacity: 0.59; font-weight: 524;"> Industrial</span><span class="token" style="opacity: 1.00; font-weight: 800;"> Revolution</span><span class="token" style="opacity: 1.00; font-weight: 800;">,</span><span class="token" style="opacity: 0.48; font-weight: 456;"> which</span><span class="token" style="opacity: 0.50; font-weight: 467;"> began</span><span class="token" style="opacity: 0.45; font-weight: 430;"> in</span><span class="token" style="opacity: 0.58; font-weight: 519;"> Britain</span><span class="token" style="opacity: 0.44; font-weight: 429;"> in</span><span class="token" style="opacity: 0.42; font-weight: 410;"> the</span><span class="token" style="opacity: 0.43; font-weight: 418;"> late</span><span class="token" style="opacity: 0.42; font-weight: 415;"> </span><span class="token" style="opacity: 0.49; font-weight: 457;">1</span><span class="token" style="opacity: 0.49; font-weight: 457;">8</span><span class="token" style="opacity: 0.49; font-weight: 457;">th</span><span class="token" style="opacity: 0.50; font-weight: 467;"> century</span><span class="token" style="opacity: 0.50; font-weight: 467;">,</span><span class="token" style="opacity: 0.49; font-weight: 460;"> marked</span><span class="token" style="opacity: 0.43; font-weight: 422;"> a</span><span class="token" style="opacity: 0.46; font-weight: 439;"> profound</span><span class="token" style="opacity: 0.43; font-weight: 418;"> turning</span><span class="token" style="opacity: 0.46; font-weight: 440;"> point</span><span class="token" style="opacity: 0.44; font-weight: 423;"> in</span><span class="token" style="opacity: 0.47; font-weight: 447;"> human</span><span class="token" style="opacity: 0.57; font-weight: 511;"> history</span><span class="token" style="opacity: 0.57; font-weight: 511;">.</span><span class="token" style="opacity: 0.48; font-weight: 456;"> It</span><span class="token" style="opacity: 0.48; font-weight: 450;"> initiated</span><span class="token" style="opacity: 0.45; font-weight: 431;"> the</span><span class="token" style="opacity: 0.44; font-weight: 426;"> transition</span><span class="token" style="opacity: 0.47; font-weight: 444;"> from</span><span class="token" style="opacity: 0.50; font-weight: 469;"> agrarian</span><span class="token" style="opacity: 0.50; font-weight: 469;">,</span><span class="token" style="opacity: 0.45; font-weight: 433;"> handic</span><span class="token" style="opacity: 0.45; font-weight: 433;">raft</span><span class="token" style="opacity: 0.48; font-weight: 451;"> economies</span><span class="token" style="opacity: 0.48; font-weight: 452;"> to</span><span class="token" style="opacity: 0.53; font-weight: 489;"> industry</span><span class="token" style="opacity: 0.45; font-weight: 432;"> and</span><span class="token" style="opacity: 0.47; font-weight: 444;"> machine</span><span class="token" style="opacity: 0.55; font-weight: 498;"> manufacturing</span><span class="token" style="opacity: 0.55; font-weight: 498;">.</span><span class="token" style="opacity: 0.49; font-weight: 461;"> The</span><span class="token" style="opacity: 0.46; font-weight: 442;"> invention</span><span class="token" style="opacity: 0.46; font-weight: 440;"> of</span><span class="token" style="opacity: 0.43; font-weight: 417;"> the</span><span class="token" style="opacity: 0.50; font-weight: 465;"> steam</span><span class="token" style="opacity: 0.52; font-weight: 483;"> engine</span><span class="token" style="opacity: 0.52; font-weight: 483;">,</span><span class="token" style="opacity: 0.45; font-weight: 434;"> pioneered</span><span class="token" style="opacity: 0.45; font-weight: 436;"> by</span><span class="token" style="opacity: 0.43; font-weight: 421;"> figures</span><span class="token" style="opacity: 0.44; font-weight: 426;"> like</span><span class="token" style="opacity: 0.42; font-weight: 414;"> James</span><span class="token" style="opacity: 0.44; font-weight: 428;"> Watt</span><span class="token" style="opacity: 0.44; font-weight: 428;">,</span><span class="token" style="opacity: 0.46; font-weight: 437;"> dramatically</span><span class="token" style="opacity: 0.47; font-weight: 445;"> increased</span><span class="token" style="opacity: 0.43; font-weight: 421;"> the</span><span class="token" style="opacity: 0.45; font-weight: 432;"> efficiency</span><span class="token" style="opacity: 0.43; font-weight: 421;"> of</span><span class="token" style="opacity: 0.50; font-weight: 467;"> factories</span><span class="token" style="opacity: 0.45; font-weight: 432;"> and</span><span class="token" style="opacity: 0.46; font-weight: 442;"> transportation</span><span class="token" style="opacity: 0.46; font-weight: 442;">,</span><span class="token" style="opacity: 0.47; font-weight: 446;"> revolution</span><span class="token" style="opacity: 0.47; font-weight: 446;">izing</span><span class="token" style="opacity: 0.42; font-weight: 415;"> the</span><span class="token" style="opacity: 0.45; font-weight: 436;"> textile</span><span class="token" style="opacity: 0.44; font-weight: 428;"> industry</span><span class="token" style="opacity: 0.45; font-weight: 433;"> and</span><span class="token" style="opacity: 0.43; font-weight: 416;"> leading</span><span class="token" style="opacity: 0.44; font-weight: 425;"> to</span><span class="token" style="opacity: 0.41; font-weight: 409;"> the</span><span class="token" style="opacity: 0.42; font-weight: 413;"> expansion</span><span class="token" style="opacity: 0.41; font-weight: 407;"> of</span><span class="token" style="opacity: 0.50; font-weight: 468;"> railways</span><span class="token" style="opacity: 0.50; font-weight: 468;">.</span><span class="token" style="opacity: 0.44; font-weight: 423;"> This</span><span class="token" style="opacity: 0.48; font-weight: 454;"> era</span><span class="token" style="opacity: 0.47; font-weight: 447;"> brought</span><span class="token" style="opacity: 0.43; font-weight: 418;"> about</span><span class="token" style="opacity: 0.45; font-weight: 434;"> unprecedented</span><span class="token" style="opacity: 0.46; font-weight: 438;"> economic</span><span class="token" style="opacity: 0.46; font-weight: 437;"> growth</span><span class="token" style="opacity: 0.45; font-weight: 431;"> and</span><span class="token" style="opacity: 0.47; font-weight: 449;"> urbanization</span><span class="token" style="opacity: 0.47; font-weight: 449;">,</span><span class="token" style="opacity: 0.44; font-weight: 424;"> fundamentally</span><span class="token" style="opacity: 0.46; font-weight: 441;"> altering</span><span class="token" style="opacity: 0.45; font-weight: 432;"> social</span><span class="token" style="opacity: 0.44; font-weight: 427;"> structures</span><span class="token" style="opacity: 0.45; font-weight: 432;"> and</span><span class="token" style="opacity: 0.43; font-weight: 419;"> paving</span><span class="token" style="opacity: 0.41; font-weight: 407;"> the</span><span class="token" style="opacity: 0.42; font-weight: 410;"> way</span><span class="token" style="opacity: 0.44; font-weight: 423;"> for</span><span class="token" style="opacity: 0.41; font-weight: 409;"> the</span><span class="token" style="opacity: 0.43; font-weight: 422;"> modern</span><span class="token" style="opacity: 0.44; font-weight: 429;"> capitalist</span><span class="token" style="opacity: 0.42; font-weight: 415;"> system</span><span class="token" style="opacity: 0.42; font-weight: 415;">,</span><span class="token" style="opacity: 0.45; font-weight: 432;"> despite</span><span class="token" style="opacity: 0.43; font-weight: 417;"> also</span><span class="token" style="opacity: 0.45; font-weight: 431;"> causing</span><span class="token" style="opacity: 0.43; font-weight: 419;"> significant</span><span class="token" style="opacity: 0.42; font-weight: 416;"> social</span><span class="token" style="opacity: 0.44; font-weight: 429;"> inequalities</span><span class="token" style="opacity: 0.42; font-weight: 415;"> and</span><span class="token" style="opacity: 0.42; font-weight: 414;"> poor</span><span class="token" style="opacity: 0.41; font-weight: 407;"> working</span><span class="token" style="opacity: 0.41; font-weight: 406;"> conditions</span><span class="token" style="opacity: 0.41; font-weight: 403;"> initially</span><span class="token" style="opacity: 0.41; font-weight: 403;">.</span>',
117
  },
118
  {
119
  "id": 3,
 
122
  "questions": [
123
  {
124
  "question": "What spectrum does the James Webb Space Telescope primarily operate in?",
125
+ "options": ["Ultraviolet", "X-ray", "Infrared", "Visible light"],
126
+ "correct": 2,
 
 
 
 
 
127
  },
128
  {
129
  "question": "Where does the telescope orbit to stay cold?",
 
131
  "Low Earth Orbit",
132
  "The Moon's orbit",
133
  "The first Lagrange point",
134
+ "The second Lagrange point",
135
  ],
136
+ "correct": 3,
137
+ },
138
  ],
139
+ "flowread_html": '<span class="token highlighted">The</span><span class="token"> James</span><span class="token highlighted"> Webb</span><span class="token highlighted"> Space</span><span class="token highlighted"> Telescope</span><span class="token highlighted"> is</span><span class="token highlighted"> the</span><span class="token highlighted"> largest</span><span class="token"> and</span><span class="token"> most</span><span class="token highlighted"> powerful</span><span class="token highlighted"> space</span><span class="token highlighted"> telescope</span><span class="token"> ever</span><span class="token highlighted"> built</span><span class="token highlighted">.</span><span class="token highlighted"> Launched</span><span class="token"> in</span><span class="token"> </span><span class="token highlighted">2</span><span class="token highlighted">0</span><span class="token highlighted">2</span><span class="token highlighted">1</span><span class="token highlighted">,</span><span class="token highlighted"> it</span><span class="token highlighted"> operates</span><span class="token"> primarily</span><span class="token"> in</span><span class="token"> the</span><span class="token highlighted"> infrared</span><span class="token highlighted"> spectrum</span><span class="token highlighted">,</span><span class="token highlighted"> allowing</span><span class="token"> it</span><span class="token"> to</span><span class="token"> peer</span><span class="token"> through</span><span class="token"> dense</span><span class="token highlighted"> cosmic</span><span class="token"> dust</span><span class="token"> and</span><span class="token highlighted"> observe</span><span class="token"> the</span><span class="token highlighted"> universe</span><span class="token highlighted">\'</span><span class="token highlighted">s</span><span class="token"> most</span><span class="token"> distant</span><span class="token">,</span><span class="token"> early</span><span class="token highlighted"> galaxies</span><span class="token highlighted">.</span><span class="token highlighted"> Unlike</span><span class="token"> its</span><span class="token"> predecessor</span><span class="token">,</span><span class="token highlighted"> Hubble</span><span class="token highlighted">,</span><span class="token highlighted"> JW</span><span class="token highlighted">ST</span><span class="token highlighted"> orbits</span><span class="token"> the</span><span class="token highlighted"> Sun</span><span class="token highlighted"> at</span><span class="token"> the</span><span class="token"> second</span><span class="token highlighted"> Lagrange</span><span class="token highlighted"> point</span><span class="token highlighted">,</span><span class="token highlighted"> keeping</span><span class="token"> it</span><span class="token"> constantly</span><span class="token highlighted"> shielded</span><span class="token highlighted"> from</span><span class="token"> the</span><span class="token highlighted"> Sun</span><span class="token highlighted">\'</span><span class="token highlighted">s</span><span class="token"> heat</span><span class="token"> and</span><span class="token"> light</span><span class="token"> by</span><span class="token"> its</span><span class="token"> massive</span><span class="token highlighted"> sun</span><span class="token highlighted">shield</span><span class="token highlighted">.</span><span class="token"> This</span><span class="token"> incredibly</span><span class="token highlighted"> cold</span><span class="token"> environment</span><span class="token"> is</span><span class="token"> necessary</span><span class="token"> to</span><span class="token highlighted"> prevent</span><span class="token"> the</span><span class="token highlighted"> telescope</span><span class="token highlighted">\'</span><span class="token highlighted">s</span><span class="token"> own</span><span class="token"> infrared</span><span class="token"> emissions</span><span class="token"> from</span><span class="token"> interfering</span><span class="token"> with</span><span class="token"> its</span><span class="token"> highly</span><span class="token"> sensitive</span><span class="token"> observations</span><span class="token"> of</span><span class="token"> ex</span><span class="token">oplan</span><span class="token">et</span><span class="token"> atmospheres</span><span class="token"> and</span><span class="token"> star</span><span class="token"> formation</span><span class="token">.</span>',
140
+ "flowread_gradient_html": '<span class="token" style="opacity: 0.49; font-weight: 458;">The</span><span class="token" style="opacity: 0.45; font-weight: 435;"> James</span><span class="token" style="opacity: 1.00; font-weight: 800;"> Webb</span><span class="token" style="opacity: 0.59; font-weight: 523;"> Space</span><span class="token" style="opacity: 0.75; font-weight: 632;"> Telescope</span><span class="token" style="opacity: 0.52; font-weight: 478;"> is</span><span class="token" style="opacity: 0.47; font-weight: 447;"> the</span><span class="token" style="opacity: 0.50; font-weight: 463;"> largest</span><span class="token" style="opacity: 0.44; font-weight: 423;"> and</span><span class="token" style="opacity: 0.42; font-weight: 412;"> most</span><span class="token" style="opacity: 0.47; font-weight: 443;"> powerful</span><span class="token" style="opacity: 0.46; font-weight: 441;"> space</span><span class="token" style="opacity: 0.54; font-weight: 491;"> telescope</span><span class="token" style="opacity: 0.43; font-weight: 422;"> ever</span><span class="token" style="opacity: 0.56; font-weight: 503;"> built</span><span class="token" style="opacity: 0.56; font-weight: 503;">.</span><span class="token" style="opacity: 0.51; font-weight: 475;"> Launched</span><span class="token" style="opacity: 0.43; font-weight: 421;"> in</span><span class="token" style="opacity: 0.43; font-weight: 418;"> </span><span class="token" style="opacity: 0.50; font-weight: 463;">2</span><span class="token" style="opacity: 0.50; font-weight: 463;">0</span><span class="token" style="opacity: 0.50; font-weight: 463;">2</span><span class="token" style="opacity: 0.50; font-weight: 463;">1</span><span class="token" style="opacity: 0.50; font-weight: 463;">,</span><span class="token" style="opacity: 0.48; font-weight: 452;"> it</span><span class="token" style="opacity: 0.47; font-weight: 449;"> operates</span><span class="token" style="opacity: 0.45; font-weight: 433;"> primarily</span><span class="token" style="opacity: 0.44; font-weight: 429;"> in</span><span class="token" style="opacity: 0.43; font-weight: 418;"> the</span><span class="token" style="opacity: 0.57; font-weight: 510;"> infrared</span><span class="token" style="opacity: 0.47; font-weight: 446;"> spectrum</span><span class="token" style="opacity: 0.47; font-weight: 446;">,</span><span class="token" style="opacity: 0.48; font-weight: 453;"> allowing</span><span class="token" style="opacity: 0.43; font-weight: 420;"> it</span><span class="token" style="opacity: 0.44; font-weight: 423;"> to</span><span class="token" style="opacity: 0.46; font-weight: 437;"> peer</span><span class="token" style="opacity: 0.46; font-weight: 437;"> through</span><span class="token" style="opacity: 0.45; font-weight: 430;"> dense</span><span class="token" style="opacity: 0.46; font-weight: 441;"> cosmic</span><span class="token" style="opacity: 0.46; font-weight: 438;"> dust</span><span class="token" style="opacity: 0.45; font-weight: 431;"> and</span><span class="token" style="opacity: 0.49; font-weight: 458;"> observe</span><span class="token" style="opacity: 0.46; font-weight: 439;"> the</span><span class="token" style="opacity: 0.48; font-weight: 451;"> universe</span><span class="token" style="opacity: 0.48; font-weight: 451;">\'</span><span class="token" style="opacity: 0.48; font-weight: 451;">s</span><span class="token" style="opacity: 0.42; font-weight: 414;"> most</span><span class="token" style="opacity: 0.46; font-weight: 436;"> distant</span><span class="token" style="opacity: 0.46; font-weight: 436;">,</span><span class="token" style="opacity: 0.42; font-weight: 414;"> early</span><span class="token" style="opacity: 0.52; font-weight: 478;"> galaxies</span><span class="token" style="opacity: 0.52; font-weight: 478;">.</span><span class="token" style="opacity: 0.47; font-weight: 445;"> Unlike</span><span class="token" style="opacity: 0.43; font-weight: 419;"> its</span><span class="token" style="opacity: 0.46; font-weight: 437;"> predecessor</span><span class="token" style="opacity: 0.46; font-weight: 437;">,</span><span class="token" style="opacity: 0.50; font-weight: 467;"> Hubble</span><span class="token" style="opacity: 0.50; font-weight: 467;">,</span><span class="token" style="opacity: 0.50; font-weight: 468;"> JW</span><span class="token" style="opacity: 0.50; font-weight: 468;">ST</span><span class="token" style="opacity: 0.55; font-weight: 496;"> orbits</span><span class="token" style="opacity: 0.45; font-weight: 430;"> the</span><span class="token" style="opacity: 0.53; font-weight: 489;"> Sun</span><span class="token" style="opacity: 0.46; font-weight: 441;"> at</span><span class="token" style="opacity: 0.43; font-weight: 417;"> the</span><span class="token" style="opacity: 0.42; font-weight: 415;"> second</span><span class="token" style="opacity: 0.49; font-weight: 456;"> Lagrange</span><span class="token" style="opacity: 0.47; font-weight: 446;"> point</span><span class="token" style="opacity: 0.47; font-weight: 446;">,</span><span class="token" style="opacity: 0.47; font-weight: 444;"> keeping</span><span class="token" style="opacity: 0.45; font-weight: 431;"> it</span><span class="token" style="opacity: 0.44; font-weight: 428;"> constantly</span><span class="token" style="opacity: 0.49; font-weight: 457;"> shielded</span><span class="token" style="opacity: 0.47; font-weight: 445;"> from</span><span class="token" style="opacity: 0.43; font-weight: 419;"> the</span><span class="token" style="opacity: 0.47; font-weight: 446;"> Sun</span><span class="token" style="opacity: 0.47; font-weight: 446;">\'</span><span class="token" style="opacity: 0.47; font-weight: 446;">s</span><span class="token" style="opacity: 0.46; font-weight: 438;"> heat</span><span class="token" style="opacity: 0.44; font-weight: 423;"> and</span><span class="token" style="opacity: 0.43; font-weight: 420;"> light</span><span class="token" style="opacity: 0.44; font-weight: 429;"> by</span><span class="token" style="opacity: 0.43; font-weight: 421;"> its</span><span class="token" style="opacity: 0.43; font-weight: 418;"> massive</span><span class="token" style="opacity: 0.50; font-weight: 465;"> sun</span><span class="token" style="opacity: 0.50; font-weight: 465;">shield</span><span class="token" style="opacity: 0.50; font-weight: 465;">.</span><span class="token" style="opacity: 0.43; font-weight: 422;"> This</span><span class="token" style="opacity: 0.42; font-weight: 415;"> incredibly</span><span class="token" style="opacity: 0.46; font-weight: 440;"> cold</span><span class="token" style="opacity: 0.46; font-weight: 439;"> environment</span><span class="token" style="opacity: 0.42; font-weight: 414;"> is</span><span class="token" style="opacity: 0.43; font-weight: 422;"> necessary</span><span class="token" style="opacity: 0.45; font-weight: 431;"> to</span><span class="token" style="opacity: 0.47; font-weight: 446;"> prevent</span><span class="token" style="opacity: 0.43; font-weight: 420;"> the</span><span class="token" style="opacity: 0.47; font-weight: 445;"> telescope</span><span class="token" style="opacity: 0.47; font-weight: 445;">\'</span><span class="token" style="opacity: 0.47; font-weight: 445;">s</span><span class="token" style="opacity: 0.44; font-weight: 429;"> own</span><span class="token" style="opacity: 0.44; font-weight: 429;"> infrared</span><span class="token" style="opacity: 0.45; font-weight: 436;"> emissions</span><span class="token" style="opacity: 0.42; font-weight: 414;"> from</span><span class="token" style="opacity: 0.43; font-weight: 417;"> interfering</span><span class="token" style="opacity: 0.43; font-weight: 419;"> with</span><span class="token" style="opacity: 0.43; font-weight: 416;"> its</span><span class="token" style="opacity: 0.41; font-weight: 407;"> highly</span><span class="token" style="opacity: 0.43; font-weight: 417;"> sensitive</span><span class="token" style="opacity: 0.43; font-weight: 421;"> observations</span><span class="token" style="opacity: 0.44; font-weight: 429;"> of</span><span class="token" style="opacity: 0.45; font-weight: 430;"> ex</span><span class="token" style="opacity: 0.45; font-weight: 430;">oplan</span><span class="token" style="opacity: 0.45; font-weight: 430;">et</span><span class="token" style="opacity: 0.43; font-weight: 418;"> atmospheres</span><span class="token" style="opacity: 0.42; font-weight: 413;"> and</span><span class="token" style="opacity: 0.41; font-weight: 408;"> star</span><span class="token" style="opacity: 0.41; font-weight: 403;"> formation</span><span class="token" style="opacity: 0.41; font-weight: 403;">.</span>',
141
+ },
142
+ ] # --- Study API Endpoints ---
143
+
144
+
145
  @app.get("/api/study/texts")
146
  def get_study_texts():
147
  return {"texts": STUDY_TEXTS}
148
 
149
+
150
  class StudySubmission(BaseModel):
151
  user_id: str
152
  text_id: int
 
155
  score: int
156
  total_questions: int
157
 
158
+
159
  class StudyPreference(BaseModel):
160
  user_id: str
161
  preference: str
162
 
163
+
164
  @app.post("/api/study/preference")
165
  def submit_study_preference(submission: StudyPreference):
166
  conn = sqlite3.connect(DB_FILE)
167
  c = conn.cursor()
168
  c.execute(
169
  "INSERT INTO study_preferences (user_id, preference) VALUES (?, ?)",
170
+ (submission.user_id, submission.preference),
171
  )
172
  conn.commit()
173
  conn.close()
174
  return {"status": "success"}
175
 
176
+
177
  @app.post("/api/study/submit")
178
  def submit_study_result(submission: StudySubmission):
179
  conn = sqlite3.connect(DB_FILE)
180
  c = conn.cursor()
181
  c.execute(
182
  "INSERT INTO study_results (user_id, text_id, condition, reading_time_ms, score, total_questions) VALUES (?, ?, ?, ?, ?, ?)",
183
+ (
184
+ submission.user_id,
185
+ submission.text_id,
186
+ submission.condition,
187
+ submission.reading_time_ms,
188
+ submission.score,
189
+ submission.total_questions,
190
+ ),
191
  )
192
  conn.commit()
193
  conn.close()
194
  return {"status": "success"}
195
 
196
+
197
  @app.get("/api/study/stats")
198
  def get_study_stats():
199
  conn = sqlite3.connect(DB_FILE)
200
  c = conn.cursor()
201
+
202
  # Calculate stats for plain
203
+ c.execute(
204
+ "SELECT AVG(reading_time_ms), AVG(CAST(score AS FLOAT) / total_questions) * 100, COUNT(*) FROM study_results WHERE condition = 'plain'"
205
+ )
206
  plain_stats = c.fetchone()
207
+
208
  # Calculate stats for flowread
209
+ c.execute(
210
+ "SELECT AVG(reading_time_ms), AVG(CAST(score AS FLOAT) / total_questions) * 100, COUNT(*) FROM study_results WHERE condition = 'flowread'"
211
+ )
212
  flowread_stats = c.fetchone()
213
+
214
  # Calculate stats for gradient
215
+ c.execute(
216
+ "SELECT AVG(reading_time_ms), AVG(CAST(score AS FLOAT) / total_questions) * 100, COUNT(*) FROM study_results WHERE condition = 'gradient'"
217
+ )
218
  gradient_stats = c.fetchone()
219
+
220
  # Calculate preferences
221
  c.execute("SELECT preference, COUNT(*) FROM study_preferences GROUP BY preference")
222
  preferences = dict(c.fetchall())
223
+
224
  conn.close()
225
+
226
  return {
227
  "plain": {
228
  "avg_reading_time_ms": plain_stats[0] or 0,
229
  "avg_accuracy_percent": plain_stats[1] or 0,
230
+ "sample_size": plain_stats[2],
231
  },
232
  "flowread": {
233
  "avg_reading_time_ms": flowread_stats[0] or 0,
234
  "avg_accuracy_percent": flowread_stats[1] or 0,
235
+ "sample_size": flowread_stats[2],
236
  },
237
  "gradient": {
238
  "avg_reading_time_ms": gradient_stats[0] or 0,
239
  "avg_accuracy_percent": gradient_stats[1] or 0,
240
+ "sample_size": gradient_stats[2],
241
  },
242
+ "preferences": preferences,
243
  }
244
 
245
+
246
  import sys
247
  import re
248
 
249
+
250
  class StderrProgressInterceptor:
251
  def __init__(self, original):
252
  self.original = original
 
255
 
256
  def write(self, s):
257
  self.original.write(s)
258
+ match = re.search(r"(\d+)%\|", s)
259
  if match and self.active_model:
260
  pct = match.group(1)
261
  self.current_progress = f"{pct}%"
262
  # Update the global status explicitly so the API returns it immediately
263
  model_status[self.active_model] = f"downloading: {self.current_progress}"
264
+
265
  def flush(self):
266
  self.original.flush()
267
 
268
+
269
  stderr_interceptor = StderrProgressInterceptor(sys.stderr)
270
  sys.stderr = stderr_interceptor
271
 
272
  # --- Saliency API (Existing) ---
273
  models = {}
274
  tokenizers = {}
275
+ model_status = {"2b": "unloaded", "27b-4a": "unloaded"}
276
+
277
+ device = torch.device(
278
+ "cuda"
279
+ if torch.cuda.is_available()
280
+ else "mps"
281
+ if torch.backends.mps.is_available()
282
+ else "cpu"
283
+ )
284
  hf_token = os.environ.get("HF_TOKEN")
285
 
286
+
287
  def load_model(model_name: str):
288
  if model_name in models:
289
  return models[model_name], tokenizers[model_name]
290
+
291
  print(f"Loading {model_name} on {device}...")
292
  model_status[model_name] = "downloading: 0%"
293
  stderr_interceptor.active_model = model_name
294
  try:
295
  if model_name == "27b-4a":
296
  # Use Gemma 4 26B A4B in 4-bit (requires CUDA)
297
+ hf_model_id = "unsloth/gemma-4-26B-A4B-bnb-4bit"
298
+ try:
299
+ tokenizer = AutoTokenizer.from_pretrained(
300
+ hf_model_id, token=hf_token
301
+ )
302
+ except Exception:
303
+ hf_model_id = "unsloth/gemma-4-26B-A4B"
304
+ tokenizer = AutoTokenizer.from_pretrained(
305
+ hf_model_id, token=hf_token
306
+ )
307
+
308
  # BitsAndBytes for 4-bit quantization
309
  from transformers import BitsAndBytesConfig
310
+
311
  bnb_config = BitsAndBytesConfig(
312
+ load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16
 
313
  )
314
+
315
  model = AutoModelForCausalLM.from_pretrained(
316
  hf_model_id,
317
  quantization_config=bnb_config,
318
  device_map="auto",
319
  attn_implementation="eager",
320
+ token=hf_token,
321
  )
322
  else:
323
  # Default to 2b (Gemma 4 E2B)
324
  hf_model_id = "unsloth/gemma-4-E2B"
325
+ tokenizer = AutoTokenizer.from_pretrained(
326
+ hf_model_id, token=hf_token, extra_special_tokens={}
327
+ )
328
  model = AutoModelForCausalLM.from_pretrained(
329
  hf_model_id,
330
  torch_dtype=torch.bfloat16,
331
  attn_implementation="eager",
332
+ token=hf_token,
333
  ).to(device)
334
+
335
  print(f"Model {model_name} loaded successfully.")
336
  models[model_name] = model
337
  tokenizers[model_name] = tokenizer
 
344
  stderr_interceptor.active_model = None
345
  raise e
346
 
347
+
348
  # Pre-load default 2b
349
  try:
350
  load_model("2b")
351
  except:
352
  print("Could not preload 2b model.")
353
 
354
+
355
  @app.get("/status")
356
  def get_model_status():
357
  return model_status
358
 
359
+
360
  class TextRequest(BaseModel):
361
  text: str
362
+ layers: Optional[List[int]] = None # Optional explicit layer selection
363
+ layer_preset: str = "middle" # "first", "middle", "last", "all"
364
+ preprompt: str = "" # Optional task-driven intent
365
+ saliency_mode: str = "local" # "local" or "global"
366
+
367
 
368
  @app.post("/analyze")
369
  def analyze_text_legacy(request: TextRequest):
370
  return analyze_text_model("2b", request)
371
 
372
+
373
  @app.post("/analyze/{model_name}")
374
  def analyze_text_model(model_name: str, request: TextRequest):
375
  text = request.text
376
  preprompt = request.preprompt.strip()
377
+
378
  if not text.strip():
379
  return {"tokens": [], "scores": []}
380
 
 
382
  model, tokenizer = load_model(model_name)
383
  except Exception as e:
384
  from fastapi import HTTPException
385
+
386
  raise HTTPException(status_code=500, detail=str(e))
387
 
388
  # Combine preprompt and text if preprompt exists
389
  full_text = f"{preprompt}\n\n{text}" if preprompt else text
390
 
391
  inputs = tokenizer(full_text, return_tensors="pt").to(model.device)
392
+
393
  # Calculate how many tokens belong to the preprompt so we can strip them later
394
+ num_preprompt_tokens = 0
395
  if preprompt:
396
  p_toks = tokenizer(f"{preprompt}\n\n")["input_ids"]
397
  num_preprompt_tokens = len(p_toks)
398
+ elif (
399
+ tokenizer.bos_token_id is not None
400
+ and len(inputs["input_ids"][0]) > 0
401
+ and inputs["input_ids"][0][0] == tokenizer.bos_token_id
402
+ ):
403
+ num_preprompt_tokens = 1 # Just the bos token
404
 
405
  with torch.no_grad():
406
  # Ensure we ask the model to output attentions explicitly
407
  outputs = model(**inputs, output_attentions=True)
408
+
409
  # Check if attentions are actually returned
410
  if not outputs.attentions:
411
  print("Warning: Model did not return attentions.")
412
  return {"words": []}
413
+
414
  num_layers = len(outputs.attentions)
415
+
416
  selected_layers = request.layers
417
  if not selected_layers:
418
+ preset = request.layer_preset
419
+ if preset == "all":
420
+ selected_layers = list(range(num_layers))
421
+ elif preset == "first":
422
+ selected_layers = list(range(0, max(1, num_layers // 4)))
423
+ elif preset == "last":
424
+ selected_layers = list(range(num_layers - (num_layers // 4), num_layers))
425
+ else: # default to "middle"
426
+ start_layer = num_layers // 4
427
+ end_layer = num_layers - (num_layers // 4)
428
+ selected_layers = list(range(start_layer, end_layer))
429
+
430
  selected_layers = [l for l in selected_layers if 0 <= l < num_layers]
431
  if not selected_layers:
432
  selected_layers = [num_layers - 1]
433
+
434
  stacked_attentions = torch.stack([outputs.attentions[l] for l in selected_layers])
435
+ avg_attention = stacked_attentions.mean(dim=(0, 2))[0]
436
+
437
  # Calculate importance: sum of attention each token *receives* from the sequence
438
  importance = avg_attention.sum(dim=0).cpu().float().numpy()
439
+
440
  import numpy as np
441
 
442
  if len(importance) > num_preprompt_tokens:
443
  text_importance = importance[num_preprompt_tokens:]
444
+
445
  if request.saliency_mode == "global":
446
  # Global Mode: absolute importance across different texts
447
  # We apply a soft root penalty so very high values don't entirely blow out the scale,
 
452
  # Local Mode: relative importance within this specific block of text
453
  min_score = text_importance.min()
454
  max_score = text_importance.max()
455
+
456
  if max_score > min_score:
457
  normalized_scores = (importance - min_score) / (max_score - min_score)
458
  else:
459
  normalized_scores = importance - min_score
460
+
461
  # Keep <bos> at max score
462
  normalized_scores[0] = 1.0
463
  normalized_scores = normalized_scores.clip(0, 1)
464
  else:
465
  normalized_scores = [1.0] * len(importance)
466
+
467
  input_ids = inputs["input_ids"][0].tolist()
468
  tokens = tokenizer.convert_ids_to_tokens(input_ids)
469
+
470
+ # Check if first token is BOS
471
+ has_bos = (input_ids[0] == tokenizer.bos_token_id) if len(input_ids) > 0 else False
472
+
473
  result = []
474
  for i, t in enumerate(tokens):
475
  # Decode properly
476
  word = tokenizer.decode([input_ids[i]])
477
+
478
+ # Byte fallback handling: if the token is a byte fallback, decode the byte
479
+ if t.startswith("<0x") and t.endswith(">"):
480
+ # Just ignore printing these raw bytes out since they render ugly
481
+ # We'll rely on word or fallback to empty
482
+ word = ""
483
+ t = ""
484
+
485
  # Special check for Gemma, decoding often removes spaces incorrectly or leaves tokens empty
486
  # Let's clean the raw token just in case
487
+ raw_clean = t.replace("\u2581", " ")
488
+
489
  # We will pass both decoded word and raw cleaned token to frontend to help render
490
+ result.append(
491
+ {"token": raw_clean, "word": word, "score": float(normalized_scores[i])}
492
+ )
493
+
494
+ # Return the actual text tokens. Keep <bos> if it exists.
495
+ if num_preprompt_tokens > 0 and len(result) > num_preprompt_tokens:
496
+ if has_bos:
497
+ result = [result[0]] + result[num_preprompt_tokens:]
498
+ else:
499
+ result = result[num_preprompt_tokens:]
500
 
501
  return {"words": result}
502
 
503
+
504
  if __name__ == "__main__":
505
  port = int(os.environ.get("PORT", 7860))
506
+ uvicorn.run("main:app", host="0.0.0.0", port=port, reload=True)
static/index.html CHANGED
@@ -247,21 +247,18 @@
247
  </div>
248
 
249
  <div>
250
- <label style="display:block; font-weight: 600; margin-bottom: 0.5rem; color: #44403c;">
251
- Network Layers (Gemma 2B: 18 Layers)
252
  </label>
253
  <p style="font-size: 0.85rem; color: #78716c; margin-top: 0; margin-bottom: 0.5rem;">
254
- Select which layers to average attention scores from. Middle layers generally capture the best semantic meaning.
255
  </p>
256
- <div style="display: flex; gap: 0.5rem; margin-bottom: 0.75rem; flex-wrap: wrap;">
257
- <button class="layer-preset" onclick="setLayerPreset('middle')" style="padding: 0.25rem 0.5rem; font-size: 0.8rem; background: #e7e5df; color: #44403c; border-radius: 0.25rem;">Middle Layers</button>
258
- <button class="layer-preset" onclick="setLayerPreset('all')" style="padding: 0.25rem 0.5rem; font-size: 0.8rem; background: #e7e5df; color: #44403c; border-radius: 0.25rem;">All Layers</button>
259
- <button class="layer-preset" onclick="setLayerPreset('first')" style="padding: 0.25rem 0.5rem; font-size: 0.8rem; background: #e7e5df; color: #44403c; border-radius: 0.25rem;">First Layer</button>
260
- <button class="layer-preset" onclick="setLayerPreset('last')" style="padding: 0.25rem 0.5rem; font-size: 0.8rem; background: #e7e5df; color: #44403c; border-radius: 0.25rem;">Last Layer</button>
261
- </div>
262
- <div id="layer-checkboxes" style="display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;">
263
- <!-- Checkboxes generated by JS -->
264
- </div>
265
  </div>
266
  </details>
267
 
@@ -402,6 +399,7 @@
402
  const inputArea = document.getElementById('text-input');
403
  const prepromptInput = document.getElementById('preprompt');
404
  const saliencyModeInput = document.getElementById('saliency-mode');
 
405
  const analyzeBtn = document.getElementById('analyze-btn');
406
  const thresholdSlider = document.getElementById('threshold');
407
  const thresholdVal = document.getElementById('threshold-val');
@@ -410,48 +408,7 @@
410
  const loading = document.getElementById('loading');
411
 
412
  let currentTokens = [];
413
-
414
- // Generate Layer Checkboxes
415
- const layerContainer = document.getElementById('layer-checkboxes');
416
- for(let i = 0; i < 18; i++) {
417
- const label = document.createElement('label');
418
- label.style.display = 'flex';
419
- label.style.alignItems = 'center';
420
- label.style.gap = '0.25rem';
421
- label.style.fontSize = '0.85rem';
422
- label.style.cursor = 'pointer';
423
-
424
- const cb = document.createElement('input');
425
- cb.type = 'checkbox';
426
- cb.value = i;
427
- cb.className = 'layer-cb';
428
-
429
- label.appendChild(cb);
430
- label.appendChild(document.createTextNode(`L${i}`));
431
- layerContainer.appendChild(label);
432
- }
433
-
434
- // Presets Logic
435
- window.setLayerPreset = function(preset) {
436
- const checkboxes = document.querySelectorAll('.layer-cb');
437
- checkboxes.forEach(cb => cb.checked = false);
438
-
439
- if(preset === 'all') {
440
- checkboxes.forEach(cb => cb.checked = true);
441
- } else if (preset === 'first') {
442
- checkboxes[0].checked = true;
443
- } else if (preset === 'last') {
444
- checkboxes[17].checked = true;
445
- } else if (preset === 'middle') {
446
- // Middle 50% for 18 layers is ~4 through 13
447
- for(let i = 4; i <= 13; i++) {
448
- checkboxes[i].checked = true;
449
- }
450
- }
451
- };
452
- // Set middle as default
453
- setLayerPreset('middle');
454
-
455
  thresholdSlider.addEventListener('input', (e) => {
456
  thresholdVal.textContent = parseFloat(e.target.value).toFixed(2);
457
  renderTokens();
@@ -463,15 +420,10 @@
463
  const text = inputArea.value.trim();
464
  const preprompt = prepromptInput.value.trim();
465
  const saliencyMode = saliencyModeInput.value;
 
466
  const modelVersion = "2b";
467
 
468
- const checkedLayers = Array.from(document.querySelectorAll('.layer-cb:checked')).map(cb => parseInt(cb.value));
469
-
470
  if (!text) return;
471
- if (checkedLayers.length === 0) {
472
- alert("Please select at least one layer!");
473
- return;
474
- }
475
 
476
  analyzeBtn.disabled = true;
477
  loading.style.display = 'block';
@@ -501,7 +453,7 @@
501
  const response = await fetch(`/analyze/${modelVersion}`, {
502
  method: 'POST',
503
  headers: { 'Content-Type': 'application/json' },
504
- body: JSON.stringify({ text, preprompt, layers: checkedLayers, saliency_mode: saliencyMode })
505
  });
506
 
507
  isFetching = false;
 
247
  </div>
248
 
249
  <div>
250
+ <label for="layer-preset" style="display:block; font-weight: 600; margin-bottom: 0.5rem; color: #44403c;">
251
+ Network Layers
252
  </label>
253
  <p style="font-size: 0.85rem; color: #78716c; margin-top: 0; margin-bottom: 0.5rem;">
254
+ Select which section of the network's layers to extract attention scores from. Middle layers generally capture the best semantic meaning.
255
  </p>
256
+ <select id="layer-preset" style="width: 100%; padding: 0.5rem; border: 1px solid #d6d3d1; border-radius: 0.25rem; font-size: 0.9rem; box-sizing: border-box; font-family: inherit;">
257
+ <option value="middle" selected>Middle Layers (Semantic focus)</option>
258
+ <option value="first">First Few Layers (Lexical/Syntax focus)</option>
259
+ <option value="last">Last Few Layers (Output formatting focus)</option>
260
+ <option value="all">All Layers (Averaged)</option>
261
+ </select>
 
 
 
262
  </div>
263
  </details>
264
 
 
399
  const inputArea = document.getElementById('text-input');
400
  const prepromptInput = document.getElementById('preprompt');
401
  const saliencyModeInput = document.getElementById('saliency-mode');
402
+ const layerPresetInput = document.getElementById('layer-preset');
403
  const analyzeBtn = document.getElementById('analyze-btn');
404
  const thresholdSlider = document.getElementById('threshold');
405
  const thresholdVal = document.getElementById('threshold-val');
 
408
  const loading = document.getElementById('loading');
409
 
410
  let currentTokens = [];
411
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  thresholdSlider.addEventListener('input', (e) => {
413
  thresholdVal.textContent = parseFloat(e.target.value).toFixed(2);
414
  renderTokens();
 
420
  const text = inputArea.value.trim();
421
  const preprompt = prepromptInput.value.trim();
422
  const saliencyMode = saliencyModeInput.value;
423
+ const layerPreset = layerPresetInput.value;
424
  const modelVersion = "2b";
425
 
 
 
426
  if (!text) return;
 
 
 
 
427
 
428
  analyzeBtn.disabled = true;
429
  loading.style.display = 'block';
 
453
  const response = await fetch(`/analyze/${modelVersion}`, {
454
  method: 'POST',
455
  headers: { 'Content-Type': 'application/json' },
456
+ body: JSON.stringify({ text, preprompt, layer_preset: layerPreset, saliency_mode: saliencyMode })
457
  });
458
 
459
  isFetching = false;
test_bos.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from transformers import AutoTokenizer
2
+ tokenizer = AutoTokenizer.from_pretrained("unsloth/gemma-4-E2B", extra_special_tokens={})
3
+ print("add_bos_token:", tokenizer.add_bos_token)
4
+ print("tokens:", tokenizer("hello")["input_ids"])
test_gguf.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoTokenizer, AutoModelForCausalLM
2
+ import torch
3
+
4
+ model_id = "unsloth/gemma-4-26B-A4B-it-GGUF"
5
+ gguf_file = "gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf"
6
+
7
+ print("Loading tokenizer...")
8
+ tokenizer = AutoTokenizer.from_pretrained(model_id, gguf_file=gguf_file)
9
+ print("Loading model...")
10
+ model = AutoModelForCausalLM.from_pretrained(
11
+ model_id,
12
+ gguf_file=gguf_file,
13
+ device_map="auto"
14
+ )
15
+ print(f"Model loaded on device {model.device}, dtype: {model.dtype}")
16
+
17
+ inputs = tokenizer("Hello world", return_tensors="pt").to(model.device)
18
+ with torch.no_grad():
19
+ out = model(**inputs, output_attentions=True)
20
+
21
+ print("Attentions returned:", len(out.attentions) if out.attentions else "No")
test_load.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoTokenizer, AutoModelForCausalLM
2
+ try:
3
+ hf_model_id = "unsloth/gemma-4-E2B"
4
+ print("Loading tokenizer...")
5
+ tokenizer = AutoTokenizer.from_pretrained(hf_model_id)
6
+ print("Loading model...")
7
+ model = AutoModelForCausalLM.from_pretrained(hf_model_id)
8
+ except Exception as e:
9
+ import traceback
10
+ traceback.print_exc()
test_model.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import traceback
2
+ from transformers import AutoTokenizer, AutoModelForCausalLM
3
+ try:
4
+ print("Loading Tokenizer...")
5
+ AutoTokenizer.from_pretrained("unsloth/gemma-4-E2B")
6
+ print("Loading Model...")
7
+ AutoModelForCausalLM.from_pretrained("unsloth/gemma-4-E2B")
8
+ except Exception as e:
9
+ traceback.print_exc()
test_model2.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoTokenizer
2
+ try:
3
+ print("Loading Tokenizer with empty dict override...")
4
+ AutoTokenizer.from_pretrained("unsloth/gemma-4-E2B", extra_special_tokens={})
5
+ print("Loaded!")
6
+ except Exception as e:
7
+ import traceback
8
+ traceback.print_exc()
test_tok_issue.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoTokenizer
2
+ import numpy as np
3
+
4
+ tokenizer = AutoTokenizer.from_pretrained("unsloth/gemma-4-E2B", extra_special_tokens={})
5
+ preprompt = "imagine"
6
+ text = "Japan's railways are the finest in the world.\xa0"
7
+
8
+ full_text = f"{preprompt}\n\n{text}" if preprompt else text
9
+ inputs = tokenizer(full_text, return_tensors="pt")
10
+
11
+ num_preprompt_tokens = 0
12
+ if preprompt:
13
+ p_toks = tokenizer(f"{preprompt}\n\n")["input_ids"]
14
+ num_preprompt_tokens = len(p_toks)
15
+ elif tokenizer.bos_token_id is not None and len(inputs["input_ids"][0]) > 0 and inputs["input_ids"][0][0] == tokenizer.bos_token_id:
16
+ num_preprompt_tokens = 1
17
+
18
+ input_ids = inputs["input_ids"][0].tolist()
19
+ tokens = tokenizer.convert_ids_to_tokens(input_ids)
20
+ has_bos = (input_ids[0] == tokenizer.bos_token_id) if len(input_ids) > 0 else False
21
+
22
+ normalized_scores = np.ones(len(tokens))
23
+
24
+ result = []
25
+ for i, t in enumerate(tokens):
26
+ word = tokenizer.decode([input_ids[i]])
27
+ if t.startswith('<0x') and t.endswith('>'):
28
+ word = ""
29
+ t = ""
30
+ raw_clean = t.replace('\u2581', ' ')
31
+ result.append({"token": raw_clean, "word": word})
32
+
33
+ if num_preprompt_tokens > 0 and len(result) > num_preprompt_tokens:
34
+ if has_bos:
35
+ result = [result[0]] + result[num_preprompt_tokens:]
36
+ else:
37
+ result = result[num_preprompt_tokens:]
38
+
39
+ print([r["token"] for r in result])