Fix AI OCR JSON parsing issues
🐛 JSON Parsing Fixes:
- Handle markdown code blocks in AI responses
- Add robust text extraction fallback when JSON parsing fails
- Improve AI prompt to request plain JSON without formatting
- Add extractDataFromRawResponse method for text parsing
- Better error handling and logging for debugging
This should fix the issue where AI returns markdown formatted responses
instead of proper card data extraction.
This commit is contained in:
parent
0bc3e854fb
commit
e86782335e
1 changed files with 76 additions and 22 deletions
|
|
@ -46,23 +46,7 @@ class AICardOCR {
|
|||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Analyze this trading card image and extract the following information in JSON format:
|
||||
|
||||
{
|
||||
"cardName": "exact card name",
|
||||
"setName": "set name if visible",
|
||||
"setCode": "3-4 letter set code if visible",
|
||||
"cardType": "creature, spell, pokemon, etc",
|
||||
"rarity": "common, uncommon, rare, etc",
|
||||
"hp": "HP value for Pokemon",
|
||||
"attacks": ["attack names"],
|
||||
"abilities": ["ability names"],
|
||||
"rawText": "all visible text on the card",
|
||||
"game": "MTG or POKEMON or LORCANA",
|
||||
"confidence": 95
|
||||
}
|
||||
|
||||
Focus on accuracy. If you can't clearly read something, leave it empty rather than guessing.`
|
||||
text: 'Analyze this trading card image and extract information. Respond with ONLY valid JSON, no markdown formatting or code blocks:\n\n{"cardName": "exact card name", "setName": "set name if visible", "setCode": "3-4 letter set code if visible", "cardType": "creature, spell, pokemon, etc", "rarity": "common, uncommon, rare, etc", "hp": "HP value for Pokemon", "attacks": ["attack names"], "abilities": ["ability names"], "rawText": "all visible text on the card", "game": "MTG or POKEMON or LORCANA", "confidence": 95}\n\nIMPORTANT: Return ONLY the JSON object, no markdown code blocks. Focus on accuracy, leave fields empty if unclear. For the cardName, use the exact name as printed on the card.'
|
||||
},
|
||||
{
|
||||
type: 'image_url',
|
||||
|
|
@ -90,9 +74,17 @@ Focus on accuracy. If you can't clearly read something, leave it empty rather th
|
|||
throw new Error('No content received from OpenAI');
|
||||
}
|
||||
|
||||
// Parse JSON response
|
||||
// Clean and parse JSON response
|
||||
try {
|
||||
const result = JSON.parse(content);
|
||||
// Remove markdown code blocks if present
|
||||
let cleanContent = content.trim();
|
||||
if (cleanContent.startsWith('```json')) {
|
||||
cleanContent = cleanContent.replace(/^```json\s*/, '').replace(/\s*```$/, '');
|
||||
} else if (cleanContent.startsWith('```')) {
|
||||
cleanContent = cleanContent.replace(/^```\s*/, '').replace(/\s*```$/, '');
|
||||
}
|
||||
|
||||
const result = JSON.parse(cleanContent);
|
||||
return {
|
||||
cardName: result.cardName || '',
|
||||
setName: result.setName || undefined,
|
||||
|
|
@ -107,12 +99,23 @@ Focus on accuracy. If you can't clearly read something, leave it empty rather th
|
|||
game: result.game || 'UNKNOWN'
|
||||
};
|
||||
} catch (parseError) {
|
||||
// If JSON parsing fails, treat the whole response as raw text
|
||||
console.log('JSON parsing failed, trying text extraction:', parseError);
|
||||
console.log('Raw content:', content);
|
||||
|
||||
// If JSON parsing fails, try to extract data from the raw text
|
||||
const extractedData = this.extractDataFromRawResponse(content);
|
||||
return {
|
||||
cardName: this.extractCardNameFromText(content),
|
||||
cardName: extractedData.cardName || 'Unknown Card',
|
||||
setName: extractedData.setName,
|
||||
setCode: extractedData.setCode,
|
||||
cardType: extractedData.cardType,
|
||||
rarity: extractedData.rarity,
|
||||
hp: extractedData.hp,
|
||||
attacks: extractedData.attacks || [],
|
||||
abilities: extractedData.abilities || [],
|
||||
rawText: content,
|
||||
confidence: 70,
|
||||
game: this.detectGameFromText(content)
|
||||
game: extractedData.game || 'UNKNOWN'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -138,6 +141,57 @@ Focus on accuracy. If you can't clearly read something, leave it empty rather th
|
|||
return lines[0] || 'Unknown Card';
|
||||
}
|
||||
|
||||
// Extract data from raw AI response when JSON parsing fails
|
||||
private extractDataFromRawResponse(text: string): Partial<CardOCRResult> {
|
||||
const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0);
|
||||
|
||||
let cardName = '';
|
||||
let setName = '';
|
||||
let setCode = '';
|
||||
let cardType = '';
|
||||
let rarity = '';
|
||||
let hp = '';
|
||||
let attacks: string[] = [];
|
||||
let abilities: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
// Skip markdown and formatting
|
||||
if (line.startsWith('```') || line.startsWith('#')) continue;
|
||||
|
||||
// Look for key-value patterns
|
||||
if (line.match(/card\s*name[:\-]?\s*(.+)/i)) {
|
||||
cardName = line.replace(/card\s*name[:\-]?\s*/i, '').replace(/['"]/g, '');
|
||||
} else if (line.match(/name[:\-]?\s*(.+)/i) && !cardName) {
|
||||
cardName = line.replace(/name[:\-]?\s*/i, '').replace(/['"]/g, '');
|
||||
} else if (line.match(/set[:\-]?\s*(.+)/i)) {
|
||||
setName = line.replace(/set[:\-]?\s*/i, '').replace(/['"]/g, '');
|
||||
} else if (line.match(/hp[:\-]?\s*(\d+)/i)) {
|
||||
hp = line.match(/hp[:\-]?\s*(\d+)/i)?.[1] || '';
|
||||
} else if (line.match(/type[:\-]?\s*(.+)/i)) {
|
||||
cardType = line.replace(/type[:\-]?\s*/i, '').replace(/['"]/g, '');
|
||||
} else if (line.match(/rarity[:\-]?\s*(.+)/i)) {
|
||||
rarity = line.replace(/rarity[:\-]?\s*/i, '').replace(/['"]/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
// If no structured data found, use the first substantial line as card name
|
||||
if (!cardName) {
|
||||
cardName = this.extractCardNameFromText(text);
|
||||
}
|
||||
|
||||
return {
|
||||
cardName: cardName || 'Unknown Card',
|
||||
setName: setName || undefined,
|
||||
setCode: setCode || undefined,
|
||||
cardType: cardType || undefined,
|
||||
rarity: rarity || undefined,
|
||||
hp: hp || undefined,
|
||||
attacks: attacks.length > 0 ? attacks : undefined,
|
||||
abilities: abilities.length > 0 ? abilities : undefined,
|
||||
game: this.detectGameFromText(text)
|
||||
};
|
||||
}
|
||||
|
||||
// Detect game type from text
|
||||
private detectGameFromText(text: string): 'MTG' | 'POKEMON' | 'LORCANA' | 'UNKNOWN' {
|
||||
const lowerText = text.toLowerCase();
|
||||
|
|
|
|||
Loading…
Reference in a new issue