TheHickman commited on
Commit
ed15844
Β·
verified Β·
1 Parent(s): 5ef7f7e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +268 -159
app.py CHANGED
@@ -4,251 +4,360 @@ import os
4
 
5
  client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
6
 
 
7
  # ---- GPT explanation backend ----
8
  def explain_text(selected_text):
9
- if selected_text is None:
10
- return "", ""
11
- selected_text = selected_text.strip()
12
- if not selected_text:
13
- return "Please select or enter some text first.", selected_text
14
-
15
  try:
16
  response = client.chat.completions.create(
17
  model="gpt-4o",
18
  messages=[
19
  {
20
  "role": "system",
21
- "content": "You are an expert machine learning instructor. Explain concepts clearly and intuitively for learners with basic ML knowledge. Keep explanations concise and educational."
 
 
 
 
22
  },
23
  {
24
  "role": "user",
25
- "content": f"Explain this text from a learning resource:\n\n\"\"\"\n{selected_text}\n\"\"\""
26
- }
27
  ],
28
  temperature=0.7,
29
- max_tokens=500
30
  )
31
- explanation = response.choices[0].message.content
32
- # Replace the selected text box with the AI explanation
33
- return explanation, explanation
34
  except Exception as e:
35
- return f"Error: {str(e)}", selected_text
36
 
37
 
38
- # ---- Your work content (UNCHANGED) ----
39
  YOUR_WORK_HTML = """
40
- <div id="content" style="max-width: 800px; margin: auto; font-size: 16px; line-height: 1.6;">
41
  <h1>Text Generation</h1>
42
  <p>
43
- Text generation is the task of producing natural language text given an input prompt.
44
  It is commonly used for chatbots, creative writing, summarization, and code generation.
45
  </p>
46
  <p>
47
- Most modern text generation models are based on the transformer architecture and are
48
  trained using next-token prediction.
49
  </p>
50
  <p>
51
- During inference, the model repeatedly samples the most likely next token until a
52
  stopping condition is reached.
53
  </p>
54
  </div>
55
  """
56
 
