File size: 13,264 Bytes
37cc56d
 
 
 
 
 
8043d18
0be81c2
8043d18
 
 
 
 
 
 
 
37cc56d
 
 
 
 
 
 
8043d18
 
 
 
 
 
 
 
 
fce0e3a
8043d18
0be81c2
 
37cc56d
fce0e3a
37cc56d
fce0e3a
0be81c2
37cc56d
fce0e3a
 
 
 
 
8814bfe
 
 
 
 
 
fce0e3a
0be81c2
 
fce0e3a
8814bfe
0be81c2
fce0e3a
 
 
 
 
0be81c2
fce0e3a
0be81c2
fce0e3a
0be81c2
37cc56d
 
 
 
8043d18
 
 
 
 
37cc56d
8814bfe
 
 
 
 
 
 
 
37cc56d
0be81c2
37cc56d
8814bfe
37cc56d
0be81c2
 
37cc56d
 
8043d18
0be81c2
37cc56d
0be81c2
8043d18
 
 
 
0be81c2
8043d18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0be81c2
 
 
37cc56d
 
0be81c2
37cc56d
 
 
 
0be81c2
 
 
37cc56d
 
 
 
 
0be81c2
37cc56d
 
 
 
 
 
 
 
 
8043d18
37cc56d
 
 
 
 
 
 
 
 
8043d18
 
37cc56d
 
0be81c2
 
37cc56d
0be81c2
37cc56d
0be81c2
37cc56d
 
 
 
0be81c2
37cc56d
 
 
 
0be81c2
 
8043d18
37cc56d
 
8043d18
0be81c2
8043d18
 
 
37cc56d
0be81c2
 
8043d18
37cc56d
 
 
8043d18
37cc56d
8043d18
 
0be81c2
 
37cc56d
8043d18
 
 
 
 
 
 
 
 
 
 
 
 
37cc56d
8043d18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37cc56d
8043d18
37cc56d
8043d18
fce0e3a
8043d18
0be81c2
 
 
 
 
37cc56d
8043d18
0be81c2
8043d18
37cc56d
0be81c2
8043d18
37cc56d
8043d18
0be81c2
8043d18
37cc56d
0be81c2
8043d18
37cc56d
 
 
8043d18
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
import gradio as gr
from transformers import pipeline
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import spacy
import traceback # Added for better error tracing

# Load the English spaCy model (lightweight, 'sm' for small)
try:
    nlp = spacy.load("en_core_web_sm")
except OSError:
    print("Downloading spaCy model 'en_core_web_sm'. Please run 'python -m spacy download en_core_web_sm' if this fails repeatedly.")
    spacy.cli.download("en_core_web_sm")
    nlp = spacy.load("en_core_web_sm")

# Initialize the sentiment analysis pipeline
sentiment_pipeline = pipeline(
    "sentiment-analysis",
    model="distilbert-base-uncased-finetuned-sst-2-english"
)

# --- Function: Detect Passive Voice using spaCy ---
def is_passive(text):
    """Checks if a sentence is passive using spaCy's dependency parser."""
    doc = nlp(text)
    for token in doc:
        if token.dep_ == 'auxpass' and token.head.pos_ == 'VERB' and token.head.tag_ == 'VBN':
            return True
    return False

