|
|
import type { PicletInstance, DiscoveryStatus } from '$lib/db/schema'; |
|
|
import type { GradioClient } from '$lib/types'; |
|
|
|
|
|
export interface CanonicalSearchResult { |
|
|
status: DiscoveryStatus; |
|
|
piclet: PicletInstance | null; |
|
|
canonicalId?: string; |
|
|
matchedAttributes?: string[]; |
|
|
suggestedVariation?: string[]; |
|
|
message?: string; |
|
|
} |
|
|
|
|
|
export interface ObjectExtractionResult { |
|
|
primaryObject: string; |
|
|
attributes: string[]; |
|
|
visualDetails: string; |
|
|
} |
|
|
|
|
|
export class CanonicalService { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static extractObjectFromCaption(caption: string): ObjectExtractionResult { |
|
|
|
|
|
const normalized = caption.toLowerCase().trim(); |
|
|
|
|
|
|
|
|
|
|
|
const objectPatterns = [ |
|
|
/(?:a|an|the)\s+(\w+(?:\s+\w+)?)\s+(?:is|sits|stands|lies|rests)/i, |
|
|
/(?:a|an|the)\s+([\w\s]+?)(?:\s+with|\s+that|\s+in|\s+on|,|\.|$)/i, |
|
|
/^([\w\s]+?)(?:\s+with|\s+that|\s+in|\s+on|,|\.|$)/i, |
|
|
]; |
|
|
|
|
|
let primaryObject = ''; |
|
|
for (const pattern of objectPatterns) { |
|
|
const match = caption.match(pattern); |
|
|
if (match && match[1]) { |
|
|
|
|
|
primaryObject = match[1] |
|
|
.trim() |
|
|
.replace(/\s+/g, ' ') |
|
|
.split(' ') |
|
|
.filter(word => !['very', 'quite', 'rather', 'extremely'].includes(word)) |
|
|
.pop() || ''; |
|
|
|
|
|
if (primaryObject) break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!primaryObject) { |
|
|
const words = normalized.split(/\s+/); |
|
|
primaryObject = words.find(w => w.length > 3 && !['with', 'that', 'this', 'from'].includes(w)) || 'object'; |
|
|
} |
|
|
|
|
|
|
|
|
const attributeWords = [ |
|
|
|
|
|
'wooden', 'metal', 'plastic', 'glass', 'leather', 'velvet', 'silk', 'cotton', 'stone', 'marble', |
|
|
'gold', 'silver', 'bronze', 'copper', 'steel', 'iron', 'aluminum', 'ceramic', 'porcelain', |
|
|
|
|
|
'modern', 'vintage', 'antique', 'rustic', 'minimalist', 'ornate', 'gothic', 'art deco', 'retro', |
|
|
|
|
|
'red', 'blue', 'green', 'yellow', 'purple', 'orange', 'black', 'white', 'gray', 'brown', |
|
|
|
|
|
'striped', 'polka dot', 'floral', 'geometric', 'plaid', 'checkered', |
|
|
|
|
|
'old', 'new', 'worn', 'shiny', 'matte', 'glossy', 'rough', 'smooth' |
|
|
]; |
|
|
|
|
|
const attributes: string[] = []; |
|
|
const lowerCaption = caption.toLowerCase(); |
|
|
|
|
|
for (const attr of attributeWords) { |
|
|
if (lowerCaption.includes(attr) && attributes.length < 3) { |
|
|
attributes.push(attr); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const visualDetails = caption |
|
|
.replace(new RegExp(primaryObject, 'gi'), '') |
|
|
.replace(new RegExp(attributes.join('|'), 'gi'), '') |
|
|
.replace(/(?:a|an|the)\s+/gi, '') |
|
|
.replace(/\s+/g, ' ') |
|
|
.trim(); |
|
|
|
|
|
return { |
|
|
primaryObject: primaryObject.toLowerCase(), |
|
|
attributes, |
|
|
visualDetails |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static async searchCanonical( |
|
|
client: GradioClient, |
|
|
objectName: string, |
|
|
attributes: string[] |
|
|
): Promise<CanonicalSearchResult | null> { |
|
|
try { |
|
|
const result = await client.predict("/search_piclet", { |
|
|
object_name: objectName, |
|
|
attributes: attributes |
|
|
}); |
|
|
|
|
|
|
|
|
const data = result.data?.[0]; |
|
|
return data || null; |
|
|
} catch (error) { |
|
|
console.error('Failed to search canonical:', error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static async createCanonical( |
|
|
client: GradioClient, |
|
|
objectName: string, |
|
|
piclet: PicletInstance, |
|
|
accessToken: string |
|
|
): Promise<any> { |
|
|
try { |
|
|
const result = await client.predict("/create_canonical", { |
|
|
object_name: objectName, |
|
|
piclet_data: JSON.stringify(piclet), |
|
|
token_or_username: accessToken |
|
|
}); |
|
|
|
|
|
|
|
|
return result.data?.[0]; |
|
|
} catch (error) { |
|
|
console.error('Failed to create canonical:', error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static async createVariation( |
|
|
client: GradioClient, |
|
|
canonicalId: string, |
|
|
objectName: string, |
|
|
attributes: string[], |
|
|
piclet: PicletInstance, |
|
|
accessToken: string |
|
|
): Promise<any> { |
|
|
try { |
|
|
const result = await client.predict("/create_variation", { |
|
|
canonical_id: canonicalId, |
|
|
attributes: attributes, |
|
|
piclet_data: JSON.stringify(piclet), |
|
|
token_or_username: accessToken, |
|
|
object_name: objectName |
|
|
}); |
|
|
|
|
|
|
|
|
return result.data?.[0]; |
|
|
} catch (error) { |
|
|
console.error('Failed to create variation:', error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static async incrementScanCount( |
|
|
client: GradioClient, |
|
|
picletId: string, |
|
|
objectName: string |
|
|
): Promise<void> { |
|
|
try { |
|
|
await client.predict("/increment_scan_count", { |
|
|
piclet_id: picletId, |
|
|
object_name: objectName |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Failed to increment scan count:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static calculateRarity(scanCount: number): string { |
|
|
if (scanCount <= 5) return 'legendary'; |
|
|
if (scanCount <= 20) return 'epic'; |
|
|
if (scanCount <= 50) return 'rare'; |
|
|
if (scanCount <= 100) return 'uncommon'; |
|
|
return 'common'; |
|
|
} |
|
|
} |