57
-
58
- # ---- Hugging Face reference content ----
59
  HF_REFERENCE_HTML = """
60
- <div id="hf-content" style="max-width: 800px; margin: auto; font-size: 16px; line-height: 1.6;">
61
  <h1>Text Generation (Hugging Face Reference)</h1>
62
- <h1>About Text Generation</h1>
63
  <p>
64
- This task covers guides on both <a href="https://huggingface.co/models?pipeline_tag=text-generation&sort=downloads">text-generation</a> and <a href="https://huggingface.co/models?other=text2text-generation&sort=downloads">text-to-text generation</a> models.
65
- Popular large language models that are used for chats or following instructions are also covered in this task.
66
- You can find the list of selected open-source large language models <a href="https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard">here</a>, ranked by their performance scores.
67
  </p>
68
-
69
  <h2>Use Cases</h2>
70
-
71
  <h3>Instruction Models</h3>
72
  <p>
73
- A model trained for text generation can be later adapted to follow instructions.
74
- You can try some of the most powerful instruction-tuned open-access models like Mixtral 8x7B, Cohere Command R+, and Meta Llama3 70B at <a href="https://huggingface.co/chat">Hugging Chat</a>.
75
  </p>
76
-
77
  <h3>Code Generation</h3>
78
  <p>
79
- A Text Generation model, also known as a causal language model, can be trained on code from scratch to help the programmers in their repetitive coding tasks.
80
- One of the most popular open-source models for code generation is StarCoder, which can generate code in 80+ languages. You can try it <a href="https://huggingface.co/spaces/bigcode/bigcode-playground">here</a>.
81
  </p>
82
-
83
  <h3>Stories Generation</h3>
84
  <p>
85
- A story generation model can receive an input like "Once upon a time" and proceed to create a story-like text based on those first words.
86
- You can try <a href="https://huggingface.co/spaces/mosaicml/mpt-7b-storywriter">this application</a> which contains a model trained on story generation, by MosaicML.
87
- If your generative model training data is different than your use case, you can train a causal language model from scratch.
88
- Learn how to do it in the free transformers <a href="https://huggingface.co/course/chapter7/6?fw=pt">course</a>!
89
  </p>
90
-
91
  <h2>Task Variants</h2>
92
-
93
  <h3>Completion Generation Models</h3>
94
  <p>
95
- A popular variant of Text Generation models predicts the next word given a bunch of words.
96
- Word by word a longer text is formed that results in for example:
97
- <ul>
98
- <li>Given an incomplete sentence, complete it.
99
- <li>Continue a story given the first sentences.
100
- <li>Provided a code description, generate the code.
101
- </ul>
102
- The most popular models for this task are GPT-based models, Mistral or Llama series.
103
- These models are trained on data that has no labels, so you just need plain text to train your own model.
104
- You can train text generation models to generate a wide variety of documents, from code to stories.
105
  </p>
106
-
107
  <h3>Text-to-Text Generation Models</h3>
108
  <p>
109
- These models are trained to learn the mapping between a pair of texts (e.g. translation from one language to another).
110
- The most popular variants of these models are NLLB, FLAN-T5, and BART.
111
- Text-to-Text models are trained with multi-tasking capabilities, they can accomplish a wide range of tasks, including summarization, translation, and text classification.
112
  </p>
113
-
114
  <h3>Language Model Variants</h3>
115
- When it comes to text generation, the underlying language model can come in several types:
116
- <ul>
117
- <li>Base models: refers to plain language models like Mistral 7B and Meta Llama-3-70b. These models are good for fine-tuning and few-shot prompting.
118
- <li>Instruction-trained models: these models are trained in a multi-task manner to follow a broad range of instructions like "Write me a recipe for chocolate cake". Models like Qwen 2 7B, Yi 1.5 34B Chat, and Meta Llama 70B Instruct are examples of instruction-trained models. In general, instruction-trained models will produce better responses to instructions than base models.
119
- <li>Human feedback models: these models extend base and instruction-trained models by incorporating human feedback that rates the quality of the generated text according to criteria like helpfulness, honesty, and harmlessness. The human feedback is then combined with an optimization technique like reinforcement learning to align the original model to be closer with human preferences. The overall methodology is often called Reinforcement Learning from Human Feedback, or RLHF for short. Zephyr ORPO 141B A35B is an open-source model aligned through human feedback.
120
- </ul>
121
-
122
- <h2>Text Generation from Image and Text</h2>
123
- <p>
124
- There are language models that can input both text and image and output text, called vision language models.
125
- IDEFICS 2 and MiniCPM Llama3 V are good examples.
126
- They accept the same generation parameters as other language models.
127
- However, since they also take images as input, you have to use them with the image-to-text pipeline.
128
- You can find more information about this in the image-to-text task page.
129
- </p>
130
-
131
  <h2>Inference</h2>
132
  <p>
133
- You can use the πŸ€— Transformers library <code>text-generation</code> pipeline to do inference
134
- with text generation models. It takes an input text and generates a continuation of that text.
135
  </p>
136
-
137
  <pre style="background: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto;">
138
  from transformers import pipeline
139
  generator = pipeline('text-generation', model='gpt2')
140
  generator("Hello, I'm a language model,", max_length=30, num_return_sequences=3)
141
  </pre>
142
-
143
  <h2>Text Generation Inference</h2>
144
  <p>
145
- Text Generation Inference (TGI) is an open-source toolkit for serving LLMs tackling challenges such as response time.
146
- TGI powers inference solutions like Inference Endpoints and Hugging Chat, as well as multiple community projects.
147
- You can use it to deploy any supported open-source large language model of your choice.
148
- </p>
149
-
150
- <h2>ChatUI Spaces</h2>
151
- <p>
152
- Hugging Face Spaces includes templates to easily deploy your own instance of a specific application.
153
- ChatUI is an open-source interface that enables serving conversational interface for large language models and can be deployed with few clicks at Spaces.
154
- TGI powers these Spaces under the hood for faster inference.
155
- Thanks to the template, you can deploy your own instance based on a large language model with only a few clicks and customize it. Learn more about it here and create your large language model instance here.
156
  </p>
157
  </div>
158
  """
159
 
160
 
161
  def switch_content(choice):
