Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:47:30 +08:00
commit 48d6099939
30 changed files with 5747 additions and 0 deletions

View File

@@ -0,0 +1,379 @@
#!/usr/bin/env python3
"""
Component matching logic for Bubble Tea Designer.
Scores and ranks components based on requirements.
"""
from typing import Dict, List, Tuple
import logging
logger = logging.getLogger(__name__)
# Component capability definitions
COMPONENT_CAPABILITIES = {
'viewport': {
'keywords': ['scroll', 'view', 'display', 'content', 'pager', 'document'],
'use_cases': ['viewing large text', 'log viewer', 'document reader'],
'complexity': 'medium'
},
'textinput': {
'keywords': ['input', 'text', 'search', 'query', 'single-line'],
'use_cases': ['search box', 'text input', 'single field'],
'complexity': 'low'
},
'textarea': {
'keywords': ['edit', 'multi-line', 'text area', 'editor', 'compose'],
'use_cases': ['text editing', 'message composition', 'multi-line input'],
'complexity': 'medium'
},
'table': {
'keywords': ['table', 'tabular', 'rows', 'columns', 'grid', 'data display'],
'use_cases': ['data table', 'spreadsheet view', 'structured data'],
'complexity': 'medium'
},
'list': {
'keywords': ['list', 'items', 'select', 'choose', 'menu', 'options'],
'use_cases': ['item selection', 'menu', 'file list'],
'complexity': 'medium'
},
'progress': {
'keywords': ['progress', 'loading', 'installation', 'percent', 'bar'],
'use_cases': ['progress indication', 'loading', 'installation progress'],
'complexity': 'low'
},
'spinner': {
'keywords': ['loading', 'spinner', 'wait', 'processing', 'busy'],
'use_cases': ['loading indicator', 'waiting', 'processing'],
'complexity': 'low'
},
'filepicker': {
'keywords': ['file', 'select file', 'choose file', 'file system', 'browse'],
'use_cases': ['file selection', 'file browser', 'file chooser'],
'complexity': 'medium'
},
'paginator': {
'keywords': ['page', 'pagination', 'pages', 'navigate pages'],
'use_cases': ['page navigation', 'chunked content', 'paged display'],
'complexity': 'low'
},
'timer': {
'keywords': ['timer', 'countdown', 'timeout', 'time limit'],
'use_cases': ['countdown', 'timeout', 'timed operation'],
'complexity': 'low'
},
'stopwatch': {
'keywords': ['stopwatch', 'elapsed', 'time tracking', 'duration'],
'use_cases': ['time tracking', 'elapsed time', 'duration measurement'],
'complexity': 'low'
},
'help': {
'keywords': ['help', 'shortcuts', 'keybindings', 'documentation'],
'use_cases': ['help menu', 'keyboard shortcuts', 'documentation'],
'complexity': 'low'
},
'tabs': {
'keywords': ['tabs', 'tabbed', 'switch views', 'navigation'],
'use_cases': ['tab navigation', 'multiple views', 'view switching'],
'complexity': 'medium'
},
'autocomplete': {
'keywords': ['autocomplete', 'suggestions', 'completion', 'dropdown'],
'use_cases': ['autocomplete', 'suggestions', 'smart input'],
'complexity': 'medium'
}
}
def match_score(requirement: str, component: str) -> int:
"""
Calculate relevance score for component given requirement.
Args:
requirement: Feature requirement description
component: Component name
Returns:
Score from 0-100 (higher = better match)
Example:
>>> match_score("scrollable log display", "viewport")
95
"""
if component not in COMPONENT_CAPABILITIES:
return 0
score = 0
requirement_lower = requirement.lower()
comp_info = COMPONENT_CAPABILITIES[component]
# Keyword matching (60 points max)
keywords = comp_info['keywords']
keyword_matches = sum(1 for kw in keywords if kw in requirement_lower)
keyword_score = min(60, (keyword_matches / len(keywords)) * 60)
score += keyword_score
# Use case matching (40 points max)
use_cases = comp_info['use_cases']
use_case_matches = sum(1 for uc in use_cases if any(
word in requirement_lower for word in uc.split()
))
use_case_score = min(40, (use_case_matches / len(use_cases)) * 40)
score += use_case_score
return int(score)
def find_best_match(requirement: str, components: List[str] = None) -> Tuple[str, int]:
"""
Find best matching component for requirement.
Args:
requirement: Feature requirement
components: List of component names to consider (None = all)
Returns:
Tuple of (best_component, score)
Example:
>>> find_best_match("need to show progress while installing")
('progress', 85)
"""
if components is None:
components = list(COMPONENT_CAPABILITIES.keys())
best_component = None
best_score = 0
for component in components:
score = match_score(requirement, component)
if score > best_score:
best_score = score
best_component = component
return best_component, best_score
def suggest_combinations(requirements: List[str]) -> List[List[str]]:
"""
Suggest component combinations for multiple requirements.
Args:
requirements: List of feature requirements
Returns:
List of component combinations (each is a list of components)
Example:
>>> suggest_combinations(["display logs", "search logs"])
[['viewport', 'textinput']]
"""
combinations = []
# Find best match for each requirement
selected_components = []
for req in requirements:
component, score = find_best_match(req)
if score > 50 and component not in selected_components:
selected_components.append(component)
if selected_components:
combinations.append(selected_components)
# Common patterns
patterns = {
'file_manager': ['filepicker', 'viewport', 'list'],
'installer': ['progress', 'spinner', 'list'],
'form': ['textinput', 'textarea', 'help'],
'viewer': ['viewport', 'paginator', 'textinput'],
'dashboard': ['tabs', 'viewport', 'table']
}
# Check if requirements match any patterns
req_text = ' '.join(requirements).lower()
for pattern_name, pattern_components in patterns.items():
if pattern_name.replace('_', ' ') in req_text:
combinations.append(pattern_components)
return combinations if combinations else [selected_components]
def get_alternatives(component: str) -> List[str]:
"""
Get alternative components that serve similar purposes.
Args:
component: Component name
Returns:
List of alternative component names
Example:
>>> get_alternatives('viewport')
['pager', 'textarea']
"""
alternatives = {
'viewport': ['pager'],
'textinput': ['textarea', 'autocomplete'],
'textarea': ['textinput', 'viewport'],
'table': ['list'],
'list': ['table', 'filepicker'],
'progress': ['spinner'],
'spinner': ['progress'],
'filepicker': ['list'],
'paginator': ['viewport'],
'tabs': ['composable-views']
}
return alternatives.get(component, [])
def explain_match(component: str, requirement: str, score: int) -> str:
"""
Generate explanation for why component matches requirement.
Args:
component: Component name
requirement: Requirement description
score: Match score
Returns:
Human-readable explanation
Example:
>>> explain_match("viewport", "scrollable display", 90)
"viewport is a strong match (90/100) for 'scrollable display' because..."
"""
if component not in COMPONENT_CAPABILITIES:
return f"{component} is not a known component"
comp_info = COMPONENT_CAPABILITIES[component]
requirement_lower = requirement.lower()
# Find which keywords matched
matched_keywords = [kw for kw in comp_info['keywords'] if kw in requirement_lower]
explanation_parts = []
if score >= 80:
explanation_parts.append(f"{component} is a strong match ({score}/100)")
elif score >= 50:
explanation_parts.append(f"{component} is a good match ({score}/100)")
else:
explanation_parts.append(f"{component} is a weak match ({score}/100)")
explanation_parts.append(f"for '{requirement}'")
if matched_keywords:
explanation_parts.append(f"because it handles: {', '.join(matched_keywords)}")
# Add use case
explanation_parts.append(f"Common use cases: {', '.join(comp_info['use_cases'])}")
return " ".join(explanation_parts) + "."
def rank_components_by_relevance(
requirements: List[str],
min_score: int = 50
) -> List[Tuple[str, int, List[str]]]:
"""
Rank all components by relevance to requirements.
Args:
requirements: List of feature requirements
min_score: Minimum score to include (default: 50)
Returns:
List of tuples: (component, total_score, matching_requirements)
Sorted by total_score descending
Example:
>>> rank_components_by_relevance(["scroll", "display text"])
[('viewport', 180, ['scroll', 'display text']), ...]
"""
component_scores = {}
component_matches = {}
all_components = list(COMPONENT_CAPABILITIES.keys())
for component in all_components:
total_score = 0
matching_reqs = []
for req in requirements:
score = match_score(req, component)
if score >= min_score:
total_score += score
matching_reqs.append(req)
if total_score > 0:
component_scores[component] = total_score
component_matches[component] = matching_reqs
# Sort by score
ranked = sorted(
component_scores.items(),
key=lambda x: x[1],
reverse=True
)
return [(comp, score, component_matches[comp]) for comp, score in ranked]
def main():
"""Test component matcher."""
print("Testing Component Matcher\n" + "=" * 50)
# Test 1: Match score
print("\n1. Testing match_score()...")
score = match_score("scrollable log display", "viewport")
print(f" Score for 'scrollable log display' + viewport: {score}")
assert score > 50, "Should have good score"
print(" ✓ Match scoring works")
# Test 2: Find best match
print("\n2. Testing find_best_match()...")
component, score = find_best_match("need to show progress while installing")
print(f" Best match: {component} ({score})")
assert component in ['progress', 'spinner'], "Should match progress-related component"
print(" ✓ Best match finding works")
# Test 3: Suggest combinations
print("\n3. Testing suggest_combinations()...")
combos = suggest_combinations(["display logs", "search logs", "scroll through logs"])
print(f" Suggested combinations: {combos}")
assert len(combos) > 0, "Should suggest at least one combination"
print(" ✓ Combination suggestion works")
# Test 4: Get alternatives
print("\n4. Testing get_alternatives()...")
alts = get_alternatives('viewport')
print(f" Alternatives to viewport: {alts}")
assert 'pager' in alts, "Should include pager as alternative"
print(" ✓ Alternative suggestions work")
# Test 5: Explain match
print("\n5. Testing explain_match()...")
explanation = explain_match("viewport", "scrollable display", 90)
print(f" Explanation: {explanation}")
assert "strong match" in explanation, "Should indicate strong match"
print(" ✓ Match explanation works")
# Test 6: Rank components
print("\n6. Testing rank_components_by_relevance()...")
ranked = rank_components_by_relevance(
["scroll", "display", "text", "search"],
min_score=40
)
print(f" Top 3 components:")
for i, (comp, score, reqs) in enumerate(ranked[:3], 1):
print(f" {i}. {comp} (score: {score}) - matches: {reqs}")
assert len(ranked) > 0, "Should rank some components"
print(" ✓ Component ranking works")
print("\n✅ All tests passed!")
if __name__ == "__main__":
main()