aghilTQ commited on
Commit
076ad22
Β·
verified Β·
1 Parent(s): d06dcb9

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +249 -57
src/streamlit_app.py CHANGED
@@ -2,7 +2,9 @@ import streamlit as st
2
  import pyphen
3
  import re
4
  import random
5
- from typing import List, Tuple
 
 
6
 
7
  # Configure page
8
  st.set_page_config(
@@ -17,9 +19,35 @@ st.set_page_config(
17
  def get_pyphen_dict():
18
  return pyphen.Pyphen(lang='en')
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  def generate_color_palette(num_colors: int) -> List[str]:
21
  """Generate gray shades for syllables"""
22
- # Use different shades of gray instead of bright colors
23
  base_grays = [
24
  "#2C3E50", "#34495E", "#5D6D7E", "#85929E", "#566573",
25
  "#515A5A", "#626567", "#797D7F", "#922B21", "#A93226",
@@ -27,9 +55,7 @@ def generate_color_palette(num_colors: int) -> List[str]:
27
  "#D7BDE2", "#BB8FCE", "#A569BD", "#8E44AD", "#7D3C98"
28
  ]
29
 
30
- # Generate more gray shades if needed
31
  while len(base_grays) < num_colors:
32
- # Generate random gray shades
33
  gray_val = random.randint(60, 140)
34
  color = f"#{gray_val:02x}{gray_val:02x}{gray_val:02x}"
35
  base_grays.append(color)
@@ -38,22 +64,18 @@ def generate_color_palette(num_colors: int) -> List[str]:
38
 
39
  def syllabify_word(word: str, pyphen_dict) -> List[str]:
40
  """Syllabify a single word using pyphen"""
41
- # Clean the word of punctuation for syllabification
42
  clean_word = re.sub(r'[^\w]', '', word)
43
 
44
  if not clean_word:
45
  return [word]
46
 
47
  try:
48
- # Use pyphen to get syllables
49
  syllabified = pyphen_dict.inserted(clean_word.lower())
50
  syllables = syllabified.split('-')
51
 
52
- # If no syllables found (single syllable word), return the word
53
  if len(syllables) == 1 and syllables[0] == clean_word.lower():
54
  return [clean_word]
55
 
56
- # Handle case preservation
57
  if clean_word.isupper():
58
  syllables = [syl.upper() for syl in syllables]
59
  elif clean_word[0].isupper():
@@ -67,124 +89,294 @@ def syllabify_word(word: str, pyphen_dict) -> List[str]:
67
  except:
68
  return [clean_word]
69
 
70
- def process_text(text: str) -> List[Tuple[str, List[Tuple[str, str]]]]:
71
- """Process text and return syllable breakdown only"""
72
  pyphen_dict = get_pyphen_dict()
73
 
74
- # Split into words
75
- words = re.findall(r'\b\w+\b', text)
 
 
 
 
 
76
  syllable_breakdown = []
 
77
 
78
- for word in words:
79
- syllables_list = syllabify_word(word, pyphen_dict)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
- # Generate gray colors for this word's syllables
82
- colors = generate_color_palette(len(syllables_list))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
- # Add to syllable breakdown
85
- syllable_colored = [(syl, colors[i % len(colors)]) for i, syl in enumerate(syllables_list)]
86
- syllable_breakdown.append((word, syllable_colored))
 
 
87
 
88
- return syllable_breakdown
 
 
 
 
 
 
89
 
90
- def display_colored_text(colored_words: List[Tuple[str, str]]):
91
- """Display text with colored words"""
92
  html_parts = []
93
- for word, color in colored_words:
94
- if word.strip():
95
- html_parts.append(f'<span style="color: {color}; font-weight: bold; font-size: 16px;">{word}</span>')
96
- else:
97
- html_parts.append(word)
 
 
 
 
 
 
 
 
 
98
 
99
  html_content = ''.join(html_parts)
100
- st.markdown(f'<div style="font-size: 16px; line-height: 1.6; padding: 15px; border: 2px solid #ddd; border-radius: 8px; background-color: #f9f9f9;">{html_content}</div>', unsafe_allow_html=True)
 
 
 
 
101
 
102
- def display_syllable_breakdown(syllable_breakdown: List[Tuple[str, List[Tuple[str, str]]]]):
103
- """Display syllable breakdown with hyphens"""
104
  html_parts = []
105
 
106
- for word, syllables in syllable_breakdown:
107
  if syllables and len(syllables) > 0:
108
- # Create hyphenated syllable display
 
 
109
  syllable_spans = []
110
  for syl, color in syllables:
111
  syllable_spans.append(f'<span style="color: {color}; font-weight: bold; font-size: 16px;">{syl}</span>')
112
 
113
- # Join with hyphens for multi-syllable words
114
  if len(syllables) > 1:
115
  syllable_display = '<span style="color: #666; font-weight: bold;">-</span>'.join(syllable_spans)
116
  else:
117
  syllable_display = syllable_spans[0]
118
 
119
- html_parts.append(f'<span style="margin-right: 12px; display: inline-block; margin-bottom: 6px;">{syllable_display}</span>')
 
 
 
 
 
 
 
 
120
 
121
  if html_parts:
122
  html_content = ''.join(html_parts)
123
- st.markdown(f'<div style="font-size: 16px; line-height: 1.6; padding: 15px; border: 2px solid #ddd; border-radius: 8px; background-color: #f0f8ff;">{html_content}</div>', unsafe_allow_html=True)
 
 
 
 
124
 
125
- # Main app
126
  def main():
127
- st.title("πŸ”€ Text Syllabification Tool")
128
- st.markdown("*Enter text to see accurate syllable breakdown with color coding*")
 
 
 
129
 
130
  # Input text area
131
  input_text = st.text_area(
132
  "Enter your text:",
133
  placeholder="Type or paste your text here...",
134
  height=100,
135
- help="Enter any text to see how words are broken down into syllables"
136
  )
137
 
138
  # Submit button
139
  submit_clicked = st.button("πŸ” Submit", type="primary", use_container_width=True)
140
 
141
  if submit_clicked and input_text.strip():
142
- # Process the text
143
- syllable_breakdown = process_text(input_text)
 
 
 
 
 
 
144
 
145
  # Display syllable breakdown
146
- st.subheader("Syllable Breakdown")
147
- display_syllable_breakdown(syllable_breakdown)
 
148
 
149
  # Statistics
150
  total_words = len(syllable_breakdown)
151
- total_syllables = sum(len(syls) for _, syls in syllable_breakdown)
152
 
153
  st.markdown(f"**πŸ“Š {total_words} words β€’ {total_syllables} syllables**")
154
 
155
- # Show syllable counts in a more compact way
156
  if syllable_breakdown:
157
- with st.expander("πŸ“ˆ Syllable Counts per Word", expanded=False):
158
- counts_text = " β€’ ".join([f"{word}: {len(syllables)}" for word, syllables in syllable_breakdown])
 
159
  st.markdown(f"<small>{counts_text}</small>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
160
 
161
  elif submit_clicked and not input_text.strip():
162
  st.warning("⚠️ Please enter some text first!")
163
 
164
  elif not submit_clicked:
165
- st.info("πŸ‘† Enter some text above and click Submit to see the syllabification!")
166
 
167
  # Example
168
  st.subheader("Example")
169
- example_text = "The preparation of abstracts is an intellectual effort requiring general familiarity."
170
  st.code(example_text)
171
 
172
  if st.button("Try Example"):
173
- # Process example
174
- syllable_breakdown = process_text(example_text)
 
 
 
175
 
176
- st.subheader("Syllable Breakdown")
177
- display_syllable_breakdown(syllable_breakdown)
178
 
179
- # Show example statistics
180
  total_words = len(syllable_breakdown)
181
- total_syllables = sum(len(syls) for _, syls in syllable_breakdown)
182
  st.markdown(f"**πŸ“Š {total_words} words β€’ {total_syllables} syllables**")
183
 
184
  # About section
185
- with st.expander("About this tool"):
186
  st.markdown("""
187
- Created by @aghilalb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  """)
189
 
190
  if __name__ == "__main__":
 
2
  import pyphen
3
  import re
4
  import random
5
+ from typing import List, Tuple, Dict
6
+ import requests
7
+ import json
8
 
9
  # Configure page
10
  st.set_page_config(
 
19
  def get_pyphen_dict():
20
  return pyphen.Pyphen(lang='en')
21
 
22
+ # Cache for IPA pronunciations to avoid repeated API calls
23
+ @st.cache_data
24
+ def get_ipa_pronunciation(word: str) -> str:
25
+ """Get IPA pronunciation for a word using a free dictionary API"""
26
+ try:
27
+ # Clean the word
28
+ clean_word = re.sub(r'[^\w]', '', word.lower())
29
+ if not clean_word:
30
+ return ""
31
+
32
+ # Try Free Dictionary API
33
+ url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{clean_word}"
34
+ response = requests.get(url, timeout=2)
35
+
36
+ if response.status_code == 200:
37
+ data = response.json()
38
+ if data and len(data) > 0:
39
+ phonetics = data[0].get('phonetics', [])
40
+ for phonetic in phonetics:
41
+ if 'text' in phonetic and phonetic['text']:
42
+ return phonetic['text']
43
+
44
+ # Fallback - return empty if no pronunciation found
45
+ return ""
46
+ except:
47
+ return ""
48
+
49
  def generate_color_palette(num_colors: int) -> List[str]:
50
  """Generate gray shades for syllables"""
 
51
  base_grays = [
52
  "#2C3E50", "#34495E", "#5D6D7E", "#85929E", "#566573",
53
  "#515A5A", "#626567", "#797D7F", "#922B21", "#A93226",
 
55
  "#D7BDE2", "#BB8FCE", "#A569BD", "#8E44AD", "#7D3C98"
56
  ]
57
 
 
58
  while len(base_grays) < num_colors:
 
59
  gray_val = random.randint(60, 140)
60
  color = f"#{gray_val:02x}{gray_val:02x}{gray_val:02x}"
61
  base_grays.append(color)
 
64
 
65
  def syllabify_word(word: str, pyphen_dict) -> List[str]:
66
  """Syllabify a single word using pyphen"""
 
67
  clean_word = re.sub(r'[^\w]', '', word)
68
 
69
  if not clean_word:
70
  return [word]
71
 
72
  try:
 
73
  syllabified = pyphen_dict.inserted(clean_word.lower())
74
  syllables = syllabified.split('-')
75
 
 
76
  if len(syllables) == 1 and syllables[0] == clean_word.lower():
77
  return [clean_word]
78
 
 
79
  if clean_word.isupper():
80
  syllables = [syl.upper() for syl in syllables]
81
  elif clean_word[0].isupper():
 
89
  except:
90
  return [clean_word]
91
 
92
+ def process_text(text: str) -> Tuple[List[Tuple[str, str, str]], List[Tuple[str, List[Tuple[str, str]]]], Dict[str, str]]:
93
+ """Process text and return original words, syllable breakdown, and IPA dictionary"""
94
  pyphen_dict = get_pyphen_dict()
95
 
96
+ # Find words with their positions in original text
97
+ word_pattern = r'\b\w+\b'
98
+ words_with_positions = []
99
+ for match in re.finditer(word_pattern, text):
100
+ words_with_positions.append((match.group(), match.start(), match.end()))
101
+
102
+ original_words = []
103
  syllable_breakdown = []
104
+ ipa_dict = {}
105
 
106
+ # Generate a consistent color for each unique word
107
+ unique_words = list(set([word.lower() for word, _, _ in words_with_positions]))
108
+ word_colors = {}
109
+ colors = generate_color_palette(len(unique_words))
110
+
111
+ for i, unique_word in enumerate(unique_words):
112
+ word_colors[unique_word] = colors[i % len(colors)]
113
+
114
+ for i, (word, start_pos, end_pos) in enumerate(words_with_positions):
115
+ # Get IPA pronunciation (cached)
116
+ ipa = get_ipa_pronunciation(word)
117
+ ipa_dict[word.lower()] = ipa
118
+
119
+ # Get color for this word
120
+ color = word_colors[word.lower()]
121
+
122
+ # Add to original words with unique identifier
123
+ word_id = f"word_{i}"
124
+ original_words.append((word, color, word_id))
125
 
126
+ # Process syllables
127
+ syllables_list = syllabify_word(word, pyphen_dict)
128
+ syllable_colored = [(syl, color) for syl in syllables_list]
129
+ syllable_breakdown.append((word, syllable_colored, word_id))
130
+
131
+ return original_words, syllable_breakdown, ipa_dict
132
+
133
+ def create_hover_css_and_js():
134
+ """Create CSS and JavaScript for hover effects and IPA tooltips"""
135
+ return """
136
+ <style>
137
+ .word-hover {
138
+ cursor: pointer;
139
+ padding: 2px 4px;
140
+ border-radius: 3px;
141
+ transition: all 0.2s ease;
142
+ position: relative;
143
+ display: inline-block;
144
+ }
145
+
146
+ .word-hover:hover {
147
+ background-color: rgba(255, 255, 0, 0.3) !important;
148
+ transform: scale(1.05);
149
+ }
150
+
151
+ .tooltip {
152
+ visibility: hidden;
153
+ background-color: #333;
154
+ color: white;
155
+ text-align: center;
156
+ border-radius: 6px;
157
+ padding: 8px 12px;
158
+ position: absolute;
159
+ z-index: 1000;
160
+ bottom: 125%;
161
+ left: 50%;
162
+ margin-left: -60px;
163
+ opacity: 0;
164
+ transition: opacity 0.3s;
165
+ font-family: 'Courier New', monospace;
166
+ font-size: 14px;
167
+ white-space: nowrap;
168
+ box-shadow: 0 4px 8px rgba(0,0,0,0.3);
169
+ }
170
+
171
+ .tooltip::after {
172
+ content: "";
173
+ position: absolute;
174
+ top: 100%;
175
+ left: 50%;
176
+ margin-left: -5px;
177
+ border-width: 5px;
178
+ border-style: solid;
179
+ border-color: #333 transparent transparent transparent;
180
+ }
181
+
182
+ .word-hover:hover .tooltip {
183
+ visibility: visible;
184
+ opacity: 1;
185
+ }
186
+
187
+ .highlighted {
188
+ background-color: rgba(255, 255, 0, 0.5) !important;
189
+ transform: scale(1.05);
190
+ }
191
+ </style>
192
+
193
+ <script>
194
+ function highlightWord(wordId) {
195
+ // Remove existing highlights
196
+ document.querySelectorAll('.word-hover').forEach(el => {
197
+ el.classList.remove('highlighted');
198
+ });
199
 
200
+ // Highlight all instances of this word
201
+ document.querySelectorAll('[data-word-id="' + wordId + '"]').forEach(el => {
202
+ el.classList.add('highlighted');
203
+ });
204
+ }
205
 
206
+ function removeHighlight() {
207
+ document.querySelectorAll('.word-hover').forEach(el => {
208
+ el.classList.remove('highlighted');
209
+ });
210
+ }
211
+ </script>
212
+ """
213
 
214
+ def display_original_text(original_words: List[Tuple[str, str, str]], ipa_dict: Dict[str, str]):
215
+ """Display original text with hover effects and IPA tooltips"""
216
  html_parts = []
217
+
218
+ for word, color, word_id in original_words:
219
+ ipa = ipa_dict.get(word.lower(), "")
220
+ tooltip_text = f"IPA: {ipa}" if ipa else "IPA: Not available"
221
+
222
+ html_parts.append(f'''
223
+ <span class="word-hover"
224
+ style="color: {color}; font-weight: bold; font-size: 16px;"
225
+ data-word-id="{word_id}"
226
+ onmouseenter="highlightWord('{word_id}')"
227
+ onmouseleave="removeHighlight()">
228
+ {word}
229
+ <span class="tooltip">{tooltip_text}</span>
230
+ </span> ''')
231
 
232
  html_content = ''.join(html_parts)
233
+ st.markdown(f'''
234
+ <div style="font-size: 16px; line-height: 1.8; padding: 15px; border: 2px solid #ddd; border-radius: 8px; background-color: #f9f9f9;">
235
+ {html_content}
236
+ </div>
237
+ ''', unsafe_allow_html=True)
238
 
239
+ def display_syllable_breakdown(syllable_breakdown: List[Tuple[str, List[Tuple[str, str]], str]], ipa_dict: Dict[str, str]):
240
+ """Display syllable breakdown with hyphens, hover effects, and IPA tooltips"""
241
  html_parts = []
242
 
243
+ for word, syllables, word_id in syllable_breakdown:
244
  if syllables and len(syllables) > 0:
245
+ ipa = ipa_dict.get(word.lower(), "")
246
+ tooltip_text = f"IPA: {ipa}" if ipa else "IPA: Not available"
247
+
248
  syllable_spans = []
249
  for syl, color in syllables:
250
  syllable_spans.append(f'<span style="color: {color}; font-weight: bold; font-size: 16px;">{syl}</span>')
251
 
 
252
  if len(syllables) > 1:
253
  syllable_display = '<span style="color: #666; font-weight: bold;">-</span>'.join(syllable_spans)
254
  else:
255
  syllable_display = syllable_spans[0]
256
 
257
+ html_parts.append(f'''
258
+ <span class="word-hover"
259
+ style="margin-right: 12px; display: inline-block; margin-bottom: 6px;"
260
+ data-word-id="{word_id}"
261
+ onmouseenter="highlightWord('{word_id}')"
262
+ onmouseleave="removeHighlight()">
263
+ {syllable_display}
264
+ <span class="tooltip">{tooltip_text}</span>
265
+ </span>''')
266
 
267
  if html_parts:
268
  html_content = ''.join(html_parts)
269
+ st.markdown(f'''
270
+ <div style="font-size: 16px; line-height: 1.8; padding: 15px; border: 2px solid #ddd; border-radius: 8px; background-color: #f0f8ff;">
271
+ {html_content}
272
+ </div>
273
+ ''', unsafe_allow_html=True)
274
 
 
275
  def main():
276
+ st.title("πŸ”€ Enhanced Text Syllabification Tool")
277
+ st.markdown("*Enter text to see syllable breakdown with IPA pronunciation and interactive highlighting*")
278
+
279
+ # Add CSS and JavaScript
280
+ st.markdown(create_hover_css_and_js(), unsafe_allow_html=True)
281
 
282
  # Input text area
283
  input_text = st.text_area(
284
  "Enter your text:",
285
  placeholder="Type or paste your text here...",
286
  height=100,
287
+ help="Enter any text to see syllables, IPA pronunciations, and interactive highlighting"
288
  )
289
 
290
  # Submit button
291
  submit_clicked = st.button("πŸ” Submit", type="primary", use_container_width=True)
292
 
293
  if submit_clicked and input_text.strip():
294
+ with st.spinner("Processing text and fetching pronunciations..."):
295
+ # Process the text
296
+ original_words, syllable_breakdown, ipa_dict = process_text(input_text)
297
+
298
+ # Display original text
299
+ st.subheader("πŸ“ Original Text")
300
+ st.markdown("*Hover over words to see IPA pronunciation and highlight matching words*")
301
+ display_original_text(original_words, ipa_dict)
302
 
303
  # Display syllable breakdown
304
+ st.subheader("πŸ”€ Syllable Breakdown")
305
+ st.markdown("*Hover over words to see IPA pronunciation and highlight matching words*")
306
+ display_syllable_breakdown(syllable_breakdown, ipa_dict)
307
 
308
  # Statistics
309
  total_words = len(syllable_breakdown)
310
+ total_syllables = sum(len(syls) for _, syls, _ in syllable_breakdown)
311
 
312
  st.markdown(f"**πŸ“Š {total_words} words β€’ {total_syllables} syllables**")
313
 
314
+ # Show syllable counts and IPA in expandable section
315
  if syllable_breakdown:
316
+ with st.expander("πŸ“ˆ Detailed Information", expanded=False):
317
+ st.markdown("**Syllable Counts:**")
318
+ counts_text = " β€’ ".join([f"{word}: {len(syllables)}" for word, syllables, _ in syllable_breakdown])
319
  st.markdown(f"<small>{counts_text}</small>", unsafe_allow_html=True)
320
+
321
+ st.markdown("**IPA Pronunciations:**")
322
+ ipa_available = {word: ipa for word, ipa in ipa_dict.items() if ipa}
323
+ if ipa_available:
324
+ for word, ipa in sorted(ipa_available.items()):
325
+ st.text(f"{word}: {ipa}")
326
+ else:
327
+ st.text("No IPA pronunciations available for this text.")
328
 
329
  elif submit_clicked and not input_text.strip():
330
  st.warning("⚠️ Please enter some text first!")
331
 
332
  elif not submit_clicked:
333
+ st.info("πŸ‘† Enter some text above and click Submit to see the enhanced syllabification!")
334
 
335
  # Example
336
  st.subheader("Example")
337
+ example_text = "The preparation of abstracts requires intellectual familiarity."
338
  st.code(example_text)
339
 
340
  if st.button("Try Example"):
341
+ with st.spinner("Processing example..."):
342
+ original_words, syllable_breakdown, ipa_dict = process_text(example_text)
343
+
344
+ st.subheader("πŸ“ Original Text")
345
+ display_original_text(original_words, ipa_dict)
346
 
347
+ st.subheader("πŸ”€ Syllable Breakdown")
348
+ display_syllable_breakdown(syllable_breakdown, ipa_dict)
349
 
 
350
  total_words = len(syllable_breakdown)
351
+ total_syllables = sum(len(syls) for _, syls, _ in syllable_breakdown)
352
  st.markdown(f"**πŸ“Š {total_words} words β€’ {total_syllables} syllables**")
353
 
354
  # About section
355
+ with st.expander("About this Enhanced Tool"):
356
  st.markdown("""
357
+ This enhanced tool provides:
358
+
359
+ **πŸ”€ Syllabification Features:**
360
+ - Accurate English syllabification using pyphen (Hunspell algorithm)
361
+ - Color-coded syllables for easy reading
362
+ - Syllable counts and statistics
363
+ - Proper capitalization handling
364
+
365
+ **🎯 Interactive Features:**
366
+ - **Two text boxes**: Original text and syllabified version
367
+ - **Hover highlighting**: Mouse over a word to highlight it in both boxes
368
+ - **IPA pronunciations**: Hover tooltips show International Phonetic Alphabet notation
369
+ - **Color consistency**: Same words use the same colors across both displays
370
+
371
+ **πŸ”Š Pronunciation Data:**
372
+ - IPA pronunciations fetched from Free Dictionary API
373
+ - Cached for performance
374
+ - Displays "Not available" for words without pronunciation data
375
+
376
+ **πŸ’‘ Usage Tips:**
377
+ - Hover over any word to see its IPA pronunciation
378
+ - Watch how the same word is highlighted in both the original and syllabified versions
379
+ - Use the detailed information section to see all pronunciations at once
380
  """)
381
 
382
  if __name__ == "__main__":