Spaces:
Running
Running
it has to be better at identifying re-issues and first presses , and also this error is back: Analysis failed: Failed to execute 'querySelector' on 'Document': '.md\\:w-80' is not a valid selector. please make sure app is bug free before completing
9bc6d93 verified | class OCRService { | |
| constructor() { | |
| this.apiKey = localStorage.getItem('openai_api_key'); | |
| this.model = localStorage.getItem('openai_model') || 'gpt-4o-mini'; | |
| } | |
| async analyzeRecordImages(imageFiles) { | |
| if (!this.apiKey) { | |
| throw new Error('OpenAI API key not configured. Please add it in Settings.'); | |
| } | |
| const base64Images = await Promise.all( | |
| imageFiles.map(file => this.fileToBase64(file)) | |
| ); | |
| const messages = [ | |
| { | |
| role: 'system', | |
| content: `You are a vinyl record identification expert specializing in pressing identification. Analyze record images and extract all visible information with special attention to first press vs reissue identification. | |
| CRITICAL - Pressing Identification Rules: | |
| 1. DEADWAX/MATRIX ANALYSIS IS ESSENTIAL - Look for: | |
| - Hand-etched vs machine-stamped matrix numbers (hand-etched often indicates early pressings) | |
| - Plant identifiers: "STERLING", "RL", "PORKY", "TML", "EMI", "CBS" | |
| - Mastering engineer initials or signatures | |
| - "A1", "B1" stampings indicate first stamper/cut | |
| - "-" or "/" separators in matrix numbers | |
| - Additional letters after main catalog number (e.g., "-A", "/1") | |
| 2. LABEL ANALYSIS for pressing identification: | |
| - Label design variations (logo style, color shades, rim text) | |
| - Address changes on labels (indicate different pressing periods) | |
| - "Made in..." country variations | |
| - Stereo/mono indicators and their placement | |
| - Copyright text differences | |
| 3. SLEEVE/COVER indicators: | |
| - Barcode presence = likely 1980s+ reissue (originals often lack barcodes) | |
| - Price codes (UK: K/T/S prefixes, US: $ prices) | |
| - Laminated vs non-laminated sleeves | |
| - "Digital Remaster" or "180g" stickers = modern reissue | |
| 4. YEAR vs ORIGINAL YEAR distinction: | |
| - Sleeve may show original release year | |
| - Label/barcode may reveal actual pressing year | |
| - Catalog number patterns indicate era | |
| Return ONLY a JSON object with this exact structure: | |
| { | |
| "artist": "string or null", | |
| "title": "string or null", | |
| "catalogueNumber": "string or null", | |
| "label": "string or null", | |
| "year": "number or null (the pressing/year shown on this copy)", | |
| "originalYear": "number or null (if different from year, the original release year)", | |
| "reissueYear": "number or null (if this is a reissue, when it was reissued)", | |
| "country": "string or null", | |
| "format": "string or null (e.g., LP, 12\\", 7\\")", | |
| "genre": "string or null", | |
| "conditionEstimate": "string or null (NM/VG+/VG/G)", | |
| "pressingInfo": "string or null (full matrix/runout details as found)", | |
| "isFirstPress": "boolean or null (true if evidence suggests first pressing)", | |
| "pressingType": "string or null ('first_press', 'repress', 'reissue', 'unknown')", | |
| "pressingConfidence": "string ('high', 'medium', 'low' - how confident you are about pressing identification)", | |
| "pressingEvidence": "array of strings (specific visual evidence for pressing determination)", | |
| "confidence": "high|medium|low", | |
| "notes": "array of strings with additional observations" | |
| } | |
| Be precise. Only include info you can clearly read. For pressing identification, be conservative - only mark as first press if you see strong evidence (A1/B1 stampers, specific plant codes matching known first presses, etc.).` | |
| }, | |
| { | |
| role: 'user', | |
| content: [ | |
| { | |
| type: 'text', | |
| text: 'Analyze these record photos. Identify the artist, title, catalogue number, label, year, and CRITICALLY: examine the deadwax/matrix area and labels closely to determine if this is a first pressing, repress, or reissue. Report any matrix numbers, plant codes, or label variations you can see.' | |
| }, | |
| ...base64Images.map(base64 => ({ | |
| type: 'image_url', | |
| image_url: { | |
| url: `data:image/jpeg;base64,${base64}`, | |
| detail: 'high' | |
| } | |
| })) | |
| ] | |
| } | |
| ]; | |
| try { | |
| const response = await fetch('https://api.openai.com/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${this.apiKey}` | |
| }, | |
| body: JSON.stringify({ | |
| model: this.model, | |
| messages: messages, | |
| max_tokens: 2000, | |
| temperature: 0.2 | |
| }) | |
| }); | |
| if (!response.ok) { | |
| const error = await response.json(); | |
| throw new Error(error.error?.message || 'OCR analysis failed'); | |
| } | |
| const data = await response.json(); | |
| const content = data.choices[0].message.content; | |
| // Extract JSON from potential markdown code blocks | |
| const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```|([\s\S]*)/); | |
| const jsonStr = jsonMatch ? (jsonMatch[1] || jsonMatch[2]) : content; | |
| try { | |
| return JSON.parse(jsonStr.trim()); | |
| } catch (e) { | |
| console.error('Failed to parse OCR response:', content); | |
| throw new Error('Failed to parse record data'); | |
| } | |
| } catch (error) { | |
| console.error('OCR Analysis Error:', error); | |
| throw error; | |
| } | |
| } | |
| async fileToBase64(file) { | |
| return new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = () => { | |
| // Return clean base64 without data URL prefix for API calls | |
| const base64 = reader.result.split(',')[1]; | |
| resolve(base64); | |
| }; | |
| reader.onerror = reject; | |
| reader.readAsDataURL(file); | |
| }); | |
| } | |
| updateApiKey(key) { | |
| this.apiKey = key; | |
| } | |
| updateModel(model) { | |
| this.model = model; | |
| } | |
| } | |
| window.ocrService = new OCRService(); | |