def analyze_sentiment_files(file1, file2, file3, file4, file5, column_name):
    """Analyze sentiment and active/passive voice for multiple TXT files or a single CSV file"""
    # analyzed_df is no longer global, it's returned by this function

    try:
        files = [f for f in [file1, file2, file3, file4, file5] if f is not None]
        
        if not files:
            return ("Please upload at least one file", None, None, None, None, gr.update(choices=[]), gr.update(choices=[]), gr.update(choices=[]), None)
        
        file_paths = [f.name for f in files]
        
        if all(path.endswith('.txt') for path in file_paths):
            all_data = []
            for i, file in enumerate(files, 1):
                try:
                    with open(file.name, 'r', encoding='utf-8') as f:
                        lines = f.readlines()
                except:
                    with open(file.name, 'r', encoding='latin-1') as f:
                        lines = f.readlines()
                texts = [line.strip() for line in lines if line.strip()]
                if not texts: continue
                file_df = pd.DataFrame({'text': texts, 'line_number': range(1, len(texts) + 1), 'file_name': f'File {i}', 'source_file': file.name.split('/')[-1].split('\\')[-1]})
                all_data.append(file_df)
            if not all_data:
                return ("Error: No valid text found in uploaded files", None, None, None, None, gr.update(choices=[]), gr.update(choices=[]), gr.update(choices=[]), None)
            df = pd.concat(all_data, ignore_index=True)
            column_name = 'text'
        elif len(files) == 1 and file_paths[0].endswith('.csv'):
            df = pd.read_csv(file_paths[0])
            if column_name not in df.columns:
                return (f"Error: Column '{column_name}' not found. Available columns: {', '.join(df.columns)}", None, None, None, None, gr.update(choices=[]), gr.update(choices=[]), gr.update(choices=[]), None)
        else:
            return ("Error: Either upload multiple TXT files OR a single CSV file (not both)", None, None, None, None, gr.update(choices=[]), gr.update(choices=[]), gr.update(choices=[]), None)
        
        # Analyze sentiment & voice
        texts = df[column_name].fillna("").astype(str).tolist()
        results = sentiment_pipeline(texts, truncation=True, max_length=512)
        df['sentiment_label'] = [r['label'] for r in results]
        df['sentiment_score'] = [r['score'] for r in results]
        df['is_passive'] = df[column_name].apply(is_passive)
        df['voice_label'] = df['is_passive'].apply(lambda x: 'PASSIVE' if x else 'ACTIVE')

        # Get all column names except sentiment/voice columns for filter options
        filter_columns = [col for col in df.columns if col not in ['sentiment_label', 'sentiment_score', 'is_passive', 'voice_label']]
        
        if 'file_name' in df.columns:
            file_summary = "\n\n📁 FILES UPLOADED:\n"
            for fname in df['file_name'].unique():
                count = len(df[df['file_name'] == fname])
                file_summary += f"  - {fname}: {count} lines\n"
            summary = create_summary(df, "All Data") + file_summary
        else:
            summary = create_summary(df, "All Data")
        
        # Return the DF as the new state value
        return (summary, df, None, None, None,
                gr.update(choices=filter_columns, value='file_name' if 'file_name' in filter_columns else None),
                gr.update(choices=[], value=None),
                gr.update(choices=[], value=None),
                df) # Return DF for the gr.State component
        
    except Exception as e:
        traceback.print_exc()
        return f"Error: {str(e)}", None, None, None, None, gr.update(choices=[]), gr.update(choices=[]), gr.update(choices=[]), None

# --- Summary Functions ---

def create_summary(df, title):
    total_lines = len(df)
    positive_pct = (df['sentiment_label'].value_counts(normalize=True).get('POSITIVE', 0) * 100)
    passive_pct = (df['is_passive'].mean() * 100)
    summary = (f"--- Summary for {title} ---\n"
               f"Total Lines Analyzed: {total_lines}\n"
               f"Positive Sentiment: {positive_pct:.1f}%\n"
               f"Negative Sentiment: {(100 - positive_pct):.1f}%\n"
               f"**Passive Voice Sentences: {passive_pct:.1f}%**\n"
               f"**Active Voice Sentences: {(100 - passive_pct):.1f}%**\n"
               f"---------------------------------")
    return summary

def create_comparison_summary(df1, df2, label1, label2):
    summary = f"📊 COMPARISON SUMMARY: {label1} vs {label2}\n\n"
    summary += create_summary(df1, label1) + "\n\n"
    summary += create_summary(df2, label2)
    return summary