162
- if choice == "My Work":
163
- return YOUR_WORK_HTML
164
- else:
165
- return HF_REFERENCE_HTML
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
- # This JS uses a more reliable approach:
169
- # 1. Listens for mouseup on the whole document
170
- # 2. Finds the Gradio textbox by its data-testid or label, then dispatches
171
- # a proper React-compatible input event so Gradio picks up the value change.
172
- SELECTION_JS = """
173
  <script>
174
- (function() {
175
- function setGradioTextbox(value) {
176
- // Find the textarea inside the component labelled "Selected text"
177
- const labels = document.querySelectorAll('label span');
178
- for (const label of labels) {
179
- if (label.textContent.trim() === 'Selected text') {
180
- const container = label.closest('label') || label.parentElement;
181
- // Walk up to find the wrapping block, then find textarea
182
- let el = container;
183
- for (let i = 0; i < 5; i++) {
184
- el = el.parentElement;
185
- if (!el) break;
186
- const ta = el.querySelector('textarea');
187
- if (ta) {
188
- // Use React's native value setter to trigger onChange
189
- const nativeSetter = Object.getOwnPropertyDescriptor(
190
- window.HTMLTextAreaElement.prototype, 'value'
191
- ).set;
192
- nativeSetter.call(ta, value);
193
- ta.dispatchEvent(new Event('input', { bubbles: true }));
194
- ta.dispatchEvent(new Event('change', { bubbles: true }));
195
- return true;
196
- }
197
- }
198
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
200
- return false;
201
  }
 
 
202
 
203
- document.addEventListener('mouseup', function () {
204
- // Small delay to let the browser finalize the selection
205
- setTimeout(function () {
206
- const selection = window.getSelection();
207
- if (!selection) return;
208
- const text = selection.toString().trim();
209
- if (text.length > 0) {
210
- setGradioTextbox(text);
211
- }
212
- }, 50);
213
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  })();
215
  </script>
216
  """
217
 
218
 
219
- with gr.Blocks(head=SELECTION_JS) as demo:
220
- gr.Markdown("### πŸ“˜ Highlight text above and click **Explain selection** to get an AI explanation")
221
-
 
 
 
222
  view_toggle = gr.Radio(
223
  choices=["My Work", "HF Reference"],
224
  value="My Work",
225
- label="View"
226
  )
227
-
228
  content_display = gr.HTML(YOUR_WORK_HTML)
229
-
230
- view_toggle.change(
231
- fn=switch_content,
232
- inputs=view_toggle,
233
- outputs=content_display
234
- )
235
-
236
- selected_text = gr.Textbox(
237
- label="Selected text",
238
- placeholder="Highlight text above to populate this box, then click Explain...",
239
- lines=3
240
- )
241
-
242
- explain_btn = gr.Button("Explain selection 🧠", variant="primary")
243
 
244
- # The explanation replaces the content of the selected_text box
245
- # so the user sees the AI output right where the selection was shown.
246
- output = gr.Markdown(label="Explanation")
 
 
 
247
 
248
- explain_btn.click(
249
- fn=explain_text,
250
- inputs=selected_text,
251
- outputs=[output, selected_text],
252
- )
253
 
254
  demo.launch()
 
4
 
5
  client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
6
 
7
+
8
  # ---- GPT explanation backend ----
9
  def explain_text(selected_text):
10
+ if not selected_text or not selected_text.strip():
11
+ return "__NO_SELECTION__"
 
 
 
 
12
  try:
13
  response = client.chat.completions.create(
14
  model="gpt-4o",
15
  messages=[
16
  {
17
  "role": "system",
18
+ "content": (
19
+ "You are an expert machine learning instructor. "
20
+ "Explain concepts clearly and intuitively for learners with basic ML knowledge. "
21
+ "Keep explanations concise and educational."
22
+ ),
23
  },
24
  {
25
  "role": "user",
26
+ "content": f'Explain this text from a learning resource:\n\n"""\n{selected_text}\n"""',
27
+ },
28
  ],
29
  temperature=0.7,
30
+ max_tokens=500,
31
  )
32
+ return response.choices[0].message.content
 
 
33
  except Exception as e:
34
+ return f"__ERROR__: {str(e)}"
35
 
36
 
37
+ # ---- Page content ----
38
  YOUR_WORK_HTML = """
39
+ <div id="content" style="max-width: 800px; margin: auto; font-size: 16px; line-height: 1.8;">
40
  <h1>Text Generation</h1>
41
  <p>
42
+ Text generation is the task of producing natural language text given an input prompt.
43
  It is commonly used for chatbots, creative writing, summarization, and code generation.
44
  </p>
45
  <p>
46
+ Most modern text generation models are based on the transformer architecture and are
47
  trained using next-token prediction.
48
  </p>
49
  <p>
50
+ During inference, the model repeatedly samples the most likely next token until a
51
  stopping condition is reached.
52
  </p>
53
  </div>
54
  """
55
 
 
 
56
  HF_REFERENCE_HTML = """
57
+ <div id="hf-content" style="max-width: 800px; margin: auto; font-size: 16px; line-height: 1.8;">
58
  <h1>Text Generation (Hugging Face Reference)</h1>
59
+ <h2>About Text Generation</h2>
60
  <p>
61
+ This task covers guides on both text-generation and text-to-text generation models.
62
+ Popular large language models that are used for chats or following instructions are also covered in this task.
63
+ You can find the list of selected open-source large language models on the Open LLM Leaderboard, ranked by their performance scores.
64
  </p>
 
65
  <h2>Use Cases</h2>
 
66
  <h3>Instruction Models</h3>
67
  <p>
68
+ A model trained for text generation can be later adapted to follow instructions.
69
+ You can try some of the most powerful instruction-tuned open-access models like Mixtral 8x7B, Cohere Command R+, and Meta Llama3 70B at Hugging Chat.
70
  </p>
 
71
  <h3>Code Generation</h3>
72
  <p>
73
+ A Text Generation model, also known as a causal language model, can be trained on code from scratch to help programmers with repetitive coding tasks.
74
+ One of the most popular open-source models for code generation is StarCoder, which can generate code in 80+ languages.
75
  </p>
 
76
  <h3>Stories Generation</h3>
77
  <p>
78
+ A story generation model can receive an input like "Once upon a time" and proceed to create a story-like text.
79
+ If your generative model training data differs from your use case, you can train a causal language model from scratch.
 
 
80
  </p>
 
81
  <h2>Task Variants</h2>
 
82
  <h3>Completion Generation Models</h3>
83
  <p>
84
+ A popular variant of Text Generation models predicts the next word given a bunch of words.
85
+ Common use cases include completing incomplete sentences, continuing a story, or generating code from a description.
86
+ The most popular models for this task are GPT-based models, Mistral or Llama series.
 
 
 
 
 
 
 
87
  </p>
 
88
  <h3>Text-to-Text Generation Models</h3>
89
  <p>
90
+ These models are trained to learn the mapping between a pair of texts, for example translation from one language to another.
91
+ The most popular variants are NLLB, FLAN-T5, and BART, which handle summarization, translation, and text classification.
 
92
  </p>
 
93
  <h3>Language Model Variants</h3>
94
+ <p>When it comes to text generation, the underlying language model can come in several types:</p>
95
+ <ul>
96
+ <li><strong>Base models:</strong> Plain language models like Mistral 7B and Meta Llama-3-70b. Good for fine-tuning and few-shot prompting.</li>
97
+ <li><strong>Instruction-trained models:</strong> Trained to follow a broad range of instructions. Examples include Qwen 2 7B and Meta Llama 70B Instruct.</li>
98
+ <li><strong>Human feedback models:</strong> Extend base models using RLHF to align with human preferences for helpfulness, honesty, and harmlessness.</li>
99
+ </ul>
 
 
 
 
 
 
 
 
 
 
100
  <h2>Inference</h2>
101
  <p>
102
+ You can use the Transformers library text-generation pipeline to do inference with text generation models.
103
+ It takes an input text and generates a continuation of that text.
104
  </p>
 
105
  <pre style="background: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto;">
106
  from transformers import pipeline
107
  generator = pipeline('text-generation', model='gpt2')
108
  generator("Hello, I'm a language model,", max_length=30, num_return_sequences=3)
109
  </pre>
 
110
  <h2>Text Generation Inference</h2>
111
  <p>
112
+ Text Generation Inference (TGI) is an open-source toolkit for serving LLMs, tackling challenges such as response time.
113
+ TGI powers inference solutions like Inference Endpoints and Hugging Chat, as well as multiple community projects.
 
 
 
 
 
 
 
 
 
114
  </p>
115
  </div>
116
  """
117
 
118
 
119
  def switch_content(choice):
120
+ return YOUR_WORK_HTML if choice == "My Work" else HF_REFERENCE_HTML
121
+
 
 
122
 
123
+ # ---- All the magic: floating sticky button + inline DOM replacement ----
124
+ HEAD_HTML = """
125
+ <style>
126
+ /* ── Floating toolbar ─────────────────────────────────────────── */
127
+ #explain-fab {
128
+ position: fixed;
129
+ bottom: 36px;
130
+ right: 36px;
131
+ z-index: 99999;
132
+ display: flex;
133
+ align-items: center;
134
+ gap: 10px;
135
+ background: #1e1b4b;
136
+ color: #fff;
137
+ border-radius: 999px;
138
+ padding: 12px 22px;
139
+ box-shadow: 0 6px 28px rgba(0,0,0,0.4);
140
+ cursor: pointer;
141
+ border: none;
142
+ font-size: 15px;
143
+ font-weight: 600;
144
+ letter-spacing: 0.02em;
145
+ opacity: 0;
146
+ transform: translateY(12px);
147
+ pointer-events: none;
148
+ transition: opacity 0.2s ease, transform 0.2s ease, background 0.15s;
149
+ white-space: nowrap;
150
+ }
151
+ #explain-fab.visible {
152
+ opacity: 1;
153
+ transform: translateY(0);
154
+ pointer-events: all;
155
+ }
156
+ #explain-fab:hover:not(:disabled) { background: #3730a3; }
157
+ #explain-fab:disabled { background: #4b5563; cursor: not-allowed; }
158
+
159
+ #explain-fab-preview {
160
+ max-width: 180px;
161
+ overflow: hidden;
162
+ text-overflow: ellipsis;
163
+ white-space: nowrap;
164
+ font-size: 12px;
165
+ font-weight: 400;
166
+ opacity: 0.7;
167
+ border-left: 1px solid rgba(255,255,255,0.3);
168
+ padding-left: 10px;
169
+ }
170
+
171
+ /* ── Inline replacement styles ───────────────────────────────── */
172
+ .ai-inline {
173
+ background: linear-gradient(135deg, #fef3c7, #fde68a);
174
+ border-left: 3px solid #f59e0b;
175
+ border-radius: 4px;
176
+ padding: 1px 6px;
177
+ font-style: italic;
178
+ color: #1c1917;
179
+ cursor: help;
180
+ transition: background 0.3s;
181
+ }
182
+ .ai-inline:hover {
183
+ background: linear-gradient(135deg, #fde68a, #fbbf24);
184
+ }
185
+ .ai-inline-loading {
186
+ background: #e5e7eb !important;
187
+ border-left-color: #9ca3af !important;
188
+ color: #6b7280 !important;
189
+ animation: ai-pulse 1.2s ease-in-out infinite;
190
+ }
191
+ @keyframes ai-pulse {
192
+ 0%, 100% { opacity: 0.5; }
193
+ 50% { opacity: 1; }
194
+ }
195
+ </style>
196
 
 
 
 
 
 
197
  <script>
198
+ (function () {
199
+ 'use strict';
200
+
201
+ let savedRange = null;
202
+ let selectedText = '';
203
+ let placeholder = null;
204
+ let isExplaining = false;
205
+ let lastSeenOutput = '';
206
+
207
+ /* ── Build the floating button ──────────────────────────────────── */
208
+ function buildFAB() {
209
+ if (document.getElementById('explain-fab')) return;
210
+ const btn = document.createElement('button');
211
+ btn.id = 'explain-fab';
212
+ btn.innerHTML = '<span id="explain-fab-label">Explain 🧠</span><span id="explain-fab-preview"></span>';
213
+ document.body.appendChild(btn);
214
+ btn.addEventListener('click', onExplainClick);
215
+ }
216
+
217
+ function showFAB(text) {
218
+ const fab = document.getElementById('explain-fab');
219
+ const label = document.getElementById('explain-fab-label');
220
+ const preview = document.getElementById('explain-fab-preview');
221
+ if (!fab) return;
222
+ label.textContent = 'Explain 🧠';
223
+ preview.textContent = text.length > 30 ? text.slice(0, 30) + '…' : text;
224
+ fab.disabled = false;
225
+ fab.classList.add('visible');
226
+ }
227
+
228
+ function hideFAB() {
229
+ const fab = document.getElementById('explain-fab');
230
+ if (fab) fab.classList.remove('visible');
231
+ }
232
+
233
+ function setFABLoading() {
234
+ const fab = document.getElementById('explain-fab');
235
+ const label = document.getElementById('explain-fab-label');
236
+ const preview = document.getElementById('explain-fab-preview');
237
+ if (!fab) return;
238
+ label.textContent = 'Thinking…';
239
+ preview.textContent = '';
240
+ fab.disabled = true;
241
+ }
242
+
243
+ /* ── Selection tracking ─────────────────────────────────────────── */
244
+ document.addEventListener('mouseup', function (e) {
245
+ if (e.target.closest('#explain-fab') || isExplaining) return;
246
+ setTimeout(function () {
247
+ const sel = window.getSelection();
248
+ if (!sel || sel.rangeCount === 0) return;
249
+ const text = sel.toString().trim();
250
+ if (text.length < 3) { hideFAB(); return; }
251
+ savedRange = sel.getRangeAt(0).cloneRange();
252
+ selectedText = text;
253
+ showFAB(text);
254
+ pushToGradioInput(text);
255
+ }, 50);
256
+ });
257
+
258
+ /* ── Explain click ──────────────────────────────────────────────── */
259
+ function onExplainClick() {
260
+ if (!selectedText || !savedRange || isExplaining) return;
261
+ isExplaining = true;
262
+ setFABLoading();
263
+
264
+ /* Delete the highlighted text and drop in a loading span */
265
+ try {
266
+ savedRange.deleteContents();
267
+ placeholder = document.createElement('span');
268
+ placeholder.className = 'ai-inline ai-inline-loading';
269
+ placeholder.textContent = '⏳ explaining…';
270
+ savedRange.insertNode(placeholder);
271
+ window.getSelection().removeAllRanges();
272
+ } catch (err) {
273
+ console.warn('[explain] range insert failed', err);
274
+ }
275
+
276
+ /* Trigger the hidden Gradio button β†’ calls Python backend */
277
+ const hiddenBtn = document.querySelector('#hidden-explain-trigger button');
278
+ if (hiddenBtn) hiddenBtn.click();
279
+ }
280
+
281
+ /* ── Poll the hidden output textarea for results ────────────────── */
282
+ function pollOutput() {
283
+ if (isExplaining) {
284
+ const outTA = document.querySelector('#hidden-output textarea');
285
+ if (outTA) {
286
+ const val = outTA.value.trim();
287
+ if (val && val !== lastSeenOutput) {
288
+ lastSeenOutput = val;
289
+ applyExplanation(val);
290
  }
291
+ }
292
  }
293
+ setTimeout(pollOutput, 250);
294
+ }
295
 
296
+ function applyExplanation(text) {
297
+ if (placeholder) {
298
+ if (!text || text.startsWith('__NO_SELECTION__') || text.startsWith('__ERROR__')) {
299
+ /* Restore original text on failure */
300
+ placeholder.replaceWith(document.createTextNode(selectedText));
301
+ } else {
302
+ placeholder.classList.remove('ai-inline-loading');
303
+ placeholder.textContent = text;
304
+ placeholder.title = 'AI explanation β€” original: "' + selectedText + '"';
305
+ }
306
+ placeholder = null;
307
+ }
308
+ isExplaining = false;
309
+ savedRange = null;
310
+ selectedText = '';
311
+ hideFAB();
312
+ }
313
+
314
+ /* ── Push selected text into the hidden Gradio textbox ─────────── */
315
+ function pushToGradioInput(value) {
316
+ const ta = document.querySelector('#hidden-input textarea');
317
+ if (!ta) return;
318
+ const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
319
+ setter.call(ta, value);
320
+ ta.dispatchEvent(new Event('input', { bubbles: true }));
321
+ ta.dispatchEvent(new Event('change', { bubbles: true }));
322
+ }
323
+
324
+ /* ── Boot ───────────────────────────────────────────────────────── */
325
+ function boot() {
326
+ buildFAB();
327
+ pollOutput();
328
+ }
329
+
330
+ if (document.readyState === 'loading') {
331
+ document.addEventListener('DOMContentLoaded', boot);
332
+ } else {
333
+ setTimeout(boot, 800);
334
+ }
335
  })();
336
  </script>
337
  """
338
 
339
 
340
+ with gr.Blocks(head=HEAD_HTML) as demo:
341
+ gr.Markdown(
342
+ "### πŸ“˜ Highlight any text β€” a floating **Explain 🧠** button will appear. "
343
+ "Click it to replace the selection with an inline AI explanation."
344
+ )
345
+
346
  view_toggle = gr.Radio(
347
  choices=["My Work", "HF Reference"],
348
  value="My Work",
349
+ label="View",
350
  )
 
351
  content_display = gr.HTML(YOUR_WORK_HTML)
352
+ view_toggle.change(fn=switch_content, inputs=view_toggle, outputs=content_display)
 
 
 
 
 
 
 
 
 
 
 
 
 
353
 
354
+ # Hidden plumbing β€” not shown to the user
355
+ with gr.Row(visible=False):
356
+ hidden_input = gr.Textbox(elem_id="hidden-input", label="hidden-input")
357
+ hidden_output = gr.Textbox(elem_id="hidden-output", label="hidden-output")
358
+ with gr.Column(elem_id="hidden-explain-trigger"):
359
+ hidden_btn = gr.Button("hidden")
360
 
361
+ hidden_btn.click(fn=explain_text, inputs=hidden_input, outputs=hidden_output)
 
 
 
 
362
 
363
  demo.launch()