File size: 11,213 Bytes
117dd64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3651cd4
 
117dd64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3651cd4
117dd64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3651cd4
 
 
 
 
117dd64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3651cd4
 
 
 
 
e30d4c0
 
 
 
 
 
 
3651cd4
e30d4c0
3651cd4
 
 
 
 
117dd64
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
"""
Progressive UI module for the Tibetan Text Metrics app.

This module provides UI components and helper functions for progressive loading
and updating of metrics as they are computed.
"""

import gradio as gr
import pandas as pd
from typing import Dict, List, Any, Callable
from .progressive_loader import ProgressiveResult, MetricType
from .visualize import generate_visualizations, generate_word_count_chart
import logging

logger = logging.getLogger(__name__)

class ProgressiveUI:
    """
    Manages progressive UI updates for the Tibetan Text Metrics app.
    
    This class handles the incremental updates of UI components as metrics
    are computed, allowing for a more responsive user experience.
    """
    
    def __init__(self, 
                 metrics_preview: gr.Dataframe,
                 word_count_plot: gr.Plot,
                 jaccard_heatmap: gr.Plot,
                 lcs_heatmap: gr.Plot,
                 fuzzy_heatmap: gr.Plot,
                 semantic_heatmap: gr.Plot,
                 warning_box: gr.Markdown,
                 progress_container: gr.Row,
                 heatmap_titles: Dict[str, str],
                 structural_btn=None):
        """
        Initialize the ProgressiveUI.
        
        Args:
            metrics_preview: Gradio Dataframe component for metrics preview
            word_count_plot: Gradio Plot component for word count visualization
            jaccard_heatmap: Gradio Plot component for Jaccard similarity heatmap
            lcs_heatmap: Gradio Plot component for LCS similarity heatmap
            fuzzy_heatmap: Gradio Plot component for fuzzy similarity heatmap
            semantic_heatmap: Gradio Plot component for semantic similarity heatmap
            warning_box: Gradio Markdown component for warnings
            progress_container: Gradio Row component to hold progress indicators
            heatmap_titles: Dictionary mapping metric names to descriptive titles
        """
        self.metrics_preview = metrics_preview
        self.word_count_plot = word_count_plot
        self.jaccard_heatmap = jaccard_heatmap
        self.lcs_heatmap = lcs_heatmap
        self.fuzzy_heatmap = fuzzy_heatmap
        self.semantic_heatmap = semantic_heatmap
        self.warning_box = warning_box
        self.progress_container = progress_container
        self.heatmap_titles = heatmap_titles
        self.structural_btn = structural_btn
        
        # Create progress indicators for each metric
        with self.progress_container:
            self.jaccard_progress = gr.Markdown("πŸ”„ **Jaccard Similarity:** Waiting...", elem_id="jaccard_progress")
            self.lcs_progress = gr.Markdown("πŸ”„ **Normalized LCS:** Waiting...", elem_id="lcs_progress")
            self.fuzzy_progress = gr.Markdown("πŸ”„ **Fuzzy Similarity:** Waiting...", elem_id="fuzzy_progress")
            self.semantic_progress = gr.Markdown("πŸ”„ **Semantic Similarity:** Waiting...", elem_id="semantic_progress")
            self.word_count_progress = gr.Markdown("πŸ”„ **Word Counts:** Waiting...", elem_id="word_count_progress")
        
        # Track which components have been updated
        self.updated_components = set()
        
    def update(self, result: ProgressiveResult) -> Dict[gr.components.Component, Any]:
        """
        Update UI components based on progressive results.
        
        Args:
            result: ProgressiveResult object containing the current state of computation
            
        Returns:
            Dictionary mapping Gradio components to their updated values
        """
        updates = {}
        
        # Always update metrics preview if we have data
        if not result.metrics_df.empty:
            updates[self.metrics_preview] = result.metrics_df.head(10)
        
        # Update warning if present
        if result.warning:
            warning_md = f"**⚠️ Warning:** {result.warning}" if result.warning else ""
            updates[self.warning_box] = gr.update(value=warning_md, visible=True)
        
        # Generate visualizations for completed metrics
        if not result.metrics_df.empty:
            # Generate heatmaps for available metrics
            heatmaps_data = generate_visualizations(
                result.metrics_df, descriptive_titles=self.heatmap_titles
            )
            
            # Update heatmaps and progress indicators for completed metrics
            for metric_type in result.completed_metrics:
                if metric_type == MetricType.JACCARD:
                    # Update progress indicator
                    updates[self.jaccard_progress] = "βœ… **Jaccard Similarity:** Complete"
                    
                    # Update heatmap if not already updated
                    if self.jaccard_heatmap not in self.updated_components:
                        if "Jaccard Similarity (%)" in heatmaps_data:
                            updates[self.jaccard_heatmap] = heatmaps_data["Jaccard Similarity (%)"]
                            self.updated_components.add(self.jaccard_heatmap)
                        
                elif metric_type == MetricType.LCS:
                    # Update progress indicator
                    updates[self.lcs_progress] = "βœ… **Normalized LCS:** Complete"
                    
                    # Update heatmap if not already updated
                    if self.lcs_heatmap not in self.updated_components:
                        if "Normalized LCS" in heatmaps_data:
                            updates[self.lcs_heatmap] = heatmaps_data["Normalized LCS"]
                            self.updated_components.add(self.lcs_heatmap)
                        
                elif metric_type == MetricType.FUZZY:
                    # Update progress indicator
                    updates[self.fuzzy_progress] = "βœ… **Fuzzy Similarity:** Complete"
                    
                    # Update heatmap if not already updated
                    if self.fuzzy_heatmap not in self.updated_components:
                        if "Fuzzy Similarity" in heatmaps_data:
                            updates[self.fuzzy_heatmap] = heatmaps_data["Fuzzy Similarity"]
                            self.updated_components.add(self.fuzzy_heatmap)
                        
                elif metric_type == MetricType.SEMANTIC:
                    # Update progress indicator
                    updates[self.semantic_progress] = "βœ… **Semantic Similarity:** Complete"
                    
                    # Update heatmap if not already updated
                    if self.semantic_heatmap not in self.updated_components:
                        if "Semantic Similarity" in heatmaps_data:
                            updates[self.semantic_heatmap] = heatmaps_data["Semantic Similarity"]
                            self.updated_components.add(self.semantic_heatmap)
        
        # Generate word count chart if we have data
        if not result.word_counts_df.empty:
            # Update progress indicator
            updates[self.word_count_progress] = "βœ… **Word Counts:** Complete"
            
            # Update chart if not already updated
            if self.word_count_plot not in self.updated_components:
                updates[self.word_count_plot] = generate_word_count_chart(result.word_counts_df)
                self.updated_components.add(self.word_count_plot)
        
        # Update progress indicators for metrics in progress
        if not result.is_complete:
            # Update progress indicators for metrics that are still in progress
            if MetricType.JACCARD not in result.completed_metrics:
                updates[self.jaccard_progress] = "⏳ **Jaccard Similarity:** In progress..."
            if MetricType.LCS not in result.completed_metrics:
                updates[self.lcs_progress] = "⏳ **Normalized LCS:** In progress..."
            if MetricType.FUZZY not in result.completed_metrics:
                updates[self.fuzzy_progress] = "⏳ **Fuzzy Similarity:** In progress..."
            if MetricType.SEMANTIC not in result.completed_metrics:
                updates[self.semantic_progress] = "⏳ **Semantic Similarity:** In progress..."
            if self.word_count_plot not in self.updated_components:
                updates[self.word_count_progress] = "⏳ **Word Counts:** In progress..."
        else:
            # If computation is complete, enable structural button if available
            if self.structural_btn is not None:
                updates[self.structural_btn] = gr.update(interactive=True)
                logger.info("Enabling structural analysis button via progressive UI")
            
        return updates