def get_filter_values(filter_column, current_df_state):
    """Get unique values for the selected filter column using the state DF"""
    if current_df_state is None or filter_column is None:
        return gr.update(choices=[]), gr.update(choices=[])
    
    unique_values = current_df_state[filter_column].dropna().unique().tolist()
    unique_values = [str(v) for v in unique_values][:100]
    
    return gr.update(choices=unique_values, value=None), gr.update(choices=unique_values, value=None)

def compare_groups(filter_column, group1_value, group2_value, current_df_state):
    """Compare two groups side by side using the state DF"""
    if current_df_state is None:
        return "Please analyze sentiment first", None, None, None, None
    
    if not filter_column or not group1_value or not group2_value:
        return "Please select a filter column and both group values", None, None, None, None
    
    df = current_df_state.copy()
    
    df1 = df[df[filter_column].astype(str) == group1_value]
    df2 = df[df[filter_column].astype(str) == group2_value]
    
    if len(df1) == 0 or len(df2) == 0:
        return "One or both groups have no data", None, None, None, None
    
    fig_pie = create_comparison_pie(df1, df2, group1_value, group2_value)
    fig_bar = create_comparison_bar(df1, df2, group1_value, group2_value)
    fig_voice_bar = create_comparison_voice_bar(df1, df2, group1_value, group2_value) 
    
    summary = create_comparison_summary(df1, df2, group1_value, group2_value)
    
    df1_display = df1.copy()
    df1_display['comparison_group'] = group1_value
    df2_display = df2.copy()
    df2_display['comparison_group'] = group2_value
    combined_df = pd.concat([df1_display, df2_display])
    
    return summary, combined_df, fig_pie, fig_bar, fig_voice_bar


def create_comparison_pie(df1, df2, label1, label2):
    # (Function body is unchanged from previous response, uses plotly)
    fig = make_subplots(rows=1, cols=2, specs=[[{'type':'pie'}, {'type':'pie'}]], subplot_titles=(f'{label1}', f'{label2}'))
    counts1 = df1['sentiment_label'].value_counts()
    fig.add_trace(go.Pie(labels=counts1.index, values=counts1.values, name=label1, marker_colors=['#10b981' if x=='POSITIVE' else '#ef4444' for x in counts1.index], textinfo='percent+label+value'), row=1, col=1)
    counts2 = df2['sentiment_label'].value_counts()
    fig.add_trace(go.Pie(labels=counts2.index, values=counts2.values, name=label2, marker_colors=['#10b981' if x=='POSITIVE' else '#ef4444' for x in counts2.index], textinfo='percent+label+value'), row=1, col=2)
    fig.update_layout(title_text='Sentiment Distribution Comparison', height=400)
    return fig

def create_comparison_bar(df1, df2, label1, label2):
    # (Function body is unchanged from previous response, uses plotly)
    counts1 = df1['sentiment_label'].value_counts(normalize=True) * 100
    counts2 = df2['sentiment_label'].value_counts(normalize=True) * 100
    sentiments = ['POSITIVE', 'NEGATIVE']
    fig = go.Figure()
    fig.add_trace(go.Bar(name=label1, x=sentiments, y=[counts1.get(s, 0) for s in sentiments], marker_color='#3b82f6', text=[f"{counts1.get(s, 0):.1f}%" for s in sentiments], textposition='auto'))
    fig.add_trace(go.Bar(name=label2, x=sentiments, y=[counts2.get(s, 0) for s in sentiments], marker_color='#ef4444', text=[f"{counts2.get(s, 0):.1f}%" for s in sentiments], textposition='auto'))
    fig.update_layout(title_text='Sentiment Percentage Comparison', barmode='group', height=400)
    return fig

def create_comparison_voice_bar(df1, df2, label1, label2):
    # (Function body is unchanged from previous response, uses plotly)
    counts1 = df1['voice_label'].value_counts(normalize=True) * 100
    counts2 = df2['voice_label'].value_counts(normalize=True) * 100
    voices = ['ACTIVE', 'PASSIVE']
    fig = go.Figure()
    fig.add_trace(go.Bar(name=label1, x=voices, y=[counts1.get(s, 0) for s in voices], marker_color='#10b981', text=[f"{counts1.get(s, 0):.1f}%" for s in voices], textposition='auto'))
    fig.add_trace(go.Bar(name=label2, x=voices, y=[counts2.get(s, 0) for s in voices], marker_color='#fbbf24', text=[f"{counts2.get(s, 0):.1f}%" for s in voices], textposition='auto'))
    fig.update_layout(title_text='Active vs. Passive Voice Percentage Comparison', barmode='group', height=400)
    return fig


# --- Gradio UI Setup ---

with gr.Blocks(title="Sentiment & Voice Analyzer") as demo:
    gr.Markdown("# Advanced Text Analyzer: Sentiment, Active vs. Passive Voice")
    # Define the state component to persist data across calls
    analyzed_df_state = gr.State(value=None) 

    with gr.Tab("Analyze Files"):
        with gr.Row():
            file_input1 = gr.File(label="Upload TXT/CSV File 1")
            file_input2 = gr.File(label="Upload TXT File 2 (Optional)")
            file_input3 = gr.File(label="Upload TXT File 3 (Optional)")
            file_input4 = gr.File(label="Upload TXT File 4 (Optional)")
            file_input5 = gr.File(label="Upload TXT File 5 (Optional)")
        
        csv_column_name = gr.Textbox(label="If CSV, specify text column name", value="text")
        analyze_button = gr.Button("Analyze Texts", variant="primary")
        
        summary_output = gr.Textbox(label="Analysis Summary", lines=10)
        dataframe_output = gr.DataFrame(label="Detailed Analysis Results")

    with gr.Tab("Compare Groups"):
        gr.Markdown("Select a column to filter by (e.g., 'file_name' for TXT uploads) and compare two values.")
        with gr.Row():
            filter_col_dropdown = gr.Dropdown(label="Select Filter Column", choices=[])
            group1_dropdown = gr.Dropdown(label="Group 1 Value", choices=[])
            group2_dropdown = gr.Dropdown(label="Group 2 Value", choices=[])
        
        compare_button = gr.Button("Compare Groups", variant="primary")
        
        comparison_summary_output = gr.Textbox(label="Comparison Summary", lines=15)
        comparison_dataframe_output = gr.DataFrame(label="Comparison Data Results")
        
        comparison_pie_chart = gr.Plot(label="Sentiment Distribution Pie Chart")
        comparison_bar_chart = gr.Plot(label="Sentiment Percentage Bar Chart")
        comparison_voice_bar_chart = gr.Plot(label="Active/Passive Voice Bar Chart")

    # --- Event Handlers ---

    analyze_button.click(
        fn=analyze_sentiment_files,
        inputs=[file_input1, file_input2, file_input3, file_input4, file_input5, csv_column_name],
        outputs=[
            summary_output, dataframe_output, comparison_pie_chart, comparison_bar_chart, 
            comparison_voice_bar_chart, filter_col_dropdown, group1_dropdown, group2_dropdown,
            analyzed_df_state # IMPORTANT: Update the State variable with the new DF
        ]
    )

    # Pass the state DF to the value-getting function
    filter_col_dropdown.change(
        fn=get_filter_values,
        inputs=[filter_col_dropdown, analyzed_df_state], 
        outputs=[group1_dropdown, group2_dropdown]
    )

    # Pass the state DF to the comparison function
    compare_button.click(
        fn=compare_groups,
        inputs=[filter_col_dropdown, group1_dropdown, group2_dropdown, analyzed_df_state], 
        outputs=[comparison_summary_output, comparison_dataframe_output, comparison_pie_chart, comparison_bar_chart, comparison_voice_bar_chart]
    )

if __name__ == "__main__":
    demo.launch()