@tamilvananmurugan/xlibs
Version:
Comprehensive UI component library with Aceternity, MagicUI, and ShadCN components
181 lines (152 loc) • 5.55 kB
text/typescript
// LLM-Based Fuzzy Component Name Matcher
import { ComponentMetadata, componentRegistry } from './component-registry';
export interface FuzzyMatchResult {
component: ComponentMetadata;
confidence: number;
reason: string;
}
export class FuzzyComponentMatcher {
/**
* Find the best matching component for a given name using fuzzy matching
* @param componentName The name to match against
* @returns The best matching component with confidence score
*/
static findBestMatch(componentName: string): FuzzyMatchResult | null {
// First try exact match
const exactMatch = Object.values(componentRegistry).find(
c => c.name.toLowerCase() === componentName.toLowerCase()
);
if (exactMatch) {
return {
component: exactMatch,
confidence: 1.0,
reason: 'Exact match found'
};
}
// If no exact match, try fuzzy matching
const candidates = this.getFuzzyCandidates(componentName);
if (candidates.length === 0) {
return null;
}
// Sort by confidence and return the best match
candidates.sort((a, b) => b.confidence - a.confidence);
return candidates[0];
}
/**
* Get fuzzy matching candidates for a component name
* @param componentName The name to match against
* @returns Array of fuzzy match results
*/
private static getFuzzyCandidates(componentName: string): FuzzyMatchResult[] {
const candidates: FuzzyMatchResult[] = [];
const lowerComponentName = componentName.toLowerCase();
// Get all components
const allComponents = Object.values(componentRegistry);
for (const component of allComponents) {
const lowerComponent = component.name.toLowerCase();
// Calculate confidence score based on various factors
let confidence = 0;
const reasons: string[] = [];
// Exact substring match
if (lowerComponent.includes(lowerComponentName) || lowerComponentName.includes(lowerComponent)) {
confidence += 0.4;
reasons.push('substring match');
}
// Levenshtein distance (simplified)
const distance = this.levenshteinDistance(lowerComponentName, lowerComponent);
const maxLength = Math.max(lowerComponentName.length, lowerComponent.length);
const similarity = 1 - (distance / maxLength);
if (similarity > 0.5) {
confidence += similarity * 0.3;
reasons.push(`similarity: ${(similarity * 100).toFixed(1)}%`);
}
// Shared prefix/suffix
const sharedPrefix = this.getSharedPrefix(lowerComponentName, lowerComponent);
const sharedSuffix = this.getSharedSuffix(lowerComponentName, lowerComponent);
if (sharedPrefix.length > 0) {
const prefixRatio = sharedPrefix.length / Math.min(lowerComponentName.length, lowerComponent.length);
confidence += prefixRatio * 0.15;
reasons.push(`shared prefix: ${sharedPrefix}`);
}
if (sharedSuffix.length > 0) {
const suffixRatio = sharedSuffix.length / Math.min(lowerComponentName.length, lowerComponent.length);
confidence += suffixRatio * 0.15;
reasons.push(`shared suffix: ${sharedSuffix}`);
}
// Only include candidates with reasonable confidence
if (confidence > 0.3) {
candidates.push({
component,
confidence,
reason: reasons.join(', ')
});
}
}
return candidates;
}
/**
* Calculate Levenshtein distance between two strings
* @param a First string
* @param b Second string
* @returns The Levenshtein distance
*/
private static levenshteinDistance(a: string, b: string): number {
if (a.length === 0) return b.length;
if (b.length === 0) return a.length;
const matrix: number[][] = [];
// Increment along the first column of each row
for (let i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
// Increment each column in the first row
for (let j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
// Fill in the rest of the matrix
for (let i = 1; i <= b.length; i++) {
for (let j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1, // substitution
matrix[i][j - 1] + 1, // insertion
matrix[i - 1][j] + 1 // deletion
);
}
}
}
return matrix[b.length][a.length];
}
/**
* Get the shared prefix between two strings
* @param a First string
* @param b Second string
* @returns The shared prefix
*/
private static getSharedPrefix(a: string, b: string): string {
let i = 0;
while (i < a.length && i < b.length && a.charAt(i) === b.charAt(i)) {
i++;
}
return a.substring(0, i);
}
/**
* Get the shared suffix between two strings
* @param a First string
* @param b Second string
* @returns The shared suffix
*/
private static getSharedSuffix(a: string, b: string): string {
let i = 0;
const minLen = Math.min(a.length, b.length);
while (i < minLen && a.charAt(a.length - 1 - i) === b.charAt(b.length - 1 - i)) {
i++;
}
return a.substring(a.length - i);
}
}
// Usage example
export const FuzzyMatcher = {
findBestMatch: (componentName: string) => FuzzyComponentMatcher.findBestMatch(componentName)
};