Update src/streamlit_app.py
Browse files- src/streamlit_app.py +66 -115
src/streamlit_app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
-
import
|
| 3 |
import re
|
| 4 |
import random
|
| 5 |
from typing import List, Tuple
|
|
@@ -11,6 +11,11 @@ st.set_page_config(
|
|
| 11 |
layout="wide"
|
| 12 |
)
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
def generate_color_palette(num_colors: int) -> List[str]:
|
| 15 |
"""Generate distinct colors for syllables"""
|
| 16 |
colors = [
|
|
@@ -22,13 +27,13 @@ def generate_color_palette(num_colors: int) -> List[str]:
|
|
| 22 |
|
| 23 |
# If we need more colors than predefined, generate random ones
|
| 24 |
while len(colors) < num_colors:
|
| 25 |
-
color = f"#{random.randint(
|
| 26 |
colors.append(color)
|
| 27 |
|
| 28 |
return colors[:num_colors]
|
| 29 |
|
| 30 |
-
def syllabify_word(word: str) -> List[str]:
|
| 31 |
-
"""Syllabify a single word using
|
| 32 |
# Clean the word of punctuation for syllabification
|
| 33 |
clean_word = re.sub(r'[^\w]', '', word)
|
| 34 |
|
|
@@ -36,107 +41,32 @@ def syllabify_word(word: str) -> List[str]:
|
|
| 36 |
return [word]
|
| 37 |
|
| 38 |
try:
|
| 39 |
-
#
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
# For basic syllabification, we'll use a simple approach
|
| 43 |
-
# The syllables library mainly provides counts, so we'll implement basic rules
|
| 44 |
-
syllable_list = simple_syllabify(clean_word)
|
| 45 |
|
| 46 |
-
# If
|
| 47 |
-
if len(
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
return syllable_list
|
| 51 |
-
except:
|
| 52 |
-
return [word]
|
| 53 |
-
|
| 54 |
-
def simple_syllabify(word: str) -> List[str]:
|
| 55 |
-
"""Simple syllabification based on vowel patterns"""
|
| 56 |
-
word = word.lower()
|
| 57 |
-
vowels = "aeiouy"
|
| 58 |
-
syllables_list = []
|
| 59 |
-
current_syllable = ""
|
| 60 |
-
|
| 61 |
-
i = 0
|
| 62 |
-
while i < len(word):
|
| 63 |
-
current_syllable += word[i]
|
| 64 |
|
| 65 |
-
#
|
| 66 |
-
if
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
# If we're at the end of the word, add everything
|
| 75 |
-
if j >= len(word):
|
| 76 |
-
current_syllable += consonant_cluster
|
| 77 |
-
syllables_list.append(current_syllable)
|
| 78 |
-
break
|
| 79 |
-
|
| 80 |
-
# Split consonant cluster
|
| 81 |
-
if len(consonant_cluster) <= 1:
|
| 82 |
-
# Single consonant goes with next syllable
|
| 83 |
-
syllables_list.append(current_syllable)
|
| 84 |
-
current_syllable = consonant_cluster
|
| 85 |
-
else:
|
| 86 |
-
# Multiple consonants: split them
|
| 87 |
-
split_point = len(consonant_cluster) // 2
|
| 88 |
-
current_syllable += consonant_cluster[:split_point]
|
| 89 |
-
syllables_list.append(current_syllable)
|
| 90 |
-
current_syllable = consonant_cluster[split_point:]
|
| 91 |
-
|
| 92 |
-
i = j - 1
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
if syllables_list:
|
| 98 |
-
syllables_list[-1] += current_syllable
|
| 99 |
-
else:
|
| 100 |
-
syllables_list.append(current_syllable)
|
| 101 |
-
|
| 102 |
-
return syllables_list if syllables_list else [word]
|
| 103 |
-
|
| 104 |
-
def adjust_syllables(word: str, target_count: int) -> List[str]:
|
| 105 |
-
"""Adjust syllable split to match target count"""
|
| 106 |
-
simple_split = simple_syllabify(word)
|
| 107 |
-
|
| 108 |
-
if len(simple_split) == target_count:
|
| 109 |
-
return simple_split
|
| 110 |
-
|
| 111 |
-
# If we have fewer syllables than target, try to split more
|
| 112 |
-
if len(simple_split) < target_count:
|
| 113 |
-
# Split longest syllable
|
| 114 |
-
longest_idx = max(range(len(simple_split)), key=lambda i: len(simple_split[i]))
|
| 115 |
-
longest = simple_split[longest_idx]
|
| 116 |
-
|
| 117 |
-
if len(longest) > 2:
|
| 118 |
-
mid = len(longest) // 2
|
| 119 |
-
simple_split[longest_idx:longest_idx+1] = [longest[:mid], longest[mid:]]
|
| 120 |
-
|
| 121 |
-
# If we have more syllables than target, try to merge
|
| 122 |
-
elif len(simple_split) > target_count and len(simple_split) > 1:
|
| 123 |
-
# Merge shortest adjacent syllables
|
| 124 |
-
min_combined_len = float('inf')
|
| 125 |
-
merge_idx = 0
|
| 126 |
-
|
| 127 |
-
for i in range(len(simple_split) - 1):
|
| 128 |
-
combined_len = len(simple_split[i]) + len(simple_split[i + 1])
|
| 129 |
-
if combined_len < min_combined_len:
|
| 130 |
-
min_combined_len = combined_len
|
| 131 |
-
merge_idx = i
|
| 132 |
-
|
| 133 |
-
merged = simple_split[merge_idx] + simple_split[merge_idx + 1]
|
| 134 |
-
simple_split[merge_idx:merge_idx+2] = [merged]
|
| 135 |
-
|
| 136 |
-
return simple_split
|
| 137 |
|
| 138 |
def process_text(text: str) -> Tuple[List[Tuple[str, str]], List[Tuple[str, List[Tuple[str, str]]]]]:
|
| 139 |
"""Process text and return colored words and syllable breakdown"""
|
|
|
|
|
|
|
| 140 |
# Split into words while preserving spaces
|
| 141 |
words = re.findall(r'\b\w+\b', text) # Only actual words, no spaces
|
| 142 |
|
|
@@ -151,7 +81,7 @@ def process_text(text: str) -> Tuple[List[Tuple[str, str]], List[Tuple[str, List
|
|
| 151 |
if part.strip() and re.match(r'\w', part): # If it's a word
|
| 152 |
if word_index < len(words):
|
| 153 |
word = words[word_index]
|
| 154 |
-
syllables_list = syllabify_word(word)
|
| 155 |
|
| 156 |
# Generate colors for this word's syllables
|
| 157 |
colors = generate_color_palette(len(syllables_list))
|
|
@@ -176,15 +106,15 @@ def display_colored_text(colored_words: List[Tuple[str, str]]):
|
|
| 176 |
html_parts = []
|
| 177 |
for word, color in colored_words:
|
| 178 |
if word.strip():
|
| 179 |
-
html_parts.append(f'<span style="color: {color}; font-weight: bold;">{word}</span>')
|
| 180 |
else:
|
| 181 |
html_parts.append(word)
|
| 182 |
|
| 183 |
html_content = ''.join(html_parts)
|
| 184 |
-
st.markdown(f'<div style="font-size: 18px; line-height: 1.
|
| 185 |
|
| 186 |
def display_syllable_breakdown(syllable_breakdown: List[Tuple[str, List[Tuple[str, str]]]]):
|
| 187 |
-
"""Display syllable breakdown"""
|
| 188 |
html_parts = []
|
| 189 |
|
| 190 |
for word, syllables in syllable_breakdown:
|
|
@@ -192,24 +122,24 @@ def display_syllable_breakdown(syllable_breakdown: List[Tuple[str, List[Tuple[st
|
|
| 192 |
# Create hyphenated syllable display
|
| 193 |
syllable_spans = []
|
| 194 |
for syl, color in syllables:
|
| 195 |
-
syllable_spans.append(f'<span style="color: {color}; font-weight: bold;">{syl}</span>')
|
| 196 |
|
| 197 |
# Join with hyphens for multi-syllable words
|
| 198 |
if len(syllables) > 1:
|
| 199 |
-
syllable_display = '<span style="color: #666;">-</span>'.join(syllable_spans)
|
| 200 |
else:
|
| 201 |
syllable_display = syllable_spans[0]
|
| 202 |
|
| 203 |
-
html_parts.append(f'<span style="margin-right:
|
| 204 |
|
| 205 |
if html_parts:
|
| 206 |
html_content = ''.join(html_parts)
|
| 207 |
-
st.markdown(f'<div style="font-size: 18px; line-height: 2.
|
| 208 |
|
| 209 |
# Main app
|
| 210 |
def main():
|
| 211 |
st.title("🔤 Text Syllabification Tool")
|
| 212 |
-
st.markdown("Enter text below to see syllable breakdown with color coding!")
|
| 213 |
|
| 214 |
# Input text area
|
| 215 |
input_text = st.text_area(
|
|
@@ -243,25 +173,27 @@ def main():
|
|
| 243 |
|
| 244 |
# Show syllable counts
|
| 245 |
if syllable_breakdown:
|
| 246 |
-
st.subheader("Syllable Counts")
|
| 247 |
-
|
| 248 |
for word, syllables in syllable_breakdown:
|
| 249 |
count = len(syllables)
|
| 250 |
-
|
| 251 |
|
| 252 |
-
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
| 254 |
|
| 255 |
else:
|
| 256 |
st.info("👆 Enter some text above to see the syllabification in action!")
|
| 257 |
|
| 258 |
# Example
|
| 259 |
st.subheader("Example")
|
| 260 |
-
example_text = "
|
| 261 |
st.code(example_text)
|
| 262 |
|
| 263 |
if st.button("Try Example"):
|
| 264 |
-
st.session_state.example_clicked = True
|
| 265 |
# Process example
|
| 266 |
colored_words, syllable_breakdown = process_text(example_text)
|
| 267 |
|
|
@@ -274,6 +206,25 @@ def main():
|
|
| 274 |
with col2:
|
| 275 |
st.subheader("Syllable Breakdown")
|
| 276 |
display_syllable_breakdown(syllable_breakdown)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
if __name__ == "__main__":
|
| 279 |
main()
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
+
import pyphen
|
| 3 |
import re
|
| 4 |
import random
|
| 5 |
from typing import List, Tuple
|
|
|
|
| 11 |
layout="wide"
|
| 12 |
)
|
| 13 |
|
| 14 |
+
# Initialize pyphen dictionary for English
|
| 15 |
+
@st.cache_resource
|
| 16 |
+
def get_pyphen_dict():
|
| 17 |
+
return pyphen.Pyphen(lang='en')
|
| 18 |
+
|
| 19 |
def generate_color_palette(num_colors: int) -> List[str]:
|
| 20 |
"""Generate distinct colors for syllables"""
|
| 21 |
colors = [
|
|
|
|
| 27 |
|
| 28 |
# If we need more colors than predefined, generate random ones
|
| 29 |
while len(colors) < num_colors:
|
| 30 |
+
color = f"#{random.randint(100, 255):02x}{random.randint(100, 255):02x}{random.randint(100, 255):02x}"
|
| 31 |
colors.append(color)
|
| 32 |
|
| 33 |
return colors[:num_colors]
|
| 34 |
|
| 35 |
+
def syllabify_word(word: str, pyphen_dict) -> List[str]:
|
| 36 |
+
"""Syllabify a single word using pyphen"""
|
| 37 |
# Clean the word of punctuation for syllabification
|
| 38 |
clean_word = re.sub(r'[^\w]', '', word)
|
| 39 |
|
|
|
|
| 41 |
return [word]
|
| 42 |
|
| 43 |
try:
|
| 44 |
+
# Use pyphen to get syllables
|
| 45 |
+
syllabified = pyphen_dict.inserted(clean_word.lower())
|
| 46 |
+
syllables = syllabified.split('-')
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
+
# If no syllables found (single syllable word), return the word
|
| 49 |
+
if len(syllables) == 1 and syllables[0] == clean_word.lower():
|
| 50 |
+
return [clean_word]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
+
# Handle case preservation
|
| 53 |
+
if clean_word.isupper():
|
| 54 |
+
syllables = [syl.upper() for syl in syllables]
|
| 55 |
+
elif clean_word[0].isupper():
|
| 56 |
+
for i, syl in enumerate(syllables):
|
| 57 |
+
if i == 0:
|
| 58 |
+
syllables[i] = syl.capitalize()
|
| 59 |
+
else:
|
| 60 |
+
syllables[i] = syl.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
+
return syllables if syllables else [clean_word]
|
| 63 |
+
except:
|
| 64 |
+
return [clean_word]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
def process_text(text: str) -> Tuple[List[Tuple[str, str]], List[Tuple[str, List[Tuple[str, str]]]]]:
|
| 67 |
"""Process text and return colored words and syllable breakdown"""
|
| 68 |
+
pyphen_dict = get_pyphen_dict()
|
| 69 |
+
|
| 70 |
# Split into words while preserving spaces
|
| 71 |
words = re.findall(r'\b\w+\b', text) # Only actual words, no spaces
|
| 72 |
|
|
|
|
| 81 |
if part.strip() and re.match(r'\w', part): # If it's a word
|
| 82 |
if word_index < len(words):
|
| 83 |
word = words[word_index]
|
| 84 |
+
syllables_list = syllabify_word(word, pyphen_dict)
|
| 85 |
|
| 86 |
# Generate colors for this word's syllables
|
| 87 |
colors = generate_color_palette(len(syllables_list))
|
|
|
|
| 106 |
html_parts = []
|
| 107 |
for word, color in colored_words:
|
| 108 |
if word.strip():
|
| 109 |
+
html_parts.append(f'<span style="color: {color}; font-weight: bold; font-size: 18px;">{word}</span>')
|
| 110 |
else:
|
| 111 |
html_parts.append(word)
|
| 112 |
|
| 113 |
html_content = ''.join(html_parts)
|
| 114 |
+
st.markdown(f'<div style="font-size: 18px; line-height: 1.8; padding: 20px; border: 2px solid #ddd; border-radius: 10px; background-color: #f9f9f9;">{html_content}</div>', unsafe_allow_html=True)
|
| 115 |
|
| 116 |
def display_syllable_breakdown(syllable_breakdown: List[Tuple[str, List[Tuple[str, str]]]]):
|
| 117 |
+
"""Display syllable breakdown with hyphens"""
|
| 118 |
html_parts = []
|
| 119 |
|
| 120 |
for word, syllables in syllable_breakdown:
|
|
|
|
| 122 |
# Create hyphenated syllable display
|
| 123 |
syllable_spans = []
|
| 124 |
for syl, color in syllables:
|
| 125 |
+
syllable_spans.append(f'<span style="color: {color}; font-weight: bold; font-size: 18px;">{syl}</span>')
|
| 126 |
|
| 127 |
# Join with hyphens for multi-syllable words
|
| 128 |
if len(syllables) > 1:
|
| 129 |
+
syllable_display = '<span style="color: #666; font-weight: bold;">-</span>'.join(syllable_spans)
|
| 130 |
else:
|
| 131 |
syllable_display = syllable_spans[0]
|
| 132 |
|
| 133 |
+
html_parts.append(f'<span style="margin-right: 25px; display: inline-block; margin-bottom: 12px;">{syllable_display}</span>')
|
| 134 |
|
| 135 |
if html_parts:
|
| 136 |
html_content = ''.join(html_parts)
|
| 137 |
+
st.markdown(f'<div style="font-size: 18px; line-height: 2.2; padding: 20px; border: 2px solid #ddd; border-radius: 10px; background-color: #f0f8ff;">{html_content}</div>', unsafe_allow_html=True)
|
| 138 |
|
| 139 |
# Main app
|
| 140 |
def main():
|
| 141 |
st.title("🔤 Text Syllabification Tool")
|
| 142 |
+
st.markdown("Enter text below to see accurate syllable breakdown with color coding using **pyphen** library!")
|
| 143 |
|
| 144 |
# Input text area
|
| 145 |
input_text = st.text_area(
|
|
|
|
| 173 |
|
| 174 |
# Show syllable counts
|
| 175 |
if syllable_breakdown:
|
| 176 |
+
st.subheader("Syllable Counts per Word")
|
| 177 |
+
counts_data = []
|
| 178 |
for word, syllables in syllable_breakdown:
|
| 179 |
count = len(syllables)
|
| 180 |
+
counts_data.append(f"**{word}**: {count}")
|
| 181 |
|
| 182 |
+
# Display in columns for better readability
|
| 183 |
+
cols = st.columns(3)
|
| 184 |
+
for i, count_info in enumerate(counts_data):
|
| 185 |
+
with cols[i % 3]:
|
| 186 |
+
st.markdown(count_info)
|
| 187 |
|
| 188 |
else:
|
| 189 |
st.info("👆 Enter some text above to see the syllabification in action!")
|
| 190 |
|
| 191 |
# Example
|
| 192 |
st.subheader("Example")
|
| 193 |
+
example_text = "The preparation of abstracts is an intellectual effort requiring general familiarity."
|
| 194 |
st.code(example_text)
|
| 195 |
|
| 196 |
if st.button("Try Example"):
|
|
|
|
| 197 |
# Process example
|
| 198 |
colored_words, syllable_breakdown = process_text(example_text)
|
| 199 |
|
|
|
|
| 206 |
with col2:
|
| 207 |
st.subheader("Syllable Breakdown")
|
| 208 |
display_syllable_breakdown(syllable_breakdown)
|
| 209 |
+
|
| 210 |
+
# Show example statistics
|
| 211 |
+
total_words = len([word for word, _ in syllable_breakdown])
|
| 212 |
+
total_syllables = sum(len(syls) for _, syls in syllable_breakdown)
|
| 213 |
+
st.markdown(f"**Example Statistics:** {total_words} words, {total_syllables} syllables")
|
| 214 |
+
|
| 215 |
+
# About section
|
| 216 |
+
with st.expander("About this tool"):
|
| 217 |
+
st.markdown("""
|
| 218 |
+
This tool uses the **pyphen** library, which implements the Hunspell hyphenation algorithm.
|
| 219 |
+
It provides much more accurate syllabification than simple rule-based approaches.
|
| 220 |
+
|
| 221 |
+
**Features:**
|
| 222 |
+
- Accurate English syllabification using pyphen
|
| 223 |
+
- Color-coded visualization
|
| 224 |
+
- Preserves original text formatting
|
| 225 |
+
- Shows syllable counts and statistics
|
| 226 |
+
- Handles capitalization properly
|
| 227 |
+
""")
|
| 228 |
|
| 229 |
if __name__ == "__main__":
|
| 230 |
main()
|