fix animal card display
This commit is contained in:
parent
baba3b8178
commit
5d21462726
@ -6,32 +6,7 @@ import { useState, useEffect } from 'react'
|
||||
import { MapPin } from 'lucide-react'
|
||||
import { fetchAnimalRecords } from '../../services/laconicQueryService'
|
||||
import Navigation from '../../components/Navigation'
|
||||
|
||||
interface AnimalRecord {
|
||||
id: string
|
||||
mainObject: string
|
||||
location: {
|
||||
latitude: number
|
||||
longitude: number
|
||||
}
|
||||
description: string
|
||||
imageUrl: string
|
||||
species: string
|
||||
// Records from Laconic Registry use `attributes` to store the content
|
||||
// and have a separate creation timestamp
|
||||
attributes?: {
|
||||
mainObject: string
|
||||
location: {
|
||||
latitude: number
|
||||
longitude: number
|
||||
}
|
||||
description: string
|
||||
imageUrl: string
|
||||
species: string
|
||||
}
|
||||
bondId?: string
|
||||
createTime: string
|
||||
}
|
||||
import { AnimalRecord } from '../../types/records'
|
||||
|
||||
const hiddenIndices = (process.env.NEXT_PUBLIC_HIDDEN_INDICES?.split(',') || [])
|
||||
.map(num => parseInt(num))
|
||||
@ -97,35 +72,35 @@ export default function AnimalsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{records.map((record, index) => (
|
||||
<div
|
||||
key={`${record.id}-${index}`}
|
||||
className="bg-emerald-900/20 rounded-xl p-6 border border-emerald-800/50 hover:border-emerald-700/50 transition-colors"
|
||||
>
|
||||
<div className="aspect-video rounded-lg overflow-hidden mb-4 bg-emerald-900/50">
|
||||
<img
|
||||
src={record.imageUrl}
|
||||
alt={`${record.species} sighting`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{records.map((record, index) => (
|
||||
<div
|
||||
key={`${record.id}-${index}`}
|
||||
className="bg-emerald-900/20 rounded-xl p-6 border border-emerald-800/50 hover:border-emerald-700/50 transition-colors"
|
||||
>
|
||||
<div className="aspect-video rounded-lg overflow-hidden mb-4 bg-emerald-900/50">
|
||||
<img
|
||||
src={record.attributes?.imageUrl}
|
||||
alt={`Wildlife sighting`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-semibold text-emerald-300 capitalize mb-2">
|
||||
{record.species}
|
||||
</h3>
|
||||
<h3 className="text-xl font-semibold text-emerald-300 capitalize mb-2">
|
||||
{record.attributes?.mainObject}
|
||||
</h3>
|
||||
|
||||
<div className="flex items-center text-emerald-200/80 text-sm mb-3">
|
||||
<MapPin className="w-4 h-4 mr-1" />
|
||||
<span>
|
||||
{record.location.latitude}°, {record.location.longitude}°
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center text-emerald-200/80 text-sm mb-3">
|
||||
<MapPin className="w-4 h-4 mr-1" />
|
||||
<span>
|
||||
{record.attributes?.location.latitude}°, {record.attributes?.location.longitude}°
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="text-emerald-200 text-sm">
|
||||
{record.description}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-emerald-200 text-sm">
|
||||
{record.attributes?.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ export interface VisionAnalysisResult {
|
||||
description: string
|
||||
mainObject: string
|
||||
isAnimal: boolean
|
||||
matchedLabel: string // Add this to store which label was matched
|
||||
matchedLabel: string
|
||||
rawResponse: any
|
||||
}
|
||||
|
||||
@ -53,25 +53,27 @@ export async function analyzeImageWithVision(imageBuffer: Buffer): Promise<Visio
|
||||
const labels = data.responses[0]?.labelAnnotations || []
|
||||
const objects = data.responses[0]?.localizedObjectAnnotations || []
|
||||
|
||||
// Find the matching animal label
|
||||
// First check if it's an animal (using ANIMAL_LABELS for validation only)
|
||||
const matchedLabel = labels.find(label =>
|
||||
ANIMAL_LABELS.includes(label.description.toLowerCase())
|
||||
)?.description || ''
|
||||
|
||||
console.log('Vision API labels:', labels.map(l => l.description))
|
||||
console.log('Matched animal label:', matchedLabel)
|
||||
|
||||
const isAnimal = !!matchedLabel
|
||||
|
||||
// Get the first animal label as main object
|
||||
const mainObject = matchedLabel || 'Unknown'
|
||||
// For mainObject, use the first/highest confidence label from Vision API
|
||||
// This will be its best guess at the specific species/type
|
||||
const mainObject = labels[0]?.description || 'Unknown'
|
||||
|
||||
console.log('Vision API labels:', labels.map(l => l.description))
|
||||
console.log('Validation label match:', matchedLabel)
|
||||
console.log('Selected main object:', mainObject)
|
||||
|
||||
const { description } = constructDescription(labels, objects)
|
||||
|
||||
return {
|
||||
description,
|
||||
mainObject,
|
||||
isAnimal,
|
||||
mainObject, // Best species guess from Vision API
|
||||
isAnimal, // Validation result using ANIMAL_LABELS
|
||||
matchedLabel,
|
||||
rawResponse: data.responses[0]
|
||||
}
|
||||
|
@ -1,17 +1,6 @@
|
||||
// src/services/laconicQueryService.ts
|
||||
|
||||
interface AnimalRecord {
|
||||
id: string
|
||||
mainObject: string
|
||||
species: string
|
||||
location: {
|
||||
latitude: number
|
||||
longitude: number
|
||||
}
|
||||
description: string
|
||||
imageUrl: string
|
||||
createTime: string
|
||||
}
|
||||
import { AnimalRecord } from '../types/records'
|
||||
|
||||
const LACONIC_GQL_ENDPOINT = process.env.NEXT_PUBLIC_LACONIC_GQL_ENDPOINT || 'https://laconicd-sapo.laconic.com/api'
|
||||
|
||||
@ -84,18 +73,21 @@ export async function fetchAnimalRecords(): Promise<AnimalRecord[]> {
|
||||
}
|
||||
|
||||
return {
|
||||
species: attributesMap.species || 'Unknown Species',
|
||||
location: {
|
||||
latitude: location.latitude || 0,
|
||||
longitude: location.longitude || 0
|
||||
id: record.id,
|
||||
attributes: {
|
||||
mainObject: attributesMap.mainObject || 'Unknown',
|
||||
location: {
|
||||
latitude: location.latitude || 0,
|
||||
longitude: location.longitude || 0
|
||||
},
|
||||
description: attributesMap.description || 'No description',
|
||||
imageUrl: attributesMap.imageUrl || null,
|
||||
},
|
||||
description: attributesMap.description || 'No description',
|
||||
imageUrl: attributesMap.imageUrl || null,
|
||||
createTime: record.createTime || ''
|
||||
}
|
||||
})
|
||||
// Filter out records without an imageUrl
|
||||
.filter((record: AnimalRecord) => record.imageUrl !== null && record.imageUrl.trim() !== '');
|
||||
.filter((record: AnimalRecord) => record.attributes.imageUrl !== null && record.attributes.imageUrl.trim() !== '');
|
||||
|
||||
console.log('Processed animal records:', records);
|
||||
|
||||
|
@ -10,7 +10,7 @@ const execAsync = promisify(exec)
|
||||
interface LaconicAnimalRecord {
|
||||
record: {
|
||||
type: 'AnimalRecord'
|
||||
species: string
|
||||
mainObject: string // changed from species
|
||||
location: {
|
||||
latitude: number
|
||||
longitude: number
|
||||
@ -21,7 +21,7 @@ interface LaconicAnimalRecord {
|
||||
}
|
||||
|
||||
export async function publishAnimalRecord(
|
||||
species: string,
|
||||
mainObject: string, // changed parameter name
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
description: string,
|
||||
@ -35,7 +35,7 @@ export async function publishAnimalRecord(
|
||||
const record: LaconicAnimalRecord = {
|
||||
record: {
|
||||
type: 'AnimalRecord',
|
||||
species,
|
||||
mainObject, // use mainObject instead of species
|
||||
location: {
|
||||
latitude,
|
||||
longitude
|
||||
|
16
src/types/records.ts
Normal file
16
src/types/records.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// src/types/records.ts
|
||||
|
||||
export interface AnimalRecord {
|
||||
id: string
|
||||
attributes: {
|
||||
mainObject: string
|
||||
location: {
|
||||
latitude: number
|
||||
longitude: number
|
||||
}
|
||||
description: string
|
||||
imageUrl: string
|
||||
}
|
||||
bondId?: string
|
||||
createTime: string
|
||||
}
|
Loading…
Reference in New Issue
Block a user