def create_progressive_callback(progressive_ui: ProgressiveUI) -> Callable:
    """
    Create a callback function for progressive updates.
    
    Args:
        progressive_ui: ProgressiveUI instance to handle updates
        
    Returns:
        Callback function that can be passed to process_texts
    """
    def callback(metrics_df: pd.DataFrame, 
                word_counts_df: pd.DataFrame,
                completed_metrics: List[MetricType],
                warning: str,
                is_complete: bool) -> None:
        """
        Callback function for progressive updates.
        
        Args:
            metrics_df: DataFrame with current metrics
            word_counts_df: DataFrame with word counts
            completed_metrics: List of completed metric types
            warning: Warning message
            is_complete: Whether computation is complete
        """
        result = ProgressiveResult(
            metrics_df=metrics_df,
            word_counts_df=word_counts_df,
            completed_metrics=completed_metrics,
            warning=warning,
            is_complete=is_complete
        )
        
        # Get updates for UI components
        updates = progressive_ui.update(result)
        
        # Apply updates to UI components
        for component, value in updates.items():
            try:
                # Handle different component types appropriately
                if isinstance(component, gr.Markdown):
                    # For Markdown components, directly set the value
                    component.value = value
                elif isinstance(value, gr.update):
                    # For gr.update objects (like button state updates)
                    component.update(**value.kwargs)
                elif isinstance(component, (gr.Plot, gr.Dataframe, gr.HTML, gr.File)):
                    # For Plot, Dataframe, HTML, and File components, use the update method with the value
                    if value is not None:  # Only update if we have a value
                        component.update(value=value)
                elif hasattr(component, 'update'):
                    # For other components with update method
                    component.update(value=value)
                else:
                    logger.warning(f"Cannot update component of type {type(component)}")
            except Exception as e:
                logger.warning(f"Error updating component: {e}")
            
    return callback