fixes
This commit is contained in:
parent
497df1e7a1
commit
a8666c94e3
@ -2,66 +2,20 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import WalletHeader from '../components/WalletHeader'
|
|
||||||
import ImageAnalysisCard from '../components/ImageAnalysisCard'
|
import ImageAnalysisCard from '../components/ImageAnalysisCard'
|
||||||
import Navigation from '../components/Navigation'
|
import Navigation from '../components/Navigation'
|
||||||
import { analyzeImage, VisionAnalysisResult, VISION_CONFIG } from '../services/googleVisionService'
|
import { analyzeImage, VisionAnalysisResult, VISION_CONFIG } from '../services/googleVisionService'
|
||||||
import { processMTMPayment } from '../services/paymentService'
|
|
||||||
import { connectWallet, WalletState } from '../services/walletService'
|
|
||||||
import { WalletType } from '../services/types'
|
|
||||||
import { FREE_MODE } from '../config/freeMode'
|
|
||||||
import { APP_CONFIG, getThemeColors } from '../config/appConfig'
|
import { APP_CONFIG, getThemeColors } from '../config/appConfig'
|
||||||
|
|
||||||
|
|
||||||
console.log('NEXT_PUBLIC_FREE_MODE value:', process.env.NEXT_PUBLIC_FREE_MODE)
|
|
||||||
console.log('FREE_MODE computed value:', FREE_MODE)
|
|
||||||
|
|
||||||
const Page: React.FC = (): React.ReactElement => {
|
const Page: React.FC = (): React.ReactElement => {
|
||||||
const [walletState, setWalletState] = useState<WalletState>({
|
|
||||||
connected: FREE_MODE ? true : false, // Always "connected" in free mode
|
|
||||||
publicKey: null,
|
|
||||||
type: null
|
|
||||||
})
|
|
||||||
|
|
||||||
const theme = getThemeColors(APP_CONFIG.theme)
|
const theme = getThemeColors(APP_CONFIG.theme)
|
||||||
|
|
||||||
|
const handleImageAnalysis = () => {
|
||||||
const handleConnect = async (walletType: WalletType): Promise<void> => {
|
|
||||||
if (FREE_MODE) return
|
|
||||||
try {
|
|
||||||
const newWalletState = await connectWallet(walletType)
|
|
||||||
setWalletState(newWalletState)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Wallet connection error:', error)
|
|
||||||
setWalletState({
|
|
||||||
connected: false,
|
|
||||||
publicKey: null,
|
|
||||||
type: null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleImageAnalysis = (cost: number) => {
|
|
||||||
return async (imageFile: File): Promise<VisionAnalysisResult> => {
|
return async (imageFile: File): Promise<VisionAnalysisResult> => {
|
||||||
// In free mode, skip wallet checks and payment
|
|
||||||
if (!FREE_MODE && (!walletState.connected || !walletState.publicKey || !walletState.type)) {
|
|
||||||
return { error: 'Wallet not connected' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only process payment if not in free mode
|
|
||||||
if (!FREE_MODE) {
|
|
||||||
const paymentResult = await processMTMPayment(
|
|
||||||
walletState.publicKey!,
|
|
||||||
cost,
|
|
||||||
walletState.type!
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!paymentResult.success) {
|
|
||||||
return { error: paymentResult.error }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then analyze the image
|
|
||||||
return analyzeImage(imageFile)
|
return analyzeImage(imageFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,12 +33,6 @@ const Page: React.FC = (): React.ReactElement => {
|
|||||||
{APP_CONFIG.description}
|
{APP_CONFIG.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{!FREE_MODE && (
|
|
||||||
<WalletHeader
|
|
||||||
walletState={walletState}
|
|
||||||
onConnect={handleConnect}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Single Analysis Card */}
|
{/* Single Analysis Card */}
|
||||||
@ -96,9 +44,7 @@ const Page: React.FC = (): React.ReactElement => {
|
|||||||
<ImageAnalysisCard
|
<ImageAnalysisCard
|
||||||
title={VISION_CONFIG.name}
|
title={VISION_CONFIG.name}
|
||||||
description={VISION_CONFIG.description}
|
description={VISION_CONFIG.description}
|
||||||
tokenCost={VISION_CONFIG.cost}
|
onAnalyze={handleImageAnalysis()} // ?
|
||||||
isWalletConnected={walletState.connected}
|
|
||||||
onAnalyze={handleImageAnalysis(VISION_CONFIG.cost)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,125 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import React, { useState } from 'react'
|
|
||||||
|
|
||||||
interface AIServiceCardProps {
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
tokenCost: number
|
|
||||||
isWalletConnected: boolean
|
|
||||||
onGenerate: (prompt: string) => Promise<{ response?: string, error?: string }>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GenerationState {
|
|
||||||
loading: boolean
|
|
||||||
response: string | null
|
|
||||||
error: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
const AIServiceCard: React.FC<AIServiceCardProps> = ({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
tokenCost,
|
|
||||||
isWalletConnected,
|
|
||||||
onGenerate
|
|
||||||
}) => {
|
|
||||||
const [inputText, setInputText] = useState<string>('')
|
|
||||||
const [generationState, setGenerationState] = useState<GenerationState>({
|
|
||||||
loading: false,
|
|
||||||
response: null,
|
|
||||||
error: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleGenerate = async (): Promise<void> => {
|
|
||||||
if (!inputText || !isWalletConnected) return
|
|
||||||
|
|
||||||
setGenerationState({
|
|
||||||
...generationState,
|
|
||||||
loading: true,
|
|
||||||
error: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await onGenerate(inputText)
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
setGenerationState({
|
|
||||||
...generationState,
|
|
||||||
loading: false,
|
|
||||||
error: result.error,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.response) {
|
|
||||||
setGenerationState({
|
|
||||||
loading: false,
|
|
||||||
response: result.response,
|
|
||||||
error: null,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
throw new Error('No response received')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setGenerationState({
|
|
||||||
...generationState,
|
|
||||||
loading: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Generation failed',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full bg-slate-900/50 backdrop-blur-lg rounded-2xl shadow-xl border border-orange-800/50 mb-8 hover:shadow-orange-500/20 transition-all duration-300">
|
|
||||||
<div className="p-6">
|
|
||||||
<div className="mb-4">
|
|
||||||
<h2 className="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-orange-400 to-amber-500">
|
|
||||||
{title}
|
|
||||||
</h2>
|
|
||||||
<p className="text-slate-300 mt-2">{description}</p>
|
|
||||||
<div className="mt-2 inline-block px-3 py-1 bg-amber-500/20 rounded-full text-amber-200 text-sm">
|
|
||||||
Cost: {tokenCost} MTM
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<textarea
|
|
||||||
value={inputText}
|
|
||||||
onChange={(e) => setInputText(e.target.value)}
|
|
||||||
placeholder="Ask me anything..."
|
|
||||||
disabled={!isWalletConnected}
|
|
||||||
className="w-full bg-slate-950/80 text-slate-200 border border-orange-900 rounded-xl p-4
|
|
||||||
placeholder-slate-500 focus:border-amber-500 focus:ring-2 focus:ring-amber-500/20
|
|
||||||
focus:outline-none min-h-[120px] transition-all duration-200
|
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
rows={4}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={handleGenerate}
|
|
||||||
disabled={!isWalletConnected || generationState.loading || !inputText}
|
|
||||||
className="w-full bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-600
|
|
||||||
hover:to-amber-600 text-white font-semibold py-4 px-6 rounded-xl
|
|
||||||
transition-all duration-200 shadow-lg hover:shadow-amber-500/25
|
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none"
|
|
||||||
>
|
|
||||||
{generationState.loading ? 'Processing...' : `Pay ${tokenCost} MTM & Chat`}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{generationState.error && (
|
|
||||||
<div className="mt-4 bg-red-900/20 border border-red-500/20 text-red-400 px-4 py-3 rounded-xl text-center">
|
|
||||||
{generationState.error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{generationState.response && (
|
|
||||||
<div className="mt-4 bg-slate-800/50 border border-orange-800/50 rounded-xl p-4">
|
|
||||||
<p className="text-slate-200 whitespace-pre-wrap">{generationState.response}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AIServiceCard
|
|
@ -3,16 +3,11 @@
|
|||||||
|
|
||||||
import React, { useState, useRef } from 'react'
|
import React, { useState, useRef } from 'react'
|
||||||
import { Leaf } from 'lucide-react'
|
import { Leaf } from 'lucide-react'
|
||||||
import { FREE_MODE } from '../config/freeMode'
|
|
||||||
|
|
||||||
console.log('ImageAnalysisCard FREE_MODE value:', FREE_MODE)
|
|
||||||
|
|
||||||
|
|
||||||
interface ImageAnalysisCardProps {
|
interface ImageAnalysisCardProps {
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
tokenCost: number
|
|
||||||
isWalletConnected: boolean
|
|
||||||
onAnalyze: (file: File) => Promise<{ description?: string, error?: string }>
|
onAnalyze: (file: File) => Promise<{ description?: string, error?: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,8 +21,6 @@ interface AnalysisState {
|
|||||||
const ImageAnalysisCard: React.FC<ImageAnalysisCardProps> = ({
|
const ImageAnalysisCard: React.FC<ImageAnalysisCardProps> = ({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
tokenCost,
|
|
||||||
isWalletConnected,
|
|
||||||
onAnalyze
|
onAnalyze
|
||||||
}) => {
|
}) => {
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
@ -53,7 +46,8 @@ const ImageAnalysisCard: React.FC<ImageAnalysisCardProps> = ({
|
|||||||
|
|
||||||
const handleAnalyze = async () => {
|
const handleAnalyze = async () => {
|
||||||
const file = fileInputRef.current?.files?.[0]
|
const file = fileInputRef.current?.files?.[0]
|
||||||
if (!file || (!FREE_MODE && !isWalletConnected)) return
|
|
||||||
|
if (!file) return
|
||||||
|
|
||||||
setAnalysisState({
|
setAnalysisState({
|
||||||
...analysisState,
|
...analysisState,
|
||||||
@ -103,11 +97,6 @@ const ImageAnalysisCard: React.FC<ImageAnalysisCardProps> = ({
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-emerald-200 mt-2">{description}</p>
|
<p className="text-emerald-200 mt-2">{description}</p>
|
||||||
{!FREE_MODE && (
|
|
||||||
<div className="mt-2 inline-block px-3 py-1 bg-emerald-500/20 rounded-full text-emerald-200 text-sm">
|
|
||||||
Cost: {tokenCost} MTM
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@ -122,7 +111,6 @@ const ImageAnalysisCard: React.FC<ImageAnalysisCardProps> = ({
|
|||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
onChange={handleFileSelect}
|
onChange={handleFileSelect}
|
||||||
disabled={!FREE_MODE && !isWalletConnected}
|
|
||||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer
|
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer
|
||||||
disabled:cursor-not-allowed"
|
disabled:cursor-not-allowed"
|
||||||
/>
|
/>
|
||||||
@ -143,13 +131,13 @@ const ImageAnalysisCard: React.FC<ImageAnalysisCardProps> = ({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleAnalyze}
|
onClick={handleAnalyze}
|
||||||
disabled={(!FREE_MODE && !isWalletConnected) || analysisState.loading || !analysisState.imageUrl}
|
disabled={analysisState.loading || !analysisState.imageUrl}
|
||||||
className="w-full bg-gradient-to-r from-emerald-500 to-teal-500 hover:from-emerald-600
|
className="w-full bg-gradient-to-r from-emerald-500 to-teal-500 hover:from-emerald-600
|
||||||
hover:to-teal-600 text-white font-semibold py-4 px-6 rounded-xl
|
hover:to-teal-600 text-white font-semibold py-4 px-6 rounded-xl
|
||||||
transition-all duration-200 shadow-lg hover:shadow-emerald-500/25
|
transition-all duration-200 shadow-lg hover:shadow-emerald-500/25
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none"
|
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none"
|
||||||
>
|
>
|
||||||
{analysisState.loading ? 'Processing...' : FREE_MODE ? 'Analyze' : `Pay ${tokenCost} MTM & Analyze`}
|
{analysisState.loading ? 'Processing...' : 'Analyze' }
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import React, { useState } from 'react'
|
|
||||||
|
|
||||||
interface TextGenerationCardProps {
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
tokenCost: number
|
|
||||||
isWalletConnected: boolean
|
|
||||||
onGenerate: (prompt: string) => Promise<{ textResponse?: string, error?: string }>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GenerationState {
|
|
||||||
loading: boolean
|
|
||||||
processing: boolean
|
|
||||||
textResponse: string | null
|
|
||||||
error: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
const TextGenerationCard: React.FC<TextGenerationCardProps> = ({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
tokenCost,
|
|
||||||
isWalletConnected,
|
|
||||||
onGenerate
|
|
||||||
}) => {
|
|
||||||
const [inputText, setInputText] = useState<string>('')
|
|
||||||
const [generationState, setGenerationState] = useState<GenerationState>({
|
|
||||||
loading: false,
|
|
||||||
processing: false,
|
|
||||||
textResponse: null,
|
|
||||||
error: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleGenerate = async (): Promise<void> => {
|
|
||||||
if (!inputText || !isWalletConnected) return
|
|
||||||
|
|
||||||
setGenerationState({
|
|
||||||
...generationState,
|
|
||||||
loading: true,
|
|
||||||
error: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await onGenerate(inputText)
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
setGenerationState({
|
|
||||||
...generationState,
|
|
||||||
loading: false,
|
|
||||||
error: result.error,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.textResponse) {
|
|
||||||
setGenerationState({
|
|
||||||
loading: false,
|
|
||||||
processing: false,
|
|
||||||
textResponse: result.textResponse,
|
|
||||||
error: null,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
throw new Error('No response received')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setGenerationState({
|
|
||||||
...generationState,
|
|
||||||
loading: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Generation failed',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full bg-gray-800/50 backdrop-blur-lg rounded-2xl shadow-xl border border-gray-700/50 mb-8">
|
|
||||||
<div className="p-6">
|
|
||||||
<div className="mb-4">
|
|
||||||
<h2 className="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-green-400 to-emerald-600">
|
|
||||||
{title}
|
|
||||||
</h2>
|
|
||||||
<p className="text-gray-400 mt-2">{description}</p>
|
|
||||||
<div className="mt-2 inline-block px-3 py-1 bg-green-500/20 rounded-full text-green-300 text-sm">
|
|
||||||
Cost: {tokenCost} MTM
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<textarea
|
|
||||||
value={inputText}
|
|
||||||
onChange={(e) => setInputText(e.target.value)}
|
|
||||||
placeholder="Enter your question here..."
|
|
||||||
disabled={!isWalletConnected}
|
|
||||||
className="w-full bg-gray-900/50 text-gray-100 border border-gray-700 rounded-xl p-4
|
|
||||||
placeholder-gray-500 focus:border-green-500 focus:ring-2 focus:ring-green-500/20
|
|
||||||
focus:outline-none min-h-[120px] transition-all duration-200
|
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
rows={4}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={handleGenerate}
|
|
||||||
disabled={!isWalletConnected || generationState.loading || !inputText}
|
|
||||||
className="w-full bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600
|
|
||||||
hover:to-emerald-600 text-white font-semibold py-4 px-6 rounded-xl
|
|
||||||
transition-all duration-200 shadow-lg hover:shadow-green-500/25
|
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none"
|
|
||||||
>
|
|
||||||
{generationState.loading ? 'Processing...' : `Pay ${tokenCost} MTM & Generate`}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{generationState.error && (
|
|
||||||
<div className="mt-4 bg-red-900/20 border border-red-500/20 text-red-400 px-4 py-3 rounded-xl text-center">
|
|
||||||
{generationState.error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{generationState.textResponse && (
|
|
||||||
<div className="mt-6 bg-gray-900/50 rounded-xl p-6 border border-gray-700">
|
|
||||||
<h3 className="text-lg font-semibold text-green-400 mb-3">Response:</h3>
|
|
||||||
<div className="text-gray-300 whitespace-pre-wrap">
|
|
||||||
{generationState.textResponse}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TextGenerationCard
|
|
@ -1,41 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import React from 'react'
|
|
||||||
import { WalletState, SUPPORTED_WALLETS } from '../services/walletService'
|
|
||||||
import { WalletType } from '../services/types'
|
|
||||||
|
|
||||||
interface WalletHeaderProps {
|
|
||||||
walletState: WalletState
|
|
||||||
onConnect: (walletType: WalletType) => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
const WalletHeader: React.FC<WalletHeaderProps> = ({ walletState, onConnect }) => {
|
|
||||||
return (
|
|
||||||
<div className="max-w-md mx-auto bg-green-950/50 backdrop-blur-lg rounded-xl shadow-lg border border-emerald-800/50 mb-8 p-4">
|
|
||||||
{!walletState.connected ? (
|
|
||||||
<div className="flex justify-center">
|
|
||||||
{SUPPORTED_WALLETS.map((wallet) => (
|
|
||||||
<button
|
|
||||||
key={wallet.type}
|
|
||||||
onClick={() => onConnect(wallet.type)}
|
|
||||||
className="bg-gradient-to-r from-emerald-500 to-teal-500 hover:from-emerald-600 hover:to-teal-600
|
|
||||||
text-white font-semibold py-3 px-8 rounded-lg transition-all duration-200
|
|
||||||
shadow-lg hover:shadow-emerald-500/25"
|
|
||||||
>
|
|
||||||
Connect {wallet.name}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-emerald-200">Connected Wallet</span>
|
|
||||||
<span className="px-3 py-1 bg-emerald-500/20 rounded-full text-emerald-200 text-sm">
|
|
||||||
{walletState.publicKey?.slice(0, 22)}...
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WalletHeader
|
|
@ -11,14 +11,12 @@ export interface VisionConfig {
|
|||||||
modelId: string
|
modelId: string
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
cost: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VISION_CONFIG: VisionConfig = {
|
export const VISION_CONFIG: VisionConfig = {
|
||||||
modelId: "google-vision-v1",
|
modelId: "google-vision-v1",
|
||||||
name: "Worldwide Animal Oracle",
|
name: "Worldwide Animal Oracle",
|
||||||
description: "Upload photos of your wildlife encounters from the real world. Verified animal images will be added to the Animal Registry.",
|
description: "Upload photos of your wildlife encounters from the real world. Verified animal images will be added to the Animal Registry.",
|
||||||
cost: 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function analyzeImage(imageFile: File): Promise<VisionAnalysisResult> {
|
export async function analyzeImage(imageFile: File): Promise<VisionAnalysisResult> {
|
||||||
|
@ -31,6 +31,9 @@ const ANIMAL_RECORDS_QUERY = `
|
|||||||
... on FloatValue {
|
... on FloatValue {
|
||||||
float: value
|
float: value
|
||||||
}
|
}
|
||||||
|
... on StringValue {
|
||||||
|
string: value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,7 +47,7 @@ export async function fetchAnimalRecords(portalName: string): Promise<AnimalReco
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
query: ANIMAL_RECORDS_QUERY,
|
query: ANIMAL_RECORDS_QUERY,
|
||||||
variables: { portalName }
|
variables: { portalName: process.env.NEXT_PUBLIC_PORTAL_NAME }
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -86,12 +89,17 @@ export async function fetchAnimalRecords(portalName: string): Promise<AnimalReco
|
|||||||
},
|
},
|
||||||
description: attributesMap.description || 'No description',
|
description: attributesMap.description || 'No description',
|
||||||
imageUrl: attributesMap.imageUrl || null,
|
imageUrl: attributesMap.imageUrl || null,
|
||||||
|
portalName: attributesMap.portalName || null
|
||||||
},
|
},
|
||||||
createTime: record.createTime || ''
|
createTime: record.createTime || ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Filter out records without an imageUrl
|
// Filter out records without an imageUrl or non matching portalName
|
||||||
.filter((record: AnimalRecord) => record.attributes.imageUrl !== null && record.attributes.imageUrl.trim() !== '');
|
.filter((record: AnimalRecord) =>
|
||||||
|
record.attributes.imageUrl !== null &&
|
||||||
|
record.attributes.imageUrl.trim() !== '' &&
|
||||||
|
record.attributes.portalName === process.env.NEXT_PUBLIC_PORTAL_NAME
|
||||||
|
);
|
||||||
|
|
||||||
console.log('Processed animal records:', records);
|
console.log('Processed animal records:', records);
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export async function publishAnimalRecord(
|
|||||||
longitude: number,
|
longitude: number,
|
||||||
description: string,
|
description: string,
|
||||||
imageUrl: string,
|
imageUrl: string,
|
||||||
portalName: string = process.env.NEXT_PUBLIC_PORTAL_NAME
|
portalName: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
// Verify config file exists
|
// Verify config file exists
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
import { Connection, PublicKey, Transaction, SystemProgram } from '@solana/web3.js'
|
|
||||||
import {
|
|
||||||
TOKEN_PROGRAM_ID,
|
|
||||||
createTransferInstruction,
|
|
||||||
getAssociatedTokenAddress,
|
|
||||||
createAssociatedTokenAccountInstruction,
|
|
||||||
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
||||||
} from '@solana/spl-token'
|
|
||||||
import { WalletType } from './types'
|
|
||||||
|
|
||||||
const MTM_TOKEN_MINT: string = '97RggLo3zV5kFGYW4yoQTxr4Xkz4Vg2WPHzNYXXWpump'
|
|
||||||
const PAYMENT_RECEIVER_ADDRESS: string = 'FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FFcowRobY'
|
|
||||||
const SOLANA_RPC_URL: string = 'https://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'
|
|
||||||
const SOLANA_WEBSOCKET_URL: string = 'wss://young-radial-orb.solana-mainnet.quiknode.pro/67612b364664616c29514e551bf5de38447ca3d4'
|
|
||||||
|
|
||||||
const connection = new Connection(
|
|
||||||
SOLANA_RPC_URL,
|
|
||||||
{
|
|
||||||
commitment: 'confirmed',
|
|
||||||
wsEndpoint: SOLANA_WEBSOCKET_URL,
|
|
||||||
confirmTransactionInitialTimeout: 60000,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export interface PaymentResult {
|
|
||||||
success: boolean
|
|
||||||
error?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findAssociatedTokenAddress(
|
|
||||||
walletAddress: PublicKey,
|
|
||||||
tokenMintAddress: PublicKey
|
|
||||||
): Promise<PublicKey> {
|
|
||||||
return PublicKey.findProgramAddressSync(
|
|
||||||
[
|
|
||||||
walletAddress.toBuffer(),
|
|
||||||
TOKEN_PROGRAM_ID.toBuffer(),
|
|
||||||
tokenMintAddress.toBuffer(),
|
|
||||||
],
|
|
||||||
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
||||||
)[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WalletAdapter {
|
|
||||||
signAndSendTransaction(transaction: Transaction): Promise<{ signature: string }>
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function processMTMPayment(
|
|
||||||
walletPublicKey: string,
|
|
||||||
tokenAmount: number,
|
|
||||||
walletType: WalletType
|
|
||||||
): Promise<PaymentResult> {
|
|
||||||
try {
|
|
||||||
let wallet: WalletAdapter | null = null;
|
|
||||||
|
|
||||||
if (walletType === 'phantom') {
|
|
||||||
wallet = window.phantom?.solana || null;
|
|
||||||
} else if (walletType === 'solflare') {
|
|
||||||
wallet = window.solflare || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wallet) {
|
|
||||||
throw new Error(`${walletType} wallet not found`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const senderPublicKey = new PublicKey(walletPublicKey)
|
|
||||||
const mintPublicKey = new PublicKey(MTM_TOKEN_MINT)
|
|
||||||
const receiverPublicKey = new PublicKey(PAYMENT_RECEIVER_ADDRESS)
|
|
||||||
|
|
||||||
console.log('Processing payment with keys:', {
|
|
||||||
sender: senderPublicKey.toBase58(),
|
|
||||||
mint: mintPublicKey.toBase58(),
|
|
||||||
receiver: receiverPublicKey.toBase58(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const senderATA = await findAssociatedTokenAddress(
|
|
||||||
senderPublicKey,
|
|
||||||
mintPublicKey
|
|
||||||
)
|
|
||||||
|
|
||||||
const receiverATA = await findAssociatedTokenAddress(
|
|
||||||
receiverPublicKey,
|
|
||||||
mintPublicKey
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log('Token accounts:', {
|
|
||||||
senderATA: senderATA.toBase58(),
|
|
||||||
receiverATA: receiverATA.toBase58(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const transaction = new Transaction()
|
|
||||||
|
|
||||||
const [senderATAInfo, receiverATAInfo] = await Promise.all([
|
|
||||||
connection.getAccountInfo(senderATA),
|
|
||||||
connection.getAccountInfo(receiverATA),
|
|
||||||
])
|
|
||||||
|
|
||||||
if (!receiverATAInfo) {
|
|
||||||
console.log('Creating receiver token account')
|
|
||||||
transaction.add(
|
|
||||||
createAssociatedTokenAccountInstruction(
|
|
||||||
senderPublicKey,
|
|
||||||
receiverATA,
|
|
||||||
receiverPublicKey,
|
|
||||||
mintPublicKey
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!senderATAInfo) {
|
|
||||||
console.log('Creating sender token account')
|
|
||||||
transaction.add(
|
|
||||||
createAssociatedTokenAccountInstruction(
|
|
||||||
senderPublicKey,
|
|
||||||
senderATA,
|
|
||||||
senderPublicKey,
|
|
||||||
mintPublicKey
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.add(
|
|
||||||
createTransferInstruction(
|
|
||||||
senderATA,
|
|
||||||
receiverATA,
|
|
||||||
senderPublicKey,
|
|
||||||
BigInt(tokenAmount * (10 ** 6))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const latestBlockhash = await connection.getLatestBlockhash('confirmed')
|
|
||||||
transaction.recentBlockhash = latestBlockhash.blockhash
|
|
||||||
transaction.feePayer = senderPublicKey
|
|
||||||
|
|
||||||
console.log('Sending transaction...')
|
|
||||||
const { signature } = await wallet.signAndSendTransaction(transaction)
|
|
||||||
console.log('Transaction sent:', signature)
|
|
||||||
|
|
||||||
const confirmation = await connection.confirmTransaction({
|
|
||||||
signature,
|
|
||||||
blockhash: latestBlockhash.blockhash,
|
|
||||||
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
||||||
}, 'confirmed')
|
|
||||||
|
|
||||||
if (confirmation.value.err) {
|
|
||||||
console.error('Transaction error:', confirmation.value.err)
|
|
||||||
throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success: true }
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Payment error:', error)
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Payment failed'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
|||||||
export type WalletType = 'solflare' | 'phantom'
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
solflare?: {
|
|
||||||
connect(): Promise<void>
|
|
||||||
disconnect(): Promise<void>
|
|
||||||
publicKey?: { toString(): string }
|
|
||||||
signAndSendTransaction(transaction: any): Promise<{ signature: string }>
|
|
||||||
}
|
|
||||||
phantom?: {
|
|
||||||
solana?: {
|
|
||||||
connect(): Promise<{ publicKey: { toString(): string } }>
|
|
||||||
disconnect(): Promise<void>
|
|
||||||
signAndSendTransaction(transaction: any): Promise<{ signature: string }>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
|||||||
import { WalletType } from './types'
|
|
||||||
|
|
||||||
export interface WalletState {
|
|
||||||
connected: boolean
|
|
||||||
publicKey: string | null
|
|
||||||
type: WalletType | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WalletConfig {
|
|
||||||
type: WalletType
|
|
||||||
name: string
|
|
||||||
connect: () => Promise<{ publicKey: string } | null>
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectSolflare = async (): Promise<{ publicKey: string } | null> => {
|
|
||||||
if (!window.solflare) return null
|
|
||||||
await window.solflare.connect()
|
|
||||||
return window.solflare.publicKey ? { publicKey: window.solflare.publicKey.toString() } : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectPhantom = async (): Promise<{ publicKey: string } | null> => {
|
|
||||||
if (!window.phantom?.solana) return null
|
|
||||||
try {
|
|
||||||
const response = await window.phantom.solana.connect()
|
|
||||||
return response.publicKey ? { publicKey: response.publicKey.toString() } : null
|
|
||||||
} catch {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SUPPORTED_WALLETS: WalletConfig[] = [
|
|
||||||
// {
|
|
||||||
// type: 'solflare',
|
|
||||||
// name: 'Solflare',
|
|
||||||
// connect: connectSolflare
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
type: 'phantom',
|
|
||||||
name: 'Phantom',
|
|
||||||
connect: connectPhantom
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export async function connectWallet(type: WalletType): Promise<WalletState> {
|
|
||||||
const wallet = SUPPORTED_WALLETS.find(w => w.type === type)
|
|
||||||
if (!wallet) throw new Error('Unsupported wallet')
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await wallet.connect()
|
|
||||||
if (!result) throw new Error(`${wallet.name} not found`)
|
|
||||||
|
|
||||||
return {
|
|
||||||
connected: true,
|
|
||||||
publicKey: result.publicKey,
|
|
||||||
type: wallet.type
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
connected: false,
|
|
||||||
publicKey: null,
|
|
||||||
type: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,6 +10,7 @@ export interface AnimalRecord {
|
|||||||
}
|
}
|
||||||
description: string
|
description: string
|
||||||
imageUrl: string
|
imageUrl: string
|
||||||
|
portalName: string
|
||||||
}
|
}
|
||||||
bondId?: string
|
bondId?: string
|
||||||
createTime: string
|
createTime: string
|
||||||
|
Loading…
Reference in New Issue
Block a user