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: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: `Analyze this trading card image and extract the following information in JSON format:
|
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.'
|
||||||
|
|
||||||
{
|
|
||||||
"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.`
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'image_url',
|
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');
|
throw new Error('No content received from OpenAI');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse JSON response
|
// Clean and parse JSON response
|
||||||
try {
|
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 {
|
return {
|
||||||
cardName: result.cardName || '',
|
cardName: result.cardName || '',
|
||||||
setName: result.setName || undefined,
|
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'
|
game: result.game || 'UNKNOWN'
|
||||||
};
|
};
|
||||||
} catch (parseError) {
|
} 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 {
|
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,
|
rawText: content,
|
||||||
confidence: 70,
|
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';
|
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
|
// Detect game type from text
|
||||||
private detectGameFromText(text: string): 'MTG' | 'POKEMON' | 'LORCANA' | 'UNKNOWN' {
|
private detectGameFromText(text: string): 'MTG' | 'POKEMON' | 'LORCANA' | 'UNKNOWN' {
|
||||||
const lowerText = text.toLowerCase();
|
const lowerText = text.toLowerCase();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue