RayLee commited on
Commit
03d0e97
·
verified ·
1 Parent(s): 851c1fe

Upload folder using huggingface_hub

Browse files
.gitignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eggs/
2
+ dist/
3
+ *.pyc
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ __tmp/*
8
+ *.pyi
9
+ .mypycache
10
+ .ruff_cache
11
+ node_modules
12
+ backend/**/templates/
13
+ .claude
14
+ test_*
README.md CHANGED
@@ -1,12 +1,514 @@
1
  ---
2
- title: Gradio Markdownlabel
3
- emoji: 💻
4
- colorFrom: pink
5
- colorTo: gray
 
6
  sdk: gradio
7
- sdk_version: 5.36.2
8
- app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ tags: [gradio-custom-component, HighlightedText]
3
+ title: gradio_markdownlabel
4
+ short_description: A gradio custom component
5
+ colorFrom: blue
6
+ colorTo: yellow
7
  sdk: gradio
 
 
8
  pinned: false
9
+ app_file: space.py
10
  ---
11
 
12
+ # `gradio_markdownlabel`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
14
+
15
+ Python library for easily interacting with trained machine learning models
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install gradio_markdownlabel
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+
27
+ import gradio as gr
28
+ from gradio_markdownlabel import MarkdownLabel
29
+
30
+ # Create a comprehensive example with rich markdown content and multiple highlights
31
+ example_data = {
32
+ "markdown_content": """# AI and Machine Learning Research Report
33
+
34
+ ## Introduction
35
+
36
+ This document explores the rapidly evolving field of **artificial intelligence** and its various applications in modern technology. The study focuses on *machine learning* techniques and their impact on different industries.
37
+
38
+ ## Key Technologies
39
+
40
+ ### Deep Learning
41
+ *Deep learning* represents a subset of machine learning that uses **neural networks** with multiple layers. This approach has revolutionized areas such as:
42
+
43
+ - Computer vision
44
+ - Natural language processing
45
+ - Speech recognition
46
+
47
+ ### Natural Language Processing
48
+ **Natural language processing** (NLP) enables computers to understand and interpret human language. Key applications include:
49
+
50
+ 1. Text analysis
51
+ 2. Sentiment analysis
52
+ 3. Language translation
53
+
54
+ ## Applications
55
+
56
+ The integration of **computer vision** in autonomous vehicles has transformed the automotive industry. Similarly, **reinforcement learning** has shown remarkable success in gaming and robotics.
57
+
58
+ ## Conclusion
59
+
60
+ As **artificial intelligence** continues to advance, we expect to see more sophisticated applications of these technologies across various domains.
61
+ """,
62
+ "highlights": [
63
+ {
64
+ "term": "artificial intelligence",
65
+ "title": "Artificial Intelligence (AI)",
66
+ "content": "**Artificial Intelligence** refers to the simulation of human intelligence in machines that are programmed to think and learn like humans. AI systems can perform tasks that typically require human intelligence, such as:\n\n- Visual perception\n- Speech recognition\n- Decision-making\n- Language translation\n\nAI is broadly categorized into:\n1. **Narrow AI** - designed for specific tasks\n2. **General AI** - hypothetical AI with human-level cognitive abilities",
67
+ "category": "Core Technology",
68
+ "color": "#e3f2fd"
69
+ },
70
+ {
71
+ "term": "machine learning",
72
+ "title": "Machine Learning (ML)",
73
+ "content": "**Machine Learning** is a subset of AI that focuses on the development of algorithms that can learn and improve from experience without being explicitly programmed.\n\n### Types of Machine Learning:\n- **Supervised Learning**: Learning with labeled examples\n- **Unsupervised Learning**: Finding patterns in unlabeled data\n- **Reinforcement Learning**: Learning through interaction with environment\n\n### Applications:\n- Recommendation systems\n- Fraud detection\n- Medical diagnosis\n- Autonomous vehicles",
74
+ "category": "Core Technology",
75
+ "color": "#f3e5f5"
76
+ },
77
+ {
78
+ "term": "deep learning",
79
+ "title": "Deep Learning",
80
+ "content": "**Deep Learning** is a specialized subset of machine learning that uses artificial neural networks with multiple layers (deep neural networks) to model and understand complex patterns.\n\n### Key Characteristics:\n- Multiple hidden layers\n- Automatic feature extraction\n- Hierarchical learning\n\n### Popular Architectures:\n- Convolutional Neural Networks (CNNs)\n- Recurrent Neural Networks (RNNs)\n- Transformers\n\n### Applications:\n- Image recognition\n- Natural language processing\n- Speech synthesis",
81
+ "category": "Advanced Technique",
82
+ "color": "#e8f5e8"
83
+ },
84
+ {
85
+ "term": "neural networks",
86
+ "title": "Neural Networks",
87
+ "content": "**Neural Networks** are computing systems inspired by biological neural networks. They consist of interconnected nodes (neurons) that process information.\n\n### Components:\n- **Input Layer**: Receives data\n- **Hidden Layers**: Process information\n- **Output Layer**: Produces results\n\n### Types:\n- Feedforward networks\n- Recurrent networks\n- Convolutional networks\n\nNeural networks learn by adjusting the weights of connections between neurons based on training data.",
88
+ "category": "Architecture",
89
+ "color": "#fff3e0"
90
+ },
91
+ {
92
+ "term": "natural language processing",
93
+ "title": "Natural Language Processing (NLP)",
94
+ "content": "**Natural Language Processing** combines computational linguistics with machine learning to enable computers to understand, interpret, and generate human language.\n\n### Core Tasks:\n- **Tokenization**: Breaking text into words/sentences\n- **Part-of-speech tagging**: Identifying grammatical roles\n- **Named entity recognition**: Identifying people, places, organizations\n- **Sentiment analysis**: Determining emotional tone\n\n### Modern Approaches:\n- Transformer models (BERT, GPT)\n- Attention mechanisms\n- Pre-trained language models",
95
+ "category": "Application Domain",
96
+ "color": "#fce4ec"
97
+ },
98
+ {
99
+ "position": [615, 632],
100
+ "title": "Computer Vision (Position-based)",
101
+ "content": "**Computer Vision** highlighted by character position rather than term matching. This demonstrates precise control over highlighting specific text segments.\n\n### Position-based Benefits:\n- Exact character-level precision\n- No ambiguity with similar terms\n- Works with any text, including special characters\n- Useful for pre-processed documents",
102
+ "category": "Position Highlight",
103
+ "color": "#ffeb3b"
104
+ },
105
+ {
106
+ "term": "computer vision",
107
+ "title": "Computer Vision",
108
+ "content": "**Computer Vision** is a field of AI that enables machines to interpret and understand visual information from the world, mimicking human vision capabilities.\n\n### Key Tasks:\n- **Object Detection**: Locating objects in images\n- **Image Classification**: Categorizing images\n- **Semantic Segmentation**: Pixel-level understanding\n- **Face Recognition**: Identifying individuals\n\n### Applications:\n- Autonomous vehicles\n- Medical imaging\n- Security systems\n- Augmented reality",
109
+ "category": "Application Domain",
110
+ "color": "#e1f5fe"
111
+ },
112
+ {
113
+ "term": "reinforcement learning",
114
+ "title": "Reinforcement Learning (RL)",
115
+ "content": "**Reinforcement Learning** is a type of machine learning where an agent learns to make decisions by performing actions in an environment to maximize cumulative reward.\n\n### Key Concepts:\n- **Agent**: The learner/decision maker\n- **Environment**: The world the agent interacts with\n- **Actions**: Choices available to the agent\n- **Rewards**: Feedback from the environment\n- **Policy**: Strategy for choosing actions\n\n### Famous Applications:\n- Game playing (AlphaGo, OpenAI Five)\n- Robotics control\n- Trading algorithms\n- Resource allocation",
116
+ "category": "Learning Paradigm",
117
+ "color": "#f1f8e9"
118
+ },
119
+ {
120
+ "position": [169, 190],
121
+ "title": "Machine Learning (Position)",
122
+ "content": "This **machine learning** instance is highlighted using position-based highlighting at characters 169-190.\n\n### Position Highlighting Use Cases:\n- Academic paper annotations\n- Legal document markup\n- Code documentation\n- Precise text analysis\n\nPosition-based highlighting ensures exact text selection without ambiguity.",
123
+ "category": "Position Demo",
124
+ "color": "#e8eaf6"
125
+ }
126
+ ]
127
+ }
128
+
129
+ with gr.Blocks(title="Markdown Label Demo") as demo:
130
+ gr.Markdown("# MarkdownLabel Component Demo")
131
+ gr.Markdown("This demo showcases the MarkdownLabel component with **both term-based and position-based** interactive highlighting and detailed side panel.")
132
+
133
+ with gr.Row():
134
+ with gr.Column():
135
+ gr.Markdown("## Full Featured Example")
136
+ gr.Markdown("Includes both term-based (e.g., 'artificial intelligence') and position-based highlighting (yellow highlights).")
137
+ MarkdownLabel(
138
+ value=example_data,
139
+ label="AI Research Report - Mixed Highlighting",
140
+ show_side_panel=True,
141
+ panel_width="350px"
142
+ )
143
+
144
+ with gr.Column():
145
+ gr.Markdown("## Compact View")
146
+ gr.Markdown("Same content without the side panel for a cleaner interface.")
147
+ MarkdownLabel(
148
+ value=example_data,
149
+ label="Compact View",
150
+ show_side_panel=False
151
+ )
152
+
153
+ # Simple position-based example
154
+ simple_example = {
155
+ "markdown_content": "The quick **brown fox** jumps over the lazy dog. This is a simple example.",
156
+ "highlights": [
157
+ {
158
+ "position": [4, 9], # "quick"
159
+ "title": "Quick (Position 4-9)",
160
+ "content": "Highlighted using exact character positions 4-9.",
161
+ "category": "Position Demo",
162
+ "color": "#ffeb3b"
163
+ },
164
+ {
165
+ "term": "brown fox",
166
+ "title": "Brown Fox (Term Match)",
167
+ "content": "Highlighted using term matching.",
168
+ "category": "Term Demo",
169
+ "color": "#e3f2fd"
170
+ },
171
+ {
172
+ "position": [35, 43], # "the lazy"
173
+ "title": "The Lazy (Position 35-43)",
174
+ "content": "Another position-based highlight at characters 35-43.",
175
+ "category": "Position Demo",
176
+ "color": "#f3e5f5"
177
+ }
178
+ ]
179
+ }
180
+
181
+ gr.Markdown("## Position vs Term Highlighting Comparison")
182
+ gr.Markdown("This example shows the difference between position-based (yellow/purple) and term-based (blue) highlighting.")
183
+ MarkdownLabel(
184
+ value=simple_example,
185
+ label="Simple Position vs Term Example",
186
+ show_side_panel=True,
187
+ panel_width="300px"
188
+ )
189
+
190
+ if __name__ == "__main__":
191
+ demo.launch()
192
+
193
+ ```
194
+
195
+ ## `MarkdownLabel`
196
+
197
+ ### Initialization
198
+
199
+ <table>
200
+ <thead>
201
+ <tr>
202
+ <th align="left">name</th>
203
+ <th align="left" style="width: 25%;">type</th>
204
+ <th align="left">default</th>
205
+ <th align="left">description</th>
206
+ </tr>
207
+ </thead>
208
+ <tbody>
209
+ <tr>
210
+ <td align="left"><code>value</code></td>
211
+ <td align="left" style="width: 25%;">
212
+
213
+ ```python
214
+ dict | Callable | None
215
+ ```
216
+
217
+ </td>
218
+ <td align="left"><code>None</code></td>
219
+ <td align="left">Dictionary containing markdown_content and highlights array. If a function is provided, the function will be called each time the app loads to set the initial value of this component.</td>
220
+ </tr>
221
+
222
+ <tr>
223
+ <td align="left"><code>show_side_panel</code></td>
224
+ <td align="left" style="width: 25%;">
225
+
226
+ ```python
227
+ bool
228
+ ```
229
+
230
+ </td>
231
+ <td align="left"><code>True</code></td>
232
+ <td align="left">Whether to show the detailed information side panel.</td>
233
+ </tr>
234
+
235
+ <tr>
236
+ <td align="left"><code>panel_width</code></td>
237
+ <td align="left" style="width: 25%;">
238
+
239
+ ```python
240
+ str
241
+ ```
242
+
243
+ </td>
244
+ <td align="left"><code>"300px"</code></td>
245
+ <td align="left">Width of the side panel (CSS value like "300px", "25%", etc.).</td>
246
+ </tr>
247
+
248
+ <tr>
249
+ <td align="left"><code>edit_mode</code></td>
250
+ <td align="left" style="width: 25%;">
251
+
252
+ ```python
253
+ str
254
+ ```
255
+
256
+ </td>
257
+ <td align="left"><code>"split"</code></td>
258
+ <td align="left">Layout for editing mode - "split" (side-by-side), "tabs", or "overlay".</td>
259
+ </tr>
260
+
261
+ <tr>
262
+ <td align="left"><code>show_preview</code></td>
263
+ <td align="left" style="width: 25%;">
264
+
265
+ ```python
266
+ bool
267
+ ```
268
+
269
+ </td>
270
+ <td align="left"><code>True</code></td>
271
+ <td align="left">Whether to show live preview in edit mode.</td>
272
+ </tr>
273
+
274
+ <tr>
275
+ <td align="left"><code>markdown_editor</code></td>
276
+ <td align="left" style="width: 25%;">
277
+
278
+ ```python
279
+ str
280
+ ```
281
+
282
+ </td>
283
+ <td align="left"><code>"textarea"</code></td>
284
+ <td align="left">Type of markdown editor - "textarea" or "codemirror" (future).</td>
285
+ </tr>
286
+
287
+ <tr>
288
+ <td align="left"><code>label</code></td>
289
+ <td align="left" style="width: 25%;">
290
+
291
+ ```python
292
+ str | I18nData | None
293
+ ```
294
+
295
+ </td>
296
+ <td align="left"><code>None</code></td>
297
+ <td align="left">the label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.</td>
298
+ </tr>
299
+
300
+ <tr>
301
+ <td align="left"><code>every</code></td>
302
+ <td align="left" style="width: 25%;">
303
+
304
+ ```python
305
+ Timer | float | None
306
+ ```
307
+
308
+ </td>
309
+ <td align="left"><code>None</code></td>
310
+ <td align="left">Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.</td>
311
+ </tr>
312
+
313
+ <tr>
314
+ <td align="left"><code>inputs</code></td>
315
+ <td align="left" style="width: 25%;">
316
+
317
+ ```python
318
+ Component | Sequence[Component] | set[Component] | None
319
+ ```
320
+
321
+ </td>
322
+ <td align="left"><code>None</code></td>
323
+ <td align="left">Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.</td>
324
+ </tr>
325
+
326
+ <tr>
327
+ <td align="left"><code>show_label</code></td>
328
+ <td align="left" style="width: 25%;">
329
+
330
+ ```python
331
+ bool | None
332
+ ```
333
+
334
+ </td>
335
+ <td align="left"><code>None</code></td>
336
+ <td align="left">if True, will display label.</td>
337
+ </tr>
338
+
339
+ <tr>
340
+ <td align="left"><code>container</code></td>
341
+ <td align="left" style="width: 25%;">
342
+
343
+ ```python
344
+ bool
345
+ ```
346
+
347
+ </td>
348
+ <td align="left"><code>True</code></td>
349
+ <td align="left">If True, will place the component in a container - providing some extra padding around the border.</td>
350
+ </tr>
351
+
352
+ <tr>
353
+ <td align="left"><code>scale</code></td>
354
+ <td align="left" style="width: 25%;">
355
+
356
+ ```python
357
+ int | None
358
+ ```
359
+
360
+ </td>
361
+ <td align="left"><code>None</code></td>
362
+ <td align="left">relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.</td>
363
+ </tr>
364
+
365
+ <tr>
366
+ <td align="left"><code>min_width</code></td>
367
+ <td align="left" style="width: 25%;">
368
+
369
+ ```python
370
+ int
371
+ ```
372
+
373
+ </td>
374
+ <td align="left"><code>160</code></td>
375
+ <td align="left">minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.</td>
376
+ </tr>
377
+
378
+ <tr>
379
+ <td align="left"><code>visible</code></td>
380
+ <td align="left" style="width: 25%;">
381
+
382
+ ```python
383
+ bool
384
+ ```
385
+
386
+ </td>
387
+ <td align="left"><code>True</code></td>
388
+ <td align="left">If False, component will be hidden.</td>
389
+ </tr>
390
+
391
+ <tr>
392
+ <td align="left"><code>elem_id</code></td>
393
+ <td align="left" style="width: 25%;">
394
+
395
+ ```python
396
+ str | None
397
+ ```
398
+
399
+ </td>
400
+ <td align="left"><code>None</code></td>
401
+ <td align="left">An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
402
+ </tr>
403
+
404
+ <tr>
405
+ <td align="left"><code>elem_classes</code></td>
406
+ <td align="left" style="width: 25%;">
407
+
408
+ ```python
409
+ list[str] | str | None
410
+ ```
411
+
412
+ </td>
413
+ <td align="left"><code>None</code></td>
414
+ <td align="left">An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
415
+ </tr>
416
+
417
+ <tr>
418
+ <td align="left"><code>render</code></td>
419
+ <td align="left" style="width: 25%;">
420
+
421
+ ```python
422
+ bool
423
+ ```
424
+
425
+ </td>
426
+ <td align="left"><code>True</code></td>
427
+ <td align="left">If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.</td>
428
+ </tr>
429
+
430
+ <tr>
431
+ <td align="left"><code>key</code></td>
432
+ <td align="left" style="width: 25%;">
433
+
434
+ ```python
435
+ int | str | tuple[int | str, ...] | None
436
+ ```
437
+
438
+ </td>
439
+ <td align="left"><code>None</code></td>
440
+ <td align="left">in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.</td>
441
+ </tr>
442
+
443
+ <tr>
444
+ <td align="left"><code>preserved_by_key</code></td>
445
+ <td align="left" style="width: 25%;">
446
+
447
+ ```python
448
+ list[str] | str | None
449
+ ```
450
+
451
+ </td>
452
+ <td align="left"><code>"value"</code></td>
453
+ <td align="left">A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.</td>
454
+ </tr>
455
+
456
+ <tr>
457
+ <td align="left"><code>interactive</code></td>
458
+ <td align="left" style="width: 25%;">
459
+
460
+ ```python
461
+ bool | None
462
+ ```
463
+
464
+ </td>
465
+ <td align="left"><code>None</code></td>
466
+ <td align="left">If True, the component will be editable allowing users to modify markdown content.</td>
467
+ </tr>
468
+
469
+ <tr>
470
+ <td align="left"><code>rtl</code></td>
471
+ <td align="left" style="width: 25%;">
472
+
473
+ ```python
474
+ bool
475
+ ```
476
+
477
+ </td>
478
+ <td align="left"><code>False</code></td>
479
+ <td align="left">If True, will display the text in right-to-left direction.</td>
480
+ </tr>
481
+ </tbody></table>
482
+
483
+
484
+ ### Events
485
+
486
+ | name | description |
487
+ |:-----|:------------|
488
+ | `change` | Triggered when the value of the MarkdownLabel changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
489
+ | `select` | Event listener for when the user selects or deselects the MarkdownLabel. Uses event data gradio.SelectData to carry `value` referring to the label of the MarkdownLabel, and `selected` to refer to state of the MarkdownLabel. See EventData documentation on how to use this event data |
490
+ | `edit` | This listener is triggered when the user edits the MarkdownLabel (e.g. image) using the built-in editor. |
491
+ | `submit` | This listener is triggered when the user presses the Enter key while the MarkdownLabel is focused. |
492
+ | `clear` | This listener is triggered when the user clears the MarkdownLabel using the clear button for the component. |
493
+
494
+
495
+
496
+ ### User function
497
+
498
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
499
+
500
+ - When used as an Input, the component only impacts the input signature of the user function.
501
+ - When used as an output, the component only impacts the return signature of the user function.
502
+
503
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
504
+
505
+ - **As output:** Is passed, passes the value as a dictionary with markdown_content and highlights.
506
+ - **As input:** Should return, expects a dictionary with 'markdown_content' and 'highlights' keys.
507
+
508
+ ```python
509
+ def predict(
510
+ value: dict | None
511
+ ) -> dict | None:
512
+ return value
513
+ ```
514
+
__init__.py ADDED
File without changes
app.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from gradio_markdownlabel import MarkdownLabel
4
+
5
+ # Create a comprehensive example with rich markdown content and multiple highlights
6
+ example_data = {
7
+ "markdown_content": """# AI and Machine Learning Research Report
8
+
9
+ ## Introduction
10
+
11
+ This document explores the rapidly evolving field of **artificial intelligence** and its various applications in modern technology. The study focuses on *machine learning* techniques and their impact on different industries.
12
+
13
+ ## Key Technologies
14
+
15
+ ### Deep Learning
16
+ *Deep learning* represents a subset of machine learning that uses **neural networks** with multiple layers. This approach has revolutionized areas such as:
17
+
18
+ - Computer vision
19
+ - Natural language processing
20
+ - Speech recognition
21
+
22
+ ### Natural Language Processing
23
+ **Natural language processing** (NLP) enables computers to understand and interpret human language. Key applications include:
24
+
25
+ 1. Text analysis
26
+ 2. Sentiment analysis
27
+ 3. Language translation
28
+
29
+ ## Applications
30
+
31
+ The integration of **computer vision** in autonomous vehicles has transformed the automotive industry. Similarly, **reinforcement learning** has shown remarkable success in gaming and robotics.
32
+
33
+ ## Conclusion
34
+
35
+ As **artificial intelligence** continues to advance, we expect to see more sophisticated applications of these technologies across various domains.
36
+ """,
37
+ "highlights": [
38
+ {
39
+ "term": "artificial intelligence",
40
+ "title": "Artificial Intelligence (AI)",
41
+ "content": "**Artificial Intelligence** refers to the simulation of human intelligence in machines that are programmed to think and learn like humans. AI systems can perform tasks that typically require human intelligence, such as:\n\n- Visual perception\n- Speech recognition\n- Decision-making\n- Language translation\n\nAI is broadly categorized into:\n1. **Narrow AI** - designed for specific tasks\n2. **General AI** - hypothetical AI with human-level cognitive abilities",
42
+ "category": "Core Technology",
43
+ "color": "#e3f2fd"
44
+ },
45
+ {
46
+ "term": "machine learning",
47
+ "title": "Machine Learning (ML)",
48
+ "content": "**Machine Learning** is a subset of AI that focuses on the development of algorithms that can learn and improve from experience without being explicitly programmed.\n\n### Types of Machine Learning:\n- **Supervised Learning**: Learning with labeled examples\n- **Unsupervised Learning**: Finding patterns in unlabeled data\n- **Reinforcement Learning**: Learning through interaction with environment\n\n### Applications:\n- Recommendation systems\n- Fraud detection\n- Medical diagnosis\n- Autonomous vehicles",
49
+ "category": "Core Technology",
50
+ "color": "#f3e5f5"
51
+ },
52
+ {
53
+ "term": "deep learning",
54
+ "title": "Deep Learning",
55
+ "content": "**Deep Learning** is a specialized subset of machine learning that uses artificial neural networks with multiple layers (deep neural networks) to model and understand complex patterns.\n\n### Key Characteristics:\n- Multiple hidden layers\n- Automatic feature extraction\n- Hierarchical learning\n\n### Popular Architectures:\n- Convolutional Neural Networks (CNNs)\n- Recurrent Neural Networks (RNNs)\n- Transformers\n\n### Applications:\n- Image recognition\n- Natural language processing\n- Speech synthesis",
56
+ "category": "Advanced Technique",
57
+ "color": "#e8f5e8"
58
+ },
59
+ {
60
+ "term": "neural networks",
61
+ "title": "Neural Networks",
62
+ "content": "**Neural Networks** are computing systems inspired by biological neural networks. They consist of interconnected nodes (neurons) that process information.\n\n### Components:\n- **Input Layer**: Receives data\n- **Hidden Layers**: Process information\n- **Output Layer**: Produces results\n\n### Types:\n- Feedforward networks\n- Recurrent networks\n- Convolutional networks\n\nNeural networks learn by adjusting the weights of connections between neurons based on training data.",
63
+ "category": "Architecture",
64
+ "color": "#fff3e0"
65
+ },
66
+ {
67
+ "term": "natural language processing",
68
+ "title": "Natural Language Processing (NLP)",
69
+ "content": "**Natural Language Processing** combines computational linguistics with machine learning to enable computers to understand, interpret, and generate human language.\n\n### Core Tasks:\n- **Tokenization**: Breaking text into words/sentences\n- **Part-of-speech tagging**: Identifying grammatical roles\n- **Named entity recognition**: Identifying people, places, organizations\n- **Sentiment analysis**: Determining emotional tone\n\n### Modern Approaches:\n- Transformer models (BERT, GPT)\n- Attention mechanisms\n- Pre-trained language models",
70
+ "category": "Application Domain",
71
+ "color": "#fce4ec"
72
+ },
73
+ {
74
+ "position": [615, 632],
75
+ "title": "Computer Vision (Position-based)",
76
+ "content": "**Computer Vision** highlighted by character position rather than term matching. This demonstrates precise control over highlighting specific text segments.\n\n### Position-based Benefits:\n- Exact character-level precision\n- No ambiguity with similar terms\n- Works with any text, including special characters\n- Useful for pre-processed documents",
77
+ "category": "Position Highlight",
78
+ "color": "#ffeb3b"
79
+ },
80
+ {
81
+ "term": "computer vision",
82
+ "title": "Computer Vision",
83
+ "content": "**Computer Vision** is a field of AI that enables machines to interpret and understand visual information from the world, mimicking human vision capabilities.\n\n### Key Tasks:\n- **Object Detection**: Locating objects in images\n- **Image Classification**: Categorizing images\n- **Semantic Segmentation**: Pixel-level understanding\n- **Face Recognition**: Identifying individuals\n\n### Applications:\n- Autonomous vehicles\n- Medical imaging\n- Security systems\n- Augmented reality",
84
+ "category": "Application Domain",
85
+ "color": "#e1f5fe"
86
+ },
87
+ {
88
+ "term": "reinforcement learning",
89
+ "title": "Reinforcement Learning (RL)",
90
+ "content": "**Reinforcement Learning** is a type of machine learning where an agent learns to make decisions by performing actions in an environment to maximize cumulative reward.\n\n### Key Concepts:\n- **Agent**: The learner/decision maker\n- **Environment**: The world the agent interacts with\n- **Actions**: Choices available to the agent\n- **Rewards**: Feedback from the environment\n- **Policy**: Strategy for choosing actions\n\n### Famous Applications:\n- Game playing (AlphaGo, OpenAI Five)\n- Robotics control\n- Trading algorithms\n- Resource allocation",
91
+ "category": "Learning Paradigm",
92
+ "color": "#f1f8e9"
93
+ },
94
+ {
95
+ "position": [169, 190],
96
+ "title": "Machine Learning (Position)",
97
+ "content": "This **machine learning** instance is highlighted using position-based highlighting at characters 169-190.\n\n### Position Highlighting Use Cases:\n- Academic paper annotations\n- Legal document markup\n- Code documentation\n- Precise text analysis\n\nPosition-based highlighting ensures exact text selection without ambiguity.",
98
+ "category": "Position Demo",
99
+ "color": "#e8eaf6"
100
+ }
101
+ ]
102
+ }
103
+
104
+ with gr.Blocks(title="Markdown Label Demo") as demo:
105
+ gr.Markdown("# MarkdownLabel Component Demo")
106
+ gr.Markdown("This demo showcases the MarkdownLabel component with **both term-based and position-based** interactive highlighting and detailed side panel.")
107
+
108
+ with gr.Row():
109
+ with gr.Column():
110
+ gr.Markdown("## Full Featured Example")
111
+ gr.Markdown("Includes both term-based (e.g., 'artificial intelligence') and position-based highlighting (yellow highlights).")
112
+ MarkdownLabel(
113
+ value=example_data,
114
+ label="AI Research Report - Mixed Highlighting",
115
+ show_side_panel=True,
116
+ panel_width="350px"
117
+ )
118
+
119
+ with gr.Column():
120
+ gr.Markdown("## Compact View")
121
+ gr.Markdown("Same content without the side panel for a cleaner interface.")
122
+ MarkdownLabel(
123
+ value=example_data,
124
+ label="Compact View",
125
+ show_side_panel=False
126
+ )
127
+
128
+ # Simple position-based example
129
+ simple_example = {
130
+ "markdown_content": "The quick **brown fox** jumps over the lazy dog. This is a simple example.",
131
+ "highlights": [
132
+ {
133
+ "position": [4, 9], # "quick"
134
+ "title": "Quick (Position 4-9)",
135
+ "content": "Highlighted using exact character positions 4-9.",
136
+ "category": "Position Demo",
137
+ "color": "#ffeb3b"
138
+ },
139
+ {
140
+ "term": "brown fox",
141
+ "title": "Brown Fox (Term Match)",
142
+ "content": "Highlighted using term matching.",
143
+ "category": "Term Demo",
144
+ "color": "#e3f2fd"
145
+ },
146
+ {
147
+ "position": [35, 43], # "the lazy"
148
+ "title": "The Lazy (Position 35-43)",
149
+ "content": "Another position-based highlight at characters 35-43.",
150
+ "category": "Position Demo",
151
+ "color": "#f3e5f5"
152
+ }
153
+ ]
154
+ }
155
+
156
+ gr.Markdown("## Position vs Term Highlighting Comparison")
157
+ gr.Markdown("This example shows the difference between position-based (yellow/purple) and term-based (blue) highlighting.")
158
+ MarkdownLabel(
159
+ value=simple_example,
160
+ label="Simple Position vs Term Example",
161
+ show_side_panel=True,
162
+ panel_width="300px"
163
+ )
164
+
165
+ if __name__ == "__main__":
166
+ demo.launch()
css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
editable_demo.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import gradio as gr
4
+ from gradio_markdownlabel import MarkdownLabel
5
+
6
+ # Sample data for the editable demo
7
+ sample_data = {
8
+ "markdown_content": """# Editable Document Example
9
+
10
+ ## Introduction
11
+
12
+ This document demonstrates the **editable functionality** of the MarkdownLabel component. You can click the "Edit" button to modify this content.
13
+
14
+ ## Features
15
+
16
+ - **Real-time editing**: Changes appear live in preview mode
17
+ - **Highlight preservation**: Existing highlights remain intact
18
+ - **Multiple edit modes**: Split view, tabs, and overlay options
19
+ - Position-based highlighting support
20
+
21
+ ## Try Editing
22
+
23
+ 1. Click the **Edit** button above
24
+ 2. Modify this text in the editor
25
+ 3. See live preview (in split mode)
26
+ 4. Save or cancel your changes
27
+
28
+ Feel free to experiment with the content!
29
+ """,
30
+ "highlights": [
31
+ {
32
+ "term": "editable functionality",
33
+ "title": "Editable Functionality",
34
+ "content": "This feature allows users to modify markdown content directly in the interface with real-time preview.",
35
+ "category": "feature",
36
+ "color": "#e3f2fd"
37
+ },
38
+ {
39
+ "position": [200, 220], # "MarkdownLabel component"
40
+ "title": "MarkdownLabel Component",
41
+ "content": "The main component that renders markdown with interactive highlights and editing capabilities.",
42
+ "category": "component",
43
+ "color": "#f3e5f5"
44
+ },
45
+ {
46
+ "term": "Real-time editing",
47
+ "title": "Real-time Editing",
48
+ "content": "Changes in the editor are immediately reflected in the preview pane, providing instant feedback.",
49
+ "category": "feature",
50
+ "color": "#e8f5e8"
51
+ },
52
+ {
53
+ "position": [450, 470], # "Split view, tabs, and"
54
+ "title": "Edit Modes",
55
+ "content": "Different layout options for the editing interface: split view shows editor and preview side-by-side, tabs separate them, and overlay mode provides full-screen editing.",
56
+ "category": "ui",
57
+ "color": "#fff3e0"
58
+ }
59
+ ]
60
+ }
61
+
62
+ def handle_content_change(value):
63
+ """Handle content changes (only on save)"""
64
+ print(f"Content saved: {len(value['markdown_content'])} characters")
65
+ return value
66
+
67
+ def handle_edit_start(value):
68
+ """Handle when user starts editing"""
69
+ print("User started editing")
70
+
71
+ def handle_save(value):
72
+ """Handle when user saves changes"""
73
+ print("Changes saved!")
74
+ gr.Info("Document saved successfully!")
75
+ return value
76
+
77
+ def handle_cancel(value):
78
+ """Handle when user cancels editing"""
79
+ print("Edit cancelled")
80
+ gr.Info("Changes cancelled")
81
+ return gr.update()
82
+
83
+ def load_sample_content():
84
+ """Load sample content"""
85
+ # Return data for both components (split and tabs editors)
86
+ return sample_data, sample_data
87
+
88
+
89
+ with gr.Blocks(title="Editable MarkdownLabel Demo") as demo:
90
+ gr.Markdown("# Editable MarkdownLabel Component Demo")
91
+ gr.Markdown("This demo showcases the **interactive editing capabilities** of the MarkdownLabel component.")
92
+
93
+ with gr.Row():
94
+ with gr.Column(scale=2):
95
+ gr.Markdown("## Split View Mode (Default)")
96
+ gr.Markdown("Editor and preview side-by-side when editing.")
97
+
98
+ editor_split = MarkdownLabel(
99
+ value=sample_data,
100
+ interactive=True,
101
+ edit_mode="split",
102
+ show_preview=True,
103
+ label="Split View Editor",
104
+ show_side_panel=True,
105
+ panel_width="300px"
106
+ )
107
+
108
+ # Event handlers for split view
109
+ # Note: Removed real-time change handler to prevent infinite loops
110
+ # Changes are now handled only on explicit save via submit event
111
+ editor_split.edit(handle_edit_start, inputs=[editor_split])
112
+ editor_split.submit(handle_save, inputs=[editor_split], outputs=[editor_split])
113
+ editor_split.clear(handle_cancel, inputs=[editor_split])
114
+
115
+ with gr.Column(scale=1):
116
+ gr.Markdown("## Tab Mode")
117
+ gr.Markdown("Switch between edit and preview tabs.")
118
+
119
+ editor_tabs = MarkdownLabel(
120
+ value=sample_data,
121
+ interactive=True,
122
+ edit_mode="tabs",
123
+ show_preview=True,
124
+ label="Tab View Editor",
125
+ show_side_panel=False
126
+ )
127
+
128
+ # Event handlers for tab view
129
+ # Note: Only handling submit (save) events to prevent loops
130
+ editor_tabs.submit(handle_save, inputs=[editor_tabs], outputs=[editor_tabs])
131
+
132
+ with gr.Row():
133
+ gr.Markdown("## Control Buttons")
134
+
135
+ with gr.Row():
136
+ load_btn = gr.Button("📄 Load Sample", variant="secondary")
137
+
138
+ # Button event handlers
139
+ load_btn.click(load_sample_content, outputs=[editor_split, editor_tabs])
140
+
141
+ with gr.Row():
142
+ gr.Markdown("## Non-Interactive (Read-Only) Version")
143
+
144
+ # Read-only version for comparison
145
+ readonly_viewer = MarkdownLabel(
146
+ value=sample_data,
147
+ interactive=False, # Read-only mode
148
+ label="Read-Only Viewer",
149
+ show_side_panel=True,
150
+ panel_width="250px"
151
+ )
152
+
153
+ gr.Markdown("""
154
+ ## How to Use
155
+
156
+ 1. **Start Editing**: Click the "✏️ Edit" button on any interactive component
157
+ 2. **Edit Content**: Modify the markdown text in the editor
158
+ 3. **Live Preview**: See changes in real-time (split mode) or switch to preview tab
159
+ 4. **Save Changes**: Click "💾 Save" to confirm and apply your changes
160
+ 5. **Cancel Changes**: Click "❌ Cancel" to discard changes and revert
161
+ 6. **Interact with Highlights**: Click on highlighted terms to see details in the side panel
162
+
163
+ ## Features Demonstrated
164
+
165
+ - ✅ **Interactive editing** with explicit save/cancel workflow
166
+ - ✅ **Multiple edit modes**: Split view and tabs
167
+ - ✅ **Live preview** with real-time markdown rendering (visual only)
168
+ - ✅ **Highlight preservation** during editing
169
+ - ✅ **Manual save workflow** - changes are applied only when you save
170
+ - ✅ **Event handling** for edit, save (submit), and cancel events
171
+ - ✅ **Mixed highlighting** with both term-based and position-based highlights
172
+
173
+ ## Important Notes
174
+
175
+ - **Changes are NOT auto-saved** - you must click "💾 Save" to apply changes
176
+ - **Live preview** is for visual feedback only - actual content updates on save
177
+ - **Cancel** reverts all changes made since editing started
178
+ """)
179
+
180
+ if __name__ == "__main__":
181
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_markdownlabel
space.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from app import demo as app
4
+ import os
5
+
6
+ _docs = {'MarkdownLabel': {'description': 'Displays markdown-formatted text with interactive term highlighting and detailed side panel.\n\nThis component allows for rich markdown content with clickable term highlights that display\ndetailed information in a side panel.', 'members': {'__init__': {'value': {'type': 'dict | Callable | None', 'default': 'None', 'description': 'Dictionary containing markdown_content and highlights array. If a function is provided, the function will be called each time the app loads to set the initial value of this component.'}, 'show_side_panel': {'type': 'bool', 'default': 'True', 'description': 'Whether to show the detailed information side panel.'}, 'panel_width': {'type': 'str', 'default': '"300px"', 'description': 'Width of the side panel (CSS value like "300px", "25%", etc.).'}, 'edit_mode': {'type': 'str', 'default': '"split"', 'description': 'Layout for editing mode - "split" (side-by-side), "tabs", or "overlay".'}, 'show_preview': {'type': 'bool', 'default': 'True', 'description': 'Whether to show live preview in edit mode.'}, 'markdown_editor': {'type': 'str', 'default': '"textarea"', 'description': 'Type of markdown editor - "textarea" or "codemirror" (future).'}, 'label': {'type': 'str | I18nData | None', 'default': 'None', 'description': 'the label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.'}, 'every': {'type': 'Timer | float | None', 'default': 'None', 'description': 'Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.'}, 'inputs': {'type': 'Component | Sequence[Component] | set[Component] | None', 'default': 'None', 'description': 'Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container - providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'key': {'type': 'int | str | tuple[int | str, ...] | None', 'default': 'None', 'description': "in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render."}, 'preserved_by_key': {'type': 'list[str] | str | None', 'default': '"value"', 'description': "A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor."}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': 'If True, the component will be editable allowing users to modify markdown content.'}, 'rtl': {'type': 'bool', 'default': 'False', 'description': 'If True, will display the text in right-to-left direction.'}}, 'postprocess': {'value': {'type': 'dict | None', 'description': "Expects a dictionary with 'markdown_content' and 'highlights' keys"}}, 'preprocess': {'return': {'type': 'dict | None', 'description': 'Passes the value as a dictionary with markdown_content and highlights.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the MarkdownLabel changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'select': {'type': None, 'default': None, 'description': 'Event listener for when the user selects or deselects the MarkdownLabel. Uses event data gradio.SelectData to carry `value` referring to the label of the MarkdownLabel, and `selected` to refer to state of the MarkdownLabel. See EventData documentation on how to use this event data'}, 'edit': {'type': None, 'default': None, 'description': 'This listener is triggered when the user edits the MarkdownLabel (e.g. image) using the built-in editor.'}, 'submit': {'type': None, 'default': None, 'description': 'This listener is triggered when the user presses the Enter key while the MarkdownLabel is focused.'}, 'clear': {'type': None, 'default': None, 'description': 'This listener is triggered when the user clears the MarkdownLabel using the clear button for the component.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'MarkdownLabel': []}}}
7
+
8
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
+
10
+ with gr.Blocks(
11
+ css=abs_path,
12
+ theme=gr.themes.Default(
13
+ font_mono=[
14
+ gr.themes.GoogleFont("Inconsolata"),
15
+ "monospace",
16
+ ],
17
+ ),
18
+ ) as demo:
19
+ gr.Markdown(
20
+ """
21
+ # `gradio_markdownlabel`
22
+
23
+ <div style="display: flex; gap: 7px;">
24
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
25
+ </div>
26
+
27
+ Python library for easily interacting with trained machine learning models
28
+ """, elem_classes=["md-custom"], header_links=True)
29
+ app.render()
30
+ gr.Markdown(
31
+ """
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install gradio_markdownlabel
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+
42
+ import gradio as gr
43
+ from gradio_markdownlabel import MarkdownLabel
44
+
45
+ # Create a comprehensive example with rich markdown content and multiple highlights
46
+ example_data = {
47
+ "markdown_content": \"\"\"# AI and Machine Learning Research Report
48
+
49
+ ## Introduction
50
+
51
+ This document explores the rapidly evolving field of **artificial intelligence** and its various applications in modern technology. The study focuses on *machine learning* techniques and their impact on different industries.
52
+
53
+ ## Key Technologies
54
+
55
+ ### Deep Learning
56
+ *Deep learning* represents a subset of machine learning that uses **neural networks** with multiple layers. This approach has revolutionized areas such as:
57
+
58
+ - Computer vision
59
+ - Natural language processing
60
+ - Speech recognition
61
+
62
+ ### Natural Language Processing
63
+ **Natural language processing** (NLP) enables computers to understand and interpret human language. Key applications include:
64
+
65
+ 1. Text analysis
66
+ 2. Sentiment analysis
67
+ 3. Language translation
68
+
69
+ ## Applications
70
+
71
+ The integration of **computer vision** in autonomous vehicles has transformed the automotive industry. Similarly, **reinforcement learning** has shown remarkable success in gaming and robotics.
72
+
73
+ ## Conclusion
74
+
75
+ As **artificial intelligence** continues to advance, we expect to see more sophisticated applications of these technologies across various domains.
76
+ \"\"\",
77
+ "highlights": [
78
+ {
79
+ "term": "artificial intelligence",
80
+ "title": "Artificial Intelligence (AI)",
81
+ "content": "**Artificial Intelligence** refers to the simulation of human intelligence in machines that are programmed to think and learn like humans. AI systems can perform tasks that typically require human intelligence, such as:\n\n- Visual perception\n- Speech recognition\n- Decision-making\n- Language translation\n\nAI is broadly categorized into:\n1. **Narrow AI** - designed for specific tasks\n2. **General AI** - hypothetical AI with human-level cognitive abilities",
82
+ "category": "Core Technology",
83
+ "color": "#e3f2fd"
84
+ },
85
+ {
86
+ "term": "machine learning",
87
+ "title": "Machine Learning (ML)",
88
+ "content": "**Machine Learning** is a subset of AI that focuses on the development of algorithms that can learn and improve from experience without being explicitly programmed.\n\n### Types of Machine Learning:\n- **Supervised Learning**: Learning with labeled examples\n- **Unsupervised Learning**: Finding patterns in unlabeled data\n- **Reinforcement Learning**: Learning through interaction with environment\n\n### Applications:\n- Recommendation systems\n- Fraud detection\n- Medical diagnosis\n- Autonomous vehicles",
89
+ "category": "Core Technology",
90
+ "color": "#f3e5f5"
91
+ },
92
+ {
93
+ "term": "deep learning",
94
+ "title": "Deep Learning",
95
+ "content": "**Deep Learning** is a specialized subset of machine learning that uses artificial neural networks with multiple layers (deep neural networks) to model and understand complex patterns.\n\n### Key Characteristics:\n- Multiple hidden layers\n- Automatic feature extraction\n- Hierarchical learning\n\n### Popular Architectures:\n- Convolutional Neural Networks (CNNs)\n- Recurrent Neural Networks (RNNs)\n- Transformers\n\n### Applications:\n- Image recognition\n- Natural language processing\n- Speech synthesis",
96
+ "category": "Advanced Technique",
97
+ "color": "#e8f5e8"
98
+ },
99
+ {
100
+ "term": "neural networks",
101
+ "title": "Neural Networks",
102
+ "content": "**Neural Networks** are computing systems inspired by biological neural networks. They consist of interconnected nodes (neurons) that process information.\n\n### Components:\n- **Input Layer**: Receives data\n- **Hidden Layers**: Process information\n- **Output Layer**: Produces results\n\n### Types:\n- Feedforward networks\n- Recurrent networks\n- Convolutional networks\n\nNeural networks learn by adjusting the weights of connections between neurons based on training data.",
103
+ "category": "Architecture",
104
+ "color": "#fff3e0"
105
+ },
106
+ {
107
+ "term": "natural language processing",
108
+ "title": "Natural Language Processing (NLP)",
109
+ "content": "**Natural Language Processing** combines computational linguistics with machine learning to enable computers to understand, interpret, and generate human language.\n\n### Core Tasks:\n- **Tokenization**: Breaking text into words/sentences\n- **Part-of-speech tagging**: Identifying grammatical roles\n- **Named entity recognition**: Identifying people, places, organizations\n- **Sentiment analysis**: Determining emotional tone\n\n### Modern Approaches:\n- Transformer models (BERT, GPT)\n- Attention mechanisms\n- Pre-trained language models",
110
+ "category": "Application Domain",
111
+ "color": "#fce4ec"
112
+ },
113
+ {
114
+ "position": [615, 632],
115
+ "title": "Computer Vision (Position-based)",
116
+ "content": "**Computer Vision** highlighted by character position rather than term matching. This demonstrates precise control over highlighting specific text segments.\n\n### Position-based Benefits:\n- Exact character-level precision\n- No ambiguity with similar terms\n- Works with any text, including special characters\n- Useful for pre-processed documents",
117
+ "category": "Position Highlight",
118
+ "color": "#ffeb3b"
119
+ },
120
+ {
121
+ "term": "computer vision",
122
+ "title": "Computer Vision",
123
+ "content": "**Computer Vision** is a field of AI that enables machines to interpret and understand visual information from the world, mimicking human vision capabilities.\n\n### Key Tasks:\n- **Object Detection**: Locating objects in images\n- **Image Classification**: Categorizing images\n- **Semantic Segmentation**: Pixel-level understanding\n- **Face Recognition**: Identifying individuals\n\n### Applications:\n- Autonomous vehicles\n- Medical imaging\n- Security systems\n- Augmented reality",
124
+ "category": "Application Domain",
125
+ "color": "#e1f5fe"
126
+ },
127
+ {
128
+ "term": "reinforcement learning",
129
+ "title": "Reinforcement Learning (RL)",
130
+ "content": "**Reinforcement Learning** is a type of machine learning where an agent learns to make decisions by performing actions in an environment to maximize cumulative reward.\n\n### Key Concepts:\n- **Agent**: The learner/decision maker\n- **Environment**: The world the agent interacts with\n- **Actions**: Choices available to the agent\n- **Rewards**: Feedback from the environment\n- **Policy**: Strategy for choosing actions\n\n### Famous Applications:\n- Game playing (AlphaGo, OpenAI Five)\n- Robotics control\n- Trading algorithms\n- Resource allocation",
131
+ "category": "Learning Paradigm",
132
+ "color": "#f1f8e9"
133
+ },
134
+ {
135
+ "position": [169, 190],
136
+ "title": "Machine Learning (Position)",
137
+ "content": "This **machine learning** instance is highlighted using position-based highlighting at characters 169-190.\n\n### Position Highlighting Use Cases:\n- Academic paper annotations\n- Legal document markup\n- Code documentation\n- Precise text analysis\n\nPosition-based highlighting ensures exact text selection without ambiguity.",
138
+ "category": "Position Demo",
139
+ "color": "#e8eaf6"
140
+ }
141
+ ]
142
+ }
143
+
144
+ with gr.Blocks(title="Markdown Label Demo") as demo:
145
+ gr.Markdown("# MarkdownLabel Component Demo")
146
+ gr.Markdown("This demo showcases the MarkdownLabel component with **both term-based and position-based** interactive highlighting and detailed side panel.")
147
+
148
+ with gr.Row():
149
+ with gr.Column():
150
+ gr.Markdown("## Full Featured Example")
151
+ gr.Markdown("Includes both term-based (e.g., 'artificial intelligence') and position-based highlighting (yellow highlights).")
152
+ MarkdownLabel(
153
+ value=example_data,
154
+ label="AI Research Report - Mixed Highlighting",
155
+ show_side_panel=True,
156
+ panel_width="350px"
157
+ )
158
+
159
+ with gr.Column():
160
+ gr.Markdown("## Compact View")
161
+ gr.Markdown("Same content without the side panel for a cleaner interface.")
162
+ MarkdownLabel(
163
+ value=example_data,
164
+ label="Compact View",
165
+ show_side_panel=False
166
+ )
167
+
168
+ # Simple position-based example
169
+ simple_example = {
170
+ "markdown_content": "The quick **brown fox** jumps over the lazy dog. This is a simple example.",
171
+ "highlights": [
172
+ {
173
+ "position": [4, 9], # "quick"
174
+ "title": "Quick (Position 4-9)",
175
+ "content": "Highlighted using exact character positions 4-9.",
176
+ "category": "Position Demo",
177
+ "color": "#ffeb3b"
178
+ },
179
+ {
180
+ "term": "brown fox",
181
+ "title": "Brown Fox (Term Match)",
182
+ "content": "Highlighted using term matching.",
183
+ "category": "Term Demo",
184
+ "color": "#e3f2fd"
185
+ },
186
+ {
187
+ "position": [35, 43], # "the lazy"
188
+ "title": "The Lazy (Position 35-43)",
189
+ "content": "Another position-based highlight at characters 35-43.",
190
+ "category": "Position Demo",
191
+ "color": "#f3e5f5"
192
+ }
193
+ ]
194
+ }
195
+
196
+ gr.Markdown("## Position vs Term Highlighting Comparison")
197
+ gr.Markdown("This example shows the difference between position-based (yellow/purple) and term-based (blue) highlighting.")
198
+ MarkdownLabel(
199
+ value=simple_example,
200
+ label="Simple Position vs Term Example",
201
+ show_side_panel=True,
202
+ panel_width="300px"
203
+ )
204
+
205
+ if __name__ == "__main__":
206
+ demo.launch()
207
+
208
+ ```
209
+ """, elem_classes=["md-custom"], header_links=True)
210
+
211
+
212
+ gr.Markdown("""
213
+ ## `MarkdownLabel`
214
+
215
+ ### Initialization
216
+ """, elem_classes=["md-custom"], header_links=True)
217
+
218
+ gr.ParamViewer(value=_docs["MarkdownLabel"]["members"]["__init__"], linkify=[])
219
+
220
+
221
+ gr.Markdown("### Events")
222
+ gr.ParamViewer(value=_docs["MarkdownLabel"]["events"], linkify=['Event'])
223
+
224
+
225
+
226
+
227
+ gr.Markdown("""
228
+
229
+ ### User function
230
+
231
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
232
+
233
+ - When used as an Input, the component only impacts the input signature of the user function.
234
+ - When used as an output, the component only impacts the return signature of the user function.
235
+
236
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
237
+
238
+ - **As input:** Is passed, passes the value as a dictionary with markdown_content and highlights.
239
+ - **As output:** Should return, expects a dictionary with 'markdown_content' and 'highlights' keys.
240
+
241
+ ```python
242
+ def predict(
243
+ value: dict | None
244
+ ) -> dict | None:
245
+ return value
246
+ ```
247
+ """, elem_classes=["md-custom", "MarkdownLabel-user-fn"], header_links=True)
248
+
249
+
250
+
251
+
252
+ demo.load(None, js=r"""function() {
253
+ const refs = {};
254
+ const user_fn_refs = {
255
+ MarkdownLabel: [], };
256
+ requestAnimationFrame(() => {
257
+
258
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
259
+ if (refs.length > 0) {
260
+ const el = document.querySelector(`.${key}-user-fn`);
261
+ if (!el) return;
262
+ refs.forEach(ref => {
263
+ el.innerHTML = el.innerHTML.replace(
264
+ new RegExp("\\b"+ref+"\\b", "g"),
265
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
266
+ );
267
+ })
268
+ }
269
+ })
270
+
271
+ Object.entries(refs).forEach(([key, refs]) => {
272
+ if (refs.length > 0) {
273
+ const el = document.querySelector(`.${key}`);
274
+ if (!el) return;
275
+ refs.forEach(ref => {
276
+ el.innerHTML = el.innerHTML.replace(
277
+ new RegExp("\\b"+ref+"\\b", "g"),
278
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
279
+ );
280
+ })
281
+ }
282
+ })
283
+ })
284
+ }
285
+
286
+ """)
287
+
288
+ demo.launch()
src/.gitignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eggs/
2
+ dist/
3
+ *.pyc
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ __tmp/*
8
+ *.pyi
9
+ .mypycache
10
+ .ruff_cache
11
+ node_modules
12
+ backend/**/templates/
13
+ .claude
14
+ test_*
src/LICENSE ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity granting the License.
13
+
14
+ "Legal Entity" shall mean the union of the acting entity and all
15
+ other entities that control, are controlled by, or are under common
16
+ control with that entity. For the purposes of this definition,
17
+ "control" means (i) the power, direct or indirect, to cause the
18
+ direction or management of such entity, whether by contract or
19
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
20
+ outstanding shares, or (iii) beneficial ownership of such entity.
21
+
22
+ "You" (or "Your") shall mean an individual or Legal Entity
23
+ exercising permissions granted by this License.
24
+
25
+ "Source" form shall mean the preferred form for making modifications,
26
+ including but not limited to software source code, documentation
27
+ source, and configuration files.
28
+
29
+ "Object" form shall mean any form resulting from mechanical
30
+ transformation or translation of a Source form, including but
31
+ not limited to compiled object code, generated documentation,
32
+ and conversions to other media types.
33
+
34
+ "Work" shall mean the work of authorship, whether in Source or
35
+ Object form, made available under the License, as indicated by a
36
+ copyright notice that is included in or attached to the work
37
+ (which shall not include communication that is conspicuously
38
+ marked or otherwise designated in writing by the copyright owner
39
+ as "Not a Contribution").
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based upon (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and derivative works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control
58
+ systems, and issue tracking systems that are managed by, or on behalf
59
+ of, the Licensor for the purpose of discussing and improving the Work,
60
+ but excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution".
62
+
63
+ 2. Grant of Copyright License. Subject to the terms and conditions of
64
+ this License, each Contributor hereby grants to You a perpetual,
65
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
66
+ copyright license to use, reproduce, modify, merge, publish,
67
+ distribute, sublicense, and/or sell copies of the Work, and to
68
+ permit persons to whom the Work is furnished to do so, subject to
69
+ the following conditions:
70
+
71
+ The above copyright notice and this permission notice shall be
72
+ included in all copies or substantial portions of the Work.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, trademark, patent,
103
+ attribution and other notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright notice(s) and may provide
125
+ additional or different license terms and conditions for use,
126
+ reproduction, or distribution of Your modifications, or for any such
127
+ Derivative Works as a whole, provided Your use, reproduction, and
128
+ distribution of the Work otherwise complies with the conditions
129
+ stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Support. You may choose to offer, and to
167
+ charge a fee for, warranty, support, indemnity or other liability
168
+ obligations and/or rights consistent with this License. However, in
169
+ accepting such obligations, You may act only on Your own behalf and on
170
+ Your sole responsibility, not on behalf of any other Contributor, and
171
+ only if You agree to indemnify, defend, and hold each Contributor
172
+ harmless for any liability incurred by, or claims asserted against,
173
+ such Contributor by reason of your accepting any such warranty or support.
174
+
175
+ END OF TERMS AND CONDITIONS
176
+
177
+ APPENDIX: How to apply the Apache License to your work.
178
+
179
+ To apply the Apache License to your work, attach the following
180
+ boilerplate notice, with the fields enclosed by brackets "[]"
181
+ replaced with your own identifying information. (Don't include
182
+ the brackets!) The text should be enclosed in the appropriate
183
+ comment syntax for the file format. We also recommend that a
184
+ file or class name and description of purpose be included on the
185
+ same page as the copyright notice for easier identification within
186
+ third-party archives.
187
+
188
+ Copyright [yyyy] [name of copyright owner]
189
+
190
+ Licensed under the Apache License, Version 2.0 (the "License");
191
+ you may not use this file except in compliance with the License.
192
+ You may obtain a copy of the License at
193
+
194
+ http://www.apache.org/licenses/LICENSE-2.0
195
+
196
+ Unless required by applicable law or agreed to in writing, software
197
+ distributed under the License is distributed on an "AS IS" BASIS,
198
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
199
+ See the License for the specific language governing permissions and
200
+ limitations under the License.
src/README.md ADDED
@@ -0,0 +1,514 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags: [gradio-custom-component, HighlightedText]
3
+ title: gradio_markdownlabel
4
+ short_description: A gradio custom component
5
+ colorFrom: blue
6
+ colorTo: yellow
7
+ sdk: gradio
8
+ pinned: false
9
+ app_file: space.py
10
+ ---
11
+
12
+ # `gradio_markdownlabel`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
14
+
15
+ Python library for easily interacting with trained machine learning models
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install gradio_markdownlabel
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+
27
+ import gradio as gr
28
+ from gradio_markdownlabel import MarkdownLabel
29
+
30
+ # Create a comprehensive example with rich markdown content and multiple highlights
31
+ example_data = {
32
+ "markdown_content": """# AI and Machine Learning Research Report
33
+
34
+ ## Introduction
35
+
36
+ This document explores the rapidly evolving field of **artificial intelligence** and its various applications in modern technology. The study focuses on *machine learning* techniques and their impact on different industries.
37
+
38
+ ## Key Technologies
39
+
40
+ ### Deep Learning
41
+ *Deep learning* represents a subset of machine learning that uses **neural networks** with multiple layers. This approach has revolutionized areas such as:
42
+
43
+ - Computer vision
44
+ - Natural language processing
45
+ - Speech recognition
46
+
47
+ ### Natural Language Processing
48
+ **Natural language processing** (NLP) enables computers to understand and interpret human language. Key applications include:
49
+
50
+ 1. Text analysis
51
+ 2. Sentiment analysis
52
+ 3. Language translation
53
+
54
+ ## Applications
55
+
56
+ The integration of **computer vision** in autonomous vehicles has transformed the automotive industry. Similarly, **reinforcement learning** has shown remarkable success in gaming and robotics.
57
+
58
+ ## Conclusion
59
+
60
+ As **artificial intelligence** continues to advance, we expect to see more sophisticated applications of these technologies across various domains.
61
+ """,
62
+ "highlights": [
63
+ {
64
+ "term": "artificial intelligence",
65
+ "title": "Artificial Intelligence (AI)",
66
+ "content": "**Artificial Intelligence** refers to the simulation of human intelligence in machines that are programmed to think and learn like humans. AI systems can perform tasks that typically require human intelligence, such as:\n\n- Visual perception\n- Speech recognition\n- Decision-making\n- Language translation\n\nAI is broadly categorized into:\n1. **Narrow AI** - designed for specific tasks\n2. **General AI** - hypothetical AI with human-level cognitive abilities",
67
+ "category": "Core Technology",
68
+ "color": "#e3f2fd"
69
+ },
70
+ {
71
+ "term": "machine learning",
72
+ "title": "Machine Learning (ML)",
73
+ "content": "**Machine Learning** is a subset of AI that focuses on the development of algorithms that can learn and improve from experience without being explicitly programmed.\n\n### Types of Machine Learning:\n- **Supervised Learning**: Learning with labeled examples\n- **Unsupervised Learning**: Finding patterns in unlabeled data\n- **Reinforcement Learning**: Learning through interaction with environment\n\n### Applications:\n- Recommendation systems\n- Fraud detection\n- Medical diagnosis\n- Autonomous vehicles",
74
+ "category": "Core Technology",
75
+ "color": "#f3e5f5"
76
+ },
77
+ {
78
+ "term": "deep learning",
79
+ "title": "Deep Learning",
80
+ "content": "**Deep Learning** is a specialized subset of machine learning that uses artificial neural networks with multiple layers (deep neural networks) to model and understand complex patterns.\n\n### Key Characteristics:\n- Multiple hidden layers\n- Automatic feature extraction\n- Hierarchical learning\n\n### Popular Architectures:\n- Convolutional Neural Networks (CNNs)\n- Recurrent Neural Networks (RNNs)\n- Transformers\n\n### Applications:\n- Image recognition\n- Natural language processing\n- Speech synthesis",
81
+ "category": "Advanced Technique",
82
+ "color": "#e8f5e8"
83
+ },
84
+ {
85
+ "term": "neural networks",
86
+ "title": "Neural Networks",
87
+ "content": "**Neural Networks** are computing systems inspired by biological neural networks. They consist of interconnected nodes (neurons) that process information.\n\n### Components:\n- **Input Layer**: Receives data\n- **Hidden Layers**: Process information\n- **Output Layer**: Produces results\n\n### Types:\n- Feedforward networks\n- Recurrent networks\n- Convolutional networks\n\nNeural networks learn by adjusting the weights of connections between neurons based on training data.",
88
+ "category": "Architecture",
89
+ "color": "#fff3e0"
90
+ },
91
+ {
92
+ "term": "natural language processing",
93
+ "title": "Natural Language Processing (NLP)",
94
+ "content": "**Natural Language Processing** combines computational linguistics with machine learning to enable computers to understand, interpret, and generate human language.\n\n### Core Tasks:\n- **Tokenization**: Breaking text into words/sentences\n- **Part-of-speech tagging**: Identifying grammatical roles\n- **Named entity recognition**: Identifying people, places, organizations\n- **Sentiment analysis**: Determining emotional tone\n\n### Modern Approaches:\n- Transformer models (BERT, GPT)\n- Attention mechanisms\n- Pre-trained language models",
95
+ "category": "Application Domain",
96
+ "color": "#fce4ec"
97
+ },
98
+ {
99
+ "position": [615, 632],
100
+ "title": "Computer Vision (Position-based)",
101
+ "content": "**Computer Vision** highlighted by character position rather than term matching. This demonstrates precise control over highlighting specific text segments.\n\n### Position-based Benefits:\n- Exact character-level precision\n- No ambiguity with similar terms\n- Works with any text, including special characters\n- Useful for pre-processed documents",
102
+ "category": "Position Highlight",
103
+ "color": "#ffeb3b"
104
+ },
105
+ {
106
+ "term": "computer vision",
107
+ "title": "Computer Vision",
108
+ "content": "**Computer Vision** is a field of AI that enables machines to interpret and understand visual information from the world, mimicking human vision capabilities.\n\n### Key Tasks:\n- **Object Detection**: Locating objects in images\n- **Image Classification**: Categorizing images\n- **Semantic Segmentation**: Pixel-level understanding\n- **Face Recognition**: Identifying individuals\n\n### Applications:\n- Autonomous vehicles\n- Medical imaging\n- Security systems\n- Augmented reality",
109
+ "category": "Application Domain",
110
+ "color": "#e1f5fe"
111
+ },
112
+ {
113
+ "term": "reinforcement learning",
114
+ "title": "Reinforcement Learning (RL)",
115
+ "content": "**Reinforcement Learning** is a type of machine learning where an agent learns to make decisions by performing actions in an environment to maximize cumulative reward.\n\n### Key Concepts:\n- **Agent**: The learner/decision maker\n- **Environment**: The world the agent interacts with\n- **Actions**: Choices available to the agent\n- **Rewards**: Feedback from the environment\n- **Policy**: Strategy for choosing actions\n\n### Famous Applications:\n- Game playing (AlphaGo, OpenAI Five)\n- Robotics control\n- Trading algorithms\n- Resource allocation",
116
+ "category": "Learning Paradigm",
117
+ "color": "#f1f8e9"
118
+ },
119
+ {
120
+ "position": [169, 190],
121
+ "title": "Machine Learning (Position)",
122
+ "content": "This **machine learning** instance is highlighted using position-based highlighting at characters 169-190.\n\n### Position Highlighting Use Cases:\n- Academic paper annotations\n- Legal document markup\n- Code documentation\n- Precise text analysis\n\nPosition-based highlighting ensures exact text selection without ambiguity.",
123
+ "category": "Position Demo",
124
+ "color": "#e8eaf6"
125
+ }
126
+ ]
127
+ }
128
+
129
+ with gr.Blocks(title="Markdown Label Demo") as demo:
130
+ gr.Markdown("# MarkdownLabel Component Demo")
131
+ gr.Markdown("This demo showcases the MarkdownLabel component with **both term-based and position-based** interactive highlighting and detailed side panel.")
132
+
133
+ with gr.Row():
134
+ with gr.Column():
135
+ gr.Markdown("## Full Featured Example")
136
+ gr.Markdown("Includes both term-based (e.g., 'artificial intelligence') and position-based highlighting (yellow highlights).")
137
+ MarkdownLabel(
138
+ value=example_data,
139
+ label="AI Research Report - Mixed Highlighting",
140
+ show_side_panel=True,
141
+ panel_width="350px"
142
+ )
143
+
144
+ with gr.Column():
145
+ gr.Markdown("## Compact View")
146
+ gr.Markdown("Same content without the side panel for a cleaner interface.")
147
+ MarkdownLabel(
148
+ value=example_data,
149
+ label="Compact View",
150
+ show_side_panel=False
151
+ )
152
+
153
+ # Simple position-based example
154
+ simple_example = {
155
+ "markdown_content": "The quick **brown fox** jumps over the lazy dog. This is a simple example.",
156
+ "highlights": [
157
+ {
158
+ "position": [4, 9], # "quick"
159
+ "title": "Quick (Position 4-9)",
160
+ "content": "Highlighted using exact character positions 4-9.",
161
+ "category": "Position Demo",
162
+ "color": "#ffeb3b"
163
+ },
164
+ {
165
+ "term": "brown fox",
166
+ "title": "Brown Fox (Term Match)",
167
+ "content": "Highlighted using term matching.",
168
+ "category": "Term Demo",
169
+ "color": "#e3f2fd"
170
+ },
171
+ {
172
+ "position": [35, 43], # "the lazy"
173
+ "title": "The Lazy (Position 35-43)",
174
+ "content": "Another position-based highlight at characters 35-43.",
175
+ "category": "Position Demo",
176
+ "color": "#f3e5f5"
177
+ }
178
+ ]
179
+ }
180
+
181
+ gr.Markdown("## Position vs Term Highlighting Comparison")
182
+ gr.Markdown("This example shows the difference between position-based (yellow/purple) and term-based (blue) highlighting.")
183
+ MarkdownLabel(
184
+ value=simple_example,
185
+ label="Simple Position vs Term Example",
186
+ show_side_panel=True,
187
+ panel_width="300px"
188
+ )
189
+
190
+ if __name__ == "__main__":
191
+ demo.launch()
192
+
193
+ ```
194
+
195
+ ## `MarkdownLabel`
196
+
197
+ ### Initialization
198
+
199
+ <table>
200
+ <thead>
201
+ <tr>
202
+ <th align="left">name</th>
203
+ <th align="left" style="width: 25%;">type</th>
204
+ <th align="left">default</th>
205
+ <th align="left">description</th>
206
+ </tr>
207
+ </thead>
208
+ <tbody>
209
+ <tr>
210
+ <td align="left"><code>value</code></td>
211
+ <td align="left" style="width: 25%;">
212
+
213
+ ```python
214
+ dict | Callable | None
215
+ ```
216
+
217
+ </td>
218
+ <td align="left"><code>None</code></td>
219
+ <td align="left">Dictionary containing markdown_content and highlights array. If a function is provided, the function will be called each time the app loads to set the initial value of this component.</td>
220
+ </tr>
221
+
222
+ <tr>
223
+ <td align="left"><code>show_side_panel</code></td>
224
+ <td align="left" style="width: 25%;">
225
+
226
+ ```python
227
+ bool
228
+ ```
229
+
230
+ </td>
231
+ <td align="left"><code>True</code></td>
232
+ <td align="left">Whether to show the detailed information side panel.</td>
233
+ </tr>
234
+
235
+ <tr>
236
+ <td align="left"><code>panel_width</code></td>
237
+ <td align="left" style="width: 25%;">
238
+
239
+ ```python
240
+ str
241
+ ```
242
+
243
+ </td>
244
+ <td align="left"><code>"300px"</code></td>
245
+ <td align="left">Width of the side panel (CSS value like "300px", "25%", etc.).</td>
246
+ </tr>
247
+
248
+ <tr>
249
+ <td align="left"><code>edit_mode</code></td>
250
+ <td align="left" style="width: 25%;">
251
+
252
+ ```python
253
+ str
254
+ ```
255
+
256
+ </td>
257
+ <td align="left"><code>"split"</code></td>
258
+ <td align="left">Layout for editing mode - "split" (side-by-side), "tabs", or "overlay".</td>
259
+ </tr>
260
+
261
+ <tr>
262
+ <td align="left"><code>show_preview</code></td>
263
+ <td align="left" style="width: 25%;">
264
+
265
+ ```python
266
+ bool
267
+ ```
268
+
269
+ </td>
270
+ <td align="left"><code>True</code></td>
271
+ <td align="left">Whether to show live preview in edit mode.</td>
272
+ </tr>
273
+
274
+ <tr>
275
+ <td align="left"><code>markdown_editor</code></td>
276
+ <td align="left" style="width: 25%;">
277
+
278
+ ```python
279
+ str
280
+ ```
281
+
282
+ </td>
283
+ <td align="left"><code>"textarea"</code></td>
284
+ <td align="left">Type of markdown editor - "textarea" or "codemirror" (future).</td>
285
+ </tr>
286
+
287
+ <tr>
288
+ <td align="left"><code>label</code></td>
289
+ <td align="left" style="width: 25%;">
290
+
291
+ ```python
292
+ str | I18nData | None
293
+ ```
294
+
295
+ </td>
296
+ <td align="left"><code>None</code></td>
297
+ <td align="left">the label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.</td>
298
+ </tr>
299
+
300
+ <tr>
301
+ <td align="left"><code>every</code></td>
302
+ <td align="left" style="width: 25%;">
303
+
304
+ ```python
305
+ Timer | float | None
306
+ ```
307
+
308
+ </td>
309
+ <td align="left"><code>None</code></td>
310
+ <td align="left">Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.</td>
311
+ </tr>
312
+
313
+ <tr>
314
+ <td align="left"><code>inputs</code></td>
315
+ <td align="left" style="width: 25%;">
316
+
317
+ ```python
318
+ Component | Sequence[Component] | set[Component] | None
319
+ ```
320
+
321
+ </td>
322
+ <td align="left"><code>None</code></td>
323
+ <td align="left">Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.</td>
324
+ </tr>
325
+
326
+ <tr>
327
+ <td align="left"><code>show_label</code></td>
328
+ <td align="left" style="width: 25%;">
329
+
330
+ ```python
331
+ bool | None
332
+ ```
333
+
334
+ </td>
335
+ <td align="left"><code>None</code></td>
336
+ <td align="left">if True, will display label.</td>
337
+ </tr>
338
+
339
+ <tr>
340
+ <td align="left"><code>container</code></td>
341
+ <td align="left" style="width: 25%;">
342
+
343
+ ```python
344
+ bool
345
+ ```
346
+
347
+ </td>
348
+ <td align="left"><code>True</code></td>
349
+ <td align="left">If True, will place the component in a container - providing some extra padding around the border.</td>
350
+ </tr>
351
+
352
+ <tr>
353
+ <td align="left"><code>scale</code></td>
354
+ <td align="left" style="width: 25%;">
355
+
356
+ ```python
357
+ int | None
358
+ ```
359
+
360
+ </td>
361
+ <td align="left"><code>None</code></td>
362
+ <td align="left">relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.</td>
363
+ </tr>
364
+
365
+ <tr>
366
+ <td align="left"><code>min_width</code></td>
367
+ <td align="left" style="width: 25%;">
368
+
369
+ ```python
370
+ int
371
+ ```
372
+
373
+ </td>
374
+ <td align="left"><code>160</code></td>
375
+ <td align="left">minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.</td>
376
+ </tr>
377
+
378
+ <tr>
379
+ <td align="left"><code>visible</code></td>
380
+ <td align="left" style="width: 25%;">
381
+
382
+ ```python
383
+ bool
384
+ ```
385
+
386
+ </td>
387
+ <td align="left"><code>True</code></td>
388
+ <td align="left">If False, component will be hidden.</td>
389
+ </tr>
390
+
391
+ <tr>
392
+ <td align="left"><code>elem_id</code></td>
393
+ <td align="left" style="width: 25%;">
394
+
395
+ ```python
396
+ str | None
397
+ ```
398
+
399
+ </td>
400
+ <td align="left"><code>None</code></td>
401
+ <td align="left">An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
402
+ </tr>
403
+
404
+ <tr>
405
+ <td align="left"><code>elem_classes</code></td>
406
+ <td align="left" style="width: 25%;">
407
+
408
+ ```python
409
+ list[str] | str | None
410
+ ```
411
+
412
+ </td>
413
+ <td align="left"><code>None</code></td>
414
+ <td align="left">An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
415
+ </tr>
416
+
417
+ <tr>
418
+ <td align="left"><code>render</code></td>
419
+ <td align="left" style="width: 25%;">
420
+
421
+ ```python
422
+ bool
423
+ ```
424
+
425
+ </td>
426
+ <td align="left"><code>True</code></td>
427
+ <td align="left">If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.</td>
428
+ </tr>
429
+
430
+ <tr>
431
+ <td align="left"><code>key</code></td>
432
+ <td align="left" style="width: 25%;">
433
+
434
+ ```python
435
+ int | str | tuple[int | str, ...] | None
436
+ ```
437
+
438
+ </td>
439
+ <td align="left"><code>None</code></td>
440
+ <td align="left">in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.</td>
441
+ </tr>
442
+
443
+ <tr>
444
+ <td align="left"><code>preserved_by_key</code></td>
445
+ <td align="left" style="width: 25%;">
446
+
447
+ ```python
448
+ list[str] | str | None
449
+ ```
450
+
451
+ </td>
452
+ <td align="left"><code>"value"</code></td>
453
+ <td align="left">A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.</td>
454
+ </tr>
455
+
456
+ <tr>
457
+ <td align="left"><code>interactive</code></td>
458
+ <td align="left" style="width: 25%;">
459
+
460
+ ```python
461
+ bool | None
462
+ ```
463
+
464
+ </td>
465
+ <td align="left"><code>None</code></td>
466
+ <td align="left">If True, the component will be editable allowing users to modify markdown content.</td>
467
+ </tr>
468
+
469
+ <tr>
470
+ <td align="left"><code>rtl</code></td>
471
+ <td align="left" style="width: 25%;">
472
+
473
+ ```python
474
+ bool
475
+ ```
476
+
477
+ </td>
478
+ <td align="left"><code>False</code></td>
479
+ <td align="left">If True, will display the text in right-to-left direction.</td>
480
+ </tr>
481
+ </tbody></table>
482
+
483
+
484
+ ### Events
485
+
486
+ | name | description |
487
+ |:-----|:------------|
488
+ | `change` | Triggered when the value of the MarkdownLabel changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
489
+ | `select` | Event listener for when the user selects or deselects the MarkdownLabel. Uses event data gradio.SelectData to carry `value` referring to the label of the MarkdownLabel, and `selected` to refer to state of the MarkdownLabel. See EventData documentation on how to use this event data |
490
+ | `edit` | This listener is triggered when the user edits the MarkdownLabel (e.g. image) using the built-in editor. |
491
+ | `submit` | This listener is triggered when the user presses the Enter key while the MarkdownLabel is focused. |
492
+ | `clear` | This listener is triggered when the user clears the MarkdownLabel using the clear button for the component. |
493
+
494
+
495
+
496
+ ### User function
497
+
498
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
499
+
500
+ - When used as an Input, the component only impacts the input signature of the user function.
501
+ - When used as an output, the component only impacts the return signature of the user function.
502
+
503
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
504
+
505
+ - **As output:** Is passed, passes the value as a dictionary with markdown_content and highlights.
506
+ - **As input:** Should return, expects a dictionary with 'markdown_content' and 'highlights' keys.
507
+
508
+ ```python
509
+ def predict(
510
+ value: dict | None
511
+ ) -> dict | None:
512
+ return value
513
+ ```
514
+
src/backend/gradio_markdownlabel/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+
2
+ from .markdownlabel import MarkdownLabel
3
+
4
+ __all__ = ['MarkdownLabel']
src/backend/gradio_markdownlabel/markdownlabel.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """gr.HighlightedText() component."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Sequence
6
+ from typing import TYPE_CHECKING, Any, Union
7
+
8
+ from gradio_client.documentation import document
9
+
10
+ from gradio.components.base import Component
11
+ from gradio.data_classes import GradioModel, GradioRootModel
12
+ from gradio.events import Events
13
+ from gradio.i18n import I18nData
14
+
15
+ if TYPE_CHECKING:
16
+ from gradio.components import Timer
17
+
18
+
19
+ class HighlightDefinition(GradioModel):
20
+ term: str = ""
21
+ position: list[int] = [] # [start, end] character positions
22
+ title: str = ""
23
+ content: str = ""
24
+ category: str = ""
25
+ color: str = ""
26
+
27
+
28
+ class MarkdownData(GradioModel):
29
+ markdown_content: str
30
+ highlights: list[HighlightDefinition] = []
31
+
32
+
33
+ class MarkdownLabelData(GradioRootModel):
34
+ root: MarkdownData
35
+
36
+
37
+ class MarkdownLabel(Component):
38
+ """
39
+ Displays markdown-formatted text with interactive term highlighting and detailed side panel.
40
+
41
+ This component allows for rich markdown content with clickable term highlights that display
42
+ detailed information in a side panel.
43
+ """
44
+
45
+ data_model = MarkdownLabelData
46
+ EVENTS = [Events.change, Events.select, Events.edit, Events.submit, Events.clear]
47
+
48
+ def __init__(
49
+ self,
50
+ value: dict | Callable | None = None,
51
+ *,
52
+ show_side_panel: bool = True,
53
+ panel_width: str = "300px",
54
+ edit_mode: str = "split",
55
+ show_preview: bool = True,
56
+ markdown_editor: str = "textarea",
57
+ label: str | I18nData | None = None,
58
+ every: Timer | float | None = None,
59
+ inputs: Component | Sequence[Component] | set[Component] | None = None,
60
+ show_label: bool | None = None,
61
+ container: bool = True,
62
+ scale: int | None = None,
63
+ min_width: int = 160,
64
+ visible: bool = True,
65
+ elem_id: str | None = None,
66
+ elem_classes: list[str] | str | None = None,
67
+ render: bool = True,
68
+ key: int | str | tuple[int | str, ...] | None = None,
69
+ preserved_by_key: list[str] | str | None = "value",
70
+ interactive: bool | None = None,
71
+ rtl: bool = False,
72
+ ):
73
+ """
74
+ Parameters:
75
+ value: Dictionary containing markdown_content and highlights array. If a function is provided, the function will be called each time the app loads to set the initial value of this component.
76
+ show_side_panel: Whether to show the detailed information side panel.
77
+ panel_width: Width of the side panel (CSS value like "300px", "25%", etc.).
78
+ edit_mode: Layout for editing mode - "split" (side-by-side), "tabs", or "overlay".
79
+ show_preview: Whether to show live preview in edit mode.
80
+ markdown_editor: Type of markdown editor - "textarea" or "codemirror" (future).
81
+ label: the label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.
82
+ every: Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
83
+ inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.
84
+ show_label: if True, will display label.
85
+ container: If True, will place the component in a container - providing some extra padding around the border.
86
+ scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.
87
+ min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
88
+ visible: If False, component will be hidden.
89
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
90
+ elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
91
+ render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
92
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
93
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
94
+ interactive: If True, the component will be editable allowing users to modify markdown content.
95
+ rtl: If True, will display the text in right-to-left direction.
96
+ """
97
+ self.show_side_panel = show_side_panel
98
+ self.panel_width = panel_width
99
+ self.edit_mode = edit_mode
100
+ self.show_preview = show_preview
101
+ self.markdown_editor = markdown_editor
102
+ self.rtl = rtl
103
+ super().__init__(
104
+ label=label,
105
+ every=every,
106
+ inputs=inputs,
107
+ show_label=show_label,
108
+ container=container,
109
+ scale=scale,
110
+ min_width=min_width,
111
+ visible=visible,
112
+ elem_id=elem_id,
113
+ elem_classes=elem_classes,
114
+ render=render,
115
+ key=key,
116
+ preserved_by_key=preserved_by_key,
117
+ value=value,
118
+ interactive=interactive,
119
+ )
120
+ self._value_description = "a dictionary with 'markdown_content' string and 'highlights' array containing term definitions."
121
+
122
+ def example_payload(self) -> Any:
123
+ return {
124
+ "markdown_content": "# Sample Document\n\nThis contains **artificial intelligence** and *machine learning* terms.",
125
+ "highlights": [
126
+ {
127
+ "term": "artificial intelligence",
128
+ "title": "Artificial Intelligence",
129
+ "content": "AI refers to computer systems that can perform tasks typically requiring human intelligence.",
130
+ "category": "technology",
131
+ "color": "#e3f2fd"
132
+ },
133
+ {
134
+ "position": [74, 90],
135
+ "title": "Machine Learning",
136
+ "content": "ML is a subset of AI that focuses on algorithms that learn from data.",
137
+ "category": "technology",
138
+ "color": "#f3e5f5"
139
+ }
140
+ ]
141
+ }
142
+
143
+ def example_value(self) -> Any:
144
+ return {
145
+ "markdown_content": "# Sample Document\n\nThis contains **artificial intelligence** and *machine learning* terms.",
146
+ "highlights": [
147
+ {
148
+ "term": "artificial intelligence",
149
+ "title": "Artificial Intelligence",
150
+ "content": "AI refers to computer systems that can perform tasks typically requiring human intelligence.",
151
+ "category": "technology",
152
+ "color": "#e3f2fd"
153
+ },
154
+ {
155
+ "position": [74, 90],
156
+ "title": "Machine Learning",
157
+ "content": "ML is a subset of AI that focuses on algorithms that learn from data.",
158
+ "category": "technology",
159
+ "color": "#f3e5f5"
160
+ }
161
+ ]
162
+ }
163
+
164
+ def preprocess(
165
+ self, payload: MarkdownLabelData | None
166
+ ) -> dict | None:
167
+ """
168
+ Parameters:
169
+ payload: An instance of MarkdownLabelData
170
+ Returns:
171
+ Passes the value as a dictionary with markdown_content and highlights.
172
+ """
173
+ if payload is None:
174
+ return None
175
+ return payload.root.model_dump()
176
+
177
+ def postprocess(
178
+ self, value: dict | None
179
+ ) -> MarkdownLabelData | None:
180
+ """
181
+ Parameters:
182
+ value: Expects a dictionary with 'markdown_content' and 'highlights' keys
183
+ Returns:
184
+ An instance of MarkdownLabelData
185
+ """
186
+ if value is None:
187
+ return None
188
+
189
+ if not isinstance(value, dict):
190
+ raise ValueError(
191
+ "Expected a dictionary with keys 'markdown_content' and 'highlights' "
192
+ "for the value of the MarkdownLabel component."
193
+ )
194
+
195
+ # Ensure required keys exist
196
+ markdown_content = value.get("markdown_content", "")
197
+ highlights = value.get("highlights", [])
198
+
199
+ # Validate highlights structure
200
+ processed_highlights = []
201
+ for highlight in highlights:
202
+ if isinstance(highlight, dict):
203
+ processed_highlights.append(HighlightDefinition(
204
+ term=highlight.get("term", ""),
205
+ position=highlight.get("position", []),
206
+ title=highlight.get("title", ""),
207
+ content=highlight.get("content", ""),
208
+ category=highlight.get("category", ""),
209
+ color=highlight.get("color", "")
210
+ ))
211
+
212
+ markdown_data = MarkdownData(
213
+ markdown_content=markdown_content,
214
+ highlights=processed_highlights
215
+ )
216
+
217
+ return MarkdownLabelData(root=markdown_data)
src/backend/gradio_markdownlabel/templates/component/index.js ADDED
The diff for this file is too large to render. See raw diff
 
src/backend/gradio_markdownlabel/templates/component/style.css ADDED
@@ -0,0 +1 @@
 
 
1
+ .markdown-container.svelte-lwvfz.svelte-lwvfz{display:flex;height:100%;position:relative;transition:all .3s ease}.markdown-content.svelte-lwvfz.svelte-lwvfz{flex:1;padding:var(--block-padding);overflow-y:auto;transition:margin-right .3s ease}.with-panel.svelte-lwvfz .markdown-content.svelte-lwvfz{margin-right:var(--spacing-md)}.side-panel.svelte-lwvfz.svelte-lwvfz{position:fixed;top:0;right:0;height:100vh;background:var(--background-fill-primary);border-left:1px solid var(--border-color-primary);box-shadow:-2px 0 10px #0000001a;z-index:1000;overflow-y:auto;transform:translate(0);transition:transform .3s ease}.panel-header.svelte-lwvfz.svelte-lwvfz{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4);border-bottom:1px solid var(--border-color-primary);background:var(--background-fill-secondary)}.panel-header.svelte-lwvfz h3.svelte-lwvfz{margin:0;font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--body-text-color)}.close-btn.svelte-lwvfz.svelte-lwvfz{background:none;border:none;font-size:var(--text-xl);cursor:pointer;color:var(--body-text-color);padding:var(--size-1);border-radius:var(--radius-sm);transition:background-color .2s}.close-btn.svelte-lwvfz.svelte-lwvfz:hover{background-color:var(--background-fill-primary)}.panel-content.svelte-lwvfz.svelte-lwvfz{padding:var(--size-4)}.category-badge.svelte-lwvfz.svelte-lwvfz{display:inline-block;padding:var(--size-1) var(--size-2);border-radius:var(--radius-full);font-size:var(--text-sm);font-weight:var(--weight-medium);margin-bottom:var(--size-3);color:var(--body-text-color)}.content-text.svelte-lwvfz.svelte-lwvfz{line-height:1.6;color:var(--body-text-color)}.markdown-content.svelte-lwvfz h1{font-size:var(--text-2xl);font-weight:var(--weight-bold);margin:var(--size-4) 0 var(--size-2) 0;color:var(--body-text-color)}.markdown-content.svelte-lwvfz h2{font-size:var(--text-xl);font-weight:var(--weight-semibold);margin:var(--size-3) 0 var(--size-2) 0;color:var(--body-text-color)}.markdown-content.svelte-lwvfz h3{font-size:var(--text-lg);font-weight:var(--weight-medium);margin:var(--size-3) 0 var(--size-1) 0;color:var(--body-text-color)}.markdown-content.svelte-lwvfz p{margin:var(--size-2) 0;line-height:1.6;color:var(--body-text-color)}.markdown-content.svelte-lwvfz strong{font-weight:var(--weight-bold)}.markdown-content.svelte-lwvfz em{font-style:italic}.markdown-content.svelte-lwvfz ul,.markdown-content.svelte-lwvfz ol{margin:var(--size-2) 0;padding-left:var(--size-4);color:var(--body-text-color)}.markdown-content.svelte-lwvfz li{margin:var(--size-1) 0}.markdown-content.svelte-lwvfz code{background-color:var(--background-fill-secondary);padding:var(--size-1);border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:var(--text-sm)}.markdown-content.svelte-lwvfz pre{background-color:var(--background-fill-secondary);padding:var(--size-3);border-radius:var(--radius-md);overflow-x:auto;margin:var(--size-2) 0}.markdown-content.svelte-lwvfz pre code{background:none;padding:0}.markdown-content.svelte-lwvfz .highlight-term:hover,.markdown-content.svelte-lwvfz .highlight-position:hover,.markdown-content.svelte-lwvfz .highlight-term:focus,.markdown-content.svelte-lwvfz .highlight-position:focus{opacity:.8;transform:scale(1.02);outline:2px solid var(--color-accent);outline-offset:1px}@media (max-width: 768px){.side-panel.svelte-lwvfz.svelte-lwvfz{width:100%!important}.with-panel.svelte-lwvfz .markdown-content.svelte-lwvfz{margin-right:0}}.markdown-container.svelte-4pjpqm.svelte-4pjpqm{display:flex;flex-direction:column;height:100%;position:relative;transition:all .3s ease}.edit-controls.svelte-4pjpqm.svelte-4pjpqm{display:flex;gap:var(--spacing-sm);padding:var(--spacing-sm);background:var(--background-fill-secondary);border-bottom:1px solid var(--border-color-primary);align-items:center}.edit-btn.svelte-4pjpqm.svelte-4pjpqm,.save-btn.svelte-4pjpqm.svelte-4pjpqm,.cancel-btn.svelte-4pjpqm.svelte-4pjpqm{padding:var(--size-1) var(--size-2);border:1px solid var(--border-color-primary);border-radius:var(--radius-sm);background:var(--background-fill-primary);cursor:pointer;font-size:var(--text-sm);transition:all .2s}.edit-btn.svelte-4pjpqm.svelte-4pjpqm:hover{background:var(--background-fill-secondary)}.save-btn.svelte-4pjpqm.svelte-4pjpqm{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}.save-btn.svelte-4pjpqm.svelte-4pjpqm:hover{opacity:.9}.cancel-btn.svelte-4pjpqm.svelte-4pjpqm{background:var(--color-red-500);color:#fff;border-color:var(--color-red-500)}.cancel-btn.svelte-4pjpqm.svelte-4pjpqm:hover{opacity:.9}.tab-controls.svelte-4pjpqm.svelte-4pjpqm{display:flex;gap:2px;margin-left:auto}.tab-btn.svelte-4pjpqm.svelte-4pjpqm{padding:var(--size-1) var(--size-3);border:1px solid var(--border-color-primary);background:var(--background-fill-primary);cursor:pointer;font-size:var(--text-sm);border-radius:var(--radius-sm) var(--radius-sm) 0 0;transition:all .2s}.tab-btn.active.svelte-4pjpqm.svelte-4pjpqm{background:var(--background-fill-secondary);border-bottom-color:var(--background-fill-secondary)}.tab-btn.svelte-4pjpqm.svelte-4pjpqm:hover:not(.active){background:var(--background-fill-secondary);opacity:.7}.content-area.svelte-4pjpqm.svelte-4pjpqm{flex:1;display:flex;overflow:hidden}.content-area.split-mode.svelte-4pjpqm.svelte-4pjpqm{flex-direction:row}.editor-section.svelte-4pjpqm.svelte-4pjpqm,.preview-section.svelte-4pjpqm.svelte-4pjpqm{flex:1;display:flex;flex-direction:column;border-right:1px solid var(--border-color-primary)}.preview-section.svelte-4pjpqm.svelte-4pjpqm{border-right:none;border-left:1px solid var(--border-color-primary)}.editor-header.svelte-4pjpqm.svelte-4pjpqm,.preview-header.svelte-4pjpqm.svelte-4pjpqm{padding:var(--size-2);background:var(--background-fill-secondary);border-bottom:1px solid var(--border-color-primary)}.editor-header.svelte-4pjpqm h4.svelte-4pjpqm,.preview-header.svelte-4pjpqm h4.svelte-4pjpqm{margin:0;font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--body-text-color)}.markdown-editor.svelte-4pjpqm.svelte-4pjpqm{flex:1;width:100%;padding:var(--size-3);border:none;resize:none;font-family:var(--font-mono);font-size:var(--text-sm);line-height:1.5;background:var(--background-fill-primary);color:var(--body-text-color);outline:none}.markdown-editor.fullwidth.svelte-4pjpqm.svelte-4pjpqm{height:400px}.tab-content.svelte-4pjpqm.svelte-4pjpqm{flex:1;display:flex;flex-direction:column}.markdown-content.svelte-4pjpqm.svelte-4pjpqm{flex:1;padding:var(--block-padding);overflow-y:auto;transition:margin-right .3s ease}.markdown-content.preview.svelte-4pjpqm.svelte-4pjpqm{background:var(--background-fill-primary)}.with-panel.svelte-4pjpqm .markdown-content.svelte-4pjpqm{margin-right:var(--spacing-md)}.side-panel.svelte-4pjpqm.svelte-4pjpqm{position:fixed;top:0;right:0;height:100vh;background:var(--background-fill-primary);border-left:1px solid var(--border-color-primary);box-shadow:-2px 0 10px #0000001a;z-index:1000;overflow-y:auto;transform:translate(0);transition:transform .3s ease}.panel-header.svelte-4pjpqm.svelte-4pjpqm{display:flex;justify-content:space-between;align-items:center;padding:var(--size-4);border-bottom:1px solid var(--border-color-primary);background:var(--background-fill-secondary)}.panel-header.svelte-4pjpqm h3.svelte-4pjpqm{margin:0;font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--body-text-color)}.close-btn.svelte-4pjpqm.svelte-4pjpqm{background:none;border:none;font-size:var(--text-xl);cursor:pointer;color:var(--body-text-color);padding:var(--size-1);border-radius:var(--radius-sm);transition:background-color .2s}.close-btn.svelte-4pjpqm.svelte-4pjpqm:hover{background-color:var(--background-fill-primary)}.panel-content.svelte-4pjpqm.svelte-4pjpqm{padding:var(--size-4)}.category-badge.svelte-4pjpqm.svelte-4pjpqm{display:inline-block;padding:var(--size-1) var(--size-2);border-radius:var(--radius-full);font-size:var(--text-sm);font-weight:var(--weight-medium);margin-bottom:var(--size-3);color:var(--body-text-color)}.content-text.svelte-4pjpqm.svelte-4pjpqm{line-height:1.6;color:var(--body-text-color)}.markdown-content.svelte-4pjpqm h1{font-size:var(--text-2xl);font-weight:var(--weight-bold);margin:var(--size-4) 0 var(--size-2) 0;color:var(--body-text-color)}.markdown-content.svelte-4pjpqm h2{font-size:var(--text-xl);font-weight:var(--weight-semibold);margin:var(--size-3) 0 var(--size-2) 0;color:var(--body-text-color)}.markdown-content.svelte-4pjpqm h3{font-size:var(--text-lg);font-weight:var(--weight-medium);margin:var(--size-3) 0 var(--size-1) 0;color:var(--body-text-color)}.markdown-content.svelte-4pjpqm p{margin:var(--size-2) 0;line-height:1.6;color:var(--body-text-color)}.markdown-content.svelte-4pjpqm strong{font-weight:var(--weight-bold)}.markdown-content.svelte-4pjpqm em{font-style:italic}.markdown-content.svelte-4pjpqm ul,.markdown-content.svelte-4pjpqm ol{margin:var(--size-2) 0;padding-left:var(--size-4);color:var(--body-text-color)}.markdown-content.svelte-4pjpqm li{margin:var(--size-1) 0}.markdown-content.svelte-4pjpqm code{background-color:var(--background-fill-secondary);padding:var(--size-1);border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:var(--text-sm)}.markdown-content.svelte-4pjpqm pre{background-color:var(--background-fill-secondary);padding:var(--size-3);border-radius:var(--radius-md);overflow-x:auto;margin:var(--size-2) 0}.markdown-content.svelte-4pjpqm pre code{background:none;padding:0}.markdown-content.svelte-4pjpqm .highlight-term:hover,.markdown-content.svelte-4pjpqm .highlight-position:hover,.markdown-content.svelte-4pjpqm .highlight-term:focus,.markdown-content.svelte-4pjpqm .highlight-position:focus{opacity:.8;transform:scale(1.02);outline:2px solid var(--color-accent);outline-offset:1px}@media (max-width: 768px){.side-panel.svelte-4pjpqm.svelte-4pjpqm{width:100%!important}.with-panel.svelte-4pjpqm .markdown-content.svelte-4pjpqm{margin-right:0}.content-area.split-mode.svelte-4pjpqm.svelte-4pjpqm{flex-direction:column}.editor-section.svelte-4pjpqm.svelte-4pjpqm,.preview-section.svelte-4pjpqm.svelte-4pjpqm{border-right:none;border-bottom:1px solid var(--border-color-primary)}.preview-section.svelte-4pjpqm.svelte-4pjpqm{border-left:none;border-top:1px solid var(--border-color-primary)}}.block.svelte-239wnu{position:relative;margin:0;box-shadow:var(--block-shadow);border-width:var(--block-border-width);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);width:100%;line-height:var(--line-sm)}.block.fullscreen.svelte-239wnu{border-radius:0}.auto-margin.svelte-239wnu{margin-left:auto;margin-right:auto}.block.border_focus.svelte-239wnu{border-color:var(--color-accent)}.block.border_contrast.svelte-239wnu{border-color:var(--body-text-color)}.padded.svelte-239wnu{padding:var(--block-padding)}.hidden.svelte-239wnu{display:none}.flex.svelte-239wnu{display:flex;flex-direction:column}.hide-container.svelte-239wnu:not(.fullscreen){margin:0;box-shadow:none;--block-border-width:0;background:transparent;padding:0;overflow:visible}.resize-handle.svelte-239wnu{position:absolute;bottom:0;right:0;width:10px;height:10px;fill:var(--block-border-color);cursor:nwse-resize}.fullscreen.svelte-239wnu{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:1000;overflow:auto}.animating.svelte-239wnu{animation:svelte-239wnu-pop-out .1s ease-out forwards}@keyframes svelte-239wnu-pop-out{0%{position:fixed;top:var(--start-top);left:var(--start-left);width:var(--start-width);height:var(--start-height);z-index:100}to{position:fixed;top:0vh;left:0vw;width:100vw;height:100vh;z-index:1000}}.placeholder.svelte-239wnu{border-radius:var(--block-radius);border-width:var(--block-border-width);border-color:var(--block-border-color);border-style:dashed}Tables */ table,tr,td,th{margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);padding:var(--spacing-xl)}.md code,.md pre{background:none;font-family:var(--font-mono);font-size:var(--text-sm);text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:2;tab-size:2;-webkit-hyphens:none;hyphens:none}.md pre[class*=language-]::selection,.md pre[class*=language-] ::selection,.md code[class*=language-]::selection,.md code[class*=language-] ::selection{text-shadow:none;background:#b3d4fc}.md pre{padding:1em;margin:.5em 0;overflow:auto;position:relative;margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);box-shadow:none;border:none;border-radius:var(--radius-md);background:var(--code-background-fill);padding:var(--spacing-xxl);font-family:var(--font-mono);text-shadow:none;border-radius:var(--radius-sm);white-space:nowrap;display:block;white-space:pre}.md :not(pre)>code{padding:.1em;border-radius:var(--radius-xs);white-space:normal;background:var(--code-background-fill);border:1px solid var(--panel-border-color);padding:var(--spacing-xxs) var(--spacing-xs)}.md .token.comment,.md .token.prolog,.md .token.doctype,.md .token.cdata{color:#708090}.md .token.punctuation{color:#999}.md .token.namespace{opacity:.7}.md .token.property,.md .token.tag,.md .token.boolean,.md .token.number,.md .token.constant,.md .token.symbol,.md .token.deleted{color:#905}.md .token.selector,.md .token.attr-name,.md .token.string,.md .token.char,.md .token.builtin,.md .token.inserted{color:#690}.md .token.atrule,.md .token.attr-value,.md .token.keyword{color:#07a}.md .token.function,.md .token.class-name{color:#dd4a68}.md .token.regex,.md .token.important,.md .token.variable{color:#e90}.md .token.important,.md .token.bold{font-weight:700}.md .token.italic{font-style:italic}.md .token.entity{cursor:help}.dark .md .token.comment,.dark .md .token.prolog,.dark .md .token.cdata{color:#5c6370}.dark .md .token.doctype,.dark .md .token.punctuation,.dark .md .token.entity{color:#abb2bf}.dark .md .token.attr-name,.dark .md .token.class-name,.dark .md .token.boolean,.dark .md .token.constant,.dark .md .token.number,.dark .md .token.atrule{color:#d19a66}.dark .md .token.keyword{color:#c678dd}.dark .md .token.property,.dark .md .token.tag,.dark .md .token.symbol,.dark .md .token.deleted,.dark .md .token.important{color:#e06c75}.dark .md .token.selector,.dark .md .token.string,.dark .md .token.char,.dark .md .token.builtin,.dark .md .token.inserted,.dark .md .token.regex,.dark .md .token.attr-value,.dark .md .token.attr-value>.token.punctuation{color:#98c379}.dark .md .token.variable,.dark .md .token.operator,.dark .md .token.function{color:#61afef}.dark .md .token.url{color:#56b6c2}span.svelte-1m32c2s div[class*=code_wrap]{position:relative}span.svelte-1m32c2s span.katex{font-size:var(--text-lg);direction:ltr}span.svelte-1m32c2s div[class*=code_wrap]>button{z-index:1;cursor:pointer;border-bottom-left-radius:var(--radius-sm);padding:var(--spacing-md);width:25px;height:25px;position:absolute;right:0}span.svelte-1m32c2s .check{opacity:0;z-index:var(--layer-top);transition:opacity .2s;background:var(--code-background-fill);color:var(--body-text-color);position:absolute;top:var(--size-1-5);left:var(--size-1-5)}span.svelte-1m32c2s p:not(:first-child){margin-top:var(--spacing-xxl)}span.svelte-1m32c2s .md-header-anchor{margin-left:-25px;padding-right:8px;line-height:1;color:var(--body-text-color-subdued);opacity:0}span.svelte-1m32c2s h1:hover .md-header-anchor,span.svelte-1m32c2s h2:hover .md-header-anchor,span.svelte-1m32c2s h3:hover .md-header-anchor,span.svelte-1m32c2s h4:hover .md-header-anchor,span.svelte-1m32c2s h5:hover .md-header-anchor,span.svelte-1m32c2s h6:hover .md-header-anchor{opacity:1}span.md.svelte-1m32c2s .md-header-anchor>svg{color:var(--body-text-color-subdued)}span.svelte-1m32c2s table{word-break:break-word}div.svelte-17qq50w>.md.prose{font-weight:var(--block-info-text-weight);font-size:var(--block-info-text-size);line-height:var(--line-sm)}div.svelte-17qq50w>.md.prose *{color:var(--block-info-text-color)}div.svelte-17qq50w{margin-bottom:var(--spacing-md)}span.has-info.svelte-zgrq3{margin-bottom:var(--spacing-xs)}span.svelte-zgrq3:not(.has-info){margin-bottom:var(--spacing-lg)}span.svelte-zgrq3{display:inline-block;position:relative;z-index:var(--layer-4);border:solid var(--block-title-border-width) var(--block-title-border-color);border-radius:var(--block-title-radius);background:var(--block-title-background-fill);padding:var(--block-title-padding);color:var(--block-title-text-color);font-weight:var(--block-title-text-weight);font-size:var(--block-title-text-size);line-height:var(--line-sm)}span[dir=rtl].svelte-zgrq3{display:block}.hide.svelte-zgrq3{margin:0;height:0}label.svelte-13ao5pu.svelte-13ao5pu{display:inline-flex;align-items:center;z-index:var(--layer-2);box-shadow:var(--block-label-shadow);border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-left:none;border-radius:var(--block-label-radius);background:var(--block-label-background-fill);padding:var(--block-label-padding);pointer-events:none;color:var(--block-label-text-color);font-weight:var(--block-label-text-weight);font-size:var(--block-label-text-size);line-height:var(--line-sm)}.gr-group label.svelte-13ao5pu.svelte-13ao5pu{border-top-left-radius:0}label.float.svelte-13ao5pu.svelte-13ao5pu{position:absolute;top:var(--block-label-margin);left:var(--block-label-margin)}label.svelte-13ao5pu.svelte-13ao5pu:not(.float){position:static;margin-top:var(--block-label-margin);margin-left:var(--block-label-margin)}.hide.svelte-13ao5pu.svelte-13ao5pu{height:0}span.svelte-13ao5pu.svelte-13ao5pu{opacity:.8;margin-right:var(--size-2);width:calc(var(--block-label-text-size) - 1px);height:calc(var(--block-label-text-size) - 1px)}.hide-label.svelte-13ao5pu.svelte-13ao5pu{box-shadow:none;border-width:0;background:transparent;overflow:visible}label[dir=rtl].svelte-13ao5pu.svelte-13ao5pu{border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-right:none;border-bottom-left-radius:var(--block-radius);border-bottom-right-radius:var(--block-label-radius);border-top-left-radius:var(--block-label-radius)}label[dir=rtl].svelte-13ao5pu span.svelte-13ao5pu{margin-left:var(--size-2);margin-right:0}button.svelte-qgco6m{display:flex;justify-content:center;align-items:center;gap:1px;z-index:var(--layer-2);border-radius:var(--radius-xs);color:var(--block-label-text-color);border:1px solid transparent;padding:var(--spacing-xxs)}button.svelte-qgco6m:hover{background-color:var(--background-fill-secondary)}button[disabled].svelte-qgco6m{opacity:.5;box-shadow:none}button[disabled].svelte-qgco6m:hover{cursor:not-allowed}.padded.svelte-qgco6m{background:var(--bg-color)}button.svelte-qgco6m:hover,button.highlight.svelte-qgco6m{cursor:pointer;color:var(--color-accent)}.padded.svelte-qgco6m:hover{color:var(--block-label-text-color)}span.svelte-qgco6m{padding:0 1px;font-size:10px}div.svelte-qgco6m{display:flex;align-items:center;justify-content:center;transition:filter .2s ease-in-out}.x-small.svelte-qgco6m{width:10px;height:10px}.small.svelte-qgco6m{width:14px;height:14px}.medium.svelte-qgco6m{width:20px;height:20px}.large.svelte-qgco6m{width:22px;height:22px}.pending.svelte-qgco6m{animation:svelte-qgco6m-flash .5s infinite}@keyframes svelte-qgco6m-flash{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.transparent.svelte-qgco6m{background:transparent;border:none;box-shadow:none}.empty.svelte-3w3rth{display:flex;justify-content:center;align-items:center;margin-top:calc(0px - var(--size-6));height:var(--size-full)}.icon.svelte-3w3rth{opacity:.5;height:var(--size-5);color:var(--body-text-color)}.small.svelte-3w3rth{min-height:calc(var(--size-32) - 20px)}.large.svelte-3w3rth{min-height:calc(var(--size-64) - 20px)}.unpadded_box.svelte-3w3rth{margin-top:0}.small_parent.svelte-3w3rth{min-height:100%!important}.dropdown-arrow.svelte-145leq6,.dropdown-arrow.svelte-ihhdbf{fill:currentColor}.circle.svelte-ihhdbf{fill:currentColor;opacity:.1}svg.svelte-pb9pol{animation:svelte-pb9pol-spin 1.5s linear infinite}@keyframes svelte-pb9pol-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}h2.svelte-1xg7h5n{font-size:var(--text-xl)!important}p.svelte-1xg7h5n,h2.svelte-1xg7h5n{white-space:pre-line}.wrap.svelte-1xg7h5n{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);line-height:var(--line-md);height:100%;padding-top:var(--size-3);text-align:center;margin:auto var(--spacing-lg)}.or.svelte-1xg7h5n{color:var(--body-text-color-subdued);display:flex}.icon-wrap.svelte-1xg7h5n{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-1xg7h5n{font-size:var(--text-lg)}}.hovered.svelte-1xg7h5n{color:var(--color-accent)}div.svelte-q32hvf{border-top:1px solid transparent;display:flex;max-height:100%;justify-content:center;align-items:center;gap:var(--spacing-sm);height:auto;align-items:flex-end;color:var(--block-label-text-color);flex-shrink:0}.show_border.svelte-q32hvf{border-top:1px solid var(--block-border-color);margin-top:var(--spacing-xxl);box-shadow:var(--shadow-drop)}.source-selection.svelte-15ls1gu{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:100%;margin-left:auto;margin-right:auto;height:var(--size-10)}.icon.svelte-15ls1gu{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.selected.svelte-15ls1gu{color:var(--color-accent)}.icon.svelte-15ls1gu:hover,.icon.svelte-15ls1gu:focus{color:var(--color-accent)}.icon-button-wrapper.svelte-109se4{display:flex;flex-direction:row;align-items:center;justify-content:center;z-index:var(--layer-3);gap:var(--spacing-sm);box-shadow:var(--shadow-drop);border:1px solid var(--border-color-primary);background:var(--block-background-fill);padding:var(--spacing-xxs)}.icon-button-wrapper.hide-top-corner.svelte-109se4{border-top:none;border-right:none;border-radius:var(--block-label-right-radius)}.icon-button-wrapper.display-top-corner.svelte-109se4{border-radius:var(--radius-sm) 0 0 var(--radius-sm);top:var(--spacing-sm);right:-1px}.icon-button-wrapper.svelte-109se4:not(.top-panel){border:1px solid var(--border-color-primary);border-radius:var(--radius-sm)}.top-panel.svelte-109se4{position:absolute;top:var(--block-label-margin);right:var(--block-label-margin);margin:0}.icon-button-wrapper.svelte-109se4 button{margin:var(--spacing-xxs);border-radius:var(--radius-xs);position:relative}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child),.icon-button-wrapper.svelte-109se4 button:not(:last-child){margin-right:var(--spacing-xxs)}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child):not(.no-border *):after,.icon-button-wrapper.svelte-109se4 button:not(:last-child):not(.no-border *):after{content:"";position:absolute;right:-4.5px;top:15%;height:70%;width:1px;background-color:var(--border-color-primary)}.icon-button-wrapper.svelte-109se4>*{height:100%}svg.svelte-43sxxs.svelte-43sxxs{width:var(--size-20);height:var(--size-20)}svg.svelte-43sxxs path.svelte-43sxxs{fill:var(--loader-color)}div.svelte-43sxxs.svelte-43sxxs{z-index:var(--layer-2)}.margin.svelte-43sxxs.svelte-43sxxs{margin:var(--size-4)}.wrap.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-2);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden}.wrap.center.svelte-17v219f.svelte-17v219f{top:0;right:0;left:0}.wrap.default.svelte-17v219f.svelte-17v219f{top:0;right:0;bottom:0;left:0}.hide.svelte-17v219f.svelte-17v219f{opacity:0;pointer-events:none}.generating.svelte-17v219f.svelte-17v219f{animation:svelte-17v219f-pulseStart 1s cubic-bezier(.4,0,.6,1),svelte-17v219f-pulse 2s cubic-bezier(.4,0,.6,1) 1s infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1);pointer-events:none}.translucent.svelte-17v219f.svelte-17v219f{background:none}@keyframes svelte-17v219f-pulseStart{0%{opacity:0}to{opacity:1}}@keyframes svelte-17v219f-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-17v219f.svelte-17v219f{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-17v219f.svelte-17v219f{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-17v219f.svelte-17v219f{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-17v219f.svelte-17v219f{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-17v219f.svelte-17v219f{position:absolute;bottom:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-17v219f.svelte-17v219f{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-17v219f.svelte-17v219f{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-17v219f.svelte-17v219f{pointer-events:none}.minimal.svelte-17v219f .progress-text.svelte-17v219f{background:var(--block-background-fill)}.border.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary)}.clear-status.svelte-17v219f.svelte-17v219f{position:absolute;display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.toast-body.svelte-1pgj5gs{display:flex;position:relative;right:0;left:0;align-items:center;margin:var(--size-6) var(--size-4);margin:auto;border-radius:var(--container-radius);overflow:hidden;pointer-events:auto}.toast-body.error.svelte-1pgj5gs{border:1px solid var(--color-red-700);background:var(--color-red-50)}.dark .toast-body.error.svelte-1pgj5gs{border:1px solid var(--color-red-500);background-color:var(--color-grey-950)}.toast-body.warning.svelte-1pgj5gs{border:1px solid var(--color-yellow-700);background:var(--color-yellow-50)}.dark .toast-body.warning.svelte-1pgj5gs{border:1px solid var(--color-yellow-500);background-color:var(--color-grey-950)}.toast-body.info.svelte-1pgj5gs{border:1px solid var(--color-grey-700);background:var (--color-grey-50)}.dark .toast-body.info.svelte-1pgj5gs{border:1px solid var(--color-grey-500);background-color:var(--color-grey-950)}.toast-body.success.svelte-1pgj5gs{border:1px solid var(--color-green-700);background:var(--color-green-50)}.dark .toast-body.success.svelte-1pgj5gs{border:1px solid var(--color-green-500);background-color:var(--color-grey-950)}.toast-title.svelte-1pgj5gs{display:flex;align-items:center;font-weight:var(--weight-bold);font-size:var(--text-lg);line-height:var(--line-sm)}.toast-title.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-title.error.svelte-1pgj5gs{color:var(--color-red-50)}.toast-title.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-title.warning.svelte-1pgj5gs{color:var(--color-yellow-50)}.toast-title.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-title.info.svelte-1pgj5gs{color:var(--color-grey-50)}.toast-title.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-title.success.svelte-1pgj5gs{color:var(--color-green-50)}.toast-close.svelte-1pgj5gs{margin:0 var(--size-3);border-radius:var(--size-3);padding:0px var(--size-1-5);font-size:var(--size-5);line-height:var(--size-5)}.toast-close.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-close.error.svelte-1pgj5gs{color:var(--color-red-500)}.toast-close.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-close.warning.svelte-1pgj5gs{color:var(--color-yellow-500)}.toast-close.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-close.info.svelte-1pgj5gs{color:var(--color-grey-500)}.toast-close.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-close.success.svelte-1pgj5gs{color:var(--color-green-500)}.toast-text.svelte-1pgj5gs{font-size:var(--text-lg);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word}.toast-text.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-text.error.svelte-1pgj5gs{color:var(--color-red-50)}.toast-text.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-text.warning.svelte-1pgj5gs{color:var(--color-yellow-50)}.toast-text.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-text.info.svelte-1pgj5gs{color:var(--color-grey-50)}.toast-text.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-text.success.svelte-1pgj5gs{color:var(--color-green-50)}.toast-details.svelte-1pgj5gs{margin:var(--size-3) var(--size-3) var(--size-3) 0;width:100%}.toast-icon.svelte-1pgj5gs{display:flex;position:absolute;position:relative;flex-shrink:0;justify-content:center;align-items:center;margin:var(--size-2);border-radius:var(--radius-full);padding:var(--size-1);padding-left:calc(var(--size-1) - 1px);width:35px;height:35px}.toast-icon.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-icon.error.svelte-1pgj5gs{color:var(--color-red-500)}.toast-icon.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-icon.warning.svelte-1pgj5gs{color:var(--color-yellow-500)}.toast-icon.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-icon.info.svelte-1pgj5gs{color:var(--color-grey-500)}.toast-icon.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-icon.success.svelte-1pgj5gs{color:var(--color-green-500)}@keyframes svelte-1pgj5gs-countdown{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.timer.svelte-1pgj5gs{position:absolute;bottom:0;left:0;transform-origin:0 0;animation:svelte-1pgj5gs-countdown 10s linear forwards;width:100%;height:var(--size-1)}.timer.error.svelte-1pgj5gs{background:var(--color-red-700)}.dark .timer.error.svelte-1pgj5gs{background:var(--color-red-500)}.timer.warning.svelte-1pgj5gs{background:var(--color-yellow-700)}.dark .timer.warning.svelte-1pgj5gs{background:var(--color-yellow-500)}.timer.info.svelte-1pgj5gs{background:var(--color-grey-700)}.dark .timer.info.svelte-1pgj5gs{background:var(--color-grey-500)}.timer.success.svelte-1pgj5gs{background:var(--color-green-700)}.dark .timer.success.svelte-1pgj5gs{background:var(--color-green-500)}.hidden.svelte-1pgj5gs{display:none}.toast-text.svelte-1pgj5gs a{text-decoration:underline}.toast-wrap.svelte-gatr8h{display:flex;position:fixed;top:var(--size-4);right:var(--size-4);flex-direction:column;align-items:end;gap:var(--size-2);z-index:var(--layer-top);width:calc(100% - var(--size-8))}@media (--screen-sm){.toast-wrap.svelte-gatr8h{width:calc(var(--size-96) + var(--size-10))}}.streaming-bar.svelte-ga0jj6{position:absolute;bottom:0;left:0;right:0;height:4px;background-color:var(--primary-600);animation:svelte-ga0jj6-countdown linear forwards;z-index:1}@keyframes svelte-ga0jj6-countdown{0%{transform:translate(0)}to{transform:translate(-100%)}}
src/demo/__init__.py ADDED
File without changes
src/demo/app.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from gradio_markdownlabel import MarkdownLabel
4
+
5
+ # Create a comprehensive example with rich markdown content and multiple highlights
6
+ example_data = {
7
+ "markdown_content": """# AI and Machine Learning Research Report
8
+
9
+ ## Introduction
10
+
11
+ This document explores the rapidly evolving field of **artificial intelligence** and its various applications in modern technology. The study focuses on *machine learning* techniques and their impact on different industries.
12
+
13
+ ## Key Technologies
14
+
15
+ ### Deep Learning
16
+ *Deep learning* represents a subset of machine learning that uses **neural networks** with multiple layers. This approach has revolutionized areas such as:
17
+
18
+ - Computer vision
19
+ - Natural language processing
20
+ - Speech recognition
21
+
22
+ ### Natural Language Processing
23
+ **Natural language processing** (NLP) enables computers to understand and interpret human language. Key applications include:
24
+
25
+ 1. Text analysis
26
+ 2. Sentiment analysis
27
+ 3. Language translation
28
+
29
+ ## Applications
30
+
31
+ The integration of **computer vision** in autonomous vehicles has transformed the automotive industry. Similarly, **reinforcement learning** has shown remarkable success in gaming and robotics.
32
+
33
+ ## Conclusion
34
+
35
+ As **artificial intelligence** continues to advance, we expect to see more sophisticated applications of these technologies across various domains.
36
+ """,
37
+ "highlights": [
38
+ {
39
+ "term": "artificial intelligence",
40
+ "title": "Artificial Intelligence (AI)",
41
+ "content": "**Artificial Intelligence** refers to the simulation of human intelligence in machines that are programmed to think and learn like humans. AI systems can perform tasks that typically require human intelligence, such as:\n\n- Visual perception\n- Speech recognition\n- Decision-making\n- Language translation\n\nAI is broadly categorized into:\n1. **Narrow AI** - designed for specific tasks\n2. **General AI** - hypothetical AI with human-level cognitive abilities",
42
+ "category": "Core Technology",
43
+ "color": "#e3f2fd"
44
+ },
45
+ {
46
+ "term": "machine learning",
47
+ "title": "Machine Learning (ML)",
48
+ "content": "**Machine Learning** is a subset of AI that focuses on the development of algorithms that can learn and improve from experience without being explicitly programmed.\n\n### Types of Machine Learning:\n- **Supervised Learning**: Learning with labeled examples\n- **Unsupervised Learning**: Finding patterns in unlabeled data\n- **Reinforcement Learning**: Learning through interaction with environment\n\n### Applications:\n- Recommendation systems\n- Fraud detection\n- Medical diagnosis\n- Autonomous vehicles",
49
+ "category": "Core Technology",
50
+ "color": "#f3e5f5"
51
+ },
52
+ {
53
+ "term": "deep learning",
54
+ "title": "Deep Learning",
55
+ "content": "**Deep Learning** is a specialized subset of machine learning that uses artificial neural networks with multiple layers (deep neural networks) to model and understand complex patterns.\n\n### Key Characteristics:\n- Multiple hidden layers\n- Automatic feature extraction\n- Hierarchical learning\n\n### Popular Architectures:\n- Convolutional Neural Networks (CNNs)\n- Recurrent Neural Networks (RNNs)\n- Transformers\n\n### Applications:\n- Image recognition\n- Natural language processing\n- Speech synthesis",
56
+ "category": "Advanced Technique",
57
+ "color": "#e8f5e8"
58
+ },
59
+ {
60
+ "term": "neural networks",
61
+ "title": "Neural Networks",
62
+ "content": "**Neural Networks** are computing systems inspired by biological neural networks. They consist of interconnected nodes (neurons) that process information.\n\n### Components:\n- **Input Layer**: Receives data\n- **Hidden Layers**: Process information\n- **Output Layer**: Produces results\n\n### Types:\n- Feedforward networks\n- Recurrent networks\n- Convolutional networks\n\nNeural networks learn by adjusting the weights of connections between neurons based on training data.",
63
+ "category": "Architecture",
64
+ "color": "#fff3e0"
65
+ },
66
+ {
67
+ "term": "natural language processing",
68
+ "title": "Natural Language Processing (NLP)",
69
+ "content": "**Natural Language Processing** combines computational linguistics with machine learning to enable computers to understand, interpret, and generate human language.\n\n### Core Tasks:\n- **Tokenization**: Breaking text into words/sentences\n- **Part-of-speech tagging**: Identifying grammatical roles\n- **Named entity recognition**: Identifying people, places, organizations\n- **Sentiment analysis**: Determining emotional tone\n\n### Modern Approaches:\n- Transformer models (BERT, GPT)\n- Attention mechanisms\n- Pre-trained language models",
70
+ "category": "Application Domain",
71
+ "color": "#fce4ec"
72
+ },
73
+ {
74
+ "position": [615, 632],
75
+ "title": "Computer Vision (Position-based)",
76
+ "content": "**Computer Vision** highlighted by character position rather than term matching. This demonstrates precise control over highlighting specific text segments.\n\n### Position-based Benefits:\n- Exact character-level precision\n- No ambiguity with similar terms\n- Works with any text, including special characters\n- Useful for pre-processed documents",
77
+ "category": "Position Highlight",
78
+ "color": "#ffeb3b"
79
+ },
80
+ {
81
+ "term": "computer vision",
82
+ "title": "Computer Vision",
83
+ "content": "**Computer Vision** is a field of AI that enables machines to interpret and understand visual information from the world, mimicking human vision capabilities.\n\n### Key Tasks:\n- **Object Detection**: Locating objects in images\n- **Image Classification**: Categorizing images\n- **Semantic Segmentation**: Pixel-level understanding\n- **Face Recognition**: Identifying individuals\n\n### Applications:\n- Autonomous vehicles\n- Medical imaging\n- Security systems\n- Augmented reality",
84
+ "category": "Application Domain",
85
+ "color": "#e1f5fe"
86
+ },
87
+ {
88
+ "term": "reinforcement learning",
89
+ "title": "Reinforcement Learning (RL)",
90
+ "content": "**Reinforcement Learning** is a type of machine learning where an agent learns to make decisions by performing actions in an environment to maximize cumulative reward.\n\n### Key Concepts:\n- **Agent**: The learner/decision maker\n- **Environment**: The world the agent interacts with\n- **Actions**: Choices available to the agent\n- **Rewards**: Feedback from the environment\n- **Policy**: Strategy for choosing actions\n\n### Famous Applications:\n- Game playing (AlphaGo, OpenAI Five)\n- Robotics control\n- Trading algorithms\n- Resource allocation",
91
+ "category": "Learning Paradigm",
92
+ "color": "#f1f8e9"
93
+ },
94
+ {
95
+ "position": [169, 190],
96
+ "title": "Machine Learning (Position)",
97
+ "content": "This **machine learning** instance is highlighted using position-based highlighting at characters 169-190.\n\n### Position Highlighting Use Cases:\n- Academic paper annotations\n- Legal document markup\n- Code documentation\n- Precise text analysis\n\nPosition-based highlighting ensures exact text selection without ambiguity.",
98
+ "category": "Position Demo",
99
+ "color": "#e8eaf6"
100
+ }
101
+ ]
102
+ }
103
+
104
+ with gr.Blocks(title="Markdown Label Demo") as demo:
105
+ gr.Markdown("# MarkdownLabel Component Demo")
106
+ gr.Markdown("This demo showcases the MarkdownLabel component with **both term-based and position-based** interactive highlighting and detailed side panel.")
107
+
108
+ with gr.Row():
109
+ with gr.Column():
110
+ gr.Markdown("## Full Featured Example")
111
+ gr.Markdown("Includes both term-based (e.g., 'artificial intelligence') and position-based highlighting (yellow highlights).")
112
+ MarkdownLabel(
113
+ value=example_data,
114
+ label="AI Research Report - Mixed Highlighting",
115
+ show_side_panel=True,
116
+ panel_width="350px"
117
+ )
118
+
119
+ with gr.Column():
120
+ gr.Markdown("## Compact View")
121
+ gr.Markdown("Same content without the side panel for a cleaner interface.")
122
+ MarkdownLabel(
123
+ value=example_data,
124
+ label="Compact View",
125
+ show_side_panel=False
126
+ )
127
+
128
+ # Simple position-based example
129
+ simple_example = {
130
+ "markdown_content": "The quick **brown fox** jumps over the lazy dog. This is a simple example.",
131
+ "highlights": [
132
+ {
133
+ "position": [4, 9], # "quick"
134
+ "title": "Quick (Position 4-9)",
135
+ "content": "Highlighted using exact character positions 4-9.",
136
+ "category": "Position Demo",
137
+ "color": "#ffeb3b"
138
+ },
139
+ {
140
+ "term": "brown fox",
141
+ "title": "Brown Fox (Term Match)",
142
+ "content": "Highlighted using term matching.",
143
+ "category": "Term Demo",
144
+ "color": "#e3f2fd"
145
+ },
146
+ {
147
+ "position": [35, 43], # "the lazy"
148
+ "title": "The Lazy (Position 35-43)",
149
+ "content": "Another position-based highlight at characters 35-43.",
150
+ "category": "Position Demo",
151
+ "color": "#f3e5f5"
152
+ }
153
+ ]
154
+ }
155
+
156
+ gr.Markdown("## Position vs Term Highlighting Comparison")
157
+ gr.Markdown("This example shows the difference between position-based (yellow/purple) and term-based (blue) highlighting.")
158
+ MarkdownLabel(
159
+ value=simple_example,
160
+ label="Simple Position vs Term Example",
161
+ show_side_panel=True,
162
+ panel_width="300px"
163
+ )
164
+
165
+ if __name__ == "__main__":
166
+ demo.launch()
src/demo/css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
src/demo/editable_demo.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import gradio as gr
4
+ from gradio_markdownlabel import MarkdownLabel
5
+
6
+ # Sample data for the editable demo
7
+ sample_data = {
8
+ "markdown_content": """# Editable Document Example
9
+
10
+ ## Introduction
11
+
12
+ This document demonstrates the **editable functionality** of the MarkdownLabel component. You can click the "Edit" button to modify this content.
13
+
14
+ ## Features
15
+
16
+ - **Real-time editing**: Changes appear live in preview mode
17
+ - **Highlight preservation**: Existing highlights remain intact
18
+ - **Multiple edit modes**: Split view, tabs, and overlay options
19
+ - Position-based highlighting support
20
+
21
+ ## Try Editing
22
+
23
+ 1. Click the **Edit** button above
24
+ 2. Modify this text in the editor
25
+ 3. See live preview (in split mode)
26
+ 4. Save or cancel your changes
27
+
28
+ Feel free to experiment with the content!
29
+ """,
30
+ "highlights": [
31
+ {
32
+ "term": "editable functionality",
33
+ "title": "Editable Functionality",
34
+ "content": "This feature allows users to modify markdown content directly in the interface with real-time preview.",
35
+ "category": "feature",
36
+ "color": "#e3f2fd"
37
+ },
38
+ {
39
+ "position": [200, 220], # "MarkdownLabel component"
40
+ "title": "MarkdownLabel Component",
41
+ "content": "The main component that renders markdown with interactive highlights and editing capabilities.",
42
+ "category": "component",
43
+ "color": "#f3e5f5"
44
+ },
45
+ {
46
+ "term": "Real-time editing",
47
+ "title": "Real-time Editing",
48
+ "content": "Changes in the editor are immediately reflected in the preview pane, providing instant feedback.",
49
+ "category": "feature",
50
+ "color": "#e8f5e8"
51
+ },
52
+ {
53
+ "position": [450, 470], # "Split view, tabs, and"
54
+ "title": "Edit Modes",
55
+ "content": "Different layout options for the editing interface: split view shows editor and preview side-by-side, tabs separate them, and overlay mode provides full-screen editing.",
56
+ "category": "ui",
57
+ "color": "#fff3e0"
58
+ }
59
+ ]
60
+ }
61
+
62
+ def handle_content_change(value):
63
+ """Handle content changes (only on save)"""
64
+ print(f"Content saved: {len(value['markdown_content'])} characters")
65
+ return value
66
+
67
+ def handle_edit_start(value):
68
+ """Handle when user starts editing"""
69
+ print("User started editing")
70
+
71
+ def handle_save(value):
72
+ """Handle when user saves changes"""
73
+ print("Changes saved!")
74
+ gr.Info("Document saved successfully!")
75
+ return value
76
+
77
+ def handle_cancel(value):
78
+ """Handle when user cancels editing"""
79
+ print("Edit cancelled")
80
+ gr.Info("Changes cancelled")
81
+ return gr.update()
82
+
83
+ def load_sample_content():
84
+ """Load sample content"""
85
+ # Return data for both components (split and tabs editors)
86
+ return sample_data, sample_data
87
+
88
+
89
+ with gr.Blocks(title="Editable MarkdownLabel Demo") as demo:
90
+ gr.Markdown("# Editable MarkdownLabel Component Demo")
91
+ gr.Markdown("This demo showcases the **interactive editing capabilities** of the MarkdownLabel component.")
92
+
93
+ with gr.Row():
94
+ with gr.Column(scale=2):
95
+ gr.Markdown("## Split View Mode (Default)")
96
+ gr.Markdown("Editor and preview side-by-side when editing.")
97
+
98
+ editor_split = MarkdownLabel(
99
+ value=sample_data,
100
+ interactive=True,
101
+ edit_mode="split",
102
+ show_preview=True,
103
+ label="Split View Editor",
104
+ show_side_panel=True,
105
+ panel_width="300px"
106
+ )
107
+
108
+ # Event handlers for split view
109
+ # Note: Removed real-time change handler to prevent infinite loops
110
+ # Changes are now handled only on explicit save via submit event
111
+ editor_split.edit(handle_edit_start, inputs=[editor_split])
112
+ editor_split.submit(handle_save, inputs=[editor_split], outputs=[editor_split])
113
+ editor_split.clear(handle_cancel, inputs=[editor_split])
114
+
115
+ with gr.Column(scale=1):
116
+ gr.Markdown("## Tab Mode")
117
+ gr.Markdown("Switch between edit and preview tabs.")
118
+
119
+ editor_tabs = MarkdownLabel(
120
+ value=sample_data,
121
+ interactive=True,
122
+ edit_mode="tabs",
123
+ show_preview=True,
124
+ label="Tab View Editor",
125
+ show_side_panel=False
126
+ )
127
+
128
+ # Event handlers for tab view
129
+ # Note: Only handling submit (save) events to prevent loops
130
+ editor_tabs.submit(handle_save, inputs=[editor_tabs], outputs=[editor_tabs])
131
+
132
+ with gr.Row():
133
+ gr.Markdown("## Control Buttons")
134
+
135
+ with gr.Row():
136
+ load_btn = gr.Button("📄 Load Sample", variant="secondary")
137
+
138
+ # Button event handlers
139
+ load_btn.click(load_sample_content, outputs=[editor_split, editor_tabs])
140
+
141
+ with gr.Row():
142
+ gr.Markdown("## Non-Interactive (Read-Only) Version")
143
+
144
+ # Read-only version for comparison
145
+ readonly_viewer = MarkdownLabel(
146
+ value=sample_data,
147
+ interactive=False, # Read-only mode
148
+ label="Read-Only Viewer",
149
+ show_side_panel=True,
150
+ panel_width="250px"
151
+ )
152
+
153
+ gr.Markdown("""
154
+ ## How to Use
155
+
156
+ 1. **Start Editing**: Click the "✏️ Edit" button on any interactive component
157
+ 2. **Edit Content**: Modify the markdown text in the editor
158
+ 3. **Live Preview**: See changes in real-time (split mode) or switch to preview tab
159
+ 4. **Save Changes**: Click "💾 Save" to confirm and apply your changes
160
+ 5. **Cancel Changes**: Click "❌ Cancel" to discard changes and revert
161
+ 6. **Interact with Highlights**: Click on highlighted terms to see details in the side panel
162
+
163
+ ## Features Demonstrated
164
+
165
+ - ✅ **Interactive editing** with explicit save/cancel workflow
166
+ - ✅ **Multiple edit modes**: Split view and tabs
167
+ - ✅ **Live preview** with real-time markdown rendering (visual only)
168
+ - ✅ **Highlight preservation** during editing
169
+ - ✅ **Manual save workflow** - changes are applied only when you save
170
+ - ✅ **Event handling** for edit, save (submit), and cancel events
171
+ - ✅ **Mixed highlighting** with both term-based and position-based highlights
172
+
173
+ ## Important Notes
174
+
175
+ - **Changes are NOT auto-saved** - you must click "💾 Save" to apply changes
176
+ - **Live preview** is for visual feedback only - actual content updates on save
177
+ - **Cancel** reverts all changes made since editing started
178
+ """)
179
+
180
+ if __name__ == "__main__":
181
+ demo.launch()
src/demo/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_markdownlabel
src/demo/space.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from app import demo as app
4
+ import os
5
+
6
+ _docs = {'MarkdownLabel': {'description': 'Displays markdown-formatted text with interactive term highlighting and detailed side panel.\n\nThis component allows for rich markdown content with clickable term highlights that display\ndetailed information in a side panel.', 'members': {'__init__': {'value': {'type': 'dict | Callable | None', 'default': 'None', 'description': 'Dictionary containing markdown_content and highlights array. If a function is provided, the function will be called each time the app loads to set the initial value of this component.'}, 'show_side_panel': {'type': 'bool', 'default': 'True', 'description': 'Whether to show the detailed information side panel.'}, 'panel_width': {'type': 'str', 'default': '"300px"', 'description': 'Width of the side panel (CSS value like "300px", "25%", etc.).'}, 'edit_mode': {'type': 'str', 'default': '"split"', 'description': 'Layout for editing mode - "split" (side-by-side), "tabs", or "overlay".'}, 'show_preview': {'type': 'bool', 'default': 'True', 'description': 'Whether to show live preview in edit mode.'}, 'markdown_editor': {'type': 'str', 'default': '"textarea"', 'description': 'Type of markdown editor - "textarea" or "codemirror" (future).'}, 'label': {'type': 'str | I18nData | None', 'default': 'None', 'description': 'the label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.'}, 'every': {'type': 'Timer | float | None', 'default': 'None', 'description': 'Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.'}, 'inputs': {'type': 'Component | Sequence[Component] | set[Component] | None', 'default': 'None', 'description': 'Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, will place the component in a container - providing some extra padding around the border.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'key': {'type': 'int | str | tuple[int | str, ...] | None', 'default': 'None', 'description': "in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render."}, 'preserved_by_key': {'type': 'list[str] | str | None', 'default': '"value"', 'description': "A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor."}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': 'If True, the component will be editable allowing users to modify markdown content.'}, 'rtl': {'type': 'bool', 'default': 'False', 'description': 'If True, will display the text in right-to-left direction.'}}, 'postprocess': {'value': {'type': 'dict | None', 'description': "Expects a dictionary with 'markdown_content' and 'highlights' keys"}}, 'preprocess': {'return': {'type': 'dict | None', 'description': 'Passes the value as a dictionary with markdown_content and highlights.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the MarkdownLabel changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'select': {'type': None, 'default': None, 'description': 'Event listener for when the user selects or deselects the MarkdownLabel. Uses event data gradio.SelectData to carry `value` referring to the label of the MarkdownLabel, and `selected` to refer to state of the MarkdownLabel. See EventData documentation on how to use this event data'}, 'edit': {'type': None, 'default': None, 'description': 'This listener is triggered when the user edits the MarkdownLabel (e.g. image) using the built-in editor.'}, 'submit': {'type': None, 'default': None, 'description': 'This listener is triggered when the user presses the Enter key while the MarkdownLabel is focused.'}, 'clear': {'type': None, 'default': None, 'description': 'This listener is triggered when the user clears the MarkdownLabel using the clear button for the component.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'MarkdownLabel': []}}}
7
+
8
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
+
10
+ with gr.Blocks(
11
+ css=abs_path,
12
+ theme=gr.themes.Default(
13
+ font_mono=[
14
+ gr.themes.GoogleFont("Inconsolata"),
15
+ "monospace",
16
+ ],
17
+ ),
18
+ ) as demo:
19
+ gr.Markdown(
20
+ """
21
+ # `gradio_markdownlabel`
22
+
23
+ <div style="display: flex; gap: 7px;">
24
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
25
+ </div>
26
+
27
+ Python library for easily interacting with trained machine learning models
28
+ """, elem_classes=["md-custom"], header_links=True)
29
+ app.render()
30
+ gr.Markdown(
31
+ """
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install gradio_markdownlabel
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+
42
+ import gradio as gr
43
+ from gradio_markdownlabel import MarkdownLabel
44
+
45
+ # Create a comprehensive example with rich markdown content and multiple highlights
46
+ example_data = {
47
+ "markdown_content": \"\"\"# AI and Machine Learning Research Report
48
+
49
+ ## Introduction
50
+
51
+ This document explores the rapidly evolving field of **artificial intelligence** and its various applications in modern technology. The study focuses on *machine learning* techniques and their impact on different industries.
52
+
53
+ ## Key Technologies
54
+
55
+ ### Deep Learning
56
+ *Deep learning* represents a subset of machine learning that uses **neural networks** with multiple layers. This approach has revolutionized areas such as:
57
+
58
+ - Computer vision
59
+ - Natural language processing
60
+ - Speech recognition
61
+
62
+ ### Natural Language Processing
63
+ **Natural language processing** (NLP) enables computers to understand and interpret human language. Key applications include:
64
+
65
+ 1. Text analysis
66
+ 2. Sentiment analysis
67
+ 3. Language translation
68
+
69
+ ## Applications
70
+
71
+ The integration of **computer vision** in autonomous vehicles has transformed the automotive industry. Similarly, **reinforcement learning** has shown remarkable success in gaming and robotics.
72
+
73
+ ## Conclusion
74
+
75
+ As **artificial intelligence** continues to advance, we expect to see more sophisticated applications of these technologies across various domains.
76
+ \"\"\",
77
+ "highlights": [
78
+ {
79
+ "term": "artificial intelligence",
80
+ "title": "Artificial Intelligence (AI)",
81
+ "content": "**Artificial Intelligence** refers to the simulation of human intelligence in machines that are programmed to think and learn like humans. AI systems can perform tasks that typically require human intelligence, such as:\n\n- Visual perception\n- Speech recognition\n- Decision-making\n- Language translation\n\nAI is broadly categorized into:\n1. **Narrow AI** - designed for specific tasks\n2. **General AI** - hypothetical AI with human-level cognitive abilities",
82
+ "category": "Core Technology",
83
+ "color": "#e3f2fd"
84
+ },
85
+ {
86
+ "term": "machine learning",
87
+ "title": "Machine Learning (ML)",
88
+ "content": "**Machine Learning** is a subset of AI that focuses on the development of algorithms that can learn and improve from experience without being explicitly programmed.\n\n### Types of Machine Learning:\n- **Supervised Learning**: Learning with labeled examples\n- **Unsupervised Learning**: Finding patterns in unlabeled data\n- **Reinforcement Learning**: Learning through interaction with environment\n\n### Applications:\n- Recommendation systems\n- Fraud detection\n- Medical diagnosis\n- Autonomous vehicles",
89
+ "category": "Core Technology",
90
+ "color": "#f3e5f5"
91
+ },
92
+ {
93
+ "term": "deep learning",
94
+ "title": "Deep Learning",
95
+ "content": "**Deep Learning** is a specialized subset of machine learning that uses artificial neural networks with multiple layers (deep neural networks) to model and understand complex patterns.\n\n### Key Characteristics:\n- Multiple hidden layers\n- Automatic feature extraction\n- Hierarchical learning\n\n### Popular Architectures:\n- Convolutional Neural Networks (CNNs)\n- Recurrent Neural Networks (RNNs)\n- Transformers\n\n### Applications:\n- Image recognition\n- Natural language processing\n- Speech synthesis",
96
+ "category": "Advanced Technique",
97
+ "color": "#e8f5e8"
98
+ },
99
+ {
100
+ "term": "neural networks",
101
+ "title": "Neural Networks",
102
+ "content": "**Neural Networks** are computing systems inspired by biological neural networks. They consist of interconnected nodes (neurons) that process information.\n\n### Components:\n- **Input Layer**: Receives data\n- **Hidden Layers**: Process information\n- **Output Layer**: Produces results\n\n### Types:\n- Feedforward networks\n- Recurrent networks\n- Convolutional networks\n\nNeural networks learn by adjusting the weights of connections between neurons based on training data.",
103
+ "category": "Architecture",
104
+ "color": "#fff3e0"
105
+ },
106
+ {
107
+ "term": "natural language processing",
108
+ "title": "Natural Language Processing (NLP)",
109
+ "content": "**Natural Language Processing** combines computational linguistics with machine learning to enable computers to understand, interpret, and generate human language.\n\n### Core Tasks:\n- **Tokenization**: Breaking text into words/sentences\n- **Part-of-speech tagging**: Identifying grammatical roles\n- **Named entity recognition**: Identifying people, places, organizations\n- **Sentiment analysis**: Determining emotional tone\n\n### Modern Approaches:\n- Transformer models (BERT, GPT)\n- Attention mechanisms\n- Pre-trained language models",
110
+ "category": "Application Domain",
111
+ "color": "#fce4ec"
112
+ },
113
+ {
114
+ "position": [615, 632],
115
+ "title": "Computer Vision (Position-based)",
116
+ "content": "**Computer Vision** highlighted by character position rather than term matching. This demonstrates precise control over highlighting specific text segments.\n\n### Position-based Benefits:\n- Exact character-level precision\n- No ambiguity with similar terms\n- Works with any text, including special characters\n- Useful for pre-processed documents",
117
+ "category": "Position Highlight",
118
+ "color": "#ffeb3b"
119
+ },
120
+ {
121
+ "term": "computer vision",
122
+ "title": "Computer Vision",
123
+ "content": "**Computer Vision** is a field of AI that enables machines to interpret and understand visual information from the world, mimicking human vision capabilities.\n\n### Key Tasks:\n- **Object Detection**: Locating objects in images\n- **Image Classification**: Categorizing images\n- **Semantic Segmentation**: Pixel-level understanding\n- **Face Recognition**: Identifying individuals\n\n### Applications:\n- Autonomous vehicles\n- Medical imaging\n- Security systems\n- Augmented reality",
124
+ "category": "Application Domain",
125
+ "color": "#e1f5fe"
126
+ },
127
+ {
128
+ "term": "reinforcement learning",
129
+ "title": "Reinforcement Learning (RL)",
130
+ "content": "**Reinforcement Learning** is a type of machine learning where an agent learns to make decisions by performing actions in an environment to maximize cumulative reward.\n\n### Key Concepts:\n- **Agent**: The learner/decision maker\n- **Environment**: The world the agent interacts with\n- **Actions**: Choices available to the agent\n- **Rewards**: Feedback from the environment\n- **Policy**: Strategy for choosing actions\n\n### Famous Applications:\n- Game playing (AlphaGo, OpenAI Five)\n- Robotics control\n- Trading algorithms\n- Resource allocation",
131
+ "category": "Learning Paradigm",
132
+ "color": "#f1f8e9"
133
+ },
134
+ {
135
+ "position": [169, 190],
136
+ "title": "Machine Learning (Position)",
137
+ "content": "This **machine learning** instance is highlighted using position-based highlighting at characters 169-190.\n\n### Position Highlighting Use Cases:\n- Academic paper annotations\n- Legal document markup\n- Code documentation\n- Precise text analysis\n\nPosition-based highlighting ensures exact text selection without ambiguity.",
138
+ "category": "Position Demo",
139
+ "color": "#e8eaf6"
140
+ }
141
+ ]
142
+ }
143
+
144
+ with gr.Blocks(title="Markdown Label Demo") as demo:
145
+ gr.Markdown("# MarkdownLabel Component Demo")
146
+ gr.Markdown("This demo showcases the MarkdownLabel component with **both term-based and position-based** interactive highlighting and detailed side panel.")
147
+
148
+ with gr.Row():
149
+ with gr.Column():
150
+ gr.Markdown("## Full Featured Example")
151
+ gr.Markdown("Includes both term-based (e.g., 'artificial intelligence') and position-based highlighting (yellow highlights).")
152
+ MarkdownLabel(
153
+ value=example_data,
154
+ label="AI Research Report - Mixed Highlighting",
155
+ show_side_panel=True,
156
+ panel_width="350px"
157
+ )
158
+
159
+ with gr.Column():
160
+ gr.Markdown("## Compact View")
161
+ gr.Markdown("Same content without the side panel for a cleaner interface.")
162
+ MarkdownLabel(
163
+ value=example_data,
164
+ label="Compact View",
165
+ show_side_panel=False
166
+ )
167
+
168
+ # Simple position-based example
169
+ simple_example = {
170
+ "markdown_content": "The quick **brown fox** jumps over the lazy dog. This is a simple example.",
171
+ "highlights": [
172
+ {
173
+ "position": [4, 9], # "quick"
174
+ "title": "Quick (Position 4-9)",
175
+ "content": "Highlighted using exact character positions 4-9.",
176
+ "category": "Position Demo",
177
+ "color": "#ffeb3b"
178
+ },
179
+ {
180
+ "term": "brown fox",
181
+ "title": "Brown Fox (Term Match)",
182
+ "content": "Highlighted using term matching.",
183
+ "category": "Term Demo",
184
+ "color": "#e3f2fd"
185
+ },
186
+ {
187
+ "position": [35, 43], # "the lazy"
188
+ "title": "The Lazy (Position 35-43)",
189
+ "content": "Another position-based highlight at characters 35-43.",
190
+ "category": "Position Demo",
191
+ "color": "#f3e5f5"
192
+ }
193
+ ]
194
+ }
195
+
196
+ gr.Markdown("## Position vs Term Highlighting Comparison")
197
+ gr.Markdown("This example shows the difference between position-based (yellow/purple) and term-based (blue) highlighting.")
198
+ MarkdownLabel(
199
+ value=simple_example,
200
+ label="Simple Position vs Term Example",
201
+ show_side_panel=True,
202
+ panel_width="300px"
203
+ )
204
+
205
+ if __name__ == "__main__":
206
+ demo.launch()
207
+
208
+ ```
209
+ """, elem_classes=["md-custom"], header_links=True)
210
+
211
+
212
+ gr.Markdown("""
213
+ ## `MarkdownLabel`
214
+
215
+ ### Initialization
216
+ """, elem_classes=["md-custom"], header_links=True)
217
+
218
+ gr.ParamViewer(value=_docs["MarkdownLabel"]["members"]["__init__"], linkify=[])
219
+
220
+
221
+ gr.Markdown("### Events")
222
+ gr.ParamViewer(value=_docs["MarkdownLabel"]["events"], linkify=['Event'])
223
+
224
+
225
+
226
+
227
+ gr.Markdown("""
228
+
229
+ ### User function
230
+
231
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
232
+
233
+ - When used as an Input, the component only impacts the input signature of the user function.
234
+ - When used as an output, the component only impacts the return signature of the user function.
235
+
236
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
237
+
238
+ - **As input:** Is passed, passes the value as a dictionary with markdown_content and highlights.
239
+ - **As output:** Should return, expects a dictionary with 'markdown_content' and 'highlights' keys.
240
+
241
+ ```python
242
+ def predict(
243
+ value: dict | None
244
+ ) -> dict | None:
245
+ return value
246
+ ```
247
+ """, elem_classes=["md-custom", "MarkdownLabel-user-fn"], header_links=True)
248
+
249
+
250
+
251
+
252
+ demo.load(None, js=r"""function() {
253
+ const refs = {};
254
+ const user_fn_refs = {
255
+ MarkdownLabel: [], };
256
+ requestAnimationFrame(() => {
257
+
258
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
259
+ if (refs.length > 0) {
260
+ const el = document.querySelector(`.${key}-user-fn`);
261
+ if (!el) return;
262
+ refs.forEach(ref => {
263
+ el.innerHTML = el.innerHTML.replace(
264
+ new RegExp("\\b"+ref+"\\b", "g"),
265
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
266
+ );
267
+ })
268
+ }
269
+ })
270
+
271
+ Object.entries(refs).forEach(([key, refs]) => {
272
+ if (refs.length > 0) {
273
+ const el = document.querySelector(`.${key}`);
274
+ if (!el) return;
275
+ refs.forEach(ref => {
276
+ el.innerHTML = el.innerHTML.replace(
277
+ new RegExp("\\b"+ref+"\\b", "g"),
278
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
279
+ );
280
+ })
281
+ }
282
+ })
283
+ })
284
+ }
285
+
286
+ """)
287
+
288
+ demo.launch()
src/example_api_usage.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Example API Usage for Editable MarkdownLabel Component
4
+ Shows the recommended event handling pattern to avoid infinite loops.
5
+ """
6
+
7
+ import gradio as gr
8
+ from gradio_markdownlabel import MarkdownLabel
9
+
10
+ def main():
11
+ # Sample document with highlights
12
+ document = {
13
+ "markdown_content": """# Project Documentation
14
+
15
+ ## Overview
16
+
17
+ This is an **editable document** that demonstrates the MarkdownLabel component's capabilities.
18
+
19
+ ## Features
20
+
21
+ - Interactive editing with save/cancel workflow
22
+ - Position-based and term-based highlighting
23
+ - Live preview during editing
24
+ - Manual save to prevent accidental changes
25
+
26
+ ## Instructions
27
+
28
+ Click "Edit" to modify this content, then "Save" to apply changes.
29
+ """,
30
+ "highlights": [
31
+ {
32
+ "term": "editable document",
33
+ "title": "Editable Document",
34
+ "content": "Documents that can be modified by users through the interface.",
35
+ "category": "feature",
36
+ "color": "#e3f2fd"
37
+ },
38
+ {
39
+ "position": [200, 220], # "MarkdownLabel component"
40
+ "title": "MarkdownLabel Component",
41
+ "content": "The custom Gradio component for displaying markdown with interactive highlights.",
42
+ "category": "technical",
43
+ "color": "#f3e5f5"
44
+ }
45
+ ]
46
+ }
47
+
48
+ # Event handlers - NO change handler to prevent loops
49
+ def on_edit_start(data):
50
+ """Called when user clicks Edit button"""
51
+ print(f"📝 User started editing document")
52
+ # Could initialize editing session, backup content, etc.
53
+ return gr.update() # No changes to component
54
+
55
+ def on_save(data):
56
+ """Called when user clicks Save button"""
57
+ print(f"💾 Document saved with {len(data['markdown_content'])} characters")
58
+ # Here you could:
59
+ # - Validate the content
60
+ # - Save to database
61
+ # - Update version history
62
+ # - Send notifications
63
+ gr.Info("Document saved successfully!")
64
+ return data # Return the saved data
65
+
66
+ def on_cancel(data):
67
+ """Called when user clicks Cancel button"""
68
+ print(f"❌ Edit cancelled")
69
+ gr.Info("Changes discarded")
70
+ return gr.update() # No changes needed
71
+
72
+ def on_highlight_select(selection_data):
73
+ """Called when user clicks a highlight"""
74
+ print(f"🎯 Highlight selected: {selection_data}")
75
+ return gr.update()
76
+
77
+ # Create the interface
78
+ with gr.Blocks(title="MarkdownLabel API Example") as demo:
79
+ gr.Markdown("# MarkdownLabel API Usage Example")
80
+ gr.Markdown("Demonstrates proper event handling without infinite loops.")
81
+
82
+ # The component
83
+ editor = MarkdownLabel(
84
+ value=document,
85
+ interactive=True, # Enable editing
86
+ edit_mode="split", # Split view: editor + preview
87
+ show_preview=True, # Show live preview
88
+ show_side_panel=True, # Show highlight details
89
+ panel_width="350px",
90
+ label="Interactive Document Editor"
91
+ )
92
+
93
+ # Event handlers - IMPORTANT: No .change() handler during editing!
94
+ editor.edit(
95
+ fn=on_edit_start,
96
+ inputs=[editor]
97
+ # No outputs = no component update
98
+ )
99
+
100
+ editor.submit( # This is the "Save" button
101
+ fn=on_save,
102
+ inputs=[editor],
103
+ outputs=[editor] # Update component with saved data
104
+ )
105
+
106
+ editor.clear( # This is the "Cancel" button
107
+ fn=on_cancel,
108
+ inputs=[editor]
109
+ # No outputs = revert handled internally
110
+ )
111
+
112
+ editor.select( # Highlight selection
113
+ fn=on_highlight_select,
114
+ inputs=[editor]
115
+ )
116
+
117
+ # Status display
118
+ gr.Markdown("""
119
+ ### Event Flow:
120
+ 1. **Edit**: User clicks edit → `on_edit_start()` called
121
+ 2. **Type**: User types → Only visual preview updates (no events)
122
+ 3. **Save**: User clicks save → `on_save()` called → Changes applied
123
+ 4. **Cancel**: User clicks cancel → `on_cancel()` called → Changes reverted
124
+
125
+ ### Key Points:
126
+ - ❌ **DON'T** use `.change()` handlers during editing (causes loops)
127
+ - ✅ **DO** use `.submit()` for save actions
128
+ - ✅ **DO** use `.clear()` for cancel actions
129
+ - ✅ **DO** use `.edit()` for edit start actions
130
+ - ✅ **DO** use `.select()` for highlight interactions
131
+ """)
132
+
133
+ return demo
134
+
135
+ if __name__ == "__main__":
136
+ demo = main()
137
+ demo.launch()
src/frontend/Index.svelte ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script context="module" lang="ts">
2
+ export { default as BaseMarkdownRenderer } from "./shared/MarkdownRenderer.svelte";
3
+ export { default as BaseEditableMarkdownRenderer } from "./shared/EditableMarkdownRenderer.svelte";
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import type { Gradio, SelectData, I18nFormatter } from "@gradio/utils";
8
+ import MarkdownRenderer from "./shared/MarkdownRenderer.svelte";
9
+ import EditableMarkdownRenderer from "./shared/EditableMarkdownRenderer.svelte";
10
+ import { Block, BlockLabel, Empty } from "@gradio/atoms";
11
+ import { TextHighlight } from "@gradio/icons";
12
+ import { StatusTracker } from "@gradio/statustracker";
13
+ import type { LoadingStatus } from "@gradio/statustracker";
14
+
15
+ export let gradio: Gradio<{
16
+ select: SelectData;
17
+ change: never;
18
+ edit: never;
19
+ submit: never;
20
+ clear: never;
21
+ clear_status: LoadingStatus;
22
+ }>;
23
+ export let elem_id = "";
24
+ export let elem_classes: string[] = [];
25
+ export let visible = true;
26
+ export let value: {
27
+ markdown_content: string;
28
+ highlights: Array<{
29
+ term?: string;
30
+ position?: number[];
31
+ title: string;
32
+ content: string;
33
+ category: string;
34
+ color: string;
35
+ }>;
36
+ } | null = null;
37
+ let old_value: typeof value;
38
+ export let show_side_panel: boolean = true;
39
+ export let panel_width: string = "300px";
40
+ export let edit_mode: string = "split";
41
+ export let show_preview: boolean = true;
42
+ export let markdown_editor: string = "textarea";
43
+ export let interactive: boolean = false;
44
+ export let label = gradio.i18n("markdown_label.markdown_label");
45
+ export let container = true;
46
+ export let scale: number | null = null;
47
+ export let min_width: number | undefined = undefined;
48
+ export let show_label = true;
49
+ export let rtl = false;
50
+
51
+ export let loading_status: LoadingStatus;
52
+
53
+ $: {
54
+ if (value !== old_value) {
55
+ old_value = value;
56
+ gradio.dispatch("change");
57
+ }
58
+ }
59
+ </script>
60
+
61
+ <Block
62
+ variant={"solid"}
63
+ test_id="markdown-label"
64
+ {visible}
65
+ {elem_id}
66
+ {elem_classes}
67
+ padding={false}
68
+ {container}
69
+ {scale}
70
+ {min_width}
71
+ {rtl}
72
+ >
73
+ <StatusTracker
74
+ autoscroll={gradio.autoscroll}
75
+ i18n={gradio.i18n}
76
+ {...loading_status}
77
+ on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
78
+ />
79
+ {#if label && show_label}
80
+ <BlockLabel
81
+ Icon={TextHighlight}
82
+ {label}
83
+ float={false}
84
+ disable={container === false}
85
+ {show_label}
86
+ {rtl}
87
+ />
88
+ {/if}
89
+
90
+ {#if value && value.markdown_content}
91
+ {#if interactive}
92
+ <EditableMarkdownRenderer
93
+ markdown_content={value.markdown_content}
94
+ highlights={value.highlights || []}
95
+ {show_side_panel}
96
+ {panel_width}
97
+ {edit_mode}
98
+ {show_preview}
99
+ {markdown_editor}
100
+ {interactive}
101
+ on:select={({ detail }) => gradio.dispatch("select", detail)}
102
+ on:change={({ detail }) => {
103
+ value = detail;
104
+ gradio.dispatch("change");
105
+ }}
106
+ on:edit={({ detail }) => gradio.dispatch("edit", detail)}
107
+ on:save={({ detail }) => {
108
+ value = detail;
109
+ gradio.dispatch("submit", detail);
110
+ }}
111
+ on:cancel={({ detail }) => gradio.dispatch("clear", detail)}
112
+ />
113
+ {:else}
114
+ <MarkdownRenderer
115
+ markdown_content={value.markdown_content}
116
+ highlights={value.highlights || []}
117
+ {show_side_panel}
118
+ {panel_width}
119
+ on:select={({ detail }) => gradio.dispatch("select", detail)}
120
+ />
121
+ {/if}
122
+ {:else}
123
+ <Empty>
124
+ <TextHighlight />
125
+ </Empty>
126
+ {/if}
127
+ </Block>
src/frontend/gradio.config.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: [],
3
+ svelte: {
4
+ preprocess: [],
5
+ },
6
+ build: {
7
+ target: "modules",
8
+ },
9
+ };
src/frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
src/frontend/package.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "gradio_markdownlabel",
3
+ "version": "0.9.7",
4
+ "description": "Gradio UI packages",
5
+ "type": "module",
6
+ "author": "",
7
+ "license": "ISC",
8
+ "private": false,
9
+ "main_changeset": true,
10
+ "dependencies": {
11
+ "@gradio/atoms": "0.16.3",
12
+ "@gradio/icons": "0.12.0",
13
+ "@gradio/statustracker": "0.10.14",
14
+ "@gradio/theme": "0.4.0",
15
+ "@gradio/utils": "0.10.2",
16
+ "marked": "^12.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@gradio/preview": "0.14.0"
20
+ },
21
+ "main": "./Index.svelte",
22
+ "exports": {
23
+ ".": {
24
+ "gradio": "./Index.svelte",
25
+ "svelte": "./dist/Index.svelte",
26
+ "types": "./dist/Index.svelte.d.ts"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "peerDependencies": {
31
+ "svelte": "^4.0.0"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/gradio-app/gradio.git",
36
+ "directory": "js/highlightedtext"
37
+ }
38
+ }
src/frontend/shared/EditableMarkdownRenderer.svelte ADDED
@@ -0,0 +1,607 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { marked } from 'marked';
3
+ import { createEventDispatcher } from 'svelte';
4
+ import type { SelectData } from '@gradio/utils';
5
+
6
+ export let markdown_content: string = '';
7
+ export let highlights: Array<{
8
+ term?: string;
9
+ position?: number[];
10
+ title: string;
11
+ content: string;
12
+ category: string;
13
+ color: string;
14
+ }> = [];
15
+ export let show_side_panel: boolean = true;
16
+ export let panel_width: string = '300px';
17
+ export let interactive: boolean = false;
18
+ export let edit_mode: string = 'split'; // 'split', 'tabs', 'overlay'
19
+ export let show_preview: boolean = true;
20
+ export const markdown_editor: string = 'textarea';
21
+
22
+ const dispatch = createEventDispatcher<{
23
+ select: SelectData;
24
+ change: { markdown_content: string; highlights: any[] };
25
+ edit: { markdown_content: string; highlights: any[] };
26
+ save: { markdown_content: string; highlights: any[] };
27
+ cancel: { markdown_content: string; highlights: any[] };
28
+ }>();
29
+
30
+ let selectedHighlight: typeof highlights[0] | null = null;
31
+ let processedHtml: string = '';
32
+ let isEditing: boolean = false;
33
+ let editingContent: string = '';
34
+ let originalContent: string = '';
35
+ let currentTab: 'edit' | 'preview' | 'highlights' = 'edit';
36
+
37
+ // Track original state for cancel functionality
38
+ $: {
39
+ if (!isEditing) {
40
+ originalContent = markdown_content;
41
+ }
42
+ }
43
+
44
+ // Process markdown and apply highlighting
45
+ $: {
46
+ const contentToRender = isEditing && edit_mode === 'split' && show_preview ? editingContent : markdown_content;
47
+ if (contentToRender) {
48
+ // First, parse the markdown to HTML
49
+ let html = marked(contentToRender);
50
+
51
+ // Separate position-based and term-based highlights
52
+ const positionHighlights = highlights.filter(h => h.position && h.position.length === 2);
53
+ const termHighlights = highlights.filter(h => h.term && h.term.trim());
54
+
55
+ // Apply term-based highlights to the HTML first
56
+ termHighlights.forEach(highlight => {
57
+ if (highlight.term && highlight.term.trim()) {
58
+ const index = highlights.indexOf(highlight);
59
+ const regex = new RegExp(`\\b${escapeRegex(highlight.term)}\\b`, 'gi');
60
+ html = html.replace(regex, (match) => {
61
+ const color = highlight.color || '#e3f2fd';
62
+ return `<span class="highlight-term"
63
+ data-index="${index}"
64
+ data-term="${highlight.term}"
65
+ style="background-color: ${color}; cursor: pointer; padding: 2px 4px; border-radius: 3px; transition: all 0.2s;"
66
+ role="button"
67
+ tabindex="0"
68
+ aria-label="Highlighted term: ${highlight.term}">
69
+ ${match}
70
+ </span>`;
71
+ });
72
+ }
73
+ });
74
+
75
+ // Apply position-based highlights to the HTML
76
+ // Convert position-based highlights to term-based for simplicity and reliability
77
+ positionHighlights.forEach(highlight => {
78
+ const [start, end] = highlight.position!;
79
+ if (start >= 0 && end <= contentToRender.length && start < end) {
80
+ const targetText = contentToRender.substring(start, end);
81
+ const color = highlight.color || '#e3f2fd';
82
+ const index = highlights.indexOf(highlight);
83
+
84
+ // Escape the target text for regex and handle multiline/whitespace
85
+ const escapedText = escapeRegex(targetText).replace(/\s+/g, '\\s+');
86
+
87
+ // Create a regex that only matches text content (not inside HTML tags)
88
+ const textRegex = new RegExp(`\\b${escapedText}\\b`, 'gi');
89
+
90
+ // Apply the highlight - this will work like term-based highlighting
91
+ // but using the exact text from the position
92
+ html = html.replace(textRegex, (match) => {
93
+ return `<span class="highlight-position"
94
+ data-index="${index}"
95
+ data-text="${encodeURIComponent(targetText)}"
96
+ style="background-color: ${color}; cursor: pointer; padding: 2px 4px; border-radius: 3px; transition: all 0.2s;"
97
+ role="button"
98
+ tabindex="0"
99
+ aria-label="Highlighted text: ${targetText.replace(/"/g, '&quot;')}">
100
+ ${match}
101
+ </span>`;
102
+ });
103
+ }
104
+ });
105
+
106
+ processedHtml = html;
107
+ }
108
+ }
109
+
110
+ function escapeRegex(string: string): string {
111
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
112
+ }
113
+
114
+ function handleTermClick(event: Event) {
115
+ const target = event.target as HTMLElement;
116
+ if (target.classList.contains('highlight-term') || target.classList.contains('highlight-position')) {
117
+ const index = parseInt(target.dataset.index || '0');
118
+ const highlight = highlights[index];
119
+ if (highlight) {
120
+ selectedHighlight = highlight;
121
+ dispatch('select', {
122
+ index,
123
+ value: highlight
124
+ });
125
+ }
126
+ }
127
+ }
128
+
129
+ function handleKeydown(event: KeyboardEvent) {
130
+ const target = event.target as HTMLElement;
131
+ if ((event.key === 'Enter' || event.key === ' ') &&
132
+ (target.classList.contains('highlight-term') || target.classList.contains('highlight-position'))) {
133
+ event.preventDefault();
134
+ handleTermClick(event);
135
+ }
136
+ }
137
+
138
+ function closeSidePanel() {
139
+ selectedHighlight = null;
140
+ }
141
+
142
+ function startEditing() {
143
+ isEditing = true;
144
+ editingContent = markdown_content;
145
+ originalContent = markdown_content;
146
+ dispatch('edit', { markdown_content, highlights });
147
+ }
148
+
149
+ function saveChanges() {
150
+ markdown_content = editingContent;
151
+ isEditing = false;
152
+ dispatch('save', { markdown_content, highlights });
153
+ // Only dispatch change event on explicit save
154
+ dispatch('change', { markdown_content, highlights });
155
+ }
156
+
157
+ function cancelEditing() {
158
+ editingContent = originalContent;
159
+ isEditing = false;
160
+ dispatch('cancel', { markdown_content: originalContent, highlights });
161
+ }
162
+
163
+ function handleTextareaInput(event: Event) {
164
+ const target = event.target as HTMLTextAreaElement;
165
+ editingContent = target.value;
166
+ // Don't dispatch change events in real-time to avoid loops
167
+ // Changes will be dispatched only on save
168
+ }
169
+
170
+ function setTab(tab: typeof currentTab) {
171
+ currentTab = tab;
172
+ }
173
+ </script>
174
+
175
+ <div class="markdown-container" class:editing={isEditing} class:with-panel={show_side_panel && selectedHighlight}>
176
+ <!-- Edit Mode Controls -->
177
+ {#if interactive && !isEditing}
178
+ <div class="edit-controls">
179
+ <button class="edit-btn" on:click={startEditing}>
180
+ ✏️ Edit
181
+ </button>
182
+ </div>
183
+ {/if}
184
+
185
+ {#if isEditing}
186
+ <div class="edit-controls">
187
+ <button class="save-btn" on:click={saveChanges}>
188
+ 💾 Save
189
+ </button>
190
+ <button class="cancel-btn" on:click={cancelEditing}>
191
+ ❌ Cancel
192
+ </button>
193
+
194
+ {#if edit_mode === 'tabs'}
195
+ <div class="tab-controls">
196
+ <button
197
+ class="tab-btn"
198
+ class:active={currentTab === 'edit'}
199
+ on:click={() => setTab('edit')}
200
+ >
201
+ Edit
202
+ </button>
203
+ <button
204
+ class="tab-btn"
205
+ class:active={currentTab === 'preview'}
206
+ on:click={() => setTab('preview')}
207
+ >
208
+ Preview
209
+ </button>
210
+ </div>
211
+ {/if}
212
+ </div>
213
+ {/if}
214
+
215
+ <!-- Content Area -->
216
+ <div class="content-area" class:split-mode={edit_mode === 'split' && isEditing}>
217
+ {#if isEditing}
218
+ {#if edit_mode === 'split'}
219
+ <!-- Split Mode: Editor and Preview Side by Side -->
220
+ <div class="editor-section">
221
+ <div class="editor-header">
222
+ <h4>Markdown Editor</h4>
223
+ </div>
224
+ <textarea
225
+ class="markdown-editor"
226
+ bind:value={editingContent}
227
+ on:input={handleTextareaInput}
228
+ placeholder="Enter your markdown content..."
229
+ spellcheck="false"
230
+ ></textarea>
231
+ </div>
232
+
233
+ {#if show_preview}
234
+ <div class="preview-section">
235
+ <div class="preview-header">
236
+ <h4>Live Preview</h4>
237
+ </div>
238
+ <div class="markdown-content preview" on:click={handleTermClick} on:keydown={handleKeydown} role="document" aria-label="Markdown preview with interactive highlights">
239
+ {@html processedHtml}
240
+ </div>
241
+ </div>
242
+ {/if}
243
+ {:else if edit_mode === 'tabs'}
244
+ <!-- Tab Mode: Switch between Edit and Preview -->
245
+ <div class="tab-content">
246
+ {#if currentTab === 'edit'}
247
+ <textarea
248
+ class="markdown-editor fullwidth"
249
+ bind:value={editingContent}
250
+ on:input={handleTextareaInput}
251
+ placeholder="Enter your markdown content..."
252
+ spellcheck="false"
253
+ ></textarea>
254
+ {:else if currentTab === 'preview'}
255
+ <div class="markdown-content preview" on:click={handleTermClick} on:keydown={handleKeydown} role="document" aria-label="Markdown preview with interactive highlights">
256
+ {@html processedHtml}
257
+ </div>
258
+ {/if}
259
+ </div>
260
+ {/if}
261
+ {:else}
262
+ <!-- View Mode: Regular markdown display -->
263
+ <div class="markdown-content" on:click={handleTermClick} on:keydown={handleKeydown} role="document" aria-label="Markdown content with interactive highlights">
264
+ {@html processedHtml}
265
+ </div>
266
+ {/if}
267
+ </div>
268
+
269
+ <!-- Side Panel (same as before) -->
270
+ {#if show_side_panel && selectedHighlight}
271
+ <div class="side-panel" style="width: {panel_width}">
272
+ <div class="panel-header">
273
+ <h3>{selectedHighlight.title || selectedHighlight.term || 'Highlighted Text'}</h3>
274
+ <button class="close-btn" on:click={closeSidePanel}>×</button>
275
+ </div>
276
+ <div class="panel-content">
277
+ {#if selectedHighlight.category}
278
+ <div class="category-badge" style="background-color: {selectedHighlight.color || '#e3f2fd'}">
279
+ {selectedHighlight.category}
280
+ </div>
281
+ {/if}
282
+ <div class="content-text">
283
+ {@html marked(selectedHighlight.content || 'No additional information available.')}
284
+ </div>
285
+ </div>
286
+ </div>
287
+ {/if}
288
+ </div>
289
+
290
+ <style>
291
+ .markdown-container {
292
+ display: flex;
293
+ flex-direction: column;
294
+ height: 100%;
295
+ position: relative;
296
+ transition: all 0.3s ease;
297
+ }
298
+
299
+ .edit-controls {
300
+ display: flex;
301
+ gap: var(--spacing-sm);
302
+ padding: var(--spacing-sm);
303
+ background: var(--background-fill-secondary);
304
+ border-bottom: 1px solid var(--border-color-primary);
305
+ align-items: center;
306
+ }
307
+
308
+ .edit-btn, .save-btn, .cancel-btn {
309
+ padding: var(--size-1) var(--size-2);
310
+ border: 1px solid var(--border-color-primary);
311
+ border-radius: var(--radius-sm);
312
+ background: var(--background-fill-primary);
313
+ cursor: pointer;
314
+ font-size: var(--text-sm);
315
+ transition: all 0.2s;
316
+ }
317
+
318
+ .edit-btn:hover {
319
+ background: var(--background-fill-secondary);
320
+ }
321
+
322
+ .save-btn {
323
+ background: var(--color-accent);
324
+ color: white;
325
+ border-color: var(--color-accent);
326
+ }
327
+
328
+ .save-btn:hover {
329
+ opacity: 0.9;
330
+ }
331
+
332
+ .cancel-btn {
333
+ background: var(--color-red-500);
334
+ color: white;
335
+ border-color: var(--color-red-500);
336
+ }
337
+
338
+ .cancel-btn:hover {
339
+ opacity: 0.9;
340
+ }
341
+
342
+ .tab-controls {
343
+ display: flex;
344
+ gap: 2px;
345
+ margin-left: auto;
346
+ }
347
+
348
+ .tab-btn {
349
+ padding: var(--size-1) var(--size-3);
350
+ border: 1px solid var(--border-color-primary);
351
+ background: var(--background-fill-primary);
352
+ cursor: pointer;
353
+ font-size: var(--text-sm);
354
+ border-radius: var(--radius-sm) var(--radius-sm) 0 0;
355
+ transition: all 0.2s;
356
+ }
357
+
358
+ .tab-btn.active {
359
+ background: var(--background-fill-secondary);
360
+ border-bottom-color: var(--background-fill-secondary);
361
+ }
362
+
363
+ .tab-btn:hover:not(.active) {
364
+ background: var(--background-fill-secondary);
365
+ opacity: 0.7;
366
+ }
367
+
368
+ .content-area {
369
+ flex: 1;
370
+ display: flex;
371
+ overflow: hidden;
372
+ }
373
+
374
+ .content-area.split-mode {
375
+ flex-direction: row;
376
+ }
377
+
378
+ .editor-section, .preview-section {
379
+ flex: 1;
380
+ display: flex;
381
+ flex-direction: column;
382
+ border-right: 1px solid var(--border-color-primary);
383
+ }
384
+
385
+ .preview-section {
386
+ border-right: none;
387
+ border-left: 1px solid var(--border-color-primary);
388
+ }
389
+
390
+ .editor-header, .preview-header {
391
+ padding: var(--size-2);
392
+ background: var(--background-fill-secondary);
393
+ border-bottom: 1px solid var(--border-color-primary);
394
+ }
395
+
396
+ .editor-header h4, .preview-header h4 {
397
+ margin: 0;
398
+ font-size: var(--text-sm);
399
+ font-weight: var(--weight-semibold);
400
+ color: var(--body-text-color);
401
+ }
402
+
403
+ .markdown-editor {
404
+ flex: 1;
405
+ width: 100%;
406
+ padding: var(--size-3);
407
+ border: none;
408
+ resize: none;
409
+ font-family: var(--font-mono);
410
+ font-size: var(--text-sm);
411
+ line-height: 1.5;
412
+ background: var(--background-fill-primary);
413
+ color: var(--body-text-color);
414
+ outline: none;
415
+ }
416
+
417
+ .markdown-editor.fullwidth {
418
+ height: 400px;
419
+ }
420
+
421
+ .tab-content {
422
+ flex: 1;
423
+ display: flex;
424
+ flex-direction: column;
425
+ }
426
+
427
+ .markdown-content {
428
+ flex: 1;
429
+ padding: var(--block-padding);
430
+ overflow-y: auto;
431
+ transition: margin-right 0.3s ease;
432
+ }
433
+
434
+ .markdown-content.preview {
435
+ background: var(--background-fill-primary);
436
+ }
437
+
438
+ .with-panel .markdown-content {
439
+ margin-right: var(--spacing-md);
440
+ }
441
+
442
+ .side-panel {
443
+ position: fixed;
444
+ top: 0;
445
+ right: 0;
446
+ height: 100vh;
447
+ background: var(--background-fill-primary);
448
+ border-left: 1px solid var(--border-color-primary);
449
+ box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
450
+ z-index: 1000;
451
+ overflow-y: auto;
452
+ transform: translateX(0);
453
+ transition: transform 0.3s ease;
454
+ }
455
+
456
+ .panel-header {
457
+ display: flex;
458
+ justify-content: space-between;
459
+ align-items: center;
460
+ padding: var(--size-4);
461
+ border-bottom: 1px solid var(--border-color-primary);
462
+ background: var(--background-fill-secondary);
463
+ }
464
+
465
+ .panel-header h3 {
466
+ margin: 0;
467
+ font-size: var(--text-lg);
468
+ font-weight: var(--weight-semibold);
469
+ color: var(--body-text-color);
470
+ }
471
+
472
+ .close-btn {
473
+ background: none;
474
+ border: none;
475
+ font-size: var(--text-xl);
476
+ cursor: pointer;
477
+ color: var(--body-text-color);
478
+ padding: var(--size-1);
479
+ border-radius: var(--radius-sm);
480
+ transition: background-color 0.2s;
481
+ }
482
+
483
+ .close-btn:hover {
484
+ background-color: var(--background-fill-primary);
485
+ }
486
+
487
+ .panel-content {
488
+ padding: var(--size-4);
489
+ }
490
+
491
+ .category-badge {
492
+ display: inline-block;
493
+ padding: var(--size-1) var(--size-2);
494
+ border-radius: var(--radius-full);
495
+ font-size: var(--text-sm);
496
+ font-weight: var(--weight-medium);
497
+ margin-bottom: var(--size-3);
498
+ color: var(--body-text-color);
499
+ }
500
+
501
+ .content-text {
502
+ line-height: 1.6;
503
+ color: var(--body-text-color);
504
+ }
505
+
506
+ /* Markdown styling (same as before) */
507
+ .markdown-content :global(h1) {
508
+ font-size: var(--text-2xl);
509
+ font-weight: var(--weight-bold);
510
+ margin: var(--size-4) 0 var(--size-2) 0;
511
+ color: var(--body-text-color);
512
+ }
513
+
514
+ .markdown-content :global(h2) {
515
+ font-size: var(--text-xl);
516
+ font-weight: var(--weight-semibold);
517
+ margin: var(--size-3) 0 var(--size-2) 0;
518
+ color: var(--body-text-color);
519
+ }
520
+
521
+ .markdown-content :global(h3) {
522
+ font-size: var(--text-lg);
523
+ font-weight: var(--weight-medium);
524
+ margin: var(--size-3) 0 var(--size-1) 0;
525
+ color: var(--body-text-color);
526
+ }
527
+
528
+ .markdown-content :global(p) {
529
+ margin: var(--size-2) 0;
530
+ line-height: 1.6;
531
+ color: var(--body-text-color);
532
+ }
533
+
534
+ .markdown-content :global(strong) {
535
+ font-weight: var(--weight-bold);
536
+ }
537
+
538
+ .markdown-content :global(em) {
539
+ font-style: italic;
540
+ }
541
+
542
+ .markdown-content :global(ul), .markdown-content :global(ol) {
543
+ margin: var(--size-2) 0;
544
+ padding-left: var(--size-4);
545
+ color: var(--body-text-color);
546
+ }
547
+
548
+ .markdown-content :global(li) {
549
+ margin: var(--size-1) 0;
550
+ }
551
+
552
+ .markdown-content :global(code) {
553
+ background-color: var(--background-fill-secondary);
554
+ padding: var(--size-1);
555
+ border-radius: var(--radius-sm);
556
+ font-family: var(--font-mono);
557
+ font-size: var(--text-sm);
558
+ }
559
+
560
+ .markdown-content :global(pre) {
561
+ background-color: var(--background-fill-secondary);
562
+ padding: var(--size-3);
563
+ border-radius: var(--radius-md);
564
+ overflow-x: auto;
565
+ margin: var(--size-2) 0;
566
+ }
567
+
568
+ .markdown-content :global(pre code) {
569
+ background: none;
570
+ padding: 0;
571
+ }
572
+
573
+ .markdown-content :global(.highlight-term:hover),
574
+ .markdown-content :global(.highlight-position:hover),
575
+ .markdown-content :global(.highlight-term:focus),
576
+ .markdown-content :global(.highlight-position:focus) {
577
+ opacity: 0.8;
578
+ transform: scale(1.02);
579
+ outline: 2px solid var(--color-accent);
580
+ outline-offset: 1px;
581
+ }
582
+
583
+ /* Responsive design */
584
+ @media (max-width: 768px) {
585
+ .side-panel {
586
+ width: 100% !important;
587
+ }
588
+
589
+ .with-panel .markdown-content {
590
+ margin-right: 0;
591
+ }
592
+
593
+ .content-area.split-mode {
594
+ flex-direction: column;
595
+ }
596
+
597
+ .editor-section, .preview-section {
598
+ border-right: none;
599
+ border-bottom: 1px solid var(--border-color-primary);
600
+ }
601
+
602
+ .preview-section {
603
+ border-left: none;
604
+ border-top: 1px solid var(--border-color-primary);
605
+ }
606
+ }
607
+ </style>
src/frontend/shared/InteractiveHighlightedtext.svelte ADDED
@@ -0,0 +1,510 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ const browser = typeof document !== "undefined";
3
+ import { get_next_color } from "@gradio/utils";
4
+ import type { SelectData } from "@gradio/utils";
5
+ import { createEventDispatcher, onMount } from "svelte";
6
+ import { correct_color_map, merge_elements } from "./utils";
7
+ import LabelInput from "./LabelInput.svelte";
8
+
9
+ export let value: {
10
+ token: string;
11
+ class_or_confidence: string | number | null;
12
+ }[] = [];
13
+ export let show_legend = false;
14
+ export let color_map: Record<string, string> = {};
15
+ export let selectable = false;
16
+
17
+ let activeElementIndex = -1;
18
+ let ctx: CanvasRenderingContext2D;
19
+ let _color_map: Record<string, { primary: string; secondary: string }> = {};
20
+ let active = "";
21
+ let selection: Selection | null;
22
+ let labelToEdit = -1;
23
+
24
+ onMount(() => {
25
+ const mouseUpHandler = (): void => {
26
+ selection = window.getSelection();
27
+ handleSelectionComplete();
28
+ window.removeEventListener("mouseup", mouseUpHandler);
29
+ };
30
+
31
+ window.addEventListener("mousedown", () => {
32
+ window.addEventListener("mouseup", mouseUpHandler);
33
+ });
34
+ });
35
+
36
+ async function handleTextSelected(
37
+ startIndex: number,
38
+ endIndex: number
39
+ ): Promise<void> {
40
+ if (
41
+ selection?.toString() &&
42
+ activeElementIndex !== -1 &&
43
+ value[activeElementIndex].token.toString().includes(selection.toString())
44
+ ) {
45
+ const tempFlag = Symbol();
46
+
47
+ const str = value[activeElementIndex].token;
48
+ const [before, selected, after] = [
49
+ str.substring(0, startIndex),
50
+ str.substring(startIndex, endIndex),
51
+ str.substring(endIndex)
52
+ ];
53
+
54
+ let tempValue: {
55
+ token: string;
56
+ class_or_confidence: string | number | null;
57
+ flag?: symbol;
58
+ }[] = [
59
+ ...value.slice(0, activeElementIndex),
60
+ { token: before, class_or_confidence: null },
61
+ {
62
+ token: selected,
63
+ class_or_confidence: mode === "scores" ? 1 : "label",
64
+ flag: tempFlag
65
+ }, // add a temp flag to the new highlighted text element
66
+ { token: after, class_or_confidence: null },
67
+ ...value.slice(activeElementIndex + 1)
68
+ ];
69
+
70
+ // store the index of the new highlighted text element and remove the flag
71
+ labelToEdit = tempValue.findIndex(({ flag }) => flag === tempFlag);
72
+ // tempValue[labelToEdit].pop();
73
+
74
+ // remove elements with empty labels
75
+ tempValue = tempValue.filter((item) => item.token.trim() !== "");
76
+ value = tempValue.map(({ flag, ...rest }) => rest);
77
+
78
+ handleValueChange();
79
+ document.getElementById(`label-input-${labelToEdit}`)?.focus();
80
+ }
81
+ }
82
+
83
+ const dispatch = createEventDispatcher<{
84
+ select: SelectData;
85
+ change: typeof value;
86
+ input: never;
87
+ }>();
88
+
89
+ function splitTextByNewline(text: string): string[] {
90
+ return text.split("\n");
91
+ }
92
+
93
+ function removeHighlightedText(index: number): void {
94
+ if (!value || index < 0 || index >= value.length) return;
95
+ value[index].class_or_confidence = null;
96
+ value = merge_elements(value, "equal");
97
+ handleValueChange();
98
+ window.getSelection()?.empty();
99
+ }
100
+
101
+ function handleValueChange(): void {
102
+ dispatch("change", value);
103
+ labelToEdit = -1;
104
+
105
+ // reset legend color maps
106
+ if (show_legend) {
107
+ color_map = {};
108
+ _color_map = {};
109
+ }
110
+ }
111
+
112
+ let mode: "categories" | "scores";
113
+
114
+ $: {
115
+ if (!color_map) {
116
+ color_map = {};
117
+ }
118
+ if (value.length > 0) {
119
+ for (let entry of value) {
120
+ if (entry.class_or_confidence !== null) {
121
+ if (typeof entry.class_or_confidence === "string") {
122
+ mode = "categories";
123
+ if (!(entry.class_or_confidence in color_map)) {
124
+ let color = get_next_color(Object.keys(color_map).length);
125
+ color_map[entry.class_or_confidence] = color;
126
+ }
127
+ } else {
128
+ mode = "scores";
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ correct_color_map(color_map, _color_map, browser, ctx);
135
+ }
136
+
137
+ function handle_mouseover(label: string): void {
138
+ active = label;
139
+ }
140
+ function handle_mouseout(): void {
141
+ active = "";
142
+ }
143
+
144
+ async function handleKeydownSelection(event: KeyboardEvent): Promise<void> {
145
+ selection = window.getSelection();
146
+
147
+ if (event.key === "Enter") {
148
+ handleSelectionComplete();
149
+ }
150
+ }
151
+
152
+ function handleSelectionComplete(): void {
153
+ if (selection && selection?.toString().trim() !== "") {
154
+ const textBeginningIndex = selection.getRangeAt(0).startOffset;
155
+ const textEndIndex = selection.getRangeAt(0).endOffset;
156
+ handleTextSelected(textBeginningIndex, textEndIndex);
157
+ }
158
+ }
159
+
160
+ function handleSelect(
161
+ i: number,
162
+ text: string,
163
+ class_or_confidence: string | number | null
164
+ ): void {
165
+ dispatch("select", {
166
+ index: i,
167
+ value: [text, class_or_confidence]
168
+ });
169
+ }
170
+ </script>
171
+
172
+ <div class="container">
173
+ {#if mode === "categories"}
174
+ {#if show_legend}
175
+ <div
176
+ class="class_or_confidence-legend"
177
+ data-testid="highlighted-text:class_or_confidence-legend"
178
+ >
179
+ {#if _color_map}
180
+ {#each Object.entries(_color_map) as [class_or_confidence, color], i}
181
+ <div
182
+ role="button"
183
+ aria-roledescription="Categories of highlighted text. Hover to see text with this class_or_confidence highlighted."
184
+ tabindex="0"
185
+ on:mouseover={() => handle_mouseover(class_or_confidence)}
186
+ on:focus={() => handle_mouseover(class_or_confidence)}
187
+ on:mouseout={() => handle_mouseout()}
188
+ on:blur={() => handle_mouseout()}
189
+ class="class_or_confidence-label"
190
+ style={"background-color:" + color.secondary}
191
+ >
192
+ {class_or_confidence}
193
+ </div>
194
+ {/each}
195
+ {/if}
196
+ </div>
197
+ {/if}
198
+
199
+ <div class="textfield">
200
+ {#each value as { token, class_or_confidence }, i}
201
+ {#each splitTextByNewline(token) as line, j}
202
+ {#if line.trim() !== ""}
203
+ <span class="text-class_or_confidence-container">
204
+ <span
205
+ role="button"
206
+ tabindex="0"
207
+ class="textspan"
208
+ style:background-color={class_or_confidence === null ||
209
+ (active && active !== class_or_confidence)
210
+ ? ""
211
+ : class_or_confidence && _color_map[class_or_confidence]
212
+ ? _color_map[class_or_confidence].secondary
213
+ : ""}
214
+ class:no-cat={class_or_confidence === null ||
215
+ (active && active !== class_or_confidence)}
216
+ class:hl={class_or_confidence !== null}
217
+ class:selectable
218
+ on:click={() => {
219
+ if (class_or_confidence !== null) {
220
+ handleSelect(i, token, class_or_confidence);
221
+ }
222
+ }}
223
+ on:keydown={(e) => {
224
+ if (class_or_confidence !== null) {
225
+ labelToEdit = i;
226
+ handleSelect(i, token, class_or_confidence);
227
+ } else {
228
+ handleKeydownSelection(e);
229
+ }
230
+ }}
231
+ on:focus={() => (activeElementIndex = i)}
232
+ on:mouseover={() => (activeElementIndex = i)}
233
+ >
234
+ <span
235
+ class:no-label={class_or_confidence === null}
236
+ class="text"
237
+ role="button"
238
+ on:keydown={(e) => handleKeydownSelection(e)}
239
+ on:focus={() => (activeElementIndex = i)}
240
+ on:mouseover={() => (activeElementIndex = i)}
241
+ on:click={() => (labelToEdit = i)}
242
+ tabindex="0">{line}</span
243
+ >
244
+ {#if !show_legend && class_or_confidence !== null && labelToEdit !== i}
245
+ <span
246
+ id={`label-tag-${i}`}
247
+ class="label"
248
+ role="button"
249
+ tabindex="0"
250
+ style:background-color={class_or_confidence === null ||
251
+ (active && active !== class_or_confidence)
252
+ ? ""
253
+ : _color_map[class_or_confidence].primary}
254
+ on:click={() => (labelToEdit = i)}
255
+ on:keydown={() => (labelToEdit = i)}
256
+ >
257
+ {class_or_confidence}
258
+ </span>
259
+ {/if}
260
+ {#if labelToEdit === i && class_or_confidence !== null}
261
+ &nbsp;
262
+ <LabelInput
263
+ bind:value
264
+ {labelToEdit}
265
+ category={class_or_confidence}
266
+ {active}
267
+ {_color_map}
268
+ indexOfLabel={i}
269
+ text={token}
270
+ {handleValueChange}
271
+ />
272
+ {/if}
273
+ </span>
274
+ {#if class_or_confidence !== null}
275
+ <span
276
+ class="label-clear-button"
277
+ role="button"
278
+ aria-roledescription="Remove label from text"
279
+ tabindex="0"
280
+ on:click={() => removeHighlightedText(i)}
281
+ on:keydown={(event) => {
282
+ if (event.key === "Enter") {
283
+ removeHighlightedText(i);
284
+ }
285
+ }}
286
+
287
+ </span>
288
+ {/if}
289
+ </span>
290
+ {/if}
291
+ {#if j < splitTextByNewline(token).length - 1}
292
+ <br />
293
+ {/if}
294
+ {/each}
295
+ {/each}
296
+ </div>
297
+ {:else}
298
+ {#if show_legend}
299
+ <div class="color-legend" data-testid="highlighted-text:color-legend">
300
+ <span>-1</span>
301
+ <span>0</span>
302
+ <span>+1</span>
303
+ </div>
304
+ {/if}
305
+
306
+ <div class="textfield" data-testid="highlighted-text:textfield">
307
+ {#each value as { token, class_or_confidence }, i}
308
+ {@const score =
309
+ typeof class_or_confidence === "string"
310
+ ? parseInt(class_or_confidence)
311
+ : class_or_confidence}
312
+ <span class="score-text-container">
313
+ <span
314
+ class="textspan score-text"
315
+ role="button"
316
+ tabindex="0"
317
+ class:no-cat={class_or_confidence === null ||
318
+ (active && active !== class_or_confidence)}
319
+ class:hl={class_or_confidence !== null}
320
+ on:mouseover={() => (activeElementIndex = i)}
321
+ on:focus={() => (activeElementIndex = i)}
322
+ on:click={() => (labelToEdit = i)}
323
+ on:keydown={(e) => {
324
+ if (e.key === "Enter") {
325
+ labelToEdit = i;
326
+ }
327
+ }}
328
+ style={"background-color: rgba(" +
329
+ (score && score < 0
330
+ ? "128, 90, 213," + -score
331
+ : "239, 68, 60," + score) +
332
+ ")"}
333
+ >
334
+ <span class="text">{token}</span>
335
+ {#if class_or_confidence && labelToEdit === i}
336
+ <LabelInput
337
+ bind:value
338
+ {labelToEdit}
339
+ {_color_map}
340
+ category={class_or_confidence}
341
+ {active}
342
+ indexOfLabel={i}
343
+ text={token}
344
+ {handleValueChange}
345
+ isScoresMode
346
+ />
347
+ {/if}
348
+ </span>
349
+ {#if class_or_confidence && activeElementIndex === i}
350
+ <span
351
+ class="label-clear-button"
352
+ role="button"
353
+ aria-roledescription="Remove label from text"
354
+ tabindex="0"
355
+ on:click={() => removeHighlightedText(i)}
356
+ on:keydown={(event) => {
357
+ if (event.key === "Enter") {
358
+ removeHighlightedText(i);
359
+ }
360
+ }}
361
+
362
+ </span>
363
+ {/if}
364
+ </span>
365
+ {/each}
366
+ </div>
367
+ {/if}
368
+ </div>
369
+
370
+ <style>
371
+ .label-clear-button {
372
+ display: none;
373
+ border-radius: var(--radius-xs);
374
+ padding-top: 2.5px;
375
+ padding-right: var(--size-1);
376
+ padding-bottom: 3.5px;
377
+ padding-left: var(--size-1);
378
+ color: black;
379
+ background-color: var(--background-fill-secondary);
380
+ user-select: none;
381
+ position: relative;
382
+ left: -3px;
383
+ border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
384
+ color: var(--block-label-text-color);
385
+ }
386
+
387
+ .text-class_or_confidence-container:hover .label-clear-button,
388
+ .text-class_or_confidence-container:focus-within .label-clear-button,
389
+ .score-text-container:hover .label-clear-button,
390
+ .score-text-container:focus-within .label-clear-button {
391
+ display: inline;
392
+ }
393
+
394
+ .text-class_or_confidence-container:hover .textspan.hl,
395
+ .text-class_or_confidence-container:focus-within .textspan.hl,
396
+ .score-text:hover {
397
+ border-radius: var(--radius-xs) 0 0 var(--radius-xs);
398
+ }
399
+
400
+ .container {
401
+ display: flex;
402
+ flex-direction: column;
403
+ gap: var(--spacing-sm);
404
+ padding: var(--block-padding);
405
+ }
406
+
407
+ .hl {
408
+ margin-left: var(--size-1);
409
+ transition: background-color 0.3s;
410
+ user-select: none;
411
+ }
412
+
413
+ .textspan:last-child > .label {
414
+ margin-right: 0;
415
+ }
416
+
417
+ .class_or_confidence-legend {
418
+ display: flex;
419
+ flex-wrap: wrap;
420
+ gap: var(--spacing-sm);
421
+ color: black;
422
+ }
423
+
424
+ .class_or_confidence-label {
425
+ cursor: pointer;
426
+ border-radius: var(--radius-xs);
427
+ padding-right: var(--size-2);
428
+ padding-left: var(--size-2);
429
+ font-weight: var(--weight-semibold);
430
+ }
431
+
432
+ .color-legend {
433
+ display: flex;
434
+ justify-content: space-between;
435
+ border-radius: var(--radius-xs);
436
+ background: linear-gradient(
437
+ to right,
438
+ var(--color-purple),
439
+ rgba(255, 255, 255, 0),
440
+ var(--color-red)
441
+ );
442
+ padding: var(--size-1) var(--size-2);
443
+ font-weight: var(--weight-semibold);
444
+ }
445
+
446
+ .textfield {
447
+ box-sizing: border-box;
448
+ border-radius: var(--radius-xs);
449
+ background: var(--background-fill-primary);
450
+ background-color: transparent;
451
+ max-width: var(--size-full);
452
+ line-height: var(--scale-4);
453
+ word-break: break-all;
454
+ }
455
+
456
+ .textspan {
457
+ transition: 150ms;
458
+ border-radius: var(--radius-xs);
459
+ padding-top: 2.5px;
460
+ padding-right: var(--size-1);
461
+ padding-bottom: 3.5px;
462
+ padding-left: var(--size-1);
463
+ color: black;
464
+ cursor: text;
465
+ }
466
+
467
+ .label {
468
+ transition: 150ms;
469
+ margin-top: 1px;
470
+ border-radius: var(--radius-xs);
471
+ padding: 1px 5px;
472
+ color: var(--body-text-color);
473
+ color: white;
474
+ font-weight: var(--weight-bold);
475
+ font-size: var(--text-sm);
476
+ text-transform: uppercase;
477
+ user-select: none;
478
+ }
479
+
480
+ .text {
481
+ color: black;
482
+ white-space: pre-wrap;
483
+ }
484
+
485
+ .textspan.hl {
486
+ user-select: none;
487
+ }
488
+
489
+ .score-text-container {
490
+ margin-right: var(--size-1);
491
+ }
492
+
493
+ .score-text .text {
494
+ color: var(--body-text-color);
495
+ }
496
+
497
+ .no-cat {
498
+ color: var(--body-text-color);
499
+ }
500
+
501
+ .no-label {
502
+ color: var(--body-text-color);
503
+ user-select: text;
504
+ }
505
+
506
+ .selectable {
507
+ cursor: text;
508
+ user-select: text;
509
+ }
510
+ </style>
src/frontend/shared/LabelInput.svelte ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ type HighlightedTextType = {
3
+ token: string;
4
+ class_or_confidence: string | number | null;
5
+ };
6
+
7
+ export let value: HighlightedTextType[];
8
+ export let category: string | number | null;
9
+ export let active: string;
10
+ export let labelToEdit: number;
11
+ export let indexOfLabel: number;
12
+ export let text: string;
13
+ export let handleValueChange: () => void;
14
+ export let isScoresMode = false;
15
+ export let _color_map: Record<string, { primary: string; secondary: string }>;
16
+
17
+ let _input_value = category;
18
+
19
+ function handleInput(e: Event): void {
20
+ let target = e.target as HTMLInputElement;
21
+ if (target) {
22
+ _input_value = target.value;
23
+ }
24
+ }
25
+
26
+ function updateLabelValue(
27
+ e: Event,
28
+ elementIndex: number,
29
+ text: string
30
+ ): void {
31
+ let target = e.target as HTMLInputElement;
32
+ value = [
33
+ ...value.slice(0, elementIndex),
34
+ {
35
+ token: text,
36
+ class_or_confidence:
37
+ target.value === ""
38
+ ? null
39
+ : isScoresMode
40
+ ? Number(target.value)
41
+ : target.value
42
+ },
43
+ ...value.slice(elementIndex + 1)
44
+ ];
45
+
46
+ handleValueChange();
47
+ }
48
+
49
+ function clearPlaceHolderOnFocus(e: FocusEvent): void {
50
+ let target = e.target as HTMLInputElement;
51
+ if (target && target.placeholder) target.placeholder = "";
52
+ }
53
+ </script>
54
+
55
+ <!-- svelte-ignore a11y-autofocus -->
56
+ <!-- autofocus should not be disorienting for a screen reader users
57
+ as input is only rendered once a new label is created -->
58
+ {#if !isScoresMode}
59
+ <input
60
+ class="label-input"
61
+ autofocus
62
+ id={`label-input-${indexOfLabel}`}
63
+ type="text"
64
+ placeholder="label"
65
+ value={category}
66
+ style:background-color={category === null || (active && active !== category)
67
+ ? ""
68
+ : _color_map[category].primary}
69
+ style:width={_input_value
70
+ ? _input_value.toString()?.length + 4 + "ch"
71
+ : "8ch"}
72
+ on:input={handleInput}
73
+ on:blur={(e) => updateLabelValue(e, indexOfLabel, text)}
74
+ on:keydown={(e) => {
75
+ if (e.key === "Enter") {
76
+ updateLabelValue(e, indexOfLabel, text);
77
+ labelToEdit = -1;
78
+ }
79
+ }}
80
+ on:focus={clearPlaceHolderOnFocus}
81
+ />
82
+ {:else}
83
+ <input
84
+ class="label-input"
85
+ autofocus
86
+ type="number"
87
+ step="0.1"
88
+ style={"background-color: rgba(" +
89
+ (typeof category === "number" && category < 0
90
+ ? "128, 90, 213," + -category
91
+ : "239, 68, 60," + category) +
92
+ ")"}
93
+ value={category}
94
+ style:width="7ch"
95
+ on:input={handleInput}
96
+ on:blur={(e) => updateLabelValue(e, indexOfLabel, text)}
97
+ on:keydown={(e) => {
98
+ if (e.key === "Enter") {
99
+ updateLabelValue(e, indexOfLabel, text);
100
+ labelToEdit = -1;
101
+ }
102
+ }}
103
+ />
104
+ {/if}
105
+
106
+ <style>
107
+ .label-input {
108
+ transition: 150ms;
109
+ margin-top: 1px;
110
+ margin-right: calc(var(--size-1));
111
+ border-radius: var(--radius-xs);
112
+ padding: 1px 5px;
113
+ color: black;
114
+ font-weight: var(--weight-bold);
115
+ font-size: var(--text-sm);
116
+ text-transform: uppercase;
117
+ line-height: 1;
118
+ color: white;
119
+ }
120
+
121
+ .label-input::placeholder {
122
+ color: rgba(1, 1, 1, 0.5);
123
+ }
124
+ </style>
src/frontend/shared/MarkdownRenderer.svelte ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { marked } from 'marked';
3
+ import { createEventDispatcher } from 'svelte';
4
+ import type { SelectData } from '@gradio/utils';
5
+
6
+ export let markdown_content: string = '';
7
+ export let highlights: Array<{
8
+ term?: string;
9
+ position?: number[];
10
+ title: string;
11
+ content: string;
12
+ category: string;
13
+ color: string;
14
+ }> = [];
15
+ export let show_side_panel: boolean = true;
16
+ export let panel_width: string = '300px';
17
+
18
+ const dispatch = createEventDispatcher<{
19
+ select: SelectData;
20
+ }>();
21
+
22
+ let selectedHighlight: typeof highlights[0] | null = null;
23
+ let processedHtml: string = '';
24
+
25
+ // Process markdown and apply highlighting
26
+ $: {
27
+ if (markdown_content) {
28
+ // First, parse the markdown to HTML
29
+ let html = marked(markdown_content);
30
+
31
+ // Separate position-based and term-based highlights
32
+ const positionHighlights = highlights.filter(h => h.position && h.position.length === 2);
33
+ const termHighlights = highlights.filter(h => h.term && h.term.trim());
34
+
35
+ // Apply term-based highlights to the HTML first
36
+ termHighlights.forEach(highlight => {
37
+ if (highlight.term && highlight.term.trim()) {
38
+ const index = highlights.indexOf(highlight);
39
+ const regex = new RegExp(`\\b${escapeRegex(highlight.term)}\\b`, 'gi');
40
+ html = html.replace(regex, (match) => {
41
+ const color = highlight.color || '#e3f2fd';
42
+ return `<span class="highlight-term"
43
+ data-index="${index}"
44
+ data-term="${highlight.term}"
45
+ style="background-color: ${color}; cursor: pointer; padding: 2px 4px; border-radius: 3px; transition: all 0.2s;"
46
+ role="button"
47
+ tabindex="0"
48
+ aria-label="Highlighted term: ${highlight.term}">
49
+ ${match}
50
+ </span>`;
51
+ });
52
+ }
53
+ });
54
+
55
+ // Apply position-based highlights to the HTML
56
+ // Convert position-based highlights to term-based for simplicity and reliability
57
+ positionHighlights.forEach(highlight => {
58
+ const [start, end] = highlight.position!;
59
+ if (start >= 0 && end <= markdown_content.length && start < end) {
60
+ const targetText = markdown_content.substring(start, end);
61
+ const color = highlight.color || '#e3f2fd';
62
+ const index = highlights.indexOf(highlight);
63
+
64
+ // Escape the target text for regex and handle multiline/whitespace
65
+ const escapedText = escapeRegex(targetText).replace(/\s+/g, '\\s+');
66
+
67
+ // Create a regex that only matches text content (not inside HTML tags)
68
+ const textRegex = new RegExp(`\\b${escapedText}\\b`, 'gi');
69
+
70
+ // Apply the highlight - this will work like term-based highlighting
71
+ // but using the exact text from the position
72
+ html = html.replace(textRegex, (match) => {
73
+ return `<span class="highlight-position"
74
+ data-index="${index}"
75
+ data-text="${encodeURIComponent(targetText)}"
76
+ style="background-color: ${color}; cursor: pointer; padding: 2px 4px; border-radius: 3px; transition: all 0.2s;"
77
+ role="button"
78
+ tabindex="0"
79
+ aria-label="Highlighted text: ${targetText.replace(/"/g, '&quot;')}">
80
+ ${match}
81
+ </span>`;
82
+ });
83
+ }
84
+ });
85
+
86
+ processedHtml = html;
87
+ }
88
+ }
89
+
90
+ function escapeRegex(string: string): string {
91
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
92
+ }
93
+
94
+ function handleTermClick(event: Event) {
95
+ const target = event.target as HTMLElement;
96
+ if (target.classList.contains('highlight-term') || target.classList.contains('highlight-position')) {
97
+ const index = parseInt(target.dataset.index || '0');
98
+ const highlight = highlights[index];
99
+ if (highlight) {
100
+ selectedHighlight = highlight;
101
+ dispatch('select', {
102
+ index,
103
+ value: highlight
104
+ });
105
+ }
106
+ }
107
+ }
108
+
109
+ function handleKeydown(event: KeyboardEvent) {
110
+ const target = event.target as HTMLElement;
111
+ if ((event.key === 'Enter' || event.key === ' ') &&
112
+ (target.classList.contains('highlight-term') || target.classList.contains('highlight-position'))) {
113
+ event.preventDefault();
114
+ handleTermClick(event);
115
+ }
116
+ }
117
+
118
+ function closeSidePanel() {
119
+ selectedHighlight = null;
120
+ }
121
+ </script>
122
+
123
+ <div class="markdown-container" class:with-panel={show_side_panel && selectedHighlight}>
124
+ <div class="markdown-content" on:click={handleTermClick} on:keydown={handleKeydown} role="document" aria-label="Markdown content with interactive highlights">
125
+ {@html processedHtml}
126
+ </div>
127
+
128
+ {#if show_side_panel && selectedHighlight}
129
+ <div class="side-panel" style="width: {panel_width}">
130
+ <div class="panel-header">
131
+ <h3>{selectedHighlight.title || selectedHighlight.term || 'Highlighted Text'}</h3>
132
+ <button class="close-btn" on:click={closeSidePanel}>×</button>
133
+ </div>
134
+ <div class="panel-content">
135
+ {#if selectedHighlight.category}
136
+ <div class="category-badge" style="background-color: {selectedHighlight.color || '#e3f2fd'}">
137
+ {selectedHighlight.category}
138
+ </div>
139
+ {/if}
140
+ <div class="content-text">
141
+ {@html marked(selectedHighlight.content || 'No additional information available.')}
142
+ </div>
143
+ </div>
144
+ </div>
145
+ {/if}
146
+ </div>
147
+
148
+ <style>
149
+ .markdown-container {
150
+ display: flex;
151
+ height: 100%;
152
+ position: relative;
153
+ transition: all 0.3s ease;
154
+ }
155
+
156
+ .markdown-content {
157
+ flex: 1;
158
+ padding: var(--block-padding);
159
+ overflow-y: auto;
160
+ transition: margin-right 0.3s ease;
161
+ }
162
+
163
+ .with-panel .markdown-content {
164
+ margin-right: var(--spacing-md);
165
+ }
166
+
167
+ .side-panel {
168
+ position: fixed;
169
+ top: 0;
170
+ right: 0;
171
+ height: 100vh;
172
+ background: var(--background-fill-primary);
173
+ border-left: 1px solid var(--border-color-primary);
174
+ box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
175
+ z-index: 1000;
176
+ overflow-y: auto;
177
+ transform: translateX(0);
178
+ transition: transform 0.3s ease;
179
+ }
180
+
181
+ .panel-header {
182
+ display: flex;
183
+ justify-content: space-between;
184
+ align-items: center;
185
+ padding: var(--size-4);
186
+ border-bottom: 1px solid var(--border-color-primary);
187
+ background: var(--background-fill-secondary);
188
+ }
189
+
190
+ .panel-header h3 {
191
+ margin: 0;
192
+ font-size: var(--text-lg);
193
+ font-weight: var(--weight-semibold);
194
+ color: var(--body-text-color);
195
+ }
196
+
197
+ .close-btn {
198
+ background: none;
199
+ border: none;
200
+ font-size: var(--text-xl);
201
+ cursor: pointer;
202
+ color: var(--body-text-color);
203
+ padding: var(--size-1);
204
+ border-radius: var(--radius-sm);
205
+ transition: background-color 0.2s;
206
+ }
207
+
208
+ .close-btn:hover {
209
+ background-color: var(--background-fill-primary);
210
+ }
211
+
212
+ .panel-content {
213
+ padding: var(--size-4);
214
+ }
215
+
216
+ .category-badge {
217
+ display: inline-block;
218
+ padding: var(--size-1) var(--size-2);
219
+ border-radius: var(--radius-full);
220
+ font-size: var(--text-sm);
221
+ font-weight: var(--weight-medium);
222
+ margin-bottom: var(--size-3);
223
+ color: var(--body-text-color);
224
+ }
225
+
226
+ .content-text {
227
+ line-height: 1.6;
228
+ color: var(--body-text-color);
229
+ }
230
+
231
+ /* Markdown styling */
232
+ .markdown-content :global(h1) {
233
+ font-size: var(--text-2xl);
234
+ font-weight: var(--weight-bold);
235
+ margin: var(--size-4) 0 var(--size-2) 0;
236
+ color: var(--body-text-color);
237
+ }
238
+
239
+ .markdown-content :global(h2) {
240
+ font-size: var(--text-xl);
241
+ font-weight: var(--weight-semibold);
242
+ margin: var(--size-3) 0 var(--size-2) 0;
243
+ color: var(--body-text-color);
244
+ }
245
+
246
+ .markdown-content :global(h3) {
247
+ font-size: var(--text-lg);
248
+ font-weight: var(--weight-medium);
249
+ margin: var(--size-3) 0 var(--size-1) 0;
250
+ color: var(--body-text-color);
251
+ }
252
+
253
+ .markdown-content :global(p) {
254
+ margin: var(--size-2) 0;
255
+ line-height: 1.6;
256
+ color: var(--body-text-color);
257
+ }
258
+
259
+ .markdown-content :global(strong) {
260
+ font-weight: var(--weight-bold);
261
+ }
262
+
263
+ .markdown-content :global(em) {
264
+ font-style: italic;
265
+ }
266
+
267
+ .markdown-content :global(ul), .markdown-content :global(ol) {
268
+ margin: var(--size-2) 0;
269
+ padding-left: var(--size-4);
270
+ color: var(--body-text-color);
271
+ }
272
+
273
+ .markdown-content :global(li) {
274
+ margin: var(--size-1) 0;
275
+ }
276
+
277
+ .markdown-content :global(code) {
278
+ background-color: var(--background-fill-secondary);
279
+ padding: var(--size-1);
280
+ border-radius: var(--radius-sm);
281
+ font-family: var(--font-mono);
282
+ font-size: var(--text-sm);
283
+ }
284
+
285
+ .markdown-content :global(pre) {
286
+ background-color: var(--background-fill-secondary);
287
+ padding: var(--size-3);
288
+ border-radius: var(--radius-md);
289
+ overflow-x: auto;
290
+ margin: var(--size-2) 0;
291
+ }
292
+
293
+ .markdown-content :global(pre code) {
294
+ background: none;
295
+ padding: 0;
296
+ }
297
+
298
+ .markdown-content :global(.highlight-term:hover),
299
+ .markdown-content :global(.highlight-position:hover),
300
+ .markdown-content :global(.highlight-term:focus),
301
+ .markdown-content :global(.highlight-position:focus) {
302
+ opacity: 0.8;
303
+ transform: scale(1.02);
304
+ outline: 2px solid var(--color-accent);
305
+ outline-offset: 1px;
306
+ }
307
+
308
+ /* Responsive design */
309
+ @media (max-width: 768px) {
310
+ .side-panel {
311
+ width: 100% !important;
312
+ }
313
+
314
+ .with-panel .markdown-content {
315
+ margin-right: 0;
316
+ }
317
+ }
318
+ </style>
src/frontend/shared/StaticHighlightedtext.svelte ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ const browser = typeof document !== "undefined";
3
+ import { get_next_color } from "@gradio/utils";
4
+ import type { SelectData } from "@gradio/utils";
5
+ import { createEventDispatcher } from "svelte";
6
+ import { correct_color_map } from "./utils";
7
+
8
+ export let value: {
9
+ token: string;
10
+ class_or_confidence: string | number | null;
11
+ }[] = [];
12
+ export let show_legend = false;
13
+ export let show_inline_category = true;
14
+ export let color_map: Record<string, string> = {};
15
+ export let selectable = false;
16
+
17
+ let ctx: CanvasRenderingContext2D;
18
+ let _color_map: Record<string, { primary: string; secondary: string }> = {};
19
+ let active = "";
20
+
21
+ function splitTextByNewline(text: string): string[] {
22
+ return text.split("\n");
23
+ }
24
+
25
+ const dispatch = createEventDispatcher<{
26
+ select: SelectData;
27
+ }>();
28
+
29
+ let mode: "categories" | "scores";
30
+
31
+ $: {
32
+ if (!color_map) {
33
+ color_map = {};
34
+ }
35
+ if (value.length > 0) {
36
+ for (let entry of value) {
37
+ if (entry.class_or_confidence !== null) {
38
+ if (typeof entry.class_or_confidence === "string") {
39
+ mode = "categories";
40
+ if (!(entry.class_or_confidence in color_map)) {
41
+ let color = get_next_color(Object.keys(color_map).length);
42
+ color_map[entry.class_or_confidence] = color;
43
+ }
44
+ } else {
45
+ mode = "scores";
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ correct_color_map(color_map, _color_map, browser, ctx);
52
+ }
53
+
54
+ function handle_mouseover(label: string): void {
55
+ active = label;
56
+ }
57
+ function handle_mouseout(): void {
58
+ active = "";
59
+ }
60
+ </script>
61
+
62
+ <!--
63
+ @todo victor: try reimplementing without flex (negative margins on container to avoid left margin on linebreak).
64
+ If not possible hijack the copy execution like this:
65
+
66
+ <svelte:window
67
+ on:copy|preventDefault={() => {
68
+ const selection =.getSelection()?.toString();
69
+ console.log(selection?.replaceAll("\n", " "));
70
+ }}
71
+ />
72
+ -->
73
+
74
+ <div class="container">
75
+ {#if mode === "categories"}
76
+ {#if show_legend}
77
+ <div
78
+ class="category-legend"
79
+ data-testid="highlighted-text:category-legend"
80
+ >
81
+ {#each Object.entries(_color_map) as [category, color], i}
82
+ <!-- TODO: fix -->
83
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
84
+ <div
85
+ on:mouseover={() => handle_mouseover(category)}
86
+ on:focus={() => handle_mouseover(category)}
87
+ on:mouseout={() => handle_mouseout()}
88
+ on:blur={() => handle_mouseout()}
89
+ class="category-label"
90
+ style={"background-color:" + color.secondary}
91
+ >
92
+ {category}
93
+ </div>
94
+ {/each}
95
+ </div>
96
+ {/if}
97
+ <div class="textfield">
98
+ {#each value as v, i}
99
+ {#each splitTextByNewline(v.token) as line, j}
100
+ {#if line.trim() !== ""}
101
+ <!-- TODO: fix -->
102
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
103
+ <!-- svelte-ignore a11y-click-events-have-key-events-->
104
+ <span
105
+ class="textspan"
106
+ style:background-color={v.class_or_confidence === null ||
107
+ (active && active !== v.class_or_confidence)
108
+ ? ""
109
+ : _color_map[v.class_or_confidence].secondary}
110
+ class:no-cat={v.class_or_confidence === null ||
111
+ (active && active !== v.class_or_confidence)}
112
+ class:hl={v.class_or_confidence !== null}
113
+ class:selectable
114
+ on:click={() => {
115
+ dispatch("select", {
116
+ index: i,
117
+ value: [v.token, v.class_or_confidence]
118
+ });
119
+ }}
120
+ >
121
+ <span
122
+ class:no-label={v.class_or_confidence === null ||
123
+ !_color_map[v.class_or_confidence]}
124
+ class="text">{line}</span
125
+ >
126
+ {#if !show_legend && show_inline_category && v.class_or_confidence !== null}
127
+ &nbsp;
128
+ <span
129
+ class="label"
130
+ style:background-color={v.class_or_confidence === null ||
131
+ (active && active !== v.class_or_confidence)
132
+ ? ""
133
+ : _color_map[v.class_or_confidence].primary}
134
+ >
135
+ {v.class_or_confidence}
136
+ </span>
137
+ {/if}
138
+ </span>
139
+ {/if}
140
+ {#if j < splitTextByNewline(v.token).length - 1}
141
+ <br />
142
+ {/if}
143
+ {/each}
144
+ {/each}
145
+ </div>
146
+ {:else}
147
+ {#if show_legend}
148
+ <div class="color-legend" data-testid="highlighted-text:color-legend">
149
+ <span>-1</span>
150
+ <span>0</span>
151
+ <span>+1</span>
152
+ </div>
153
+ {/if}
154
+ <div class="textfield" data-testid="highlighted-text:textfield">
155
+ {#each value as v}
156
+ {@const score =
157
+ typeof v.class_or_confidence === "string"
158
+ ? parseInt(v.class_or_confidence)
159
+ : v.class_or_confidence}
160
+ <span
161
+ class="textspan score-text"
162
+ style={"background-color: rgba(" +
163
+ (score && score < 0
164
+ ? "128, 90, 213," + -score
165
+ : "239, 68, 60," + score) +
166
+ ")"}
167
+ >
168
+ <span class="text">{v.token}</span>
169
+ </span>
170
+ {/each}
171
+ </div>
172
+ {/if}
173
+ </div>
174
+
175
+ <style>
176
+ .container {
177
+ display: flex;
178
+ flex-direction: column;
179
+ gap: var(--spacing-sm);
180
+ padding: var(--block-padding);
181
+ }
182
+ .hl + .hl {
183
+ margin-left: var(--size-1);
184
+ }
185
+
186
+ .textspan:last-child > .label {
187
+ margin-right: 0;
188
+ }
189
+
190
+ .category-legend {
191
+ display: flex;
192
+ flex-wrap: wrap;
193
+ gap: var(--spacing-sm);
194
+ color: black;
195
+ }
196
+
197
+ .category-label {
198
+ cursor: pointer;
199
+ border-radius: var(--radius-xs);
200
+ padding-right: var(--size-2);
201
+ padding-left: var(--size-2);
202
+ font-weight: var(--weight-semibold);
203
+ }
204
+
205
+ .color-legend {
206
+ display: flex;
207
+ justify-content: space-between;
208
+ border-radius: var(--radius-xs);
209
+ background: linear-gradient(
210
+ to right,
211
+ var(--color-purple),
212
+ rgba(255, 255, 255, 0),
213
+ var(--color-red)
214
+ );
215
+ padding: var(--size-1) var(--size-2);
216
+ font-weight: var(--weight-semibold);
217
+ }
218
+
219
+ .textfield {
220
+ box-sizing: border-box;
221
+ border-radius: var(--radius-xs);
222
+ background: var(--background-fill-primary);
223
+ background-color: transparent;
224
+ max-width: var(--size-full);
225
+ line-height: var(--scale-4);
226
+ word-break: break-all;
227
+ }
228
+
229
+ .textspan {
230
+ transition: 150ms;
231
+ border-radius: var(--radius-xs);
232
+ padding-top: 2.5px;
233
+ padding-right: var(--size-1);
234
+ padding-bottom: 3.5px;
235
+ padding-left: var(--size-1);
236
+ color: black;
237
+ }
238
+
239
+ .label {
240
+ transition: 150ms;
241
+ margin-top: 1px;
242
+ border-radius: var(--radius-xs);
243
+ padding: 1px 5px;
244
+ color: var(--body-text-color);
245
+ color: white;
246
+ font-weight: var(--weight-bold);
247
+ font-size: var(--text-sm);
248
+ text-transform: uppercase;
249
+ }
250
+
251
+ .text {
252
+ color: black;
253
+ white-space: pre-wrap;
254
+ }
255
+
256
+ .score-text .text {
257
+ color: var(--body-text-color);
258
+ }
259
+
260
+ .score-text {
261
+ margin-right: var(--size-1);
262
+ padding: var(--size-1);
263
+ }
264
+
265
+ .no-cat {
266
+ color: var(--body-text-color);
267
+ }
268
+
269
+ .no-label {
270
+ color: var(--body-text-color);
271
+ }
272
+
273
+ .selectable {
274
+ cursor: pointer;
275
+ }
276
+ </style>
src/frontend/shared/utils.ts ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { colors } from "@gradio/theme";
2
+
3
+ export function name_to_rgba(
4
+ name: string,
5
+ a: number,
6
+ ctx: CanvasRenderingContext2D | null
7
+ ): string {
8
+ if (!ctx) {
9
+ var canvas = document.createElement("canvas");
10
+ ctx = canvas.getContext("2d")!;
11
+ }
12
+ ctx.fillStyle = name;
13
+ ctx.fillRect(0, 0, 1, 1);
14
+ const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
15
+ ctx.clearRect(0, 0, 1, 1);
16
+ return `rgba(${r}, ${g}, ${b}, ${255 / a})`;
17
+ }
18
+
19
+ export function correct_color_map(
20
+ color_map: Record<string, string>,
21
+ _color_map: Record<string, { primary: string; secondary: string }>,
22
+ browser: any,
23
+ ctx: CanvasRenderingContext2D | null
24
+ ): void {
25
+ for (const col in color_map) {
26
+ const _c = color_map[col].trim();
27
+
28
+ if (_c in colors) {
29
+ _color_map[col] = colors[_c as keyof typeof colors];
30
+ } else {
31
+ _color_map[col] = {
32
+ primary: browser
33
+ ? name_to_rgba(color_map[col], 1, ctx)
34
+ : color_map[col],
35
+ secondary: browser
36
+ ? name_to_rgba(color_map[col], 0.5, ctx)
37
+ : color_map[col]
38
+ };
39
+ }
40
+ }
41
+ }
42
+
43
+ export function merge_elements(
44
+ value: { token: string; class_or_confidence: string | number | null }[],
45
+ mergeMode: "empty" | "equal"
46
+ ): { token: string; class_or_confidence: string | number | null }[] {
47
+ let result: typeof value = [];
48
+ let tempStr: string | null = null;
49
+ let tempVal: string | number | null = null;
50
+
51
+ for (const val of value) {
52
+ if (
53
+ (mergeMode === "empty" && val.class_or_confidence === null) ||
54
+ (mergeMode === "equal" && tempVal === val.class_or_confidence)
55
+ ) {
56
+ tempStr = tempStr ? tempStr + val.token : val.token;
57
+ } else {
58
+ if (tempStr !== null) {
59
+ result.push({
60
+ token: tempStr,
61
+ class_or_confidence: tempVal
62
+ });
63
+ }
64
+ tempStr = val.token;
65
+ tempVal = val.class_or_confidence;
66
+ }
67
+ }
68
+
69
+ if (tempStr !== null) {
70
+ result.push({
71
+ token: tempStr,
72
+ class_or_confidence: tempVal
73
+ });
74
+ }
75
+
76
+ return result;
77
+ }
src/frontend/tsconfig.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "checkJs": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "resolveJsonModule": true,
8
+ "skipLibCheck": true,
9
+ "sourceMap": true,
10
+ "strict": true,
11
+ "verbatimModuleSyntax": true
12
+ },
13
+ "exclude": ["node_modules", "dist", "./gradio.config.js"]
14
+ }
src/pyproject.toml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = [
3
+ "hatchling",
4
+ "hatch-requirements-txt",
5
+ "hatch-fancy-pypi-readme>=22.5.0",
6
+ ]
7
+ build-backend = "hatchling.build"
8
+
9
+ [project]
10
+ name = "gradio_markdownlabel"
11
+ version = "0.0.1"
12
+ description = "Python library for easily interacting with trained machine learning models"
13
+ readme = "README.md"
14
+ license = "Apache-2.0"
15
+ requires-python = ">=3.8"
16
+ authors = [{ name = "YOUR NAME", email = "YOUREMAIL@domain.com" }]
17
+ keywords = [
18
+ "gradio-custom-component",
19
+ "gradio-template-HighlightedText"
20
+ ]
21
+ # Add dependencies here
22
+ dependencies = ["gradio>=4.0,<6.0"]
23
+ classifiers = [
24
+ 'Development Status :: 3 - Alpha',
25
+ 'Operating System :: OS Independent',
26
+ 'Programming Language :: Python :: 3',
27
+ 'Programming Language :: Python :: 3 :: Only',
28
+ 'Programming Language :: Python :: 3.10',
29
+ 'Programming Language :: Python :: 3.11',
30
+ 'Programming Language :: Python :: 3.12',
31
+ 'Topic :: Scientific/Engineering',
32
+ 'Topic :: Scientific/Engineering :: Artificial Intelligence',
33
+ 'Topic :: Scientific/Engineering :: Visualization',
34
+ ]
35
+
36
+ # The repository and space URLs are optional, but recommended.
37
+ # Adding a repository URL will create a badge in the auto-generated README that links to the repository.
38
+ # Adding a space URL will create a badge in the auto-generated README that links to the space.
39
+ # This will make it easy for people to find your deployed demo or source code when they
40
+ # encounter your project in the wild.
41
+
42
+ # [project.urls]
43
+ # repository = "your github repository"
44
+ # space = "your space url"
45
+
46
+ [project.optional-dependencies]
47
+ dev = ["build", "twine"]
48
+
49
+ [tool.hatch.build]
50
+ artifacts = ["/backend/gradio_markdownlabel/templates", "*.pyi"]
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ packages = ["/backend/gradio_markdownlabel"]