feat: add Usage Limits Panel component with usage tracking and visual indicators for limits
style: implement custom color schemes and gradients for account section, enhancing visual hierarchy
This commit is contained in:
137
backend/igny8_core/utils/word_counter.py
Normal file
137
backend/igny8_core/utils/word_counter.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
Word Counter Utility
|
||||
Standardized word counting from HTML content
|
||||
Single source of truth for Content.word_count calculation
|
||||
"""
|
||||
import re
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Try to import BeautifulSoup, fallback to regex if not available
|
||||
try:
|
||||
from bs4 import BeautifulSoup
|
||||
HAS_BEAUTIFULSOUP = True
|
||||
except ImportError:
|
||||
HAS_BEAUTIFULSOUP = False
|
||||
logger.warning("BeautifulSoup4 not available, using regex fallback for word counting")
|
||||
|
||||
|
||||
def calculate_word_count(html_content: str) -> int:
|
||||
"""
|
||||
Calculate word count from HTML content by stripping tags and counting words.
|
||||
|
||||
This is the SINGLE SOURCE OF TRUTH for word counting in IGNY8.
|
||||
All limit tracking and billing should use Content.word_count which is calculated using this function.
|
||||
|
||||
Args:
|
||||
html_content: HTML string to count words from
|
||||
|
||||
Returns:
|
||||
int: Number of words in the content
|
||||
|
||||
Examples:
|
||||
>>> calculate_word_count("<p>Hello world</p>")
|
||||
2
|
||||
>>> calculate_word_count("<h1>Title</h1><p>This is a paragraph.</p>")
|
||||
5
|
||||
>>> calculate_word_count("")
|
||||
0
|
||||
>>> calculate_word_count(None)
|
||||
0
|
||||
"""
|
||||
# Handle None or empty content
|
||||
if not html_content or not isinstance(html_content, str):
|
||||
return 0
|
||||
|
||||
html_content = html_content.strip()
|
||||
if not html_content:
|
||||
return 0
|
||||
|
||||
try:
|
||||
# Use BeautifulSoup if available (more accurate)
|
||||
if HAS_BEAUTIFULSOUP:
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
# Get text, removing all HTML tags
|
||||
text = soup.get_text(separator=' ', strip=True)
|
||||
else:
|
||||
# Fallback to regex (less accurate but functional)
|
||||
# Remove all HTML tags
|
||||
text = re.sub(r'<[^>]+>', ' ', html_content)
|
||||
# Remove extra whitespace
|
||||
text = ' '.join(text.split())
|
||||
|
||||
# Count words (split on whitespace)
|
||||
if not text:
|
||||
return 0
|
||||
|
||||
words = text.split()
|
||||
word_count = len(words)
|
||||
|
||||
logger.debug(f"Calculated word count: {word_count} from {len(html_content)} chars of HTML")
|
||||
return word_count
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating word count: {e}", exc_info=True)
|
||||
# Return 0 on error rather than failing
|
||||
return 0
|
||||
|
||||
|
||||
def format_word_count(word_count: int) -> str:
|
||||
"""
|
||||
Format word count for display (e.g., 1000 -> "1K", 100000 -> "100K")
|
||||
|
||||
Args:
|
||||
word_count: Number of words
|
||||
|
||||
Returns:
|
||||
str: Formatted word count
|
||||
|
||||
Examples:
|
||||
>>> format_word_count(500)
|
||||
'500'
|
||||
>>> format_word_count(1500)
|
||||
'1.5K'
|
||||
>>> format_word_count(100000)
|
||||
'100K'
|
||||
>>> format_word_count(1500000)
|
||||
'1.5M'
|
||||
"""
|
||||
if word_count >= 1000000:
|
||||
return f"{word_count / 1000000:.1f}M"
|
||||
elif word_count >= 1000:
|
||||
# Show .5K if not a whole number, otherwise just show K
|
||||
result = word_count / 1000
|
||||
if result == int(result):
|
||||
return f"{int(result)}K"
|
||||
return f"{result:.1f}K"
|
||||
return str(word_count)
|
||||
|
||||
|
||||
def validate_word_count_limit(current_words: int, requested_words: int, limit: int) -> dict:
|
||||
"""
|
||||
Validate if requested word generation would exceed limit.
|
||||
|
||||
Args:
|
||||
current_words: Current word count used in period
|
||||
requested_words: Words being requested
|
||||
limit: Maximum words allowed
|
||||
|
||||
Returns:
|
||||
dict with:
|
||||
- allowed (bool): Whether operation is allowed
|
||||
- remaining (int): Remaining words available
|
||||
- would_exceed_by (int): How many words over limit if not allowed
|
||||
"""
|
||||
remaining = max(0, limit - current_words)
|
||||
allowed = current_words + requested_words <= limit
|
||||
would_exceed_by = max(0, (current_words + requested_words) - limit)
|
||||
|
||||
return {
|
||||
'allowed': allowed,
|
||||
'remaining': remaining,
|
||||
'would_exceed_by': would_exceed_by,
|
||||
'current': current_words,
|
||||
'requested': requested_words,
|
||||
'limit': limit,
|
||||
}
|
||||
Reference in New Issue
Block a user