Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .gitignore +14 -0
- README.md +509 -7
- __init__.py +0 -0
- app.py +166 -0
- css.css +157 -0
- editable_demo.py +181 -0
- requirements.txt +1 -0
- space.py +288 -0
- src/.gitignore +14 -0
- src/LICENSE +200 -0
- src/README.md +514 -0
- src/backend/gradio_markdownlabel/__init__.py +4 -0
- src/backend/gradio_markdownlabel/markdownlabel.py +217 -0
- src/backend/gradio_markdownlabel/templates/component/index.js +0 -0
- src/backend/gradio_markdownlabel/templates/component/style.css +1 -0
- src/demo/__init__.py +0 -0
- src/demo/app.py +166 -0
- src/demo/css.css +157 -0
- src/demo/editable_demo.py +181 -0
- src/demo/requirements.txt +1 -0
- src/demo/space.py +288 -0
- src/example_api_usage.py +137 -0
- src/frontend/Index.svelte +127 -0
- src/frontend/gradio.config.js +9 -0
- src/frontend/package-lock.json +0 -0
- src/frontend/package.json +38 -0
- src/frontend/shared/EditableMarkdownRenderer.svelte +607 -0
- src/frontend/shared/InteractiveHighlightedtext.svelte +510 -0
- src/frontend/shared/LabelInput.svelte +124 -0
- src/frontend/shared/MarkdownRenderer.svelte +318 -0
- src/frontend/shared/StaticHighlightedtext.svelte +276 -0
- src/frontend/shared/utils.ts +77 -0
- src/frontend/tsconfig.json +14 -0
- src/pyproject.toml +53 -0
.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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 5.36.2
|
| 8 |
-
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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, '"')}">
|
| 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 |
+
|
| 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, '"')}">
|
| 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 |
+
|
| 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"